const smSelect = document.createElement('template') smSelect.innerHTML = `
`; customElements.define('sm-select', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(smSelect.content.cloneNode(true)) this.reset = this.reset.bind(this) } static get observedAttributes() { return ['value'] } get value() { return this.getAttribute('value') } set value(val) { this.setAttribute('value', val) } reset(){ } collapse() { this.chevron.classList.remove('rotate') this.optionList.animate(this.slideUp, this.animationOptions) .onfinish = () => { this.optionList.classList.add('hide') this.open = false } } connectedCallback() { this.availableOptions this.optionList = this.shadowRoot.querySelector('.options') this.chevron = this.shadowRoot.querySelector('.toggle') let slot = this.shadowRoot.querySelector('.options slot'), selection = this.shadowRoot.querySelector('.selection'), previousOption this.open = false; 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' } selection.addEventListener('click', e => { if (!this.open) { this.optionList.classList.remove('hide') this.optionList.animate(this.slideDown, this.animationOptions) this.chevron.classList.add('rotate') this.open = true } else { this.collapse() } }) selection.addEventListener('keydown', e => { if (e.code === 'ArrowDown' || e.code === 'ArrowRight') { e.preventDefault() this.availableOptions[0].focus() } if (e.code === 'Enter' || e.code === 'Space') if (!this.open) { this.optionList.classList.remove('hide') this.optionList.animate(this.slideDown, this.animationOptions) this.chevron.classList.add('rotate') this.open = true } else { this.collapse() } }) this.optionList.addEventListener('keydown', e => { if (e.code === 'ArrowUp' || e.code === 'ArrowRight') { e.preventDefault() if (document.activeElement.previousElementSibling) { document.activeElement.previousElementSibling.focus() } else { this.availableOptions[this.availableOptions.length - 1].focus() } } if (e.code === 'ArrowDown' || e.code === 'ArrowLeft') { e.preventDefault() if (document.activeElement.nextElementSibling) { document.activeElement.nextElementSibling.focus() } else{ this.availableOptions[0].focus() } } }) this.addEventListener('optionSelected', e => { if (previousOption !== e.target) { this.setAttribute('value', e.detail.value) this.shadowRoot.querySelector('.option-text').textContent = e.detail.text; this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: e.detail.value } })) if (previousOption) { previousOption.classList.remove('check-selected') } previousOption = e.target; } if (!e.detail.switching) this.collapse() e.target.classList.add('check-selected') }) slot.addEventListener('slotchange', e => { this.availableOptions = slot.assignedElements() if (this.availableOptions[0]) { let firstElement = this.availableOptions[0]; previousOption = firstElement; firstElement.classList.add('check-selected') this.setAttribute('value', firstElement.getAttribute('value')) this.shadowRoot.querySelector('.option-text').textContent = firstElement.textContent this.availableOptions.forEach((element, index) => { element.setAttribute('data-rank', index + 1); element.setAttribute('tabindex', "0"); }) } }); document.addEventListener('mousedown', e => { if (!this.contains(e.target) && this.open) { this.collapse() } }) } }) // option const smOption = document.createElement('template') smOption.innerHTML = `
`; customElements.define('sm-option', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(smOption.content.cloneNode(true)) } sendDetails(switching) { let optionSelected = new CustomEvent('optionSelected', { bubbles: true, composed: true, detail: { text: this.textContent, value: this.getAttribute('value'), switching: switching } }) this.dispatchEvent(optionSelected) } connectedCallback() { let validKey = [ 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight' ] this.addEventListener('click', e => { this.sendDetails() }) this.addEventListener('keyup', e => { if (e.code === 'Enter' || e.code === 'Space') { e.preventDefault() this.sendDetails(false) } if (validKey.includes(e.code)) { e.preventDefault() this.sendDetails(true) } }) if (this.hasAttribute('default')) { setTimeout(() => { this.sendDetails() }, 0); } } })