UNPKG

@universal-material/web

Version:
222 lines 8.02 kB
import { __decorate } from "tslib"; import { html, render, svg } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { map } from 'lit/directives/map.js'; import { html as staticHtml } from 'lit/static-html.js'; import { UmTextFieldBase } from '../shared/text-field-base/text-field-base.js'; import { UmOption } from './option.js'; import { SelectNavigationController } from './select-navigation-controller.js'; import { styles } from './select.styles.js'; import './option.js'; let UmSelect = class UmSelect extends UmTextFieldBase { static { this.styles = [UmTextFieldBase.styles, styles]; } #list; #navigationController; #resizeObserver; #connected; /** * The `value` of the selected option */ get value() { return this._nativeSelect.value; } set value(value) { this._nativeSelect.value = value; if (!this.#connected) { return; } this.elementInternals.setFormValue(value); for (const option of this._options) { option._writeNativeSelected(); } } /** * The index of the selected option. When there's no selected option the value is `-1`. */ get selectedIndex() { return this._nativeSelect.selectedIndex; } set selectedIndex(index) { this._nativeSelect.selectedIndex = index; } /** * An `Array` containing the selected `UmOption` or empty if there's no selected option. Multiple selection is not supported. */ get selectedOptions() { return this._nativeSelect.selectedOptions.length ? [this._options[this._nativeSelect.selectedIndex]] : []; } get _options() { const options = Array.from(this.querySelectorAll('u-option')); return options.filter(o => o instanceof UmOption); } constructor() { super(); this._nativeSelect = document.createElement('select'); this.#list = (() => { const list = document.createElement('div'); list.role = 'listbox'; list.id = 'list'; list.className = 'list'; return list; })(); this.#navigationController = new SelectNavigationController(this); this.#resizeObserver = new ResizeObserver(() => this.#setMenuWidthProperty()); this.#connected = false; this.menuPositioning = 'relative'; this.#handleClick = (e) => { this._menu.toggle(); if (!this._menu.open || !this.selectedOptions.length) { return; } this.#navigationController.focusMenu(this.selectedOptions[0], 0, e.detail === 0, false); }; this.#handleMenuOpen = () => { this._button.setAttribute('aria-expanded', 'true'); }; this.#handleMenuOpened = () => { if (!this.selectedOptions.length) { return; } const option = this.selectedOptions[0]; option.scrollIntoView({ block: 'nearest' }); }; this.#handleMenuClose = () => { this._button.setAttribute('aria-expanded', 'false'); this.#navigationController.blurMenu(); }; this._nativeSelect.setAttribute('tabindex', '-1'); this._nativeSelect.setAttribute('part', 'select'); } #setMenuWidthProperty() { this.style.setProperty('--_menu-width', `${this.clientWidth}px`); } renderControl() { return staticHtml ` <button class="button" role="combobox" aria-expanded="false" aria-owns="select" ?disabled=${this.disabled}></button> <div class="input"></div>`; } renderAfterContent() { return html ` <u-menu positioning="${this.menuPositioning}"> <slot @slotchange=${this._renderOptionRelatedElements}></slot> </u-menu> `; } renderDefaultTrailingIcon() { return svg ` <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 -960 960 960" width="1em" fill="currentColor"> <path d="M480-360 280-560h400L480-360Z"/> </svg>`; } updated(changedProperties) { super.updated(changedProperties); this.empty = !this.selectedOptions[0]?.textContent?.trim(); this.elementInternals.setFormValue(this._nativeSelect.value || null); } attributeChangedCallback(name, _old, value) { super.attributeChangedCallback(name, _old, value); if (name !== 'disabled') { return; } this._nativeSelect.disabled = value === null; } connectedCallback() { super.connectedCallback(); this.#connected = true; this.#attach(); } disconnectedCallback() { super.disconnectedCallback(); this.#detach(); } #handleClick; #handleMenuClick(e) { e.stopPropagation(); } #handleMenuOpen; #handleMenuOpened; #handleMenuClose; async #attach() { this.#resizeObserver.observe(this); this._renderOptionRelatedElements(); await this.updateComplete; this._nativeSelect.disabled = this.hasAttribute('disabled'); this.#navigationController.attach(this); this._input.appendChild(this._nativeSelect); this._input.appendChild(this.#list); this._button.addEventListener('click', this.#handleClick); this._menu.anchorElement = this._container; this._menu.addEventListener('click', this.#handleMenuClick); this._menu.addEventListener('open', this.#handleMenuOpen); this._menu.addEventListener('opened', this.#handleMenuOpened); this._menu.addEventListener('close', this.#handleMenuClose); } #detach() { this.#resizeObserver.disconnect(); this._nativeSelect.remove(); this.#list.remove(); this.#navigationController.detach(); this.#connected = false; this._button.removeEventListener('click', this.#handleClick); this._menu.removeEventListener('click', this.#handleMenuClick); this._menu.removeEventListener('open', this.#handleMenuOpen); this._menu.removeEventListener('opened', this.#handleMenuOpened); this._menu.removeEventListener('close', this.#handleMenuClose); } _renderOptionRelatedElements() { this.#renderNativeOptions(); this.#renderAccessibilityList(); this._updateEmpty(); this._syncSelectedOptions(); } _updateEmpty() { this.empty = !this.selectedOptions[0]?.textContent?.trim(); } get _menuItems() { return this._options; } #renderNativeOptions() { render(map(this._options, option => html `<option value=${option.value} ?selected=${option.selected}>${option.textContent}</option>`), this._nativeSelect); } #renderAccessibilityList() { render(map(this._options, (option, index) => html `<div role="option" id="${`item-${index + 1}`}">${option.textContent}</div>`), this.#list); } _syncSelectedOptions() { for (let i = 0; i < this._options.length; i++) { const option = this._options[i]; const nativeOption = this._nativeSelect.children[i]; option.selected = nativeOption.selected; option._nativeOption = nativeOption; } } }; __decorate([ state() ], UmSelect.prototype, "value", null); __decorate([ query('u-menu', true) ], UmSelect.prototype, "_menu", void 0); __decorate([ query('.button', true) ], UmSelect.prototype, "_button", void 0); __decorate([ query('.input', true) ], UmSelect.prototype, "_input", void 0); __decorate([ property({ reflect: true, attribute: 'menu-positioning' }) ], UmSelect.prototype, "menuPositioning", void 0); __decorate([ state() ], UmSelect.prototype, "selectedIndex", null); UmSelect = __decorate([ customElement('u-select') ], UmSelect); export { UmSelect }; //# sourceMappingURL=select.js.map