/*! * Draggabilly PACKAGED v2.2.0 * Make that shiz draggable * https://draggabilly.desandro.com * MIT license */ !function(i,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(t){return e(i,t)}):"object"==typeof module&&module.exports?module.exports=e(i,require("jquery")):i.jQueryBridget=e(i,i.jQuery)}(window,function(t,i){"use strict";var c=Array.prototype.slice,e=t.console,p=void 0===e?function(){}:function(t){e.error(t)};function n(d,o,u){(u=u||i||t.jQuery)&&(o.prototype.option||(o.prototype.option=function(t){u.isPlainObject(t)&&(this.options=u.extend(!0,this.options,t))}),u.fn[d]=function(t){if("string"==typeof t){var i=c.call(arguments,1);return s=i,a="$()."+d+'("'+(r=t)+'")',(e=this).each(function(t,i){var e=u.data(i,d);if(e){var n=e[r];if(n&&"_"!=r.charAt(0)){var o=n.apply(e,s);h=void 0===h?o:h}else p(a+" is not a valid method")}else p(d+" not initialized. Cannot call methods, i.e. "+a)}),void 0!==h?h:e}var e,r,s,h,a,n;return n=t,this.each(function(t,i){var e=u.data(i,d);e?(e.option(n),e._init()):(e=new o(i,n),u.data(i,d,e))}),this},r(u))}function r(t){!t||t&&t.bridget||(t.bridget=n)}return r(i||t.jQuery),n}),function(t,i){"use strict";"function"==typeof define&&define.amd?define("get-size/get-size",[],function(){return i()}):"object"==typeof module&&module.exports?module.exports=i():t.getSize=i()}(window,function(){"use strict";function m(t){var i=parseFloat(t);return-1==t.indexOf("%")&&!isNaN(i)&&i}var e="undefined"==typeof console?function(){}:function(t){console.error(t)},y=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],b=y.length;function E(t){var i=getComputedStyle(t);return i||e("Style returned "+i+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),i}var _,x=!1;function P(t){if(function(){if(!x){x=!0;var t=document.createElement("div");t.style.width="200px",t.style.padding="1px 2px 3px 4px",t.style.borderStyle="solid",t.style.borderWidth="1px 2px 3px 4px",t.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(t);var e=E(t);P.isBoxSizeOuter=_=200==m(e.width),i.removeChild(t)}}(),"string"==typeof t&&(t=document.querySelector(t)),t&&"object"==typeof t&&t.nodeType){var i=E(t);if("none"==i.display)return function(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},i=0;i {} const closest = (value, array) => { let closest = Infinity let closestIndex = -1 array.forEach((v, i) => { if (Math.abs(value - v) < closest) { closest = Math.abs(value - v) closestIndex = i } }) return closestIndex } const tabTemplate = `
×
` const defaultTapProperties = { title: 'New tab' } let instanceId = 0 class ChromeTabs { constructor() { this.draggabillies = [] } init(el) { this.el = el this.instanceId = instanceId this.el.setAttribute('data-chrome-tabs-instance-id', this.instanceId) instanceId += 1 this.setupCustomProperties() this.setupStyleEl() this.setupEvents() this.layoutTabs() this.setupDraggabilly() this.setVisibility() } emit(eventName, data) { this.el.dispatchEvent(new CustomEvent(eventName, { detail: data })) } setupCustomProperties() { this.el.style.setProperty('--tab-content-margin', `${ TAB_CONTENT_MARGIN }px`) } setupStyleEl() { this.styleEl = document.createElement('style') this.el.appendChild(this.styleEl) } setupEvents() { window.addEventListener('resize', _ => { this.cleanUpPreviouslyDraggedTabs() this.layoutTabs() }) this.tabEls.forEach((tabEl) => this.setTabCloseEventListener(tabEl)) } setVisibility() { this.el.style.display = this.tabEls.length > 1 ? "block" : "none"; } get tabEls() { return Array.prototype.slice.call(this.el.querySelectorAll('.chrome-tab')) } get tabContentEl() { return this.el.querySelector('.chrome-tabs-content') } get tabContentWidths() { const numberOfTabs = this.tabEls.length const tabsContentWidth = this.tabContentEl.clientWidth const tabsCumulativeOverlappedWidth = (numberOfTabs - 1) * TAB_CONTENT_OVERLAP_DISTANCE const targetWidth = (tabsContentWidth - (2 * TAB_CONTENT_MARGIN) + tabsCumulativeOverlappedWidth) / numberOfTabs const clampedTargetWidth = Math.max(TAB_CONTENT_MIN_WIDTH, Math.min(TAB_CONTENT_MAX_WIDTH, targetWidth)) const flooredClampedTargetWidth = Math.floor(clampedTargetWidth) const totalTabsWidthUsingTarget = (flooredClampedTargetWidth * numberOfTabs) + (2 * TAB_CONTENT_MARGIN) - tabsCumulativeOverlappedWidth const totalExtraWidthDueToFlooring = tabsContentWidth - totalTabsWidthUsingTarget // TODO - Support tabs with different widths / e.g. "pinned" tabs const widths = [] let extraWidthRemaining = totalExtraWidthDueToFlooring for (let i = 0; i < numberOfTabs; i += 1) { const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0 widths.push(flooredClampedTargetWidth + extraWidth) if (extraWidthRemaining > 0) extraWidthRemaining -= 1 } return widths } get tabContentPositions() { const positions = [] const tabContentWidths = this.tabContentWidths let position = TAB_CONTENT_MARGIN tabContentWidths.forEach((width, i) => { const offset = i * TAB_CONTENT_OVERLAP_DISTANCE positions.push(position - offset) position += width }) return positions } get tabPositions() { const positions = [] this.tabContentPositions.forEach((contentPosition) => { positions.push(contentPosition - TAB_CONTENT_MARGIN) }) return positions } layoutTabs() { const tabContentWidths = this.tabContentWidths this.tabEls.forEach((tabEl, i) => { const contentWidth = tabContentWidths[i] const width = contentWidth + (2 * TAB_CONTENT_MARGIN) tabEl.style.width = width + 'px' tabEl.removeAttribute('is-small') tabEl.removeAttribute('is-smaller') tabEl.removeAttribute('is-mini') if (contentWidth < TAB_SIZE_SMALL) tabEl.setAttribute('is-small', '') if (contentWidth < TAB_SIZE_SMALLER) tabEl.setAttribute('is-smaller', '') if (contentWidth < TAB_SIZE_MINI) tabEl.setAttribute('is-mini', '') }) let styleHTML = '' this.tabPositions.forEach((position, i) => { styleHTML += ` .chrome-tabs[data-chrome-tabs-instance-id="${ this.instanceId }"] .chrome-tab:nth-child(${ i + 1 }) { transform: translate3d(${ position }px, 0, 0) } ` }) this.styleEl.innerHTML = styleHTML } createNewTabEl() { const div = document.createElement('div') div.innerHTML = tabTemplate return div.firstElementChild } addTab(tabProperties, { animate = true, background = false } = {}) { const tabEl = this.createNewTabEl() if (animate) { tabEl.classList.add('chrome-tab-was-just-added') setTimeout(() => tabEl.classList.remove('chrome-tab-was-just-added'), 500) } tabProperties = Object.assign({}, defaultTapProperties, tabProperties) this.tabContentEl.appendChild(tabEl) this.setVisibility() this.setTabCloseEventListener(tabEl) this.updateTab(tabEl, tabProperties) this.emit('tabAdd', { tabEl }) if (!background) this.setCurrentTab(tabEl) this.cleanUpPreviouslyDraggedTabs() this.layoutTabs() this.setupDraggabilly() return tabEl } setTabCloseEventListener(tabEl) { tabEl.querySelector('.chrome-tab-close').addEventListener('click', _ => this.removeTab(tabEl)) } get activeTabEl() { return this.el.querySelector('.chrome-tab[active]') } get previousTabEl() { const tabEls = this.tabEls; if (tabEls.length <= 1) { return null; } let prev = tabEls[tabEls.length - 1]; for (const tabEl of tabEls) { if (tabEl.hasAttribute("active")) { return prev; } prev = tabEl; } return null; } get nextTabEl() { const tabEls = this.tabEls; if (tabEls.length <= 1) { return null; } let prev = tabEls[tabEls.length - 1]; for (const tabEl of tabEls) { if (prev.hasAttribute("active")) { return tabEl; } prev = tabEl; } return null; } hasActiveTab() { return !!this.activeTabEl } setCurrentTab(tabEl) { const activeTabEl = this.activeTabEl if (activeTabEl === tabEl) return if (activeTabEl) activeTabEl.removeAttribute('active') tabEl.setAttribute('active', '') this.emit('activeTabChange', { tabEl }) } removeTab(tabEl) { if (tabEl === this.activeTabEl) { if (tabEl.nextElementSibling) { this.setCurrentTab(tabEl.nextElementSibling) } else if (tabEl.previousElementSibling) { this.setCurrentTab(tabEl.previousElementSibling) } } tabEl.parentNode.removeChild(tabEl) this.emit('tabRemove', { tabEl }) this.cleanUpPreviouslyDraggedTabs() this.layoutTabs() this.setupDraggabilly() this.setVisibility() } removeAllTabsExceptForThis(remainingTabEl) { for (const tabEl of this.tabEls) { if (remainingTabEl !== tabEl) { this.removeTab(tabEl); } } } updateTab(tabEl, tabProperties) { tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title if (tabProperties.id) { tabEl.setAttribute('data-tab-id', tabProperties.id) } } cleanUpPreviouslyDraggedTabs() { this.tabEls.forEach((tabEl) => tabEl.classList.remove('chrome-tab-was-just-dragged')) } setupDraggabilly() { const tabEls = this.tabEls const tabPositions = this.tabPositions if (this.isDragging) { this.isDragging = false this.el.classList.remove('chrome-tabs-is-sorting') this.draggabillyDragging.element.classList.remove('chrome-tab-is-dragging') this.draggabillyDragging.element.style.transform = '' this.draggabillyDragging.dragEnd() this.draggabillyDragging.isDragging = false this.draggabillyDragging.positionDrag = noop // Prevent Draggabilly from updating tabEl.style.transform in later frames this.draggabillyDragging.destroy() this.draggabillyDragging = null } this.draggabillies.forEach(d => d.destroy()) tabEls.forEach((tabEl, originalIndex) => { const originalTabPositionX = tabPositions[originalIndex] const draggabilly = new Draggabilly(tabEl, { axis: 'x', handle: '.chrome-tab-drag-handle', containment: this.tabContentEl }) this.draggabillies.push(draggabilly) draggabilly.on('pointerDown', _ => { this.setCurrentTab(tabEl) }) draggabilly.on('dragStart', _ => { this.isDragging = true this.draggabillyDragging = draggabilly tabEl.classList.add('chrome-tab-is-dragging') this.el.classList.add('chrome-tabs-is-sorting') }) draggabilly.on('dragEnd', _ => { this.isDragging = false const finalTranslateX = parseFloat(tabEl.style.left, 10) tabEl.style.transform = `translate3d(0, 0, 0)` // Animate dragged tab back into its place requestAnimationFrame(_ => { tabEl.style.left = '0' tabEl.style.transform = `translate3d(${ finalTranslateX }px, 0, 0)` requestAnimationFrame(_ => { tabEl.classList.remove('chrome-tab-is-dragging') this.el.classList.remove('chrome-tabs-is-sorting') tabEl.classList.add('chrome-tab-was-just-dragged') requestAnimationFrame(_ => { tabEl.style.transform = '' this.layoutTabs() this.setupDraggabilly() }) }) }) }) draggabilly.on('dragMove', (event, pointer, moveVector) => { // Current index be computed within the event since it can change during the dragMove const tabEls = this.tabEls const currentIndex = tabEls.indexOf(tabEl) const currentTabPositionX = originalTabPositionX + moveVector.x const destinationIndexTarget = closest(currentTabPositionX, tabPositions) const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget)) if (currentIndex !== destinationIndex) { this.animateTabMove(tabEl, currentIndex, destinationIndex) } }) }) } animateTabMove(tabEl, originIndex, destinationIndex) { if (destinationIndex < originIndex) { tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex]) } else { tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1]) } this.emit('tabReorder', { tabEl, originIndex, destinationIndex }) this.layoutTabs() } } window.ChromeTabs = ChromeTabs })()