@universal-material/web
Version:
Material web components
268 lines • 9.3 kB
JavaScript
import { __decorate } from "tslib";
import { html, svg } from 'lit';
import { customElement, query, state } from 'lit/decorators.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;
#mutationObserver;
#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);
}
/**
* 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._nativeSelect.selectedOptions[0]._parent] : [];
}
get _options() {
const options = Array.from(this.querySelectorAll('u-option'));
return options.filter(o => o instanceof UmOption);
}
constructor() {
super();
this._nativeSelect = (() => {
const select = document.createElement('select');
select.setAttribute('tabindex', '-1');
select.setAttribute('part', 'select');
return 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.#connected = false;
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.#setSelectedOption();
};
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.#mutationObserver = new MutationObserver(() => this._updateOptions());
this.#mutationObserver.observe(this, {
characterData: true,
childList: true,
subtree: true,
});
}
_updateOptions() {
const options = this._options;
for (const option of options) {
option._select = this;
}
this.#updateOptions(options);
this.#updateAccessibilityList(options);
const selectedOption = this.selectedOptions[0];
if (!selectedOption) {
return;
}
selectedOption.selected = selectedOption.selected;
this.empty = !selectedOption.textContent?.trim();
// this._button.setAttribute('aria-labelledby', selectedOption._listItem.id);
}
#updateOptions(options) {
const maxLength = Math.max(options.length, this._nativeSelect.children.length);
for (let i = 0; i < maxLength; i++) {
const option = options[i];
const nativeOption = this._nativeSelect.children[i];
if (!option) {
nativeOption.remove();
continue;
}
option._nativeOption.textContent = option.textContent;
if (!nativeOption) {
this._nativeSelect.appendChild(option._nativeOption);
continue;
}
nativeOption.insertAdjacentElement('beforebegin', option._nativeOption);
}
}
#updateAccessibilityList(options) {
const maxLength = Math.max(options.length, this.#list.children.length);
for (let i = 0; i < maxLength; i++) {
const option = options[i];
let item = this.#list.children[i];
if (!option) {
item.remove();
continue;
}
if (!item) {
item = this.#createListItem(`item-${i + 1}`);
this.#list.appendChild(item);
}
item.textContent = option.textContent;
}
}
#createListItem(id) {
const item = document.createElement('div');
item.role = 'option';
item.id = id;
item.textContent = this.textContent;
return item;
}
#setSelectedOption() {
const options = this._options;
const selectedClassOptions = options.filter(o => o.classList.contains('selected'));
let found = false;
for (const option of selectedClassOptions) {
if (option.selected) {
found = true;
continue;
}
option.classList.remove('selected');
}
if (found) {
return;
}
const selectedOption = this.selectedOptions[0];
if (selectedOption) {
selectedOption.classList.add('selected');
}
}
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>
<slot></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() {
await this.updateComplete;
this._nativeSelect.disabled = this.hasAttribute('disabled');
this.#navigationController.attach(this);
this._updateOptions();
if (this._nativeSelect.parentElement !== this._input) {
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);
}
async #detach() {
await this.updateComplete;
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);
}
get _menuItems() {
return this._options;
}
};
__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([
state()
], UmSelect.prototype, "selectedIndex", null);
UmSelect = __decorate([
customElement('u-select')
], UmSelect);
export { UmSelect };
//# sourceMappingURL=select.js.map