diff --git a/components/dist/input.js b/components/dist/input.js index 26296bb..835f6fe 100644 --- a/components/dist/input.js +++ b/components/dist/input.js @@ -32,7 +32,7 @@ smInput.innerHTML = ` box-shadow: none; } ::-moz-focus-inner{ - border: none; + border: none; } :host{ display: flex; @@ -46,6 +46,7 @@ smInput.innerHTML = ` .hide{ display: none !important; } + button{ display: flex; border: none; @@ -88,6 +89,9 @@ smInput.innerHTML = ` margin-right: -2rem; pointer-events: none !important; } + .clear{ + visibility: hidden; + } .readonly{ pointer-events: none; } @@ -194,7 +198,7 @@ smInput.innerHTML = `
-
@@ -222,7 +226,7 @@ customElements.define('sm-input', this._errorText = ''; this.isRequired = false; this.validationFunction = undefined; - this.reflectedAttributes = ['value', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step']; + this.reflectedAttributes = ['value', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'list', 'autocomplete']; this.reset = this.reset.bind(this); this.clear = this.clear.bind(this); @@ -235,7 +239,7 @@ customElements.define('sm-input', } static get observedAttributes() { - return ['value', 'placeholder', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'helper-text', 'error-text', 'hiderequired']; + return ['value', 'placeholder', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'helper-text', 'error-text']; } get value() { @@ -326,6 +330,7 @@ customElements.define('sm-input', clear() { this.value = ''; this.input.focus(); + this.fireEvent(); } focusIn() { @@ -347,11 +352,7 @@ customElements.define('sm-input', checkInput(e) { if (!this.hasAttribute('readonly')) { - if (this.input.value.trim() !== '') { - this.clearBtn.classList.remove('hide'); - } else { - this.clearBtn.classList.add('hide'); - } + this.clearBtn.style.visibility = this.input.value !== '' ? 'visible' : 'hidden'; } if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder').trim() === '') return; if (this.input.value !== '') { diff --git a/components/dist/input.min.js b/components/dist/input.min.js index 5da5b32..c97ec58 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.outerContainer=this.shadowRoot.querySelector(".outer-container"),this._helperText="",this._errorText="",this.isRequired=!1,this.validationFunction=void 0,this.reflectedAttributes=["value","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step"],this.reset=this.reset.bind(this),this.clear=this.clear.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),this.handleKeydown=this.handleKeydown.bind(this),this.vibrate=this.vibrate.bind(this)}static get observedAttributes(){return["value","placeholder","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step","helper-text","error-text","hiderequired"]}get value(){return this.input.value}set value(t){t!==this.input.value&&(this.input.value=t,this.checkInput())}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 validity(){return this.input.validity}get disabled(){return this.hasAttribute("disabled")}set disabled(t){t?this.inputParent.classList.add("disabled"):this.inputParent.classList.remove("disabled")}get readOnly(){return this.hasAttribute("readonly")}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}get isValid(){if(""!==this.input.value){const t=this.input.checkValidity();let e=!0;return this.validationFunction&&(e=Boolean(this.validationFunction(this.input.value))),t&&e?(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 `),t&&e}}reset(){this.value=""}clear(){this.value="",this.input.focus()}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.trim()?this.clearBtn.classList.remove("hide"):this.clearBtn.classList.add("hide")),this.hasAttribute("placeholder")&&""!==this.getAttribute("placeholder").trim()&&(""!==this.input.value?this.animate?this.inputParent.classList.add("animate-placeholder"):this.label.classList.add("hide"):(this.animate?this.inputParent.classList.remove("animate-placeholder"):this.label.classList.remove("hide"),this.feedbackText.textContent=""))}handleKeydown(t){1===t.key.length&&(["0","1","2","3","4","5","6","7","8","9","."].includes(t.key)?"."===t.key&&t.target.value.includes(".")&&t.preventDefault():t.preventDefault())}vibrate(){this.outerContainer.animate([{transform:"translateX(-1rem)"},{transform:"translateX(1rem)"},{transform:"translateX(-0.5rem)"},{transform:"translateX(0.5rem)"},{transform:"translateX(0)"}],{duration:300,easing:"ease"})}connectedCallback(){this.animate=this.hasAttribute("animate"),this.setAttribute("role","textbox"),this.input.addEventListener("input",this.checkInput),this.clearBtn.addEventListener("click",this.clear)}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","decimal"),this.input.addEventListener("keydown",this.handleKeydown)):this.input.removeEventListener("keydown",this.handleKeydown):"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.isRequired?this.setAttribute("aria-required","true"):this.setAttribute("aria-required","false")):"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.clear),this.input.removeEventListener("keydown",this.handleKeydown)}}); \ 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.outerContainer=this.shadowRoot.querySelector(".outer-container"),this._helperText="",this._errorText="",this.isRequired=!1,this.validationFunction=void 0,this.reflectedAttributes=["value","required","disabled","type","inputmode","readonly","min","max","pattern","minlength","maxlength","step","list","autocomplete"],this.reset=this.reset.bind(this),this.clear=this.clear.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),this.handleKeydown=this.handleKeydown.bind(this),this.vibrate=this.vibrate.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){t!==this.input.value&&(this.input.value=t,this.checkInput())}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 validity(){return this.input.validity}get disabled(){return this.hasAttribute("disabled")}set disabled(t){t?this.inputParent.classList.add("disabled"):this.inputParent.classList.remove("disabled")}get readOnly(){return this.hasAttribute("readonly")}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}get isValid(){if(""!==this.input.value){const t=this.input.checkValidity();let e=!0;return this.validationFunction&&(e=Boolean(this.validationFunction(this.input.value))),t&&e?(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 `),t&&e}}reset(){this.value=""}clear(){this.value="",this.input.focus(),this.fireEvent()}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.clearBtn.style.visibility=""!==this.input.value?"visible":"hidden"),this.hasAttribute("placeholder")&&""!==this.getAttribute("placeholder").trim()&&(""!==this.input.value?this.animate?this.inputParent.classList.add("animate-placeholder"):this.label.classList.add("hide"):(this.animate?this.inputParent.classList.remove("animate-placeholder"):this.label.classList.remove("hide"),this.feedbackText.textContent=""))}handleKeydown(t){1===t.key.length&&(["0","1","2","3","4","5","6","7","8","9","."].includes(t.key)?"."===t.key&&t.target.value.includes(".")&&t.preventDefault():t.preventDefault())}vibrate(){this.outerContainer.animate([{transform:"translateX(-1rem)"},{transform:"translateX(1rem)"},{transform:"translateX(-0.5rem)"},{transform:"translateX(0.5rem)"},{transform:"translateX(0)"}],{duration:300,easing:"ease"})}connectedCallback(){this.animate=this.hasAttribute("animate"),this.setAttribute("role","textbox"),this.input.addEventListener("input",this.checkInput),this.clearBtn.addEventListener("click",this.clear)}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","decimal"),this.input.addEventListener("keydown",this.handleKeydown)):this.input.removeEventListener("keydown",this.handleKeydown):"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.isRequired?this.setAttribute("aria-required","true"):this.setAttribute("aria-required","false")):"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.clear),this.input.removeEventListener("keydown",this.handleKeydown)}}); \ No newline at end of file diff --git a/components/dist/select.js b/components/dist/select.js index c6ebead..94685df 100644 --- a/components/dist/select.js +++ b/components/dist/select.js @@ -89,8 +89,7 @@ smSelect.innerHTML = ` border: solid 1px rgba(var(--text-color,(17,17,17)), 0.2); border-radius: var(--border-radius, 0.5rem); z-index: 1; - -webkit-box-shadow: 0.4rem 0.8rem 1.2rem #00000030; - box-shadow: 0.4rem 0.8rem 1.2rem #00000030; + box-shadow: 0 1rem 1.5rem rgba(0 0 0 /0.2); } :host([open]) .toggle-icon{ -webkit-transform: rotate(180deg); @@ -141,8 +140,9 @@ customElements.define('sm-select', class extends HTMLElement { 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.availableOptions + this.availableOptions = [] this.previousOption this.isOpen = false; this.label = '' @@ -189,6 +189,15 @@ customElements.define('sm-select', class extends HTMLElement { console.warn(`There is no option with ${val} as value`) } } + debounce(callback, wait) { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, wait); + }; + } reset(fire = true) { if (this.availableOptions[0] && this.previousOption !== this.availableOptions[0]) { @@ -202,8 +211,8 @@ customElements.define('sm-select', class extends HTMLElement { selectOption(selectedOption) { if (this.previousOption !== selectedOption) { this.querySelectorAll('[selected]').forEach(option => option.removeAttribute('selected')) - selectedOption.setAttribute('selected', '') this.selectedOptionText.textContent = `${this.label}${selectedOption.textContent}`; + selectedOption.setAttribute('selected', '') this.previousOption = selectedOption } } @@ -213,15 +222,18 @@ customElements.define('sm-select', class extends HTMLElement { } open() { + this.availableOptions.forEach(option => option.setAttribute('tabindex', 0)) this.optionList.classList.remove('hide') this.optionList.animate(this.slideDown, this.animationOptions) - this.setAttribute('open', '') + this.setAttribute('open', ''); + (this.availableOptions.find(option => option.hasAttribute('selected')) || this.availableOptions[0]).focus() this.isOpen = true } collapse() { this.removeAttribute('open') this.optionList.animate(this.slideUp, this.animationOptions) .onfinish = () => { + this.availableOptions.forEach(option => option.removeAttribute('tabindex')) this.optionList.classList.add('hide') this.isOpen = false } @@ -252,8 +264,7 @@ customElements.define('sm-select', class extends HTMLElement { } else { this.availableOptions[this.availableOptions.length - 1].focus() } - } - else if (e.key === 'ArrowDown') { + } else if (e.key === 'ArrowDown') { e.preventDefault() if (document.activeElement.nextElementSibling) { document.activeElement.nextElementSibling.focus() @@ -280,21 +291,20 @@ customElements.define('sm-select', class extends HTMLElement { handleKeydown(e) { if (e.target === this) { if (this.isOpen && e.key === 'ArrowDown') { - e.preventDefault() - this.availableOptions[0].focus() + e.preventDefault(); + (this.availableOptions.find(option => option.hasAttribute('selected')) || this.availableOptions[0]).focus() this.handleOptionSelection(e) - } - else if (e.key === 'Enter' || e.key === ' ') { + } else if (e.key === ' ') { e.preventDefault() this.toggle() } - } - else { + } else { this.handleOptionsNavigation(e) this.handleOptionSelection(e) - if (e.key === 'Enter' || e.key === ' ') { + if (['Enter', ' ', 'Escape', 'Tab'].includes(e.key)) { e.preventDefault() this.collapse() + this.focusIn() } } } @@ -309,10 +319,22 @@ customElements.define('sm-select', class extends HTMLElement { this.selection.setAttribute('tabindex', '0') } let slot = this.shadowRoot.querySelector('slot') - slot.addEventListener('slotchange', e => { + slot.addEventListener('slotchange', this.debounce(e => { this.availableOptions = slot.assignedElements() this.reset(false) - }); + }, 100)); + new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const offsetLeft = this.selection.getBoundingClientRect().left + if (offsetLeft < window.innerWidth / 2) { + this.setAttribute('align-select', 'left') + } else { + this.setAttribute('align-select', 'right') + } + } + }) + }).observe(this) this.addEventListener('click', this.handleClick) this.addEventListener('keydown', this.handleKeydown) document.addEventListener('mousedown', this.handleClickOutside) @@ -350,12 +372,15 @@ smOption.innerHTML = ` display: -webkit-box; display: -ms-flexbox; display: flex; + overflow: hidden; + border-radius: var(--border-radius, 0.3rem); } .option{ + position: relative; display: grid; -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; + -ms-flex-align: center; + align-items: center; width: 100%; gap: 0.5rem; grid-template-columns: max-content minmax(0, 1fr); @@ -363,35 +388,39 @@ smOption.innerHTML = ` cursor: pointer; outline: none; user-select: none; - border-radius: var(--border-radius, 0.3rem); +} +.option::before{ + position: absolute; + content: ''; + display: block; + width: 0.2rem; + height: 1em; + border-radius: 0 1em 1em 0; + background: rgba(var(--text-color,(17,17,17)), 0.5); + transition: all 0.2s ease-in-out; + opacity: 0; } :host(:focus){ outline: none; background: rgba(var(--text-color,(17,17,17)), 0.1); } -.icon { - opacity: 0; - height: 1.2rem; - width: 1.2rem; - fill: rgba(var(--text-color,(17,17,17)), 0.8); -} -:host(:focus) .option .icon{ - opacity: 0.4 -} -:host([selected]) .icon{ +:host(:focus) .option::before{ opacity: 1 } +:host([selected]) .option::before{ + opacity: 1; + background: var(--accent-color, teal); +} @media (hover: hover){ .option:hover{ background: rgba(var(--text-color,(17,17,17)), 0.1); } - :host(:not([selected]):hover) .icon{ - opacity: 0.4 + :host(:not([selected]):hover) .option::before{ + opacity: 1 } }
-
`; customElements.define('sm-option', class extends HTMLElement { @@ -404,6 +433,5 @@ customElements.define('sm-option', class extends HTMLElement { connectedCallback() { this.setAttribute('role', 'option') - this.setAttribute('tabindex', '0') } }) \ No newline at end of file diff --git a/components/dist/select.min.js b/components/dist/select.min.js index 8c8c7bf..42ce859 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.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 +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.debounce=this.debounce.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`)}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("hide"),this.optionList.animate(this.slideDown,this.animationOptions),this.setAttribute("open",""),(this.availableOptions.find(t=>t.hasAttribute("selected"))||this.availableOptions[0]).focus(),this.isOpen=!0}collapse(){this.removeAttribute("open"),this.optionList.animate(this.slideUp,this.animationOptions).onfinish=(()=>{this.availableOptions.forEach(t=>t.removeAttribute("tabindex")),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.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()}connectedCallback(){this.setAttribute("role","listbox"),this.hasAttribute("disabled")||this.selection.setAttribute("tabindex","0");let t=this.shadowRoot.querySelector("slot");t.addEventListener("slotchange",this.debounce(e=>{this.availableOptions=t.assignedElements(),this.reset(!1)},100)),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: grid;\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n width: 100%;\n gap: 0.5rem;\n grid-template-columns: max-content minmax(0, 1fr);\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 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/dist/switch.js b/components/dist/switch.js index c23d9d0..7822cc6 100644 --- a/components/dist/switch.js +++ b/components/dist/switch.js @@ -4,24 +4,22 @@ const smSwitch = document.createElement('template') smSwitch.innerHTML = ` \n',customElements.define("sm-switch",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smSwitch.content.cloneNode(!0)),this.switch=this.shadowRoot.querySelector(".switch"),this.input=this.shadowRoot.querySelector("input"),this.isChecked=!1,this.isDisabled=!1,this.dispatch=this.dispatch.bind(this)}static get observedAttributes(){return["disabled","checked"]}get disabled(){return this.isDisabled}set disabled(n){n?this.setAttribute("disabled",""):this.removeAttribute("disabled")}get checked(){return this.isChecked}set checked(n){n?this.setAttribute("checked",""):this.removeAttribute("checked")}get value(){return this.isChecked}reset(){}dispatch(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this.isChecked}}))}connectedCallback(){this.addEventListener("keydown",n=>{" "!==n.key||this.isDisabled||(n.preventDefault(),this.input.click())}),this.input.addEventListener("click",n=>{this.input.checked?this.checked=!0:this.checked=!1,this.dispatch()})}attributeChangedCallback(n,e,t){e!==t&&("disabled"===n?this.hasAttribute("disabled")?this.disabled=!0:this.disabled=!1:"checked"===n&&(this.hasAttribute("checked")?(this.isChecked=!0,this.input.checked=!0):(this.isChecked=!1,this.input.checked=!1)))}}); \ No newline at end of file +const smSwitch=document.createElement("template");smSwitch.innerHTML='\t\n\n',customElements.define("sm-switch",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smSwitch.content.cloneNode(!0)),this.switch=this.shadowRoot.querySelector(".switch"),this.input=this.shadowRoot.querySelector("input"),this.isChecked=!1,this.isDisabled=!1,this.dispatch=this.dispatch.bind(this)}static get observedAttributes(){return["disabled","checked"]}get disabled(){return this.isDisabled}set disabled(e){e?this.setAttribute("disabled",""):this.removeAttribute("disabled")}get checked(){return this.isChecked}set checked(e){e?this.setAttribute("checked",""):this.removeAttribute("checked")}get value(){return this.isChecked}reset(){}dispatch(){this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{value:this.isChecked}}))}connectedCallback(){this.addEventListener("keydown",e=>{" "!==e.key||this.isDisabled||(e.preventDefault(),this.input.click())}),this.input.addEventListener("click",e=>{this.input.checked?this.checked=!0:this.checked=!1,this.dispatch()})}attributeChangedCallback(e,t,n){t!==n&&("disabled"===e?this.hasAttribute("disabled")?this.disabled=!0:this.disabled=!1:"checked"===e&&(this.hasAttribute("checked")?(this.isChecked=!0,this.input.checked=!0):(this.isChecked=!1,this.input.checked=!1)))}}); \ No newline at end of file diff --git a/components/test.html b/components/test.html new file mode 100644 index 0000000..d6f56c4 --- /dev/null +++ b/components/test.html @@ -0,0 +1,35 @@ + + + + + + + + Document + + + + + + + + + + + + +
+ + +
+ + + + \ No newline at end of file