const stripSelect = document.createElement('template') stripSelect.innerHTML = `
` customElements.define('strip-select', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(stripSelect.content.cloneNode(true)) this.stripSelect = this.shadowRoot.querySelector('.strip-select') this.slottedOptions this._value this.scrollDistance this.scrollLeft = this.scrollLeft.bind(this) this.scrollRight = this.scrollRight.bind(this) this.fireEvent = this.fireEvent.bind(this) } get value() { return this._value } scrollLeft() { this.stripSelect.scrollBy({ left: -this.scrollDistance, behavior: 'smooth' }) } scrollRight() { this.stripSelect.scrollBy({ left: this.scrollDistance, behavior: 'smooth' }) } fireEvent() { this.dispatchEvent( new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this._value } }) ) } connectedCallback() { this.setAttribute('role', 'listbox') const slot = this.shadowRoot.querySelector('slot') const coverLeft = this.shadowRoot.querySelector('.cover--left') const coverRight = this.shadowRoot.querySelector('.cover--right') const navButtonLeft = this.shadowRoot.querySelector('.nav-button--left') const navButtonRight = this.shadowRoot.querySelector('.nav-button--right') slot.addEventListener('slotchange', e => { const assignedElements = slot.assignedElements() assignedElements.forEach(elem => { if (elem.hasAttribute('selected')) { elem.setAttribute('active', '') this._value = elem.value } }) if (!this.hasAttribute('multiline')) { if (assignedElements.length > 0) { firstOptionObserver.observe(slot.assignedElements()[0]) lastOptionObserver.observe(slot.assignedElements()[slot.assignedElements().length - 1]) } else { navButtonLeft.classList.add('hide') navButtonRight.classList.add('hide') coverLeft.classList.add('hide') coverRight.classList.add('hide') firstOptionObserver.disconnect() lastOptionObserver.disconnect() } } }) const resObs = new ResizeObserver(entries => { entries.forEach(entry => { if (entry.contentBoxSize) { // Firefox implements `contentBoxSize` as a single content rect, rather than an array const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize; this.scrollDistance = contentBoxSize.inlineSize * 0.6 } else { this.scrollDistance = entry.contentRect.width * 0.6 } }) }) resObs.observe(this) this.stripSelect.addEventListener('option-clicked', e => { if (this._value !== e.target.value) { this._value = e.target.value slot.assignedElements().forEach(elem => elem.removeAttribute('active')) e.target.setAttribute('active', '') e.target.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }) this.fireEvent() } }) const firstOptionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { navButtonLeft.classList.add('hide') coverLeft.classList.add('hide') } else { navButtonLeft.classList.remove('hide') coverLeft.classList.remove('hide') } }) }, { threshold: 0.9, root: this }) const lastOptionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { navButtonRight.classList.add('hide') coverRight.classList.add('hide') } else { navButtonRight.classList.remove('hide') coverRight.classList.remove('hide') } }) }, { threshold: 0.9, root: this }) navButtonLeft.addEventListener('click', this.scrollLeft) navButtonRight.addEventListener('click', this.scrollRight) } disconnectedCallback() { navButtonLeft.removeEventListener('click', this.scrollLeft) navButtonRight.removeEventListener('click', this.scrollRight) } }) //Strip option const stripOption = document.createElement('template') stripOption.innerHTML = ` ` customElements.define('strip-option', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(stripOption.content.cloneNode(true)) this._value 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: true, composed: true, detail: { value: this._value } }) ) } handleKeyDown(e) { if (e.key === 'Enter' || e.key === 'Space') { 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) } })