diff --git a/components/dist/form.js b/components/dist/form.js index 8c59563..83effbb 100644 --- a/components/dist/form.js +++ b/components/dist/form.js @@ -34,6 +34,7 @@ customElements.define('sm-form', class extends HTMLElement { this.submitButton this.resetButton this.invalidFields = false; + this.mutationObserver this.debounce = this.debounce.bind(this) this._checkValidity = this._checkValidity.bind(this) @@ -87,18 +88,18 @@ customElements.define('sm-form', class extends HTMLElement { this.shadowRoot.querySelector('slot').addEventListener('slotchange', this.elementsChanged) this.addEventListener('input', this.debounce(this._checkValidity, 100)); this.addEventListener('keydown', this.debounce(this.handleKeydown, 100)); - const mutationObserver = new MutationObserver(mutations => { + this.mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList') { this.elementsChanged() } }) }) - mutationObserver.observe(this, { childList: true, subtree: true }) + this.mutationObserver.observe(this, { childList: true, subtree: true }) } disconnectedCallback() { this.removeEventListener('input', this.debounce(this._checkValidity, 100)); this.removeEventListener('keydown', this.debounce(this.handleKeydown, 100)); - mutationObserver.disconnect() + this.mutationObserver.disconnect() } }) \ No newline at end of file diff --git a/components/dist/form.min.js b/components/dist/form.min.js index a91384b..9747001 100644 --- a/components/dist/form.min.js +++ b/components/dist/form.min.js @@ -1 +1 @@ -const smForm=document.createElement("template");smForm.innerHTML='\n \n
\n \n
\n ',customElements.define("sm-form",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smForm.content.cloneNode(!0)),this.form=this.shadowRoot.querySelector("form"),this.formElements,this.requiredElements,this.submitButton,this.resetButton,this.invalidFields=!1,this.debounce=this.debounce.bind(this),this._checkValidity=this._checkValidity.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.reset=this.reset.bind(this),this.elementsChanged=this.elementsChanged.bind(this)}debounce(e,t){let i=null;return(...s)=>{window.clearTimeout(i),i=window.setTimeout(()=>{e.apply(null,s)},t)}}_checkValidity(){this.submitButton&&(this.invalidFields=this.requiredElements.filter(e=>!e.isValid),this.submitButton.disabled=this.invalidFields.length)}handleKeydown(e){"Enter"===e.key&&e.target.tagName.includes("SM-INPUT")&&(this.invalidFields.length?this.requiredElements.forEach(e=>{e.isValid||e.vibrate()}):(this.submitButton&&this.submitButton.click(),this.dispatchEvent(new CustomEvent("submit",{bubbles:!0,composed:!0}))))}reset(){this.formElements.forEach(e=>e.reset())}elementsChanged(){this.formElements=[...this.querySelectorAll("sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio")],this.requiredElements=this.formElements.filter(e=>e.hasAttribute("required")),this.submitButton=this.querySelector('[variant="primary"], [type="submit"]'),this.resetButton=this.querySelector('[type="reset"]'),this.resetButton&&this.resetButton.addEventListener("click",this.reset),this._checkValidity()}connectedCallback(){this.shadowRoot.querySelector("slot").addEventListener("slotchange",this.elementsChanged),this.addEventListener("input",this.debounce(this._checkValidity,100)),this.addEventListener("keydown",this.debounce(this.handleKeydown,100));const e=new MutationObserver(e=>{e.forEach(e=>{"childList"===e.type&&this.elementsChanged()})});e.observe(this,{childList:!0,subtree:!0})}disconnectedCallback(){this.removeEventListener("input",this.debounce(this._checkValidity,100)),this.removeEventListener("keydown",this.debounce(this.handleKeydown,100)),mutationObserver.disconnect()}}); \ No newline at end of file +const smForm=document.createElement("template");smForm.innerHTML='\n \n
\n \n
\n ',customElements.define("sm-form",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smForm.content.cloneNode(!0)),this.form=this.shadowRoot.querySelector("form"),this.formElements,this.requiredElements,this.submitButton,this.resetButton,this.invalidFields=!1,this.mutationObserver,this.debounce=this.debounce.bind(this),this._checkValidity=this._checkValidity.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.reset=this.reset.bind(this),this.elementsChanged=this.elementsChanged.bind(this)}debounce(t,e){let i=null;return(...s)=>{window.clearTimeout(i),i=window.setTimeout(()=>{t.apply(null,s)},e)}}_checkValidity(){this.submitButton&&(this.invalidFields=this.requiredElements.filter(t=>!t.isValid),this.submitButton.disabled=this.invalidFields.length)}handleKeydown(t){"Enter"===t.key&&t.target.tagName.includes("SM-INPUT")&&(this.invalidFields.length?this.requiredElements.forEach(t=>{t.isValid||t.vibrate()}):(this.submitButton&&this.submitButton.click(),this.dispatchEvent(new CustomEvent("submit",{bubbles:!0,composed:!0}))))}reset(){this.formElements.forEach(t=>t.reset())}elementsChanged(){this.formElements=[...this.querySelectorAll("sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio")],this.requiredElements=this.formElements.filter(t=>t.hasAttribute("required")),this.submitButton=this.querySelector('[variant="primary"], [type="submit"]'),this.resetButton=this.querySelector('[type="reset"]'),this.resetButton&&this.resetButton.addEventListener("click",this.reset),this._checkValidity()}connectedCallback(){this.shadowRoot.querySelector("slot").addEventListener("slotchange",this.elementsChanged),this.addEventListener("input",this.debounce(this._checkValidity,100)),this.addEventListener("keydown",this.debounce(this.handleKeydown,100)),this.mutationObserver=new MutationObserver(t=>{t.forEach(t=>{"childList"===t.type&&this.elementsChanged()})}),this.mutationObserver.observe(this,{childList:!0,subtree:!0})}disconnectedCallback(){this.removeEventListener("input",this.debounce(this._checkValidity,100)),this.removeEventListener("keydown",this.debounce(this.handleKeydown,100)),this.mutationObserver.disconnect()}}); \ No newline at end of file diff --git a/components/dist/select.js b/components/dist/select.js index 4f196ce..c6ebead 100644 --- a/components/dist/select.js +++ b/components/dist/select.js @@ -144,7 +144,6 @@ customElements.define('sm-select', class extends HTMLElement { this.availableOptions this.previousOption - this._value = undefined; this.isOpen = false; this.label = '' this.slideDown = [{ @@ -182,7 +181,7 @@ customElements.define('sm-select', class extends HTMLElement { return this.getAttribute('value') } set value(val) { - const selectedOption = this.availableOptions.find(option => option.getAttribute('value') === val) + const selectedOption = this.shadowRoot.querySelector('slot').assignedElements().find(option => option.getAttribute('value') === val) if (selectedOption) { this.setAttribute('value', val) this.selectOption(selectedOption) @@ -201,12 +200,8 @@ customElements.define('sm-select', class extends HTMLElement { } } selectOption(selectedOption) { - if (this.previousOption) { - this.previousOption.classList.remove('check-selected') - this.previousOption.removeAttribute('selected') - } if (this.previousOption !== selectedOption) { - selectedOption.classList.add('check-selected') + this.querySelectorAll('[selected]').forEach(option => option.removeAttribute('selected')) selectedOption.setAttribute('selected', '') this.selectedOptionText.textContent = `${this.label}${selectedOption.textContent}`; this.previousOption = selectedOption @@ -316,11 +311,6 @@ customElements.define('sm-select', class extends HTMLElement { let slot = this.shadowRoot.querySelector('slot') slot.addEventListener('slotchange', e => { this.availableOptions = slot.assignedElements() - this.availableOptions.forEach(elem => { - if (elem.hasAttribute('selected')) { - this._value = elem.value; - } - }); this.reset(false) }); this.addEventListener('click', this.handleClick) @@ -328,6 +318,7 @@ customElements.define('sm-select', class extends HTMLElement { document.addEventListener('mousedown', this.handleClickOutside) } disconnectedCallback() { + this.removeEventListener('click', this.handleClick) this.removeEventListener('click', this.toggle) this.removeEventListener('keydown', this.handleKeydown) document.removeEventListener('mousedown', this.handleClickOutside) @@ -387,14 +378,14 @@ smOption.innerHTML = ` :host(:focus) .option .icon{ opacity: 0.4 } -:host(.check-selected) .icon{ +:host([selected]) .icon{ opacity: 1 } @media (hover: hover){ .option:hover{ background: rgba(var(--text-color,(17,17,17)), 0.1); } - :host(:not(.check-selected):hover) .icon{ + :host(:not([selected]):hover) .icon{ opacity: 0.4 } } diff --git a/components/dist/select.min.js b/components/dist/select.min.js index 948f2d0..8c8c7bf 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.focusIn=this.focusIn.bind(this),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.selectOption=this.selectOption.bind(this),this.availableOptions,this.previousOption,this._value=void 0,this.isOpen=!1,this.label="",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.selection=this.shadowRoot.querySelector(".selection"),this.selectedOptionText=this.shadowRoot.querySelector(".selected-option-text")}static get observedAttributes(){return["disabled","label"]}get value(){return this.getAttribute("value")}set value(t){const e=this.availableOptions.find(e=>e.getAttribute("value")===t);e?(this.setAttribute("value",t),this.selectOption(e)):console.warn(`There is no option with ${t} as value`)}reset(t=!0){if(this.availableOptions[0]&&this.previousOption!==this.availableOptions[0]){const e=this.availableOptions.find(t=>t.hasAttribute("selected"))||this.availableOptions[0];this.value=e.getAttribute("value"),t&&this.fireEvent()}}selectOption(t){this.previousOption&&(this.previousOption.classList.remove("check-selected"),this.previousOption.removeAttribute("selected")),this.previousOption!==t&&(t.classList.add("check-selected"),t.setAttribute("selected",""),this.selectedOptionText.textContent=`${this.label}${t.textContent}`,this.previousOption=t)}focusIn(){this.selection.focus()}open(){this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.setAttribute("open",""),this.isOpen=!0}collapse(){this.removeAttribute("open"),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(t){"ArrowUp"===t.key?(t.preventDefault(),document.activeElement.previousElementSibling?document.activeElement.previousElementSibling.focus():this.availableOptions[this.availableOptions.length-1].focus()):"ArrowDown"===t.key&&(t.preventDefault(),document.activeElement.nextElementSibling?document.activeElement.nextElementSibling.focus():this.availableOptions[0].focus())}handleOptionSelection(t){this.previousOption!==document.activeElement&&(this.value=document.activeElement.getAttribute("value"),this.fireEvent())}handleClick(t){t.target===this?this.toggle():(this.handleOptionSelection(),this.collapse())}handleKeydown(t){t.target===this?this.isOpen&&"ArrowDown"===t.key?(t.preventDefault(),this.availableOptions[0].focus(),this.handleOptionSelection(t)):"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.toggle()):(this.handleOptionsNavigation(t),this.handleOptionSelection(t),"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.collapse()))}handleClickOutside(t){this.isOpen&&!this.contains(t.target)&&this.collapse()}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let t=this.shadowRoot.querySelector("slot");t.addEventListener("slotchange",e=>{this.availableOptions=t.assignedElements(),this.availableOptions.forEach(t=>{t.hasAttribute("selected")&&(this._value=t.value)}),this.reset(!1)}),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(t){"disabled"===t?this.hasAttribute("disabled")?this.selection.removeAttribute("tabindex"):this.selection.setAttribute("tabindex","0"):"label"===t&&(this.label=this.hasAttribute("label")?`${this.getAttribute("label")} `:"")}});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"),this.setAttribute("tabindex","0")}}); \ 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.focusIn=this.focusIn.bind(this),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.selectOption=this.selectOption.bind(this),this.availableOptions,this.previousOption,this.isOpen=!1,this.label="",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.selection=this.shadowRoot.querySelector(".selection"),this.selectedOptionText=this.shadowRoot.querySelector(".selected-option-text")}static get observedAttributes(){return["disabled","label"]}get value(){return this.getAttribute("value")}set value(t){const e=this.shadowRoot.querySelector("slot").assignedElements().find(e=>e.getAttribute("value")===t);e?(this.setAttribute("value",t),this.selectOption(e)):console.warn(`There is no option with ${t} as value`)}reset(t=!0){if(this.availableOptions[0]&&this.previousOption!==this.availableOptions[0]){const e=this.availableOptions.find(t=>t.hasAttribute("selected"))||this.availableOptions[0];this.value=e.getAttribute("value"),t&&this.fireEvent()}}selectOption(t){this.previousOption!==t&&(this.querySelectorAll("[selected]").forEach(t=>t.removeAttribute("selected")),t.setAttribute("selected",""),this.selectedOptionText.textContent=`${this.label}${t.textContent}`,this.previousOption=t)}focusIn(){this.selection.focus()}open(){this.optionList.classList.remove("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.setAttribute("open",""),this.isOpen=!0}collapse(){this.removeAttribute("open"),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(t){"ArrowUp"===t.key?(t.preventDefault(),document.activeElement.previousElementSibling?document.activeElement.previousElementSibling.focus():this.availableOptions[this.availableOptions.length-1].focus()):"ArrowDown"===t.key&&(t.preventDefault(),document.activeElement.nextElementSibling?document.activeElement.nextElementSibling.focus():this.availableOptions[0].focus())}handleOptionSelection(t){this.previousOption!==document.activeElement&&(this.value=document.activeElement.getAttribute("value"),this.fireEvent())}handleClick(t){t.target===this?this.toggle():(this.handleOptionSelection(),this.collapse())}handleKeydown(t){t.target===this?this.isOpen&&"ArrowDown"===t.key?(t.preventDefault(),this.availableOptions[0].focus(),this.handleOptionSelection(t)):"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.toggle()):(this.handleOptionsNavigation(t),this.handleOptionSelection(t),"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.collapse()))}handleClickOutside(t){this.isOpen&&!this.contains(t.target)&&this.collapse()}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let t=this.shadowRoot.querySelector("slot");t.addEventListener("slotchange",e=>{this.availableOptions=t.assignedElements(),this.reset(!1)}),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown),document.addEventListener("mousedown",this.handleClickOutside)}disconnectedCallback(){this.removeEventListener("click",this.handleClick),this.removeEventListener("click",this.toggle),this.removeEventListener("keydown",this.handleKeydown),document.removeEventListener("mousedown",this.handleClickOutside)}attributeChangedCallback(t){"disabled"===t?this.hasAttribute("disabled")?this.selection.removeAttribute("tabindex"):this.selection.setAttribute("tabindex","0"):"label"===t&&(this.label=this.hasAttribute("label")?`${this.getAttribute("label")} `:"")}});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"),this.setAttribute("tabindex","0")}}); \ No newline at end of file