import {Et2WidgetWithSelectMixin} from "./Et2WidgetWithSelectMixin"; import {RowLimitedMixin} from "../Layout/RowLimitedMixin"; import shoelace from "../Styles/shoelace"; import {css, html, LitElement, TemplateResult} from "lit"; import {SelectOption} from "./FindSelectOptions"; import {repeat} from "lit/directives/repeat.js"; import {property} from "lit/decorators/property.js"; import {SlMenuItem} from "@shoelace-style/shoelace"; /** * A selectbox that shows more than one row at a time * * Set rows attribute to adjust how many rows are visible at once * * Use Et2Selectbox in most cases, it's better. */ export class Et2Listbox extends RowLimitedMixin(Et2WidgetWithSelectMixin(LitElement)) { static get styles() { return [ // Parent (SlMenu) returns a single cssResult, not an array shoelace, super.styles, css` :host { display: block; flex: 1 0 auto; --icon-width: 20px; } ::slotted(img), img { vertical-align: middle; } .menu { /* Get rid of padding before/after options */ padding: 0px; /* No horizontal scrollbar, even if options are long */ overflow-x: clip; } /* Ellipsis when too small */ sl-option.option__label { display: block; text-overflow: ellipsis; /* This is usually not used due to flex, but is the basis for ellipsis calculation */ width: 10ex; } :host([rows]) .menu { height: calc(var(--rows, 5) * 1.9rem); overflow-y: auto; } ` ]; } @property({type: Boolean, reflect: true}) multiple = false; private __value : String[] | null; constructor(...args : any[]) { super(); this.handleSelect = this.handleSelect.bind(this); } connectedCallback() { super.connectedCallback(); this.addEventListener("sl-select", this.handleSelect); this.updateComplete.then(() => { this.addEventListener("sl-change", this._triggerChange); }); } private getAllItems() : SlMenuItem[] { return Array.from(this.shadowRoot?.querySelectorAll('sl-menu-item')) ?? []; } /** * Handle an item was selected * * Toggle the checkmark and fire the changed event * * @param {MouseEvent} event */ handleSelect(event : CustomEvent) { let item = event.detail?.item; if(!item) { return; } if(!this.multiple) { this.getAllItems().forEach((i) => i.checked = false); item.checked = true; } else { item.checked = !item.checked; } this.dispatchEvent(new Event("change")); } @property() get value() { let value = this.hasUpdated ? this.getAllItems() .filter((item) => item.checked) .map((item) => item.value) : this.__value ?? [] return this.multiple ? value : value.pop(); } set value(new_value : String[] | String) { const oldValue = this.value; if(typeof new_value == "string") { new_value = [new_value] } this.__value = new_value; this.requestUpdate("value", oldValue); } _optionTemplate(option : SelectOption) : TemplateResult { let icon = option.icon ? html` ` : ""; let checked = this.__value == null ? option.value === this.value || this.multiple && this.value.indexOf(option.value) >= 0 : this.__value.indexOf(option.value) >= 0; // Tag used must match this.optionTag, but you can't use the variable directly. // Pass option along so SearchMixin can grab it if needed return html` ${icon} ${this.noLang ? option.label : this.egw().lang(option.label)} `; } render() { return html` ${repeat(this.select_options, (o) => o.value, (option : SelectOption) => this._optionTemplate(option))} ` } } customElements.define("et2-listbox", Et2Listbox);