/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { createRef, html, ref, classMap, ifDefined, } from "../vendor/lit.all.mjs"; import { MozBaseInputElement, MozLitElement } from "../lit-utils.mjs"; /** * A select dropdown with options provided via custom `moz-option` elements. * * @tagname moz-select * @property {string} label - The text of the label element * @property {string} name - The name of the input control * @property {string} value - The value of the selected option * @property {boolean} disabled - The disabled state of the input control * @property {string} iconSrc - The src for an optional icon * @property {string} description - The text for the description element that helps describe the input control * @property {string} supportPage - Name of the SUMO support page to link to. * @property {string} ariaLabel - The aria-label text when there is no visible label. * @property {string} ariaDescription - The aria-description text when there is no visible description. * @property {array} options - The array of options, populated by children in the * default slot. Do not set directly, these will be overridden by children. */ export default class MozSelect extends MozBaseInputElement { #optionIconSrcMap = new Map(); static properties = { options: { type: Array, state: true }, }; static inputLayout = "block"; constructor() { super(); this.value = ""; this.options = []; this.slotRef = createRef(); this.optionsMutationObserver = new MutationObserver( this.populateOptions.bind(this) ); } firstUpdated(changedProperties) { super.firstUpdated(changedProperties); this.optionsMutationObserver.observe(this, { attributeFilter: ["label", "value", "iconsrc"], childList: true, subtree: true, }); } update(changedProperties) { super.update(changedProperties); if (this.hasUpdated && changedProperties.has("options")) { // Match the select's value on initial render or options change. this.value = this.inputEl.value; } } get _selectedOptionIconSrc() { if (!this.inputEl || !this.options.length) { return ""; } return this.#optionIconSrcMap.get(this.value) ?? ""; } /** * Internal - populates the select element with options from the light DOM slot. */ populateOptions() { this.options = []; this.#optionIconSrcMap.clear(); for (const node of this.slotRef.value.assignedNodes()) { if (node.localName === "moz-option") { const optionValue = node.getAttribute("value"); const optionLabel = node.getAttribute("label"); const optionIconSrc = node.getAttribute("iconsrc"); this.options.push({ value: optionValue, label: optionLabel, iconSrc: optionIconSrc, }); if (optionIconSrc) { this.#optionIconSrcMap.set(optionValue, optionIconSrc); } } } } /** * Handles change events and updates the selected value. * * @param {Event} event * @memberof MozSelect */ handleStateChange(event) { this.value = event.target.value; } /** * @type {MozBaseInputElement['inputStylesTemplate']} */ inputStylesTemplate() { return html` `; } selectedOptionIconTemplate() { if (this._selectedOptionIconSrc) { return html``; } return null; } inputTemplate() { const classes = classMap({ "select-wrapper": true, "with-icon": !!this._selectedOptionIconSrc, }); return html`
${this.selectedOptionIconTemplate()}
`; } } customElements.define("moz-select", MozSelect); /** * A custom option element for use in moz-select. * * @tagname moz-option * @property {string} value - The value of the option * @property {string} label - The label of the option * @property {string} iconSrc - The path to the icon of the the option */ export class MozOption extends MozLitElement { static properties = { // Reflect the attribute so that moz-select can detect changes with a MutationObserver value: { type: String, reflect: true }, // Reflect the attribute so that moz-select can detect changes with a MutationObserver label: { type: String, reflect: true, fluent: true }, iconSrc: { type: String, reflect: true }, }; constructor() { super(); this.value = ""; this.label = ""; this.iconSrc = ""; } render() { // This is just a placeholder to pass values into moz-select. return ""; } } customElements.define("moz-option", MozOption);