standard-ui/components/dist/tabs.js
2022-09-04 17:37:45 +05:30

383 lines
11 KiB
JavaScript

const smTabHeader = document.createElement('template')
smTabHeader.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
--gap: 1rem;
--justify-content: flex-start;
--tab-indicator-border-radius: 0.3rem;
}
.tabs{
position: relative;
display: -ms-grid;
display: grid;
width: 100%;
}
.tab-header{
display: -ms-grid;
display: grid;
grid-auto-flow: column;
justify-content: var(--justify-content);
gap: var(--gap);
position: relative;
overflow: auto hidden;
max-width: 100%;
scrollbar-width: 0;
}
.indicator{
position: absolute;
left: 0;
bottom: 0;
height: 0.15rem;
border-radius: 1rem 1rem 0 0;
background: var(--accent-color, tea);
-webkit-transition: width 0.3s, -webkit-transform 0.3s;
transition: width 0.3s, -webkit-transform 0.3s;
-o-transition: transform 0.3s, width 0.3s;
transition: transform 0.3s, width 0.3s;
transition: transform 0.3s, width 0.3s, -webkit-transform 0.3s;
pointer-events: none;
}
:host([variant="tab"]) .indicator{
height: 100%;
border-radius: var(--tab-indicator-border-radius);
}
:host([variant="tab"]) .tab-header{
border-bottom: none;
}
.hide-completely{
display: none;
}
:host([variant="tab"]) .tab-header{
gap: 0.2rem;
display: -ms-inline-grid;
display: inline-grid;
justify-self: flex-start;
border-radius: 0.3rem;
}
:host([variant="tab"]) slot::slotted(.active){
color: rgba(var(--background-color, (255,255,255)), 1);
}
slot::slotted(.active){
color: var(--accent-color, tea);
opacity: 1;
}
@media (any-hover: none){
.tab-header::-webkit-scrollbar-track {
-webkit-box-shadow: none !important;
background-color: transparent !important;
}
.tab-header::-webkit-scrollbar {
height: 0;
background-color: transparent;
}
}
@media (any-hover: hover){
.tab-header{
overflow: hidden;
}
}
</style>
<div part="tab-container" class="tabs">
<div part="tab-header" class="tab-header">
<slot></slot>
<div part="indicator" class="indicator"></div>
</div>
</div>
`;
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 = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
position: relative;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
z-index: 1;
--padding: 0.8rem 1rem;
}
.tab{
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
white-space: nowrap;
padding: var(--padding);
font-weight: 500;
word-spacing: 0.1rem;
text-align: center;
-webkit-transition: color 0.3s;
-o-transition: color 0.3s;
transition: color 0.3s;
text-transform: capitalize;
height: 100%;
}
@media (hover: hover){
:host(.active) .tab{
opacity: 1;
}
.tab{
opacity: 0.7
}
.tab:hover{
opacity: 1
}
}
</style>
<div part="tab" class="tab">
<slot></slot>
</div>
`;
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 = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
width: 100%;
}
.panel-container{
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
scroll-snap-type: x mandatory;
content-visibility: auto;
}
::slotted(*){
min-width: 100%;
scroll-snap-align: center;
}
@media (any-hover: none) {
.panel-container{
overflow-x: auto;
scrollbar-width: none;
}
.container {
overflow-y: scroll;
}
::-webkit-scrollbar {
width: 0;
height: 0;
}
}
</style>
<div part="panel-container" class="panel-container">
<slot>Nothing to see here.</slot>
</div>
`;
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)
}
})