import { LitElement, css, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { translateText } from "../../Utils"; interface selectItems { label: string; value: any; image?: string; } @customElement("o-select") export class OSelect extends LitElement { /** * Array of selectable items. */ @property({ type: Array }) items: selectItems[] = []; /** * Currently selected value. */ @property({ type: String }) selectedValue: string = ""; /** * Error message to display. */ @property({ type: String }) errorMessage: string = ""; /** * Enables search filtering in the dropdown. */ @property({ type: Boolean }) filterEnabled: boolean = false; /** * If true, show image next to label. */ @property({ type: Boolean }) showImageWithLabel: boolean = false; /** * Label shown above the select. */ @property({ type: String }) label: string = ""; @property({ type: String }) translationKey = ""; @state() private selectedItem: selectItems | null = null; @state() private filter: string = ""; @state() private isOpen: boolean = false; static styles = css` .c-label { color: var(--fontColorLight); font-size: 14px; } .c-select { position: relative; cursor: pointer; padding: 0 10px; border: 1px solid transparent; border-radius: var(--borderRadius--md); background: #1e1e1e; height: 50px; &.is-error { border-color: var(--errorColor); + .c-message { color: var(--errorColor); } } } .c-select__display { align-items: center; color: var(--fontColorLight); width: 100%; display: flex; gap: 10px; span { flex: 1; } img { width: 24px; } } .c-select__listWrapper { border: 1px solid var(--secondaryBorderColor); position: absolute; left: -1px; right: -1px; background: var(--boxBackgroundColor); backdrop-filter: blur(var(--blur-md)); top: 50px; z-index: 1000; } .c-select__list { list-style: none; overflow: scroll; max-height: 35dvh; display: flex; flex-direction: column; gap: 10px; padding: 5px; margin: 0; } .c-select__item { align-items: center; display: flex; min-height: 35px; color: var(--fontColorLight); gap: 10px; img { max-width: 35px; height: auto; } } .c-select__input { outline: none; padding: 4px 8px; margin-bottom: 5px; } `; connectedCallback() { super.connectedCallback(); window.addEventListener("click", this._handleOutsideClick); } disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("click", this._handleOutsideClick); } willUpdate(changedProps: Map) { if (changedProps.has("items") || changedProps.has("selectedValue")) { const match = this.items.find( (item) => item.value === this.selectedValue, ); if (match) { this.selectedItem = match; } } } private _handleOutsideClick = (event: MouseEvent) => { if (!this.contains(event.target as Node)) { this.isOpen = false; } }; private selectItem(item: selectItems) { this.selectedItem = item; this.filter = ""; this.isOpen = false; this.dispatchEvent( new CustomEvent("o-select-change", { detail: item.value, bubbles: true, composed: true, }), ); } private renderSelectedDisplay() { if (!this.selectedItem) { return html`Select`; } const { image, label } = this.selectedItem; if (this.showImageWithLabel) { return html` ${image ? html`${label} flag` : null} ${label} `; } return image ? html`${label} flag` : html`${label}`; } get filteredItems() { return this.items.filter((item) => item.label.toLowerCase().includes(this.filter.toLowerCase()), ); } render() { return html`
(this.isOpen = !this.isOpen)} > ${this.label ? html`` : null}
${this.renderSelectedDisplay()}
${this.isOpen ? html`
${this.filterEnabled ? html` { this.filter = (e.target as HTMLInputElement).value; e.stopPropagation(); e.stopImmediatePropagation(); }} @click=${(e: Event) => e.stopPropagation()} /> ` : null}
    ${this.filteredItems.map( (item) => html`
  • { this.selectItem(item); e.stopPropagation(); e.stopImmediatePropagation(); }} > ${item.image ? html`${item.label}` : ""}
    ${item.label}
  • `, )}
` : ""}
${this.errorMessage ? html`
${this.errorMessage}
` : ""} `; } }