const smMenu = document.createElement('template') smMenu.innerHTML = `
`; customElements.define('sm-menu', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(smMenu.content.cloneNode(true)) this.open = false; this.availableOptions this.containerDimensions this.optionList = this.shadowRoot.querySelector('.options') this.menu = this.shadowRoot.querySelector('.menu') this.icon = this.shadowRoot.querySelector('.icon') this.expand = this.expand.bind(this) } static get observedAttributes() { return ['value'] } get value() { return this.getAttribute('value') } set value(val) { this.setAttribute('value', val) } expand(){ if (!this.open) { this.optionList.classList.remove('hide') this.optionList.classList.add('no-transformations') this.open = true this.icon.classList.add('focused') this.availableOptions.forEach(option => { option.setAttribute('tabindex', '0') }) } } collapse() { if (this.open) { this.open = false this.icon.classList.remove('focused') this.optionList.classList.add('hide') this.optionList.classList.remove('no-transformations') this.availableOptions.forEach(option => { option.removeAttribute('tabindex') }) } } connectedCallback() { this.setAttribute('role', 'listbox') const slot = this.shadowRoot.querySelector('.options slot') slot.addEventListener('slotchange', e => { this.availableOptions = slot.assignedElements() this.containerDimensions = this.optionList.getBoundingClientRect() }); this.menu.addEventListener('click', e => { if (!this.open) { this.expand() } else { this.collapse() } }) this.menu.addEventListener('keydown', e => { if (e.code === 'ArrowDown' || e.code === 'ArrowRight') { e.preventDefault() this.availableOptions[0].focus() } if (e.code === 'Enter' || e.code === 'Space') { e.preventDefault() if (!this.open) { this.expand() } 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.optionList.addEventListener('click', e => { this.collapse() }) window.addEventListener('mousedown', e => { if (!this.contains(e.target) && e.button !== 2) { this.collapse() } }) } }) // option const menuOption = document.createElement('template') menuOption.innerHTML = `
`; customElements.define('menu-option', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(menuOption.content.cloneNode(true)) } connectedCallback() { this.setAttribute('role', 'option') this.addEventListener('keyup', e => { if (e.code === 'Enter' || e.code === 'Space') { e.preventDefault() this.click() } }) } })