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.prevTab
this.allTabs
this.activeTab
this.indicator = this.shadowRoot.querySelector('.indicator');
this.tabSlot = this.shadowRoot.querySelector('slot');
this.tabHeader = this.shadowRoot.querySelector('.tab-header');
this.changeTab = this.changeTab.bind(this)
this.handleClick = this.handleClick.bind(this)
this.handlePanelChange = this.handlePanelChange.bind(this)
this.moveIndiactor = this.moveIndiactor.bind(this)
}
fireEvent(index) {
this.dispatchEvent(
new CustomEvent(`switchedtab${this.target}`, {
bubbles: true,
detail: {
index: parseInt(index)
}
})
)
}
moveIndiactor(tabDimensions) {
this.indicator.setAttribute('style', `width: ${tabDimensions.width}px; transform: translateX(${tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px)`)
}
changeTab(target) {
if (target === this.prevTab || !target.closest('sm-tab'))
return
if (this.prevTab)
this.prevTab.classList.remove('active')
target.classList.add('active')
this.tabHeader.scrollTo({
behavior: 'smooth',
left: target.getBoundingClientRect().left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft
})
this.moveIndiactor(target.getBoundingClientRect())
this.prevTab = target;
this.activeTab = target;
}
handleClick(e) {
if (e.target.closest('sm-tab')) {
this.changeTab(e.target)
this.fireEvent(e.target.dataset.index)
}
}
handlePanelChange(e) {
this.changeTab(this.allTabs[e.detail.index])
}
connectedCallback() {
if (!this.hasAttribute('target') || this.getAttribute('target').value === '') return;
this.target = this.getAttribute('target')
this.tabSlot.addEventListener('slotchange', () => {
this.allTabs = this.tabSlot.assignedElements();
this.allTabs.forEach((tab, index) => {
tab.dataset.index = index
})
})
this.addEventListener('click', this.handleClick)
document.addEventListener(`switchedpanel${this.target}`, this.handlePanelChange)
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.fireEvent(0)
this.prevTab = this.tabSlot.assignedElements()[0];
this.activeTab = this.prevTab;
}
}
})
}, {
threshold: 1.0
})
observer.observe(this)
}
disconnectedCallback() {
this.removeEventListener('click', this.handleClick)
document.removeEventListener(`switchedpanel${this.target}`, this.handlePanelChange)
}
})
// 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.isTransitioning = false
this.panelContainer = this.shadowRoot.querySelector('.panel-container');
this.handleTabChange = this.handleTabChange.bind(this)
}
handleTabChange(e) {
this.isTransitioning = true
this.panelContainer.scrollTo({
left: this.allPanels[e.detail.index].getBoundingClientRect().left - this.panelContainer.getBoundingClientRect().left + this.panelContainer.scrollLeft,
behavior: 'smooth'
})
setTimeout(() => {
this.isTransitioning = false
}, 300);
}
fireEvent(index) {
this.dispatchEvent(
new CustomEvent(`switchedpanel${this.id}`, {
bubbles: true,
detail: {
index: parseInt(index)
}
})
)
}
connectedCallback() {
const slot = this.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
this.allPanels = e.target.assignedElements()
this.allPanels.forEach((panel, index) => {
panel.dataset.index = index
intersectionObserver.observe(panel)
})
})
document.addEventListener(`switchedtab${this.id}`, this.handleTabChange)
const intersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!this.isTransitioning && entry.isIntersecting) {
this.fireEvent(entry.target.dataset.index)
}
})
}, {
threshold: 0.6
})
}
disconnectedCallback() {
intersectionObserver.disconnect()
document.removeEventListener(`switchedtab${this.id}`, this.handleTabChange)
}
})