import { html, nothing, type TemplateResult } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { BootstrapElement, defineElement, type Size } from '@bootstrap-wc/core'; export interface SelectOption { value: string; label: string; disabled?: boolean; selected?: boolean; } type SlottedOption = { kind: 'option'; value: string; label: string; disabled: boolean; selected: boolean; }; type SlottedGroup = { kind: 'group'; label: string; disabled: boolean; children: SlottedOption[]; }; type SlottedHr = { kind: 'hr' }; type SlottedNode = SlottedOption | SlottedGroup | SlottedHr; /** * `` — Bootstrap `.form-select`. Renders the native ``. */ export class BsSelect extends BootstrapElement { static formAssociated = true; @property({ type: String }) value = ''; @property({ type: String }) name = ''; @property({ type: String }) autocomplete?: string; @property({ type: Array }) options: SelectOption[] = []; @property({ type: String }) size?: Size; @property({ type: Number }) rows?: number; @property({ type: Boolean, reflect: true }) disabled = false; @property({ type: Boolean, reflect: true }) required = false; @property({ type: Boolean }) multiple = false; @property({ type: Boolean }) invalid = false; @property({ type: Boolean }) valid = false; @query('select') private _select!: HTMLSelectElement; @state() private _initialSlotted: SlottedNode[] = []; protected override createRenderRoot(): HTMLElement { return this; } override connectedCallback() { // Snapshot author-provided option/optgroup/hr children BEFORE Lit's // light-DOM render replaces them. if (!this._initialSlotted.length) { this._initialSlotted = this._readUserOptions(); // Warn the author if they slotted children we don't recognise — most // commonly a custom-tag `` typo. Without this warning the // bad children would render as inert text via display:contents. this._warnOnUnknownChildren(); // Remove the user's option/optgroup/hr nodes; the render template // re-emits them inside the native . Did you mean `, ) : nothing} ${this._initialSlotted.map((n) => { if (n.kind === 'option') return this._renderOption(n); if (n.kind === 'hr') return html`
`; return html` ${n.children.map((c) => this._renderOption(c))} `; })} `; } } defineElement('bs-select', BsSelect); declare global { interface HTMLElementTagNameMap { 'bs-select': BsSelect; } }