const smChips = document.createElement('template'); smChips.innerHTML = `
`; customElements.define('sm-chips', class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }).append(smChips.content.cloneNode(true)); this.chipsWrapper = this.shadowRoot.querySelector('.sm-chips'); this.coverLeft = this.shadowRoot.querySelector('.cover--left'); this.coverRight = this.shadowRoot.querySelector('.cover--right'); this.navButtonLeft = this.shadowRoot.querySelector('.nav-button--left'); this.navButtonRight = this.shadowRoot.querySelector('.nav-button--right'); this.slottedOptions = undefined; this._value = undefined; this.scrollDistance = 0; this.assignedElements = []; this.scrollLeft = this.scrollLeft.bind(this); this.scrollRight = this.scrollRight.bind(this); this.fireEvent = this.fireEvent.bind(this); this.setSelectedOption = this.setSelectedOption.bind(this); } get value() { return this._value; } set value(val) { this.setSelectedOption(val); } scrollLeft() { this.chipsWrapper.scrollBy({ left: -this.scrollDistance, behavior: 'smooth' }); } scrollRight() { this.chipsWrapper.scrollBy({ left: this.scrollDistance, behavior: 'smooth' }); } setSelectedOption(value) { if (this._value === value) return this._value = value; this.assignedElements.forEach(elem => { if (elem.value == value) { elem.setAttribute('selected', ''); elem.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }); } else elem.removeAttribute('selected') }); } 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'); slot.addEventListener('slotchange', e => { firstOptionObserver.disconnect(); lastOptionObserver.disconnect(); this.observeSelf.disconnect(); // debounce to wait for all elements to be assigned clearTimeout(this.slotChangeTimeout); this.slotChangeTimeout = setTimeout(() => { this.assignedElements = slot.assignedElements(); this.assignedElements.forEach(elem => { if (elem.hasAttribute('selected')) { this._value = elem.value; } }); this.observeSelf.observe(this); }, 0); }); 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.observeSelf = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting && !this.hasAttribute('multiline') && this.assignedElements.length > 0) { firstOptionObserver.observe(this.assignedElements[0]); lastOptionObserver.observe(this.assignedElements[this.assignedElements.length - 1]); observer.unobserve(this); } }); }, { threshold: 1, }); this.chipsWrapper.addEventListener('option-clicked', e => { if (this._value !== e.target.value) { this.setSelectedOption(e.target.value); this.fireEvent(); } }); const firstOptionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { this.navButtonLeft.classList.add('hide'); this.coverLeft.classList.add('hide'); } else { this.navButtonLeft.classList.remove('hide'); this.coverLeft.classList.remove('hide'); } }); }, { threshold: 1, root: this }); const lastOptionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { this.navButtonRight.classList.add('hide'); this.coverRight.classList.add('hide'); } else { this.navButtonRight.classList.remove('hide'); this.coverRight.classList.remove('hide'); } }); }, { threshold: 1, root: this }); this.navButtonLeft.addEventListener('click', this.scrollLeft); this.navButtonRight.addEventListener('click', this.scrollRight); } disconnectedCallback() { this.navButtonLeft.removeEventListener('click', this.scrollLeft); this.navButtonRight.removeEventListener('click', this.scrollRight); } }); const smChip = document.createElement('template'); smChip.innerHTML = ` `; customElements.define('sm-chip', class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }).append(smChip.content.cloneNode(true)); this._value = undefined; 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); } });