lion/packages/ui/components/select/LionSelect.js
2022-10-31 16:55:07 +01:00

118 lines
3.3 KiB
JavaScript

/* eslint-disable max-classes-per-file */
import { LionField } from '@lion/ui/form-core.js';
/**
* @typedef {import('@lion/localize/types/LocalizeMixinTypes').FormatNumberOptions} FormatOptions
*/
class LionFieldWithSelect extends LionField {
/** @type {any} */
static get properties() {
return { autocomplete: { type: String } };
}
constructor() {
super();
/**
* Delegates autocomplete to select
* @type {string|undefined}
*/
this.autocomplete = undefined;
}
/**
* @returns {HTMLSelectElement}
*/
get _inputNode() {
return /** @type {HTMLSelectElement} */ (
Array.from(this.children).find(el => el.slot === 'input')
);
}
}
/**
* LionSelect: wraps the native HTML element select
*
* <lion-select>
* <label slot="label">My Input</label>
* <select slot="input">
* <option value="top">top</option>
* <option value="bottom">bottom</option>
* </select>
* </lion-select>
*
* You can preselect an option by setting the property modelValue.
* Example:
* <lion-select .modelValue="${'<value of option 2>'}">
*
* It extends LionField so it inherits required and disabled.
*
* The option element needs to be a direct child of the select element.
*
* You cannot use interactive elements inside the options. Avoid very long names to
* facilitate the understandability and perceivability for screen reader users. Sets of options
* where each option name starts with the same word or phrase can also significantly degrade
* usability for keyboard and screen reader users.
*
* @customElement lion-select
*/
export class LionSelect extends LionFieldWithSelect {
connectedCallback() {
super.connectedCallback();
this._inputNode.addEventListener('change', this._proxyChangeEvent);
}
/** @param {import('@lion/core').PropertyValues } changedProperties */
updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has('disabled')) {
this._inputNode.disabled = this.disabled;
this.validate();
}
if (changedProperties.has('name')) {
this._inputNode.name = this.name;
}
if (changedProperties.has('autocomplete')) {
this._inputNode.autocomplete = /** @type {string} */ (this.autocomplete);
}
}
disconnectedCallback() {
super.disconnectedCallback();
this._inputNode.removeEventListener('change', this._proxyChangeEvent);
}
/**
* @override FormatMixin - set formattedValue to selected option text
* @param {*} v - modelValue: can be an Object, Number, String depending on the
* input type(date, number, email etc)
* @returns {string} formattedValue
*/
formatter(v) {
// The selectedIndex is not yet updated
const found = Array.from(this._inputNode.options).find(option => option.value === v);
return found ? found.text : '';
}
/**
* @override FormatMixin - set value equal to modelValue instead of formattedValue
*/
_reflectBackFormattedValueToUser() {
if (this._reflectBackOn()) {
// Text 'undefined' should not end up in <input>
this.value = typeof this.modelValue !== 'undefined' ? this.modelValue : '';
}
}
/** @protected */
_proxyChangeEvent() {
this.dispatchEvent(
new CustomEvent('user-input-changed', {
bubbles: true,
composed: true,
}),
);
}
}