const smTabHeader = document.createElement('template') smTabHeader.innerHTML = `
`; customElements.define('sm-tab-header', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(smTabHeader.content.cloneNode(true)) this.indicator = this.shadowRoot.querySelector('.indicator'); this.tabSlot = this.shadowRoot.querySelector('slot'); this.tabHeader = this.shadowRoot.querySelector('.tab-header'); } sendDetails(element) { this.dispatchEvent( new CustomEvent("switchtab", { bubbles: true, detail: { target: this.target, rank: parseInt(element.getAttribute('rank')) } }) ) } moveIndiactor(tabDimensions) { //if(this.isTab) this.indicator.setAttribute('style', `width: ${tabDimensions.width}px; transform: translateX(${tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px)`) //else //this.indicator.setAttribute('style', `width: calc(${tabDimensions.width}px - 1.6rem); transform: translateX(calc(${ tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px + 0.8rem)`) } connectedCallback() { if (!this.hasAttribute('target') || this.getAttribute('target').value === '') return; this.prevTab this.allTabs this.activeTab this.isTab = false this.target = this.getAttribute('target') if (this.hasAttribute('variant') && this.getAttribute('variant') === 'tab') { this.isTab = true } this.tabSlot.addEventListener('slotchange', () => { this.tabSlot.assignedElements().forEach((tab, index) => { tab.setAttribute('rank', index) }) }) this.allTabs = this.tabSlot.assignedElements(); this.tabSlot.addEventListener('click', e => { if (e.target === this.prevTab || !e.target.closest('sm-tab')) return if (this.prevTab) this.prevTab.classList.remove('active') e.target.classList.add('active') e.target.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }) this.moveIndiactor(e.target.getBoundingClientRect()) this.sendDetails(e.target) this.prevTab = e.target; this.activeTab = e.target; }) let resizeObserver = new ResizeObserver(entries => { entries.forEach((entry) => { if (this.prevTab) { let tabDimensions = this.activeTab.getBoundingClientRect(); this.moveIndiactor(tabDimensions) } }) }) resizeObserver.observe(this) let observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { this.indicator.style.transition = 'none' if (this.activeTab) { let tabDimensions = this.activeTab.getBoundingClientRect(); this.moveIndiactor(tabDimensions) } else { this.allTabs[0].classList.add('active') let tabDimensions = this.allTabs[0].getBoundingClientRect(); this.moveIndiactor(tabDimensions) this.sendDetails(this.allTabs[0]) this.prevTab = this.tabSlot.assignedElements()[0]; this.activeTab = this.prevTab; } } }) }, { threshold: 1.0 }) observer.observe(this) } }) // tab const smTab = document.createElement('template') smTab.innerHTML = `
`; customElements.define('sm-tab', class extends HTMLElement { constructor() { super() this.shadow = this.attachShadow({ mode: 'open' }).append(smTab.content.cloneNode(true)) } }) // tab-panels const smTabPanels = document.createElement('template') smTabPanels.innerHTML = `
Nothing to see here.
`; customElements.define('sm-tab-panels', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(smTabPanels.content.cloneNode(true)) this.panelSlot = this.shadowRoot.querySelector('slot'); } connectedCallback() { //animations let flyInLeft = [{ opacity: 0, transform: 'translateX(-1rem)' }, { opacity: 1, transform: 'none' } ], flyInRight = [{ opacity: 0, transform: 'translateX(1rem)' }, { opacity: 1, transform: 'none' } ], flyOutLeft = [{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translateX(-1rem)' } ], flyOutRight = [{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translateX(1rem)' } ], animationOptions = { duration: 300, fill: 'forwards', easing: 'ease' } this.prevPanel this.allPanels this.previousRank this.panelSlot.addEventListener('slotchange', () => { this.panelSlot.assignedElements().forEach((panel) => { panel.classList.add('hide-completely') }) }) this.allPanels = this.panelSlot.assignedElements() this._targetBodyFlyRight = (targetBody) => { targetBody.classList.remove('hide-completely') targetBody.animate(flyInRight, animationOptions) } this._targetBodyFlyLeft = (targetBody) => { targetBody.classList.remove('hide-completely') targetBody.animate(flyInLeft, animationOptions) } document.addEventListener('switchtab', e => { if (e.detail.target !== this.id) return if (this.prevPanel) { let targetBody = this.allPanels[e.detail.rank], currentBody = this.prevPanel; if (this.previousRank < e.detail.rank) { if (currentBody && !targetBody) currentBody.animate(flyOutLeft, animationOptions).onfinish = () => { currentBody.classList.add('hide-completely') } else if (targetBody && !currentBody) { this._targetBodyFlyRight(targetBody) } else if (currentBody && targetBody) { currentBody.animate(flyOutLeft, animationOptions).onfinish = () => { currentBody.classList.add('hide-completely') this._targetBodyFlyRight(targetBody) } } } else { if (currentBody && !targetBody) currentBody.animate(flyOutRight, animationOptions).onfinish = () => { currentBody.classList.add('hide-completely') } else if (targetBody && !currentBody) { this._targetBodyFlyLeft(targetBody) } else if (currentBody && targetBody) { currentBody.animate(flyOutRight, animationOptions).onfinish = () => { currentBody.classList.add('hide-completely') this._targetBodyFlyLeft(targetBody) } } } } else { this.allPanels[e.detail.rank].classList.remove('hide-completely') } this.previousRank = e.detail.rank this.prevPanel = this.allPanels[e.detail.rank]; }) } })