diff --git a/components/dist/chips.js b/components/dist/chips.js index 52fca1a..9e2b76b 100644 --- a/components/dist/chips.js +++ b/components/dist/chips.js @@ -181,7 +181,7 @@ customElements.define('sm-chips', class extends HTMLElement { this._value = value; this.assignedElements.forEach(elem => { if (elem.value == value) { - elem.setAttribute('selected', 'true'); + elem.setAttribute('selected', ''); elem.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }); } else @@ -213,7 +213,7 @@ customElements.define('sm-chips', class extends HTMLElement { this.slotChangeTimeout = setTimeout(() => { this.assignedElements = slot.assignedElements(); this.assignedElements.forEach(elem => { - if (elem.hasAttribute('selected') && elem.getAttribute('selected') === 'true') { + if (elem.hasAttribute('selected')) { this._value = elem.value; } }); @@ -304,7 +304,7 @@ smChip.innerHTML = ` :host(:focus-within) .sm-chip{ box-shadow: 0 0 0 0.1rem var(--accent-color,teal) inset; } - :host(:hover:not([selected="true"])) .sm-chip{ + :host(:hover:not([selected])) .sm-chip{ background-color: rgba(var(--text-color,(17,17,17)), 0.06); } .sm-chip{ @@ -318,7 +318,7 @@ smChip.innerHTML = ` -webkit-tap-highlight-color: transparent; background: var(--background,inherit); } - :host([selected="true"]) .sm-chip{ + :host([selected]) .sm-chip{ background-color: var(--accent-color, teal); color: rgba(var(--background-color, (255,255,255)), 1); } diff --git a/components/dist/chips.min.js b/components/dist/chips.min.js index dcfd8f0..a3d5ba3 100644 --- a/components/dist/chips.min.js +++ b/components/dist/chips.min.js @@ -1 +1 @@ -const smChips=document.createElement("template");smChips.innerHTML='\n\n
\n
\n \n
\n \n
\n \n
\n
\n\n',customElements.define("sm-chips",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smChips.content.cloneNode(!0)),this.chipsWrapper=this.shadowRoot.querySelector(".sm-chips"),this.coverLeft=this.shadowRoot.querySelector(".cover--left"),this.coverRight=this.shadowRoot.querySelector(".cover--right"),this.navButtonLeft=this.shadowRoot.querySelector(".nav-button--left"),this.navButtonRight=this.shadowRoot.querySelector(".nav-button--right"),this.slottedOptions=void 0,this._value=void 0,this.scrollDistance=0,this.assignedElements=[],this.scrollLeft=this.scrollLeft.bind(this),this.scrollRight=this.scrollRight.bind(this),this.fireEvent=this.fireEvent.bind(this),this.setSelectedOption=this.setSelectedOption.bind(this)}get value(){return this._value}set value(t){this.setSelectedOption(t)}scrollLeft(){this.chipsWrapper.scrollBy({left:-this.scrollDistance,behavior:"smooth"})}scrollRight(){this.chipsWrapper.scrollBy({left:this.scrollDistance,behavior:"smooth"})}setSelectedOption(t){this._value!==t&&(this._value=t,this.assignedElements.forEach(e=>{e.value==t?(e.setAttribute("selected","true"),e.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"})):e.removeAttribute("selected")}))}fireEvent(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this._value}}))}connectedCallback(){this.setAttribute("role","listbox");const t=this.shadowRoot.querySelector("slot");t.addEventListener("slotchange",e=>{n.disconnect(),i.disconnect(),this.observeSelf.disconnect(),clearTimeout(this.slotChangeTimeout),this.slotChangeTimeout=setTimeout(()=>{this.assignedElements=t.assignedElements(),this.assignedElements.forEach(t=>{t.hasAttribute("selected")&&"true"===t.getAttribute("selected")&&(this._value=t.value)}),this.observeSelf.observe(this)},0)});const e=new ResizeObserver(t=>{t.forEach(t=>{if(t.contentBoxSize){const e=Array.isArray(t.contentBoxSize)?t.contentBoxSize[0]:t.contentBoxSize;this.scrollDistance=.6*e.inlineSize}else this.scrollDistance=.6*t.contentRect.width})});e.observe(this),this.observeSelf=new IntersectionObserver((t,e)=>{t.forEach(t=>{t.isIntersecting&&!this.hasAttribute("multiline")&&this.assignedElements.length>0&&(n.observe(this.assignedElements[0]),i.observe(this.assignedElements[this.assignedElements.length-1]),e.unobserve(this))})},{threshold:1}),this.chipsWrapper.addEventListener("option-clicked",t=>{this._value!==t.target.value&&(this.setSelectedOption(t.target.value),this.fireEvent())});const n=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting?(this.navButtonLeft.classList.add("hide"),this.coverLeft.classList.add("hide")):(this.navButtonLeft.classList.remove("hide"),this.coverLeft.classList.remove("hide"))})},{threshold:1,root:this}),i=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting?(this.navButtonRight.classList.add("hide"),this.coverRight.classList.add("hide")):(this.navButtonRight.classList.remove("hide"),this.coverRight.classList.remove("hide"))})},{threshold:1,root:this});this.navButtonLeft.addEventListener("click",this.scrollLeft),this.navButtonRight.addEventListener("click",this.scrollRight)}disconnectedCallback(){this.navButtonLeft.removeEventListener("click",this.scrollLeft),this.navButtonRight.removeEventListener("click",this.scrollRight)}});const smChip=document.createElement("template");smChip.innerHTML='\n\n\n \n\n',customElements.define("sm-chip",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smChip.content.cloneNode(!0)),this._value=void 0,this.radioButton=this.shadowRoot.querySelector("input"),this.fireEvent=this.fireEvent.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}get value(){return this._value}fireEvent(){this.dispatchEvent(new CustomEvent("option-clicked",{bubbles:!0,composed:!0,detail:{value:this._value}}))}handleKeyDown(t){"Enter"!==t.key&&"Space"!==t.key||this.fireEvent()}connectedCallback(){this.setAttribute("role","option"),this.setAttribute("tabindex","0"),this._value=this.getAttribute("value"),this.addEventListener("click",this.fireEvent),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){this.removeEventListener("click",this.fireEvent),this.removeEventListener("keydown",this.handleKeyDown)}}); \ No newline at end of file +const smChips=document.createElement("template");smChips.innerHTML='\n\n
\n
\n \n
\n \n
\n \n
\n
\n\n',customElements.define("sm-chips",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smChips.content.cloneNode(!0)),this.chipsWrapper=this.shadowRoot.querySelector(".sm-chips"),this.coverLeft=this.shadowRoot.querySelector(".cover--left"),this.coverRight=this.shadowRoot.querySelector(".cover--right"),this.navButtonLeft=this.shadowRoot.querySelector(".nav-button--left"),this.navButtonRight=this.shadowRoot.querySelector(".nav-button--right"),this.slottedOptions=void 0,this._value=void 0,this.scrollDistance=0,this.assignedElements=[],this.scrollLeft=this.scrollLeft.bind(this),this.scrollRight=this.scrollRight.bind(this),this.fireEvent=this.fireEvent.bind(this),this.setSelectedOption=this.setSelectedOption.bind(this)}get value(){return this._value}set value(t){this.setSelectedOption(t)}scrollLeft(){this.chipsWrapper.scrollBy({left:-this.scrollDistance,behavior:"smooth"})}scrollRight(){this.chipsWrapper.scrollBy({left:this.scrollDistance,behavior:"smooth"})}setSelectedOption(t){this._value!==t&&(this._value=t,this.assignedElements.forEach(e=>{e.value==t?(e.setAttribute("selected",""),e.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"})):e.removeAttribute("selected")}))}fireEvent(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this._value}}))}connectedCallback(){this.setAttribute("role","listbox");const t=this.shadowRoot.querySelector("slot");t.addEventListener("slotchange",e=>{n.disconnect(),i.disconnect(),this.observeSelf.disconnect(),clearTimeout(this.slotChangeTimeout),this.slotChangeTimeout=setTimeout(()=>{this.assignedElements=t.assignedElements(),this.assignedElements.forEach(t=>{t.hasAttribute("selected")&&(this._value=t.value)}),this.observeSelf.observe(this)},0)});const e=new ResizeObserver(t=>{t.forEach(t=>{if(t.contentBoxSize){const e=Array.isArray(t.contentBoxSize)?t.contentBoxSize[0]:t.contentBoxSize;this.scrollDistance=.6*e.inlineSize}else this.scrollDistance=.6*t.contentRect.width})});e.observe(this),this.observeSelf=new IntersectionObserver((t,e)=>{t.forEach(t=>{t.isIntersecting&&!this.hasAttribute("multiline")&&this.assignedElements.length>0&&(n.observe(this.assignedElements[0]),i.observe(this.assignedElements[this.assignedElements.length-1]),e.unobserve(this))})},{threshold:1}),this.chipsWrapper.addEventListener("option-clicked",t=>{this._value!==t.target.value&&(this.setSelectedOption(t.target.value),this.fireEvent())});const n=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting?(this.navButtonLeft.classList.add("hide"),this.coverLeft.classList.add("hide")):(this.navButtonLeft.classList.remove("hide"),this.coverLeft.classList.remove("hide"))})},{threshold:1,root:this}),i=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting?(this.navButtonRight.classList.add("hide"),this.coverRight.classList.add("hide")):(this.navButtonRight.classList.remove("hide"),this.coverRight.classList.remove("hide"))})},{threshold:1,root:this});this.navButtonLeft.addEventListener("click",this.scrollLeft),this.navButtonRight.addEventListener("click",this.scrollRight)}disconnectedCallback(){this.navButtonLeft.removeEventListener("click",this.scrollLeft),this.navButtonRight.removeEventListener("click",this.scrollRight)}});const smChip=document.createElement("template");smChip.innerHTML='\n\n\n \n\n',customElements.define("sm-chip",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smChip.content.cloneNode(!0)),this._value=void 0,this.radioButton=this.shadowRoot.querySelector("input"),this.fireEvent=this.fireEvent.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}get value(){return this._value}fireEvent(){this.dispatchEvent(new CustomEvent("option-clicked",{bubbles:!0,composed:!0,detail:{value:this._value}}))}handleKeyDown(t){"Enter"!==t.key&&"Space"!==t.key||this.fireEvent()}connectedCallback(){this.setAttribute("role","option"),this.setAttribute("tabindex","0"),this._value=this.getAttribute("value"),this.addEventListener("click",this.fireEvent),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){this.removeEventListener("click",this.fireEvent),this.removeEventListener("keydown",this.handleKeyDown)}}); \ No newline at end of file diff --git a/components/dist/form.js b/components/dist/form.js index fe5e384..16148b3 100644 --- a/components/dist/form.js +++ b/components/dist/form.js @@ -35,11 +35,6 @@ customElements.define('sm-form', class extends HTMLElement { this.supportedElements = 'input, sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio'; this.formElements = []; this._requiredElements = [] - 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); } static get observedAttributes() { return ['skip-submit']; @@ -47,7 +42,7 @@ customElements.define('sm-form', class extends HTMLElement { get validity() { return this.isFormValid; } - debounce(callback, wait) { + debounce = (callback, wait) => { let timeoutId = null; return (...args) => { window.clearTimeout(timeoutId); @@ -56,7 +51,7 @@ customElements.define('sm-form', class extends HTMLElement { }, wait); }; } - _checkValidity() { + _checkValidity = () => { if (!this.submitButton || this._requiredElements.length === 0) return; this.invalidFieldsCount = 0 this._requiredElements.forEach(([elem, isWC]) => { @@ -72,7 +67,7 @@ customElements.define('sm-form', class extends HTMLElement { if (!this.skipSubmit) this.submitButton.disabled = !this.isFormValid; } - handleKeydown(e) { + handleKeydown = (e) => { if (e.key === 'Enter' && e.target.tagName.includes('INPUT')) { if (this.invalidFieldsCount === 0) { if (this.submitButton) { @@ -96,15 +91,19 @@ customElements.define('sm-form', class extends HTMLElement { duration: 300, easing: 'ease' }); - if (isWC) elem.focusIn(); - else elem.focus(); + if (isWC) { + elem.focusIn(); + if (elem.tagName === 'SM-INPUT' && elem.value.trim() === '') { + elem.showError() + } + } else elem.focus(); break; } } } } } - reset() { + reset = () => { this.formElements.forEach(([elem, isWC]) => { if (isWC) elem.reset(); else { @@ -121,7 +120,7 @@ customElements.define('sm-form', class extends HTMLElement { }); this._checkValidity(); } - elementsChanged() { + elementsChanged = () => { this.formElements = [...this.querySelectorAll(this.supportedElements)].map(elem => { return [elem, elem.tagName.includes('-')]; }); diff --git a/components/dist/form.min.js b/components/dist/form.min.js index b1e6053..a7b5084 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.invalidFieldsCount, this.skipSubmit = !1, this.isFormValid = void 0, this.supportedElements = "input, sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio", this.formElements = [], this._requiredElements = [], 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) } static get observedAttributes() { return ["skip-submit"] } get validity() { return this.isFormValid } debounce(e, t) { let i = null; return (...s) => { window.clearTimeout(i), i = window.setTimeout((() => { e.apply(null, s) }), t) } } _checkValidity() { this.submitButton && 0 !== this._requiredElements.length && (this.invalidFieldsCount = 0, this._requiredElements.forEach((([e, t]) => { (!e.disabled && t && !e.isValid || !t && !e.checkValidity()) && this.invalidFieldsCount++ })), this.isFormValid !== (0 === this.invalidFieldsCount) && (this.isFormValid = 0 === this.invalidFieldsCount, this.dispatchEvent(new CustomEvent(this.isFormValid ? "valid" : "invalid", { bubbles: !0, composed: !0 })), this.skipSubmit || (this.submitButton.disabled = !this.isFormValid))) } handleKeydown(e) { if ("Enter" === e.key && e.target.tagName.includes("INPUT")) if (0 === this.invalidFieldsCount) this.submitButton && this.submitButton.click(), this.dispatchEvent(new CustomEvent("submit", { bubbles: !0, composed: !0 })); else for (const [e, t] of this._requiredElements) { if (t ? !e.isValid : !e.checkValidity()) { (e?.shadowRoot?.lastElementChild || e).animate([{ transform: "translateX(-1rem)" }, { transform: "translateX(1rem)" }, { transform: "translateX(-0.5rem)" }, { transform: "translateX(0.5rem)" }, { transform: "translateX(0)" }], { duration: 300, easing: "ease" }), t ? e.focusIn() : e.focus(); break } } } reset() { this.formElements.forEach((([e, t]) => { if (t) e.reset(); else switch (e.type) { case "checkbox": case "radio": e.checked = !1; break; default: e.value = "" } })), this._checkValidity() } elementsChanged() { this.formElements = [...this.querySelectorAll(this.supportedElements)].map((e => [e, e.tagName.includes("-")])), 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() { const e = this.debounce(this.elementsChanged, 100); this.addEventListener("input", this.debounce(this._checkValidity, 100)), this.addEventListener("keydown", this.debounce(this.handleKeydown, 100)), this.shadowRoot.querySelector("slot").addEventListener("slotchange", e), this.mutationObserver = new MutationObserver((t => { t.forEach((t => { ("childList" === t.type && [...t.addedNodes].some((e => 1 === e.nodeType && e.querySelector(this.supportedElements))) || [...t.removedNodes].some((e => 1 === e.nodeType && e.querySelector(this.supportedElements)))) && e() })) })), this.mutationObserver.observe(this, { childList: !0, subtree: !0 }) } attributeChangedCallback(e, t, i) { "skip-submit" === e && (this.skipSubmit = null !== i) } 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 +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.invalidFieldsCount,this.skipSubmit=!1,this.isFormValid=void 0,this.supportedElements="input, sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio",this.formElements=[],this._requiredElements=[]}static get observedAttributes(){return["skip-submit"]}get validity(){return this.isFormValid}debounce=(callback,wait)=>{let timeoutId=null;return(...args)=>{window.clearTimeout(timeoutId),timeoutId=window.setTimeout((()=>{callback.apply(null,args)}),wait)}};_checkValidity=()=>{this.submitButton&&0!==this._requiredElements.length&&(this.invalidFieldsCount=0,this._requiredElements.forEach((([elem,isWC])=>{(!elem.disabled&&isWC&&!elem.isValid||!isWC&&!elem.checkValidity())&&this.invalidFieldsCount++})),this.isFormValid!==(0===this.invalidFieldsCount)&&(this.isFormValid=0===this.invalidFieldsCount,this.dispatchEvent(new CustomEvent(this.isFormValid?"valid":"invalid",{bubbles:!0,composed:!0})),this.skipSubmit||(this.submitButton.disabled=!this.isFormValid)))};handleKeydown=e=>{if("Enter"===e.key&&e.target.tagName.includes("INPUT"))if(0===this.invalidFieldsCount)this.submitButton&&this.submitButton.click(),this.dispatchEvent(new CustomEvent("submit",{bubbles:!0,composed:!0}));else for(const[elem,isWC]of this._requiredElements){if(isWC?!elem.isValid:!elem.checkValidity()){(elem?.shadowRoot?.lastElementChild||elem).animate([{transform:"translateX(-1rem)"},{transform:"translateX(1rem)"},{transform:"translateX(-0.5rem)"},{transform:"translateX(0.5rem)"},{transform:"translateX(0)"}],{duration:300,easing:"ease"}),isWC?(elem.focusIn(),"SM-INPUT"===elem.tagName&&""===elem.value.trim()&&elem.showError()):elem.focus();break}}};reset=()=>{this.formElements.forEach((([elem,isWC])=>{if(isWC)elem.reset();else switch(elem.type){case"checkbox":case"radio":elem.checked=!1;break;default:elem.value=""}})),this._checkValidity()};elementsChanged=()=>{this.formElements=[...this.querySelectorAll(this.supportedElements)].map((elem=>[elem,elem.tagName.includes("-")])),this._requiredElements=this.formElements.filter((([elem])=>elem.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(){const updateFormDecedents=this.debounce(this.elementsChanged,100);this.addEventListener("input",this.debounce(this._checkValidity,100)),this.addEventListener("keydown",this.debounce(this.handleKeydown,100)),this.shadowRoot.querySelector("slot").addEventListener("slotchange",updateFormDecedents),this.mutationObserver=new MutationObserver((mutations=>{mutations.forEach((mutation=>{("childList"===mutation.type&&[...mutation.addedNodes].some((node=>1===node.nodeType&&node.querySelector(this.supportedElements)))||[...mutation.removedNodes].some((node=>1===node.nodeType&&node.querySelector(this.supportedElements))))&&updateFormDecedents()}))})),this.mutationObserver.observe(this,{childList:!0,subtree:!0})}attributeChangedCallback(name,oldValue,newValue){"skip-submit"===name&&(this.skipSubmit=null!==newValue)}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 a44db29..c756f2f 100644 --- a/components/dist/select.js +++ b/components/dist/select.js @@ -188,7 +188,7 @@ customElements.define('sm-select', class extends HTMLElement { reset(fire = true) { if (this.availableOptions[0] && this.previousOption !== this.availableOptions[0]) { - const selectedOption = this.availableOptions.find(option => option.hasAttribute('selected') && option.getAttribute('selected') === 'true') || this.availableOptions[0]; + const selectedOption = this.availableOptions.find(option => option.hasAttribute('selected')) || this.availableOptions[0]; this.value = selectedOption.getAttribute('value') if (fire) { this.fireEvent() @@ -197,9 +197,9 @@ customElements.define('sm-select', class extends HTMLElement { } selectOption(selectedOption) { if (this.previousOption !== selectedOption) { - this.querySelectorAll('[selected="true"]').forEach(option => option.removeAttribute('selected')) + this.querySelectorAll('[selected').forEach(option => option.removeAttribute('selected')) this.selectedOptionText.textContent = `${this.label}${selectedOption.textContent}`; - selectedOption.setAttribute('selected', 'true') + selectedOption.setAttribute('selected', '') this.previousOption = selectedOption } } @@ -229,7 +229,7 @@ customElements.define('sm-select', class extends HTMLElement { ], this.animationOptions) this.setAttribute('open', ''); this.style.zIndex = 1000; - (this.availableOptions.find(option => option.hasAttribute('selected') && option.getAttribute('selected') === 'true') || this.availableOptions[0]).focus() + (this.availableOptions.find(option => option.hasAttribute('selected')) || this.availableOptions[0]).focus() document.addEventListener('mousedown', this.handleClickOutside) this.isOpen = true } @@ -307,7 +307,7 @@ customElements.define('sm-select', class extends HTMLElement { if (e.target === this) { if (this.isOpen && e.key === 'ArrowDown') { e.preventDefault(); - (this.availableOptions.find(option => option.hasAttribute('selected') && option.getAttribute('selected') === 'true') || this.availableOptions[0]).focus() + (this.availableOptions.find(option => option.hasAttribute('selected')) || this.availableOptions[0]).focus() this.handleOptionSelection(e) } else if (e.key === ' ') { e.preventDefault() @@ -355,7 +355,7 @@ customElements.define('sm-select', class extends HTMLElement { } }); if (attributesChanged) { - const selectedOption = this.availableOptions.find(option => option.hasAttribute('selected') && option.getAttribute('selected') === 'true') || this.availableOptions[0]; + const selectedOption = this.availableOptions.find(option => option.hasAttribute('selected')) || this.availableOptions[0]; this.selectedOptionText.textContent = `${this.label}${selectedOption.textContent}`; this.setAttribute('value', selectedOption.getAttribute('value')); } @@ -444,7 +444,7 @@ smOption.innerHTML = ` :host(:focus) .option::before{ opacity: 1 } -:host([selected="true"]) .option::before{ +:host([selected]) .option::before{ opacity: 1; background: var(--accent-color, teal); } @@ -452,7 +452,7 @@ smOption.innerHTML = ` .option:hover{ background: rgba(var(--text-color,(17,17,17)), 0.1); } - :host(:not([selected="true"]):hover) .option::before{ + :host(:not([selected]):hover) .option::before{ opacity: 1 } } diff --git a/components/dist/select.min.js b/components/dist/select.min.js index b652b79..efd6bdd 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
',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.debounce=this.debounce.bind(this),this.elementsChanged=this.elementsChanged.bind(this),this.availableOptions=[],this.previousOption,this.isOpen=!1,this.label="",this.defaultSelected="",this.isUnderViewport=!1,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","readonly"]}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`)}debounce(t,e){let n=null;return(...i)=>{window.clearTimeout(n),n=window.setTimeout(()=>{t.apply(null,i)},e)}}reset(t=!0){if(this.availableOptions[0]&&this.previousOption!==this.availableOptions[0]){const e=this.availableOptions.find(t=>t.hasAttribute("selected")&&"true"===t.getAttribute("selected"))||this.availableOptions[0];this.value=e.getAttribute("value"),t&&this.fireEvent()}}selectOption(t){this.previousOption!==t&&(this.querySelectorAll('[selected="true"]').forEach(t=>t.removeAttribute("selected")),this.selectedOptionText.textContent=`${this.label}${t.textContent}`,t.setAttribute("selected","true"),this.previousOption=t)}focusIn(){this.selection.focus()}open(){this.availableOptions.forEach(t=>t.setAttribute("tabindex",0)),this.optionList.classList.remove("hidden"),this.isUnderViewport=this.getBoundingClientRect().bottom+this.optionList.getBoundingClientRect().height>window.innerHeight,this.isUnderViewport?this.setAttribute("isUnder",""):this.removeAttribute("isUnder"),this.optionList.animate([{transform:`translateY(${this.isUnderViewport?"":"-"}0.5rem)`,opacity:0},{transform:"translateY(0)",opacity:1}],this.animationOptions),this.setAttribute("open",""),this.style.zIndex=1e3,(this.availableOptions.find(t=>t.hasAttribute("selected")&&"true"===t.getAttribute("selected"))||this.availableOptions[0]).focus(),document.addEventListener("mousedown",this.handleClickOutside),this.isOpen=!0}collapse(){this.removeAttribute("open"),this.optionList.animate([{transform:"translateY(0)",opacity:1},{transform:`translateY(${this.isUnderViewport?"":"-"}0.5rem)`,opacity:0}],this.animationOptions).onfinish=(()=>{this.availableOptions.forEach(t=>t.removeAttribute("tabindex")),document.removeEventListener("mousedown",this.handleClickOutside),this.optionList.classList.add("hidden"),this.isOpen=!1,this.style.zIndex="auto"})}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.find(t=>t.hasAttribute("selected")&&"true"===t.getAttribute("selected"))||this.availableOptions[0]).focus(),this.handleOptionSelection(t)):" "===t.key&&(t.preventDefault(),this.toggle()):(this.handleOptionsNavigation(t),this.handleOptionSelection(t),["Enter"," ","Escape","Tab"].includes(t.key)&&(t.preventDefault(),this.collapse(),this.focusIn()))}handleClickOutside(t){this.isOpen&&!this.contains(t.target)&&this.collapse()}elementsChanged(){this.availableOptions=[...this.querySelectorAll("sm-option")],this.reset(!1),this.defaultSelected=this.value}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.hasAttribute("readonly")||(this.selection.setAttribute("tabindex","0"),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown));const t=this.debounce(this.elementsChanged,100);this.shadowRoot.querySelector("slot").addEventListener("slotchange",t),this.mutationObserver=new MutationObserver(e=>{let n=!1;if(e.forEach(e=>{switch(e.type){case"childList":t();break;case"attributes":n=!0}}),n){const t=this.availableOptions.find(t=>t.hasAttribute("selected")&&"true"===t.getAttribute("selected"))||this.availableOptions[0];this.selectedOptionText.textContent=`${this.label}${t.textContent}`,this.setAttribute("value",t.getAttribute("value"))}}),this.mutationObserver.observe(this,{subtree:!0,childList:!0,attributeFilter:["selected"]}),new IntersectionObserver((t,e)=>{t.forEach(t=>{if(t.isIntersecting){const t=this.selection.getBoundingClientRect().left;t \n*{\n padding: 0;\n margin: 0;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n} \n:host{\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n overflow: hidden;\n border-radius: var(--border-radius, 0.3rem);\n}\n.option{\n position: relative;\n display: flex;\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n width: 100%;\n gap: 0.5rem;\n padding: var(--padding, 0.6rem 1rem);\n cursor: pointer;\n outline: none;\n user-select: none;\n}\n.option::before{\n position: absolute;\n content: \'\';\n display: block;\n width: 0.2rem;\n height: 1em;\n left: 0;\n border-radius: 0 1em 1em 0;\n background: rgba(var(--text-color,(17,17,17)), 0.5);\n transition: all 0.2s ease-in-out;\n opacity: 0;\n}\n:host(:focus){\n outline: none;\n background: rgba(var(--text-color,(17,17,17)), 0.1);\n}\n:host(:focus) .option::before{\n opacity: 1\n}\n:host([selected="true"]) .option::before{\n opacity: 1;\n background: var(--accent-color, teal);\n}\n@media (hover: hover){\n .option:hover{\n background: rgba(var(--text-color,(17,17,17)), 0.1);\n }\n :host(:not([selected="true"]):hover) .option::before{\n opacity: 1\n }\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
',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.debounce=this.debounce.bind(this),this.elementsChanged=this.elementsChanged.bind(this),this.availableOptions=[],this.previousOption,this.isOpen=!1,this.label="",this.defaultSelected="",this.isUnderViewport=!1,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","readonly"]}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`)}debounce(t,e){let n=null;return(...i)=>{window.clearTimeout(n),n=window.setTimeout(()=>{t.apply(null,i)},e)}}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")),this.selectedOptionText.textContent=`${this.label}${t.textContent}`,t.setAttribute("selected",""),this.previousOption=t)}focusIn(){this.selection.focus()}open(){this.availableOptions.forEach(t=>t.setAttribute("tabindex",0)),this.optionList.classList.remove("hidden"),this.isUnderViewport=this.getBoundingClientRect().bottom+this.optionList.getBoundingClientRect().height>window.innerHeight,this.isUnderViewport?this.setAttribute("isUnder",""):this.removeAttribute("isUnder"),this.optionList.animate([{transform:`translateY(${this.isUnderViewport?"":"-"}0.5rem)`,opacity:0},{transform:"translateY(0)",opacity:1}],this.animationOptions),this.setAttribute("open",""),this.style.zIndex=1e3,(this.availableOptions.find(t=>t.hasAttribute("selected"))||this.availableOptions[0]).focus(),document.addEventListener("mousedown",this.handleClickOutside),this.isOpen=!0}collapse(){this.removeAttribute("open"),this.optionList.animate([{transform:"translateY(0)",opacity:1},{transform:`translateY(${this.isUnderViewport?"":"-"}0.5rem)`,opacity:0}],this.animationOptions).onfinish=(()=>{this.availableOptions.forEach(t=>t.removeAttribute("tabindex")),document.removeEventListener("mousedown",this.handleClickOutside),this.optionList.classList.add("hidden"),this.isOpen=!1,this.style.zIndex="auto"})}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.find(t=>t.hasAttribute("selected"))||this.availableOptions[0]).focus(),this.handleOptionSelection(t)):" "===t.key&&(t.preventDefault(),this.toggle()):(this.handleOptionsNavigation(t),this.handleOptionSelection(t),["Enter"," ","Escape","Tab"].includes(t.key)&&(t.preventDefault(),this.collapse(),this.focusIn()))}handleClickOutside(t){this.isOpen&&!this.contains(t.target)&&this.collapse()}elementsChanged(){this.availableOptions=[...this.querySelectorAll("sm-option")],this.reset(!1),this.defaultSelected=this.value}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.hasAttribute("readonly")||(this.selection.setAttribute("tabindex","0"),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown));const t=this.debounce(this.elementsChanged,100);this.shadowRoot.querySelector("slot").addEventListener("slotchange",t),this.mutationObserver=new MutationObserver(e=>{let n=!1;if(e.forEach(e=>{switch(e.type){case"childList":t();break;case"attributes":n=!0}}),n){const t=this.availableOptions.find(t=>t.hasAttribute("selected"))||this.availableOptions[0];this.selectedOptionText.textContent=`${this.label}${t.textContent}`,this.setAttribute("value",t.getAttribute("value"))}}),this.mutationObserver.observe(this,{subtree:!0,childList:!0,attributeFilter:["selected"]}),new IntersectionObserver((t,e)=>{t.forEach(t=>{if(t.isIntersecting){const t=this.selection.getBoundingClientRect().left;t \n*{\n padding: 0;\n margin: 0;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n} \n:host{\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n overflow: hidden;\n border-radius: var(--border-radius, 0.3rem);\n}\n.option{\n position: relative;\n display: flex;\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n width: 100%;\n gap: 0.5rem;\n padding: var(--padding, 0.6rem 1rem);\n cursor: pointer;\n outline: none;\n user-select: none;\n}\n.option::before{\n position: absolute;\n content: \'\';\n display: block;\n width: 0.2rem;\n height: 1em;\n left: 0;\n border-radius: 0 1em 1em 0;\n background: rgba(var(--text-color,(17,17,17)), 0.5);\n transition: all 0.2s ease-in-out;\n opacity: 0;\n}\n:host(:focus){\n outline: none;\n background: rgba(var(--text-color,(17,17,17)), 0.1);\n}\n:host(:focus) .option::before{\n opacity: 1\n}\n:host([selected]) .option::before{\n opacity: 1;\n background: var(--accent-color, teal);\n}\n@media (hover: hover){\n .option:hover{\n background: rgba(var(--text-color,(17,17,17)), 0.1);\n }\n :host(:not([selected]):hover) .option::before{\n opacity: 1\n }\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/test.html b/components/test.html index b2b3d47..afc5a2f 100644 --- a/components/test.html +++ b/components/test.html @@ -36,22 +36,69 @@ - - + + + Submit + + + diff --git a/components/test.min.html b/components/test.min.html new file mode 100644 index 0000000..76f68b5 --- /dev/null +++ b/components/test.min.html @@ -0,0 +1 @@ +DocumentSubmit \ No newline at end of file