moved to root
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
(function(){
|
||||
const isNodeContext = typeof module !== 'undefined' && typeof module.exports !== 'undefined'
|
||||
if (isNodeContext) {
|
||||
Draggabilly = require('draggabilly')
|
||||
}
|
||||
|
||||
const tabTemplate = `
|
||||
<div class="chrome-tab">
|
||||
<div class="chrome-tab-background">
|
||||
</div>
|
||||
<div class="chrome-tab-favicon"></div>
|
||||
<div class="chrome-tab-spinner"></div>
|
||||
<div class="chrome-tab-title"></div>
|
||||
<div class="chrome-tab-close"></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const defaultTabProperties = {
|
||||
title: '',
|
||||
favicon: ''
|
||||
}
|
||||
|
||||
let instanceId = 0
|
||||
let tabId = 0
|
||||
|
||||
class ChromeTabs {
|
||||
constructor() {
|
||||
this.draggabillyInstances = []
|
||||
}
|
||||
|
||||
init(el, options) {
|
||||
this.el = el
|
||||
this.options = options
|
||||
|
||||
this.instanceId = instanceId
|
||||
this.el.setAttribute('data-chrome-tabs-instance-id', this.instanceId)
|
||||
instanceId += 1
|
||||
|
||||
this.tabId = tabId
|
||||
|
||||
this.setupStyleEl()
|
||||
this.setupEvents()
|
||||
this.layoutTabs()
|
||||
this.fixZIndexes()
|
||||
this.setupDraggabilly()
|
||||
}
|
||||
|
||||
emit(eventName, data) {
|
||||
this.el.dispatchEvent(new CustomEvent(eventName, { detail: data }))
|
||||
}
|
||||
|
||||
setupStyleEl() {
|
||||
this.animationStyleEl = document.createElement('style')
|
||||
this.el.appendChild(this.animationStyleEl)
|
||||
}
|
||||
|
||||
setupEvents() {
|
||||
window.addEventListener('resize', event => this.layoutTabs())
|
||||
|
||||
document.body.querySelector('#chrome-tabs-add-tab').addEventListener('click', event => this.emit('requestNewTab'))
|
||||
|
||||
this.el.addEventListener('click', ({target}) => {
|
||||
if (target.classList.contains('chrome-tab')) {
|
||||
this.setCurrentTab(target)
|
||||
} else if (target.classList.contains('chrome-tab-close')) {
|
||||
this.emit('requestTabClose', {tabEl: target.parentNode})
|
||||
} else if (target.classList.contains('chrome-tab-title') || target.classList.contains('chrome-tab-favicon')) {
|
||||
this.setCurrentTab(target.parentNode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get tabEls() {
|
||||
return Array.prototype.slice.call(this.el.querySelectorAll('.chrome-tab'))
|
||||
}
|
||||
|
||||
get tabContentEl() {
|
||||
return this.el.querySelector('.chrome-tabs-content')
|
||||
}
|
||||
|
||||
get tabWidth() {
|
||||
const tabsContentWidth = this.tabContentEl.clientWidth - this.options.tabOverlapDistance
|
||||
const width = (tabsContentWidth / this.tabEls.length) + this.options.tabOverlapDistance
|
||||
return Math.max(this.options.minWidth, Math.min(this.options.maxWidth, width))
|
||||
}
|
||||
|
||||
get tabEffectiveWidth() {
|
||||
return this.tabWidth - this.options.tabOverlapDistance
|
||||
}
|
||||
|
||||
get tabPositions() {
|
||||
const tabEffectiveWidth = this.tabEffectiveWidth
|
||||
let left = 0
|
||||
let positions = []
|
||||
|
||||
this.tabEls.forEach((tabEl, i) => {
|
||||
positions.push(left)
|
||||
left += tabEffectiveWidth
|
||||
})
|
||||
return positions
|
||||
}
|
||||
|
||||
layoutTabs() {
|
||||
const tabWidth = this.tabWidth
|
||||
|
||||
this.cleanUpPreviouslyDraggedTabs()
|
||||
this.tabEls.forEach((tabEl) => tabEl.style.width = tabWidth + 'px')
|
||||
requestAnimationFrame(() => {
|
||||
let styleHTML = ''
|
||||
this.tabPositions.forEach((left, i) => {
|
||||
styleHTML += `
|
||||
.chrome-tabs[data-chrome-tabs-instance-id="${ this.instanceId }"] .chrome-tab:nth-child(${ i + 1}) {
|
||||
transform: translate3d(${ left }px, 0, 0)
|
||||
}
|
||||
`
|
||||
})
|
||||
this.animationStyleEl.innerHTML = styleHTML
|
||||
})
|
||||
}
|
||||
|
||||
fixZIndexes() {
|
||||
const bottomBarEl = this.el.querySelector('.chrome-tabs-bottom-bar')
|
||||
const tabEls = this.tabEls
|
||||
|
||||
tabEls.forEach((tabEl, i) => {
|
||||
let zIndex = tabEls.length - i
|
||||
|
||||
if (tabEl.classList.contains('chrome-tab-current')) {
|
||||
bottomBarEl.style.zIndex = tabEls.length + 1
|
||||
zIndex = tabEls.length + 2
|
||||
}
|
||||
tabEl.style.zIndex = zIndex
|
||||
})
|
||||
}
|
||||
|
||||
createNewTabEl() {
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = tabTemplate
|
||||
|
||||
return div.firstElementChild
|
||||
}
|
||||
|
||||
addTab(tabProperties) {
|
||||
const tabEl = this.createNewTabEl()
|
||||
|
||||
tabEl.setAttribute('data-tab-id', tabProperties.id)
|
||||
|
||||
tabEl.classList.add('chrome-tab-just-added')
|
||||
setTimeout(() => tabEl.classList.remove('chrome-tab-just-added'), 500)
|
||||
|
||||
tabProperties = Object.assign({}, defaultTabProperties, tabProperties)
|
||||
this.tabContentEl.appendChild(tabEl)
|
||||
this.updateTab(tabEl, tabProperties)
|
||||
this.emit('tabAdd', { tabEl })
|
||||
this.setCurrentTab(tabEl)
|
||||
this.layoutTabs()
|
||||
this.fixZIndexes()
|
||||
this.setupDraggabilly()
|
||||
}
|
||||
|
||||
setCurrentTab(tabEl) {
|
||||
const currentTab = this.el.querySelector('.chrome-tab-current')
|
||||
if (currentTab) currentTab.classList.remove('chrome-tab-current')
|
||||
tabEl.classList.add('chrome-tab-current')
|
||||
this.fixZIndexes()
|
||||
this.emit('activeTabChange', { tabEl })
|
||||
}
|
||||
|
||||
removeTab(tabEl) {
|
||||
if (tabEl.classList.contains('chrome-tab-current')) {
|
||||
if (tabEl.previousElementSibling) {
|
||||
this.setCurrentTab(tabEl.previousElementSibling)
|
||||
} else if (tabEl.nextElementSibling) {
|
||||
this.setCurrentTab(tabEl.nextElementSibling)
|
||||
}
|
||||
}
|
||||
tabEl.parentNode.removeChild(tabEl)
|
||||
this.emit('tabRemove', { tabEl })
|
||||
this.layoutTabs()
|
||||
this.fixZIndexes()
|
||||
this.setupDraggabilly()
|
||||
}
|
||||
|
||||
updateTab(tabEl, tabProperties) {
|
||||
tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title
|
||||
tabEl.querySelector('.chrome-tab-favicon').style.backgroundImage = `url('${tabProperties.favicon}')`
|
||||
tabEl.querySelector('.chrome-tab-favicon').style.display = tabProperties.loading ? 'none' : 'inline-block'
|
||||
tabEl.querySelector('.chrome-tab-spinner').style.display = tabProperties.loading ? 'inline-block' : 'none'
|
||||
}
|
||||
|
||||
cleanUpPreviouslyDraggedTabs() {
|
||||
this.tabEls.forEach((tabEl) => tabEl.classList.remove('chrome-tab-just-dragged'))
|
||||
}
|
||||
|
||||
setupDraggabilly() {
|
||||
const tabEls = this.tabEls
|
||||
const tabEffectiveWidth = this.tabEffectiveWidth
|
||||
const tabPositions = this.tabPositions
|
||||
|
||||
this.draggabillyInstances.forEach(draggabillyInstance => draggabillyInstance.destroy())
|
||||
|
||||
tabEls.forEach((tabEl, originalIndex) => {
|
||||
const originalTabPositionX = tabPositions[originalIndex]
|
||||
const draggabillyInstance = new Draggabilly(tabEl, {
|
||||
axis: 'x',
|
||||
containment: this.tabContentEl
|
||||
})
|
||||
|
||||
this.draggabillyInstances.push(draggabillyInstance)
|
||||
|
||||
draggabillyInstance.on('dragStart', () => {
|
||||
this.cleanUpPreviouslyDraggedTabs()
|
||||
tabEl.classList.add('chrome-tab-currently-dragged')
|
||||
this.el.classList.add('chrome-tabs-sorting')
|
||||
this.fixZIndexes()
|
||||
})
|
||||
|
||||
draggabillyInstance.on('dragEnd', () => {
|
||||
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-currently-dragged')
|
||||
this.el.classList.remove('chrome-tabs-sorting')
|
||||
|
||||
this.setCurrentTab(tabEl)
|
||||
tabEl.classList.add('chrome-tab-just-dragged')
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
tabEl.style.transform = ''
|
||||
|
||||
this.setupDraggabilly()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
draggabillyInstance.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 destinationIndex = Math.max(0, Math.min(tabEls.length, Math.floor((currentTabPositionX + (tabEffectiveWidth / 2)) / tabEffectiveWidth)))
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNodeContext) {
|
||||
module.exports = ChromeTabs
|
||||
} else {
|
||||
window.ChromeTabs = ChromeTabs
|
||||
}
|
||||
})()
|
||||
Reference in New Issue
Block a user