From f9b2b79133cc004955ad89aa7d3cd4cb9a11dc42 Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Sun, 18 Jul 2021 01:21:25 +0530 Subject: [PATCH] tab.js code refactoring --- components/dist/input.js | 10 -- components/dist/input.min.js | 2 +- components/dist/select.js | 4 +- components/dist/select.min.js | 2 +- components/dist/tabs.js | 248 +++++++++++++++------------------- components/dist/tabs.min.js | 2 +- components/index.html | 23 +--- components/index.min.html | 4 +- 8 files changed, 118 insertions(+), 177 deletions(-) diff --git a/components/dist/input.js b/components/dist/input.js index 673f56b..a54c1b7 100644 --- a/components/dist/input.js +++ b/components/dist/input.js @@ -250,7 +250,6 @@ customElements.define('sm-input', this.focusIn = this.focusIn.bind(this) this.focusOut = this.focusOut.bind(this) this.fireEvent = this.fireEvent.bind(this) - this.debounce = this.debounce.bind(this) this.checkInput = this.checkInput.bind(this) } @@ -339,15 +338,6 @@ customElements.define('sm-input', }); this.dispatchEvent(event); } - debounce(callback, wait){ - let timeoutId = null; - return (...args) => { - window.clearTimeout(timeoutId); - timeoutId = window.setTimeout(() => { - callback.apply(null, args); - }, wait); - }; - } checkInput(e){ if (!this.hasAttribute('readonly')) { diff --git a/components/dist/input.min.js b/components/dist/input.min.js index 7fc2fe6..3e73ea6 100644 --- a/components/dist/input.min.js +++ b/components/dist/input.min.js @@ -1 +1 @@ -const smInput=document.createElement("template");smInput.innerHTML='\n\n
\n \n \n
\n',customElements.define("sm-input",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smInput.content.cloneNode(!0)),this.inputParent=this.shadowRoot.querySelector(".input"),this.input=this.shadowRoot.querySelector("input"),this.clearBtn=this.shadowRoot.querySelector(".clear"),this.label=this.shadowRoot.querySelector(".label"),this.feedbackText=this.shadowRoot.querySelector(".feedback-text"),this._helperText,this._errorText,this.isRequired=!1,this.validationFunction,this.reflectedAttributes=["value","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step"],this.reset=this.reset.bind(this),this.focusIn=this.focusIn.bind(this),this.focusOut=this.focusOut.bind(this),this.fireEvent=this.fireEvent.bind(this),this.debounce=this.debounce.bind(this),this.checkInput=this.checkInput.bind(this)}static get observedAttributes(){return["value","placeholder","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step","helper-text","error-text"]}get value(){return this.input.value}set value(t){this.input.value=t,this.checkInput(),this.fireEvent()}get placeholder(){return this.getAttribute("placeholder")}set placeholder(t){this.setAttribute("placeholder",t)}get type(){return this.getAttribute("type")}set type(t){this.setAttribute("type",t)}get isValid(){const t=this.input.checkValidity();let e=!0;return this.customValidation&&(e=this.validationFunction(this.input.value)),t&&e}get validity(){return this.input.validity}set disabled(t){t?this.inputParent.classList.add("disabled"):this.inputParent.classList.remove("disabled")}set readOnly(t){t?this.setAttribute("readonly",""):this.removeAttribute("readonly")}set customValidation(t){this.validationFunction=t}set errorText(t){this._errorText=t}set helperText(t){this._helperText=t}reset(){this.value=""}focusIn(){this.input.focus()}focusOut(){this.input.blur()}fireEvent(){let t=new Event("input",{bubbles:!0,cancelable:!0,composed:!0});this.dispatchEvent(t)}debounce(t,e){let n=null;return(...i)=>{window.clearTimeout(n),n=window.setTimeout(()=>{t.apply(null,i)},e)}}checkInput(t){this.hasAttribute("readonly")||(""!==this.input.value?this.clearBtn.classList.remove("hide"):(this.clearBtn.classList.add("hide"),this.isRequired&&(this.feedbackText.textContent="* required")),this.isValid?(this.feedbackText.classList.remove("error"),this.feedbackText.classList.add("success"),this.feedbackText.textContent=""):this._errorText&&(this.feedbackText.classList.add("error"),this.feedbackText.classList.remove("success"),this.feedbackText.innerHTML=`\n \n ${this._errorText}\n `)),this.hasAttribute("placeholder")&&""!==this.getAttribute("placeholder").trim()&&(""!==this.input.value?this.animate?this.inputParent.classList.add("animate-label"):this.label.classList.add("hide"):this.animate?this.inputParent.classList.remove("animate-label"):this.label.classList.remove("hide"))}connectedCallback(){this.animate=this.hasAttribute("animate"),this.input.addEventListener("input",this.checkInput),this.clearBtn.addEventListener("click",this.reset)}attributeChangedCallback(t,e,n){e!==n&&(this.reflectedAttributes.includes(t)&&(this.hasAttribute(t)?this.input.setAttribute(t,this.getAttribute(t)?this.getAttribute(t):""):this.input.removeAttribute(t)),"placeholder"===t?(this.label.textContent=n,this.setAttribute("aria-label",n)):this.hasAttribute("value")?this.checkInput():"type"===t?this.hasAttribute("type")&&"number"===this.getAttribute("type")&&this.input.setAttribute("inputmode","numeric"):"helper-text"===t?this._helperText=this.getAttribute("helper-text"):"error-text"===t?this._errorText=this.getAttribute("error-text"):"required"===t?(this.isRequired=this.hasAttribute("required"),this.feedbackText.textContent="* required"):"readonly"===t?this.hasAttribute("readonly")?this.inputParent.classList.add("readonly"):this.inputParent.classList.remove("readonly"):"disabled"===t&&(this.hasAttribute("disabled")?this.inputParent.classList.add("disabled"):this.inputParent.classList.remove("disabled")))}disconnectedCallback(){this.input.removeEventListener("input",this.checkInput),this.clearBtn.removeEventListener("click",this.reset)}}); \ No newline at end of file +const smInput=document.createElement("template");smInput.innerHTML='\n\n
\n \n \n
\n',customElements.define("sm-input",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smInput.content.cloneNode(!0)),this.inputParent=this.shadowRoot.querySelector(".input"),this.input=this.shadowRoot.querySelector("input"),this.clearBtn=this.shadowRoot.querySelector(".clear"),this.label=this.shadowRoot.querySelector(".label"),this.feedbackText=this.shadowRoot.querySelector(".feedback-text"),this._helperText,this._errorText,this.isRequired=!1,this.validationFunction,this.reflectedAttributes=["value","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step"],this.reset=this.reset.bind(this),this.focusIn=this.focusIn.bind(this),this.focusOut=this.focusOut.bind(this),this.fireEvent=this.fireEvent.bind(this),this.checkInput=this.checkInput.bind(this)}static get observedAttributes(){return["value","placeholder","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step","helper-text","error-text"]}get value(){return this.input.value}set value(t){this.input.value=t,this.checkInput(),this.fireEvent()}get placeholder(){return this.getAttribute("placeholder")}set placeholder(t){this.setAttribute("placeholder",t)}get type(){return this.getAttribute("type")}set type(t){this.setAttribute("type",t)}get isValid(){const t=this.input.checkValidity();let e=!0;return this.customValidation&&(e=this.validationFunction(this.input.value)),t&&e}get validity(){return this.input.validity}set disabled(t){t?this.inputParent.classList.add("disabled"):this.inputParent.classList.remove("disabled")}set readOnly(t){t?this.setAttribute("readonly",""):this.removeAttribute("readonly")}set customValidation(t){this.validationFunction=t}set errorText(t){this._errorText=t}set helperText(t){this._helperText=t}reset(){this.value=""}focusIn(){this.input.focus()}focusOut(){this.input.blur()}fireEvent(){let t=new Event("input",{bubbles:!0,cancelable:!0,composed:!0});this.dispatchEvent(t)}checkInput(t){this.hasAttribute("readonly")||(""!==this.input.value?this.clearBtn.classList.remove("hide"):(this.clearBtn.classList.add("hide"),this.isRequired&&(this.feedbackText.textContent="* required")),this.isValid?(this.feedbackText.classList.remove("error"),this.feedbackText.classList.add("success"),this.feedbackText.textContent=""):this._errorText&&(this.feedbackText.classList.add("error"),this.feedbackText.classList.remove("success"),this.feedbackText.innerHTML=`\n \n ${this._errorText}\n `)),this.hasAttribute("placeholder")&&""!==this.getAttribute("placeholder").trim()&&(""!==this.input.value?this.animate?this.inputParent.classList.add("animate-label"):this.label.classList.add("hide"):this.animate?this.inputParent.classList.remove("animate-label"):this.label.classList.remove("hide"))}connectedCallback(){this.animate=this.hasAttribute("animate"),this.input.addEventListener("input",this.checkInput),this.clearBtn.addEventListener("click",this.reset)}attributeChangedCallback(t,e,n){e!==n&&(this.reflectedAttributes.includes(t)&&(this.hasAttribute(t)?this.input.setAttribute(t,this.getAttribute(t)?this.getAttribute(t):""):this.input.removeAttribute(t)),"placeholder"===t?(this.label.textContent=n,this.setAttribute("aria-label",n)):this.hasAttribute("value")?this.checkInput():"type"===t?this.hasAttribute("type")&&"number"===this.getAttribute("type")&&this.input.setAttribute("inputmode","numeric"):"helper-text"===t?this._helperText=this.getAttribute("helper-text"):"error-text"===t?this._errorText=this.getAttribute("error-text"):"required"===t?(this.isRequired=this.hasAttribute("required"),this.feedbackText.textContent="* required"):"readonly"===t?this.hasAttribute("readonly")?this.inputParent.classList.add("readonly"):this.inputParent.classList.remove("readonly"):"disabled"===t&&(this.hasAttribute("disabled")?this.inputParent.classList.add("disabled"):this.inputParent.classList.remove("disabled")))}disconnectedCallback(){this.input.removeEventListener("input",this.checkInput),this.clearBtn.removeEventListener("click",this.reset)}}); \ No newline at end of file diff --git a/components/dist/select.js b/components/dist/select.js index cc5566c..6764bcc 100644 --- a/components/dist/select.js +++ b/components/dist/select.js @@ -243,7 +243,7 @@ customElements.define('sm-select', class extends HTMLElement { handleOptionSelection(e) { if (this.previousOption !== document.activeElement) { this.value = document.activeElement.getAttribute('value') - this.selectedOptionText.textContent = this.value; + this.selectedOptionText.textContent = document.activeElement.textContent; this.fireEvent() if (this.previousOption) { this.previousOption.classList.remove('check-selected') @@ -263,7 +263,7 @@ customElements.define('sm-select', class extends HTMLElement { } handleKeydown(e) { if (e.target === this) { - if (e.code === 'ArrowDown') { + if (this.isOpen && e.code === 'ArrowDown') { e.preventDefault() this.availableOptions[0].focus() this.handleOptionSelection(e) diff --git a/components/dist/select.min.js b/components/dist/select.min.js index 7b3ab27..f7bda18 100644 --- a/components/dist/select.min.js +++ b/components/dist/select.min.js @@ -1 +1 @@ -const smSelect=document.createElement("template");smSelect.innerHTML='\n\n
\n
\n
\n \n
\n
\n \n
\n
',customElements.define("sm-select",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smSelect.content.cloneNode(!0)),this.reset=this.reset.bind(this),this.open=this.open.bind(this),this.collapse=this.collapse.bind(this),this.toggle=this.toggle.bind(this),this.handleOptionsNavigation=this.handleOptionsNavigation.bind(this),this.handleOptionSelection=this.handleOptionSelection.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.handleClickOutside=this.handleClickOutside.bind(this),this.availableOptions,this.previousOption,this.isOpen=!1,this.slideDown=[{transform:"translateY(-0.5rem)",opacity:0},{transform:"translateY(0)",opacity:1}],this.slideUp=[{transform:"translateY(0)",opacity:1},{transform:"translateY(-0.5rem)",opacity:0}],this.animationOptions={duration:300,fill:"forwards",easing:"ease"},this.optionList=this.shadowRoot.querySelector(".options"),this.chevron=this.shadowRoot.querySelector(".toggle"),this.selection=this.shadowRoot.querySelector(".selection"),this.selectedOptionText=this.shadowRoot.querySelector(".selected-option-text")}static get observedAttributes(){return["value","disabled"]}get value(){return this.getAttribute("value")}set value(e){this.setAttribute("value",e)}reset(){}open(){this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.chevron.classList.add("rotate"),this.isOpen=!0}collapse(){this.chevron.classList.remove("rotate"),this.optionList.animate(this.slideUp,this.animationOptions).onfinish=(()=>{this.optionList.classList.add("hide"),this.isOpen=!1})}toggle(){this.isOpen||this.hasAttribute("disabled")?this.collapse():this.open()}fireEvent(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this.value}}))}handleOptionsNavigation(e){"ArrowUp"===e.code?(e.preventDefault(),document.activeElement.previousElementSibling?document.activeElement.previousElementSibling.focus():this.availableOptions[this.availableOptions.length-1].focus()):"ArrowDown"===e.code&&(e.preventDefault(),document.activeElement.nextElementSibling?document.activeElement.nextElementSibling.focus():this.availableOptions[0].focus())}handleOptionSelection(e){this.previousOption!==document.activeElement&&(this.value=document.activeElement.getAttribute("value"),this.selectedOptionText.textContent=this.value,this.fireEvent(),this.previousOption&&this.previousOption.classList.remove("check-selected"),document.activeElement.classList.add("check-selected"),this.previousOption=document.activeElement)}handleClick(e){e.target===this?this.toggle():(this.handleOptionSelection(),this.collapse())}handleKeydown(e){e.target===this?"ArrowDown"===e.code?(e.preventDefault(),this.availableOptions[0].focus(),this.handleOptionSelection(e)):"Enter"!==e.code&&"Space"!==e.code||(e.preventDefault(),this.toggle()):(this.handleOptionsNavigation(e),this.handleOptionSelection(e),"Enter"!==e.code&&"Space"!==e.code||(e.preventDefault(),this.collapse()))}handleClickOutside(e){this.isOpen&&!this.contains(e.target)&&this.collapse()}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let e=this.shadowRoot.querySelector("slot");e.addEventListener("slotchange",t=>{if(this.availableOptions=e.assignedElements(),this.availableOptions[0]){let e=this.availableOptions[0];this.previousOption=e,e.classList.add("check-selected"),this.value=e.getAttribute("value"),this.selectedOptionText.textContent=e.textContent,this.availableOptions.forEach(e=>{e.setAttribute("tabindex","0")})}}),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown),document.addEventListener("mousedown",this.handleClickOutside)}disconnectedCallback(){this.removeEventListener("click",this.toggle),this.removeEventListener("keydown",this.handleKeydown),document.removeEventListener("mousedown",this.handleClickOutside)}attributeChangedCallback(e){"disabled"===e&&(this.hasAttribute("disabled")?this.selection.removeAttribute("tabindex"):this.selection.setAttribute("tabindex","0"))}});const smOption=document.createElement("template");smOption.innerHTML='\n\n
\n \n \n
',customElements.define("sm-option",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smOption.content.cloneNode(!0))}connectedCallback(){this.setAttribute("role","option")}}); \ No newline at end of file +const smSelect=document.createElement("template");smSelect.innerHTML='\n\n
\n
\n
\n \n
\n
\n \n
\n
',customElements.define("sm-select",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smSelect.content.cloneNode(!0)),this.reset=this.reset.bind(this),this.open=this.open.bind(this),this.collapse=this.collapse.bind(this),this.toggle=this.toggle.bind(this),this.handleOptionsNavigation=this.handleOptionsNavigation.bind(this),this.handleOptionSelection=this.handleOptionSelection.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.handleClickOutside=this.handleClickOutside.bind(this),this.availableOptions,this.previousOption,this.isOpen=!1,this.slideDown=[{transform:"translateY(-0.5rem)",opacity:0},{transform:"translateY(0)",opacity:1}],this.slideUp=[{transform:"translateY(0)",opacity:1},{transform:"translateY(-0.5rem)",opacity:0}],this.animationOptions={duration:300,fill:"forwards",easing:"ease"},this.optionList=this.shadowRoot.querySelector(".options"),this.chevron=this.shadowRoot.querySelector(".toggle"),this.selection=this.shadowRoot.querySelector(".selection"),this.selectedOptionText=this.shadowRoot.querySelector(".selected-option-text")}static get observedAttributes(){return["value","disabled"]}get value(){return this.getAttribute("value")}set value(e){this.setAttribute("value",e)}reset(){}open(){this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.chevron.classList.add("rotate"),this.isOpen=!0}collapse(){this.chevron.classList.remove("rotate"),this.optionList.animate(this.slideUp,this.animationOptions).onfinish=(()=>{this.optionList.classList.add("hide"),this.isOpen=!1})}toggle(){this.isOpen||this.hasAttribute("disabled")?this.collapse():this.open()}fireEvent(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this.value}}))}handleOptionsNavigation(e){"ArrowUp"===e.code?(e.preventDefault(),document.activeElement.previousElementSibling?document.activeElement.previousElementSibling.focus():this.availableOptions[this.availableOptions.length-1].focus()):"ArrowDown"===e.code&&(e.preventDefault(),document.activeElement.nextElementSibling?document.activeElement.nextElementSibling.focus():this.availableOptions[0].focus())}handleOptionSelection(e){this.previousOption!==document.activeElement&&(this.value=document.activeElement.getAttribute("value"),this.selectedOptionText.textContent=document.activeElement.textContent,this.fireEvent(),this.previousOption&&this.previousOption.classList.remove("check-selected"),document.activeElement.classList.add("check-selected"),this.previousOption=document.activeElement)}handleClick(e){e.target===this?this.toggle():(this.handleOptionSelection(),this.collapse())}handleKeydown(e){e.target===this?this.isOpen&&"ArrowDown"===e.code?(e.preventDefault(),this.availableOptions[0].focus(),this.handleOptionSelection(e)):"Enter"!==e.code&&"Space"!==e.code||(e.preventDefault(),this.toggle()):(this.handleOptionsNavigation(e),this.handleOptionSelection(e),"Enter"!==e.code&&"Space"!==e.code||(e.preventDefault(),this.collapse()))}handleClickOutside(e){this.isOpen&&!this.contains(e.target)&&this.collapse()}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let e=this.shadowRoot.querySelector("slot");e.addEventListener("slotchange",t=>{if(this.availableOptions=e.assignedElements(),this.availableOptions[0]){let e=this.availableOptions[0];this.previousOption=e,e.classList.add("check-selected"),this.value=e.getAttribute("value"),this.selectedOptionText.textContent=e.textContent,this.availableOptions.forEach(e=>{e.setAttribute("tabindex","0")})}}),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown),document.addEventListener("mousedown",this.handleClickOutside)}disconnectedCallback(){this.removeEventListener("click",this.toggle),this.removeEventListener("keydown",this.handleKeydown),document.removeEventListener("mousedown",this.handleClickOutside)}attributeChangedCallback(e){"disabled"===e&&(this.hasAttribute("disabled")?this.selection.removeAttribute("tabindex"):this.selection.setAttribute("tabindex","0"))}});const smOption=document.createElement("template");smOption.innerHTML='\n\n
\n \n \n
',customElements.define("sm-option",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smOption.content.cloneNode(!0))}connectedCallback(){this.setAttribute("role","option")}}); \ No newline at end of file diff --git a/components/dist/tabs.js b/components/dist/tabs.js index 62b9cf0..550ac92 100644 --- a/components/dist/tabs.js +++ b/components/dist/tabs.js @@ -101,66 +101,77 @@ customElements.define('sm-tab-header', class extends HTMLElement { 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) } - sendDetails(element) { + fireEvent(index) { this.dispatchEvent( - new CustomEvent("switchtab", { + new CustomEvent(`switchedtab${this.target}`, { bubbles: true, detail: { - target: this.target, - rank: parseInt(element.getAttribute('rank')) + index: parseInt(index) } }) ) } 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)`) + } + + + changeTab(target) { + if (target === this.prevTab || !target.closest('sm-tab')) + return + if (this.prevTab) + this.prevTab.classList.remove('active') + target.classList.add('active') + + target.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }) + 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) { + console.log(this.allTabs) + this.changeTab(this.allTabs[e.detail.index]) } 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.allTabs.forEach((tab, index) => { + tab.dataset.index = 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') + this.addEventListener('click', this.handleClick) + document.addEventListener(`switchedpanel${this.target}`, this.handlePanelChange) - 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) { @@ -181,7 +192,7 @@ customElements.define('sm-tab-header', class extends HTMLElement { this.allTabs[0].classList.add('active') let tabDimensions = this.allTabs[0].getBoundingClientRect(); this.moveIndiactor(tabDimensions) - this.sendDetails(this.allTabs[0]) + this.fireEvent(0) this.prevTab = this.tabSlot.assignedElements()[0]; this.activeTab = this.prevTab; } @@ -192,6 +203,10 @@ customElements.define('sm-tab-header', class extends HTMLElement { }) observer.observe(this) } + disconnectedCallback() { + this.removeEventListener('click', this.handleClick) + document.removeEventListener(`switchedpanel${this.target}`, this.handlePanelChange) + } }) // tab @@ -280,21 +295,27 @@ smTabPanels.innerHTML = ` display: flex; width: 100%; height: 100%; - overflow: hidden auto; + overflow: hidden; + scroll-snap-type: x mandatory; + content-visibility: auto; } -slot::slotted(.hide-completely){ - display: none; +::slotted(*){ + min-width: 100%; + scroll-snap-align: center; } -@media (hover: none){ - .tab-header::-webkit-scrollbar-track { - -webkit-box-shadow: none !important; - background-color: transparent !important; +@media (any-hover: none) { + .panel-container{ + overflow-x: auto; + scrollbar-width: none; } - .tab-header::-webkit-scrollbar { + .container { + overflow-y: scroll; + } + ::-webkit-scrollbar { + width: 0; height: 0; - background-color: transparent; } -} +}
Nothing to see here. @@ -307,109 +328,56 @@ customElements.define('sm-tab-panels', class extends HTMLElement { this.attachShadow({ mode: 'open' }).append(smTabPanels.content.cloneNode(true)) + + this.isTransitioning = false + + this.panelContainer = this.shadowRoot.querySelector('.panel-container'); this.panelSlot = this.shadowRoot.querySelector('slot'); + 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() { - - //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.allPanels.forEach((panel, index) => { + panel.dataset.index = index + intersectionObserver.observe(panel) }) }) - 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 + document.addEventListener(`switchedtab${this.id}`, this.handleTabChange) - 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) - } - } + const intersectionObserver = new IntersectionObserver(entries => { + + entries.forEach(entry => { + if (!this.isTransitioning && entry.isIntersecting) { + this.fireEvent(entry.target.dataset.index), 3000 } - } else { - this.allPanels[e.detail.rank].classList.remove('hide-completely') - } - this.previousRank = e.detail.rank - this.prevPanel = this.allPanels[e.detail.rank]; + }) + }, { + threshold: 0.9 }) } + disconnectedCallback() { + intersectionObserver.disconnect() + document.removeEventListener(`switchedtab${this.id}`, this.handleTabChange) + } }) diff --git a/components/dist/tabs.min.js b/components/dist/tabs.min.js index b3d32ec..41bff48 100644 --- a/components/dist/tabs.min.js +++ b/components/dist/tabs.min.js @@ -1 +1 @@ -const smTabHeader=document.createElement("template");smTabHeader.innerHTML='\n\n
\n
\n \n
\n
\n
\n',customElements.define("sm-tab-header",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabHeader.content.cloneNode(!0)),this.indicator=this.shadowRoot.querySelector(".indicator"),this.tabSlot=this.shadowRoot.querySelector("slot"),this.tabHeader=this.shadowRoot.querySelector(".tab-header")}sendDetails(t){this.dispatchEvent(new CustomEvent("switchtab",{bubbles:!0,detail:{target:this.target,rank:parseInt(t.getAttribute("rank"))}}))}moveIndiactor(t){this.indicator.setAttribute("style",`width: ${t.width}px; transform: translateX(${t.left-this.tabHeader.getBoundingClientRect().left+this.tabHeader.scrollLeft}px)`)}connectedCallback(){if(!this.hasAttribute("target")||""===this.getAttribute("target").value)return;this.prevTab,this.allTabs,this.activeTab,this.isTab=!1,this.target=this.getAttribute("target"),this.hasAttribute("variant")&&"tab"===this.getAttribute("variant")&&(this.isTab=!0),this.tabSlot.addEventListener("slotchange",()=>{this.tabSlot.assignedElements().forEach((t,e)=>{t.setAttribute("rank",e)})}),this.allTabs=this.tabSlot.assignedElements(),this.tabSlot.addEventListener("click",t=>{t.target!==this.prevTab&&t.target.closest("sm-tab")&&(this.prevTab&&this.prevTab.classList.remove("active"),t.target.classList.add("active"),t.target.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"}),this.moveIndiactor(t.target.getBoundingClientRect()),this.sendDetails(t.target),this.prevTab=t.target,this.activeTab=t.target)});let t=new ResizeObserver(t=>{t.forEach(t=>{if(this.prevTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}})});t.observe(this);let e=new IntersectionObserver(t=>{t.forEach(t=>{if(t.isIntersecting)if(this.indicator.style.transition="none",this.activeTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}else{this.allTabs[0].classList.add("active");let t=this.allTabs[0].getBoundingClientRect();this.moveIndiactor(t),this.sendDetails(this.allTabs[0]),this.prevTab=this.tabSlot.assignedElements()[0],this.activeTab=this.prevTab}})},{threshold:1});e.observe(this)}});const smTab=document.createElement("template");smTab.innerHTML='\n\n
\n\n
\n',customElements.define("sm-tab",class extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}).append(smTab.content.cloneNode(!0))}});const smTabPanels=document.createElement("template");smTabPanels.innerHTML='\n\n
\n Nothing to see here.\n
\n',customElements.define("sm-tab-panels",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabPanels.content.cloneNode(!0)),this.panelSlot=this.shadowRoot.querySelector("slot")}connectedCallback(){let t=[{opacity:0,transform:"translateX(-1rem)"},{opacity:1,transform:"none"}],e=[{opacity:0,transform:"translateX(1rem)"},{opacity:1,transform:"none"}],n=[{opacity:1,transform:"none"},{opacity:0,transform:"translateX(-1rem)"}],a=[{opacity:1,transform:"none"},{opacity:0,transform:"translateX(1rem)"}],i={duration:300,fill:"forwards",easing:"ease"};this.prevPanel,this.allPanels,this.previousRank,this.panelSlot.addEventListener("slotchange",()=>{this.panelSlot.assignedElements().forEach(t=>{t.classList.add("hide-completely")})}),this.allPanels=this.panelSlot.assignedElements(),this._targetBodyFlyRight=(t=>{t.classList.remove("hide-completely"),t.animate(e,i)}),this._targetBodyFlyLeft=(e=>{e.classList.remove("hide-completely"),e.animate(t,i)}),document.addEventListener("switchtab",t=>{if(t.detail.target===this.id){if(this.prevPanel){let e=this.allPanels[t.detail.rank],s=this.prevPanel;this.previousRank{s.classList.add("hide-completely")}):e&&!s?this._targetBodyFlyRight(e):s&&e&&(s.animate(n,i).onfinish=(()=>{s.classList.add("hide-completely"),this._targetBodyFlyRight(e)})):s&&!e?s.animate(a,i).onfinish=(()=>{s.classList.add("hide-completely")}):e&&!s?this._targetBodyFlyLeft(e):s&&e&&(s.animate(a,i).onfinish=(()=>{s.classList.add("hide-completely"),this._targetBodyFlyLeft(e)}))}else this.allPanels[t.detail.rank].classList.remove("hide-completely");this.previousRank=t.detail.rank,this.prevPanel=this.allPanels[t.detail.rank]}})}}); \ No newline at end of file +const smTabHeader=document.createElement("template");smTabHeader.innerHTML='\n\n
\n
\n \n
\n
\n
\n',customElements.define("sm-tab-header",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabHeader.content.cloneNode(!0)),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)}fireEvent(t){this.dispatchEvent(new CustomEvent(`switchedtab${this.target}`,{bubbles:!0,detail:{index:parseInt(t)}}))}moveIndiactor(t){this.indicator.setAttribute("style",`width: ${t.width}px; transform: translateX(${t.left-this.tabHeader.getBoundingClientRect().left+this.tabHeader.scrollLeft}px)`)}changeTab(t){t!==this.prevTab&&t.closest("sm-tab")&&(this.prevTab&&this.prevTab.classList.remove("active"),t.classList.add("active"),t.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"}),this.moveIndiactor(t.getBoundingClientRect()),this.prevTab=t,this.activeTab=t)}handleClick(t){t.target.closest("sm-tab")&&(this.changeTab(t.target),this.fireEvent(t.target.dataset.index))}handlePanelChange(t){console.log(this.allTabs),this.changeTab(this.allTabs[t.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((t,n)=>{t.dataset.index=n})}),this.addEventListener("click",this.handleClick),document.addEventListener(`switchedpanel${this.target}`,this.handlePanelChange);let t=new ResizeObserver(t=>{t.forEach(t=>{if(this.prevTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}})});t.observe(this);let n=new IntersectionObserver(t=>{t.forEach(t=>{if(t.isIntersecting)if(this.indicator.style.transition="none",this.activeTab){let t=this.activeTab.getBoundingClientRect();this.moveIndiactor(t)}else{this.allTabs[0].classList.add("active");let t=this.allTabs[0].getBoundingClientRect();this.moveIndiactor(t),this.fireEvent(0),this.prevTab=this.tabSlot.assignedElements()[0],this.activeTab=this.prevTab}})},{threshold:1});n.observe(this)}disconnectedCallback(){this.removeEventListener("click",this.handleClick),document.removeEventListener(`switchedpanel${this.target}`,this.handlePanelChange)}});const smTab=document.createElement("template");smTab.innerHTML='\n\n
\n\n
\n',customElements.define("sm-tab",class extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"}).append(smTab.content.cloneNode(!0))}});const smTabPanels=document.createElement("template");smTabPanels.innerHTML='\n\n
\n Nothing to see here.\n
\n',customElements.define("sm-tab-panels",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smTabPanels.content.cloneNode(!0)),this.isTransitioning=!1,this.panelContainer=this.shadowRoot.querySelector(".panel-container"),this.panelSlot=this.shadowRoot.querySelector("slot"),this.handleTabChange=this.handleTabChange.bind(this)}handleTabChange(t){this.isTransitioning=!0,this.panelContainer.scrollTo({left:this.allPanels[t.detail.index].getBoundingClientRect().left-this.panelContainer.getBoundingClientRect().left+this.panelContainer.scrollLeft,behavior:"smooth"}),setTimeout(()=>{this.isTransitioning=!1},300)}fireEvent(t){this.dispatchEvent(new CustomEvent(`switchedpanel${this.id}`,{bubbles:!0,detail:{index:parseInt(t)}}))}connectedCallback(){this.panelSlot.addEventListener("slotchange",()=>{this.allPanels=this.panelSlot.assignedElements(),this.allPanels.forEach((n,e)=>{n.dataset.index=e,t.observe(n)})}),document.addEventListener(`switchedtab${this.id}`,this.handleTabChange);const t=new IntersectionObserver(t=>{t.forEach(t=>{!this.isTransitioning&&t.isIntersecting&&this.fireEvent(t.target.dataset.index)})},{threshold:.9})}disconnectedCallback(){intersectionObserver.disconnect(),document.removeEventListener(`switchedtab${this.id}`,this.handleTabChange)}}); \ No newline at end of file diff --git a/components/index.html b/components/index.html index 9c5afb9..fd04657 100644 --- a/components/index.html +++ b/components/index.html @@ -871,7 +871,7 @@ also identical.

Interactive demo

- + option1 option2 option3 @@ -1055,10 +1055,8 @@ To start using SM Components

- inbox - sent - draft - spam + Audio + Video @@ -1070,21 +1068,6 @@ flkmgklfmzkl
hbdsfhb
- - Lorem ipsum dolor, sit amet consectetur adipisicing elit. Facere neque incidunt aut laudantium, - quam - id, - molestiae vero blanditiis nisi alias in magnam autem quasi cumque eveniet qui cupiditate nam - corrupti? - - - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis fuga ipsam, explicabo, eius - accusamus - consectetur ex sunt soluta voluptatem iure totam nulla expedita suscipit minus molestiae - similique - odio optio - quibusdam. -
diff --git a/components/index.min.html b/components/index.min.html index 20db38c..2149684 100644 --- a/components/index.min.html +++ b/components/index.min.html @@ -92,7 +92,7 @@ <div>Disabled switch</div> </sm-radio> -

Attributes

All the native HTML radio attributes are valid

Attribute

Description

checked (boolean)

If present, radio button is set to checked state as default.

disabled (boolean)

If present radio button is set to disabled state. all the interactions are disabled

name (string)

Can be used to group radio buttons with same name. only one radio button will be active in a group.

value (string)

Sets value of radio button which can be accessed by value property with JS

Select

<sm-select> is very similar to starndatd HTML5 select and it's markup stucture is also identical.

Interactive demo

option1option2option3
+

Attributes

All the native HTML radio attributes are valid

Attribute

Description

checked (boolean)

If present, radio button is set to checked state as default.

disabled (boolean)

If present radio button is set to disabled state. all the interactions are disabled

name (string)

Can be used to group radio buttons with same name. only one radio button will be active in a group.

value (string)

Sets value of radio button which can be accessed by value property with JS

Select

<sm-select> is very similar to starndatd HTML5 select and it's markup stucture is also identical.

Interactive demo

option1option2option3
 
 <sm-select>
     <sm-option value="1">option1</sm-option>
@@ -146,7 +146,7 @@
 
 <sm-spinner></sm-spinner>
 
-

Strip select

To start using SM Components

MovieTV seriesVideoMusic

Tabs

To start using SM Components

inboxsentdraftspamgjdhnsrfijbgn
bdfjnbj
jadifjoaijdiajdo dosfighjoi
flkmgklfmzkl
hbdsfhb
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Facere neque incidunt aut laudantium, quam id, molestiae vero blanditiis nisi alias in magnam autem quasi cumque eveniet qui cupiditate nam corrupti?Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis fuga ipsam, explicabo, eius accusamus consectetur ex sunt soluta voluptatem iure totam nulla expedita suscipit minus molestiae similique odio optio quibusdam.

Tags input

To start using SM Components

Textarea

To start using SM Components

Text field

To start using SM Components

Theme toggle

Use theme-toggle to create light/dark theme easily with CSS variables.

When this is toggled by user the component changes the custom data attribute data-theme="" on HTML body tag to either light or dark

By default theme is set to OS level preferred-color-scheme ( supported by Android, iOS, Windows and MacOS ).

Interactive demo

HTML

+

Strip select

To start using SM Components

MovieTV seriesVideoMusic

Tabs

To start using SM Components

AudioVideogjdhnsrfijbgn
bdfjnbj
jadifjoaijdiajdo dosfighjoi
flkmgklfmzkl
hbdsfhb

Tags input

To start using SM Components

Textarea

To start using SM Components

Text field

To start using SM Components

Theme toggle

Use theme-toggle to create light/dark theme easily with CSS variables.

When this is toggled by user the component changes the custom data attribute data-theme="" on HTML body tag to either light or dark

By default theme is set to OS level preferred-color-scheme ( supported by Android, iOS, Windows and MacOS ).

Interactive demo

HTML

 
 <theme-toggle></theme-toggle>