1 line
11 KiB
JavaScript
1 line
11 KiB
JavaScript
const smSelect=document.createElement("template");smSelect.innerHTML='\n<style> \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}\n:host([disabled]) .select{\n opacity: 0.6;\n cursor: not-allowed;\n}\n.select{\n position: relative;\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n -ms-flex-direction: column;\n flex-direction: column;\n cursor: pointer;\n width: 100%;\n -webkit-tap-highlight-color: transparent;\n}\n.icon {\n height: 1.2rem;\n width: 1.2rem;\n margin-left: 0.5rem;\n fill: rgba(var(--text-color, (17,17,17)), 0.7);\n} \n.selected-option-text{\n font-size: inherit;\n overflow: hidden;\n -o-text-overflow: ellipsis;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n.selection{\n border-radius: var(--select-border-radius,0.5rem);\n display: -ms-grid;\n display: grid;\n -ms-grid-columns: 1fr auto;\n grid-template-columns: 1fr auto;\n grid-template-areas: \'heading heading\' \'. .\';\n padding: var(--padding,0.6rem 0.8rem);\n background: var(--background, rgba(var(--text-color,(17,17,17)), 0.06));\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n outline: none;\n z-index: 2;\n height: 100%;\n}\n.selection:focus{\n -webkit-box-shadow: 0 0 0 0.1rem var(--accent-color, teal) inset;\n box-shadow: 0 0 0 0.1rem var(--accent-color, teal) inset; \n}\n:host([align-select="left"]) .options{\n left: 0;\n}\n:host([align-select="right"]) .options{\n right: 0;\n}\n.options{\n top: 100%;\n padding: var(--options-padding, 0.3rem);\n margin-top: 0.2rem; \n overflow: hidden auto;\n position: absolute;\n grid-area: options;\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n -ms-flex-direction: column;\n flex-direction: column;\n width: var(--options-width, 100%);\n min-width: var(--min-width, auto);\n max-height: var(--max-height, auto);\n background: rgba(var(--foreground-color,(255,255,255)), 1);\n border: solid 1px rgba(var(--text-color,(17,17,17)), 0.2);\n border-radius: var(--options-border-radius, 0.5rem);\n z-index: 1;\n box-shadow: 0 1rem 1.5rem rgba(0 0 0 /0.2);\n}\n:host([isUnder]) .options{\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.2rem;\n box-shadow: 0 -1rem 1.5rem rgba(0 0 0 /0.2);\n}\n:host([open]) .icon--expand{\n display: none;\n}\n:host([open]) .icon--collapse{\n display: block;\n}\n.icon--expand{\n display: block;\n}\n.icon--collapse{\n display: none;\n}\n.hidden{\n display: none;\n}\n@media (any-hover: hover){\n ::-webkit-scrollbar{\n width: 0.5rem;\n height: 0.5rem;\n }\n \n ::-webkit-scrollbar-thumb{\n background: rgba(var(--text-color,(17,17,17)), 0.3);\n border-radius: 1rem;\n &:hover{\n background: rgba(var(--text-color,(17,17,17)), 0.5);\n }\n }\n}\n</style>\n<div class="select">\n <div class="selection">\n <div class="selected-option-text"></div>\n <svg class="icon icon--expand" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z"/></svg>\n <svg class="icon icon--collapse" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M24 0v24H0V0h24z" fill="none" opacity=".87"/><path d="M7.41 18.59L8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"/></svg>\n </div>\n <div part="options" class="options hidden">\n <slot></slot> \n </div>\n</div>',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.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"]}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("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()}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),this.defaultSelected=this.value},100)),new IntersectionObserver((t,e)=>{t.forEach(t=>{if(t.isIntersecting){const t=this.selection.getBoundingClientRect().left;t<window.innerWidth/2?this.setAttribute("align-select","left"):this.setAttribute("align-select","right")}})}).observe(this),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeydown)}disconnectedCallback(){this.removeEventListener("click",this.handleClick),this.removeEventListener("click",this.toggle),this.removeEventListener("keydown",this.handleKeydown)}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<style> \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</style>\n<div class=\"option\">\n <slot></slot> \n</div>",customElements.define("sm-option",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}).append(smOption.content.cloneNode(!0))}connectedCallback(){this.setAttribute("role","option")}}); |