luxen-ui 0.21.2 → 0.21.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdn/custom-elements.json +214 -214
- package/cdn/elements/avatar/avatar.js +1 -1
- package/cdn/elements/avatar/avatar.js.map +1 -1
- package/cdn/elements/combobox/combobox.js +1 -1
- package/cdn/elements/combobox/combobox.js.map +1 -1
- package/cdn/elements/dialog/dialog.styles.js +1 -1
- package/cdn/elements/dialog/dialog.styles.js.map +1 -1
- package/cdn/elements/dropdown-label/dropdown-label.js +1 -1
- package/cdn/elements/dropdown-label/dropdown-label.js.map +1 -1
- package/cdn/elements/select/select.js +1 -1
- package/cdn/elements/select/select.js.map +1 -1
- package/cdn/elements/stories-viewer/stories-viewer.js +1 -1
- package/cdn/elements/stories-viewer/stories-viewer.js.map +1 -1
- package/cdn/standalone.css +9 -9
- package/cdn/styles/elements/disclosure.css +1 -1
- package/cdn/styles/elements/divider.css +1 -1
- package/cdn/styles/elements/kbd.css +1 -1
- package/cdn/styles/elements/prose-editor.css +1 -1
- package/cdn/styles/elements/segmented-control.css +1 -1
- package/cdn/styles/elements/select.css +1 -1
- package/cdn/styles/elements/story.css +1 -1
- package/cdn/styles/elements/tabs/enclosed.css +1 -1
- package/cdn/styles/elements/tabs/line.css +1 -1
- package/dist/css/elements/disclosure.css +1 -1
- package/dist/css/elements/divider.css +1 -1
- package/dist/css/elements/kbd.css +1 -1
- package/dist/css/elements/prose-editor.css +1 -1
- package/dist/css/elements/segmented-control.css +1 -1
- package/dist/css/elements/select.css +1 -1
- package/dist/css/elements/story.css +1 -1
- package/dist/css/elements/tabs/enclosed.css +1 -1
- package/dist/css/elements/tabs/line.css +1 -1
- package/dist/custom-elements.json +214 -214
- package/dist/elements/avatar/avatar.css +2 -2
- package/dist/elements/combobox/combobox.css +1 -1
- package/dist/elements/dialog/dialog.css +1 -1
- package/dist/elements/dropdown-label/dropdown-label.css +1 -1
- package/dist/elements/select/select.css +2 -2
- package/dist/elements/stories-viewer/stories-viewer.css +1 -1
- package/dist/metadata/index.json +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.js","names":[],"sources":["../../../src/html/elements/combobox/combobox.css?inline","../../../src/html/elements/combobox/combobox.ts"],"sourcesContent":[":host {\n --height: var(--l-form-control-height);\n --border-radius: var(--l-form-control-border-radius);\n --background: var(--l-color-surface-overlay);\n\n display: block;\n inline-size: 100%;\n}\n\n:host([size='xs']) {\n --height: var(--l-size-control-xs);\n}\n:host([size='sm']) {\n --height: var(--l-size-control-sm);\n}\n:host([size='lg']) {\n --height: var(--l-size-control-lg);\n}\n:host([size='xl']) {\n --height: var(--l-size-control-xl);\n}\n\n:host([disabled]) {\n opacity: 0.5;\n pointer-events: none;\n}\n\n.base {\n position: relative;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅲🅾🅽🆃🆁🅾🅻 (trigger)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.control {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n block-size: var(--height);\n padding-inline: var(--l-form-control-padding-inline);\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: var(--border-radius);\n background-color: var(--l-form-control-background-color);\n cursor: text;\n}\n\n@media (hover: hover) {\n .control:hover {\n border-color: var(--l-form-control-border-color-hover);\n }\n}\n\n.control:has(.input:focus-visible) {\n border-color: var(--l-focus-ring);\n box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);\n}\n\n.input {\n flex: 1;\n min-inline-size: 0;\n border: 0;\n background: transparent;\n color: var(--l-form-control-value-color);\n font: inherit;\n outline: none;\n}\n\n.input::placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.chevron {\n flex: none;\n inline-size: 1em;\n block-size: 1em;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_chevron-icon) center / contain no-repeat;\n transition: rotate 0.15s ease;\n}\n\n/* ≥24px hit area (WCAG 2.5.8) with a 1em icon centred inside. */\n.clear {\n flex: none;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border: 0;\n padding: 0;\n cursor: pointer;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_x-icon) center / 1em no-repeat;\n}\n\n@media (hover: hover) {\n .clear:hover {\n color: var(--l-form-control-value-color);\n }\n}\n\n.control:has(.input[aria-expanded='true']) .chevron {\n rotate: 180deg;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅿🅰🅽🅴🅻 (popover listbox)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.panel {\n inset: auto;\n margin: 0;\n padding: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius);\n background-color: var(--background);\n box-shadow: var(--l-shadow-md);\n overflow: hidden;\n opacity: 0;\n scale: 0.98;\n transition:\n opacity 0.12s ease,\n scale 0.12s ease,\n display 0.12s allow-discrete,\n overlay 0.12s allow-discrete;\n}\n\n.panel:popover-open {\n opacity: 1;\n scale: 1;\n}\n\n@starting-style {\n .panel:popover-open {\n opacity: 0;\n scale: 0.98;\n }\n}\n\n.listbox {\n margin: 0;\n padding: 0.25rem;\n list-style: none;\n max-block-size: 16rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n min-block-size: 2rem;\n padding-block: 0.25rem;\n padding-inline: 0.5rem var(--height);\n position: relative;\n border-radius: calc(var(--border-radius) - 2px);\n color: var(--l-color-text-primary);\n cursor: pointer;\n}\n\n.option[aria-selected='true'] {\n background-color: var(--l-color-bg-state-hover);\n}\n\n.option[aria-disabled='true'] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Checkmark on the currently committed value. */\n.option[data-current]::after {\n content: '';\n position: absolute;\n inset-inline-end: 0.5rem;\n inline-size: 1em;\n block-size: 1em;\n background-color: var(--l-form-control-activated-color);\n mask: var(--_check-icon) center / contain no-repeat;\n}\n\n.option-label {\n flex: 1;\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-label mark {\n background: transparent;\n color: inherit;\n font-weight: 600;\n}\n\n/* Rich options — same authoring classes as l-select, styled inside the shadow. */\n.option-rich {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-media {\n flex: none;\n display: inline-flex;\n align-items: center;\n font-size: 1.25em;\n}\n\n.option-rich .l-select-item-text {\n display: flex;\n flex-direction: column;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-rich .l-select-item-description {\n font-size: 0.85em;\n color: var(--l-color-text-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.empty {\n padding: 0.5rem;\n color: var(--l-color-text-tertiary);\n text-align: center;\n}\n\n.visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅸🅲🅾🅽🆂\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n:host {\n --_chevron-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");\n --_x-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E\");\n --_check-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E\");\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel,\n .chevron {\n transition-duration: 0ms;\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport type { TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { LuxenFormAssociatedElement } from '../../shared/luxen-form-associated-element.js';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { ListboxNavController } from '../../shared/controllers/listbox-nav.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls, uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './combobox.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type ComboboxSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** A single option read from the `<datalist>`. */\nexport interface ComboboxItem {\n value: string;\n /** Plain text used for filtering and the input display. */\n label: string;\n disabled: boolean;\n /** Rich inner markup (from a non-empty `<option>`), projected into the row. */\n html?: string;\n}\n\n/** Default filter: accent/case-insensitive, every space-separated keyword must match. */\nexport type ComboboxFilter = (item: ComboboxItem, query: string) => boolean;\n\nfunction normalize(s: string): string {\n return s\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase();\n}\n\nconst defaultFilter: ComboboxFilter = (item, query) => {\n if (!query) return true;\n const label = normalize(item.label);\n return normalize(query)\n .split(/\\s+/)\n .filter(Boolean)\n .every((kw) => label.includes(kw));\n};\n\n/** Fired when an option is committed (selected). Bubbles; not composed. */\nexport class ComboboxChangeEvent extends Event {\n readonly value: string;\n constructor(value: string) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\n/** Fired as the user types (filter query changes). Bubbles; not composed. */\nexport class ComboboxInputEvent extends Event {\n /** The current query text. */\n readonly value: string;\n constructor(value: string) {\n super('input', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface ComboboxEventMap {\n change: ComboboxChangeEvent;\n input: ComboboxInputEvent;\n}\n\n/**\n * A text input paired with a filterable list of options (the ARIA combobox\n * pattern). Options are authored as a native `<datalist>` of `<option>` —\n * the same authoring surface as `l-select`. Form-associated.\n *\n * @summary A searchable text input with a filtered list of options.\n *\n * @example\n * ```html\n * <l-combobox label=\"Country\" name=\"country\" placeholder=\"Search…\">\n * <datalist>\n * <option value=\"us\">United States</option>\n * <option value=\"fr\">France</option>\n * </datalist>\n * </l-combobox>\n * ```\n *\n * @event change - Fired when an option is selected. Bubbles. Not cancelable. Properties: `value: string`.\n * @event input - Fired as the user types. Bubbles. Not cancelable. Properties: `value: string` (the query).\n * @event show - Fired before the list opens. Cancelable.\n * @event hide - Fired before the list closes. Cancelable.\n *\n * @csspart base - The host wrapper.\n * @csspart control - The input control row (input + clear + chevron).\n * @csspart input - The text input.\n * @csspart panel - The floating popover panel.\n * @csspart listbox - The options container.\n * @csspart option - Each option row.\n * @csspart empty - The \"no results\" message.\n *\n * @cssproperty [--height] - Control height. Defaults to the form-control height.\n * @cssproperty [--border-radius] - Control + panel radius.\n * @cssproperty [--background] - Panel background.\n *\n * @customElement l-combobox\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Combobox extends LuxenFormAssociatedElement {\n static override styles = [hostStyles, styles];\n\n private _localize = new LocalizeController(this);\n private readonly _listId = uniqueId('combobox-list');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._controlEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _nav = new ListboxNavController(this, {\n listboxId: this._listId,\n getCount: () => this._filtered.length,\n isDisabled: (i) => this._filtered[i]?.disabled ?? false,\n isOpen: () => this._open,\n open: () => this.show(),\n getOptionElements: () => this.shadowRoot?.querySelectorAll<HTMLElement>('.option'),\n });\n\n /** Selected value (the chosen option's `value`). */\n @property({ reflect: true })\n accessor value = '';\n\n /** Placeholder text in the input. */\n @property()\n accessor placeholder = '';\n\n /** Accessible label for the input. */\n @property()\n accessor label = '';\n\n /** Control size. */\n @property({ reflect: true })\n accessor size: ComboboxSize = 'md';\n\n /** Show a button to clear the value. */\n @property({ type: Boolean, reflect: true, attribute: 'with-clear' })\n accessor withClear = false;\n\n /** Accept a typed value that matches no option. */\n @property({ type: Boolean, reflect: true, attribute: 'allow-custom' })\n accessor allowCustom = false;\n\n /** Wrap the matching substring of each option in `<mark>`. */\n @property({ type: Boolean })\n accessor highlight = true;\n\n /** Panel placement relative to the control. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Override the option filter. `(item, query) => boolean`. */\n filter: ComboboxFilter = defaultFilter;\n\n @state() private accessor _open = false;\n @state() private accessor _query = '';\n @state() private accessor _items: ComboboxItem[] = [];\n\n private get _controlEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.control') ?? null;\n }\n private get _panelEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n }\n private get _inputEl() {\n return this.shadowRoot?.querySelector<HTMLInputElement>('.input') ?? null;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._readItems();\n this._defaultFormValue = this.value;\n this._syncFormValue(this.value);\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value')) {\n this._syncFormValue(this.value);\n this._updateValidity();\n if (!this._open) this._resetDisplay();\n }\n }\n\n override formResetCallback() {\n this.value = this._defaultFormValue;\n this._query = '';\n this._nav.reset();\n super.formResetCallback();\n this._resetDisplay();\n }\n\n override formStateRestoreCallback(state: string) {\n this.value = state;\n this._syncFormValue(state);\n }\n\n // --- Items (read from the light-DOM <datalist>) ---\n\n private _readItems() {\n const list = this.querySelector('datalist');\n const opts = list ? Array.from(list.options) : [];\n this._items = opts.map((o) => {\n const rich = o.childElementCount > 0;\n // Filter/display label: `label` attribute → rich title → flattened text.\n const title = o.querySelector(`.${cls('select-item-title')}`)?.textContent?.trim();\n const label = (o.getAttribute('label') || title || o.textContent || o.value).trim();\n return { value: o.value, label, disabled: o.disabled, html: rich ? o.innerHTML : undefined };\n });\n }\n\n private _labelForValue(value: string): string {\n return this._items.find((i) => i.value === value)?.label ?? (this.allowCustom ? value : '');\n }\n\n private get _filtered(): ComboboxItem[] {\n return this._items.filter((i) => this.filter(i, this._query));\n }\n\n private _resetDisplay() {\n const input = this._inputEl;\n if (input) input.value = this._labelForValue(this.value);\n }\n\n // --- Validity ---\n\n private _updateValidity() {\n if (this.required && !this.value) {\n this.setValidity(\n { valueMissing: true },\n this._localize.term('selectOption'),\n this._inputEl ?? undefined,\n );\n } else {\n this.setValidity({});\n }\n }\n\n // --- Open / close ---\n\n show() {\n if (this._open || this.disabled) return;\n this._readItems();\n if (!this.dispatchEvent(new Event('show', { cancelable: true }))) return;\n this._open = true;\n // Pre-activate the chosen option so a screen reader announces the current\n // selection when the list opens (only when not actively filtering).\n if (this.value && !this._query) {\n this._nav.setActive(this._filtered.findIndex((i) => i.value === this.value));\n }\n this.updateComplete.then(() => {\n const panel = this._panelEl as (HTMLElement & { showPopover?: () => void }) | null;\n panel?.showPopover?.();\n this._floating.startPositioning({\n placement: this.placement,\n distance: 4,\n minWidth: 'trigger',\n });\n });\n }\n\n hide() {\n if (!this._open) return;\n if (!this.dispatchEvent(new Event('hide', { cancelable: true }))) return;\n this._open = false;\n this._nav.reset();\n const panel = this._panelEl as (HTMLElement & { hidePopover?: () => void }) | null;\n if (panel?.matches?.(':popover-open')) (panel as { hidePopover?: () => void }).hidePopover?.();\n this._floating.stopPositioning();\n this._resetDisplay();\n }\n\n // --- Selection ---\n\n private _commit(item: ComboboxItem) {\n if (item.disabled) return;\n this.value = item.value;\n this._query = '';\n this.hasInteracted = true;\n const input = this._inputEl;\n if (input) input.value = item.label;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n input?.focus();\n }\n\n private _commitActiveOrCustom() {\n const filtered = this._filtered;\n const active = this._nav.activeIndex;\n if (active >= 0 && filtered[active]) {\n this._commit(filtered[active]);\n return;\n }\n const input = this._inputEl;\n if (this.allowCustom && input) {\n this.value = input.value;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n }\n }\n\n private _clear() {\n this.value = '';\n this._query = '';\n this._nav.reset();\n if (this._inputEl) this._inputEl.value = '';\n this._syncFormValue('');\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(''));\n this._inputEl?.focus();\n }\n\n // --- Keyboard (navigation delegated to ListboxNavController) ---\n\n private _onKeyDown = (e: KeyboardEvent) => {\n // Arrow Up/Down move aria-activedescendant. Home/End are left to the input's\n // text cursor (APG editable combobox).\n if (this._nav.onKeyDown(e)) return;\n switch (e.key) {\n case 'Enter':\n if (this._open) {\n e.preventDefault();\n this._commitActiveOrCustom();\n }\n break;\n case 'Escape':\n if (this._open) {\n e.preventDefault();\n this.hide();\n } else if (this.value || this._inputEl?.value) {\n this._clear();\n }\n break;\n case 'Tab':\n if (this._open && this._nav.activeIndex >= 0) this._commitActiveOrCustom();\n break;\n default:\n break;\n }\n };\n\n private _onInput = (e: Event) => {\n this._query = (e.target as HTMLInputElement).value;\n this._nav.reset();\n if (!this._open) this.show();\n this.dispatchEvent(new ComboboxInputEvent(this._query));\n };\n\n private _onControlPointerDown = (e: PointerEvent) => {\n // Toggle on clicking the control chrome (not while typing).\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this._open) return;\n this.show();\n };\n\n private _onFocusOut = (e: FocusEvent) => {\n const next = e.relatedTarget as Node | null;\n if (next && this.shadowRoot?.contains(next)) return;\n this.hasInteracted = true;\n this._updateValidity();\n this.hide();\n };\n\n // --- Render ---\n\n private _highlight(label: string): TemplateResult | string {\n const q = this._query.trim();\n if (!this.highlight || !q) return label;\n const idx = normalize(label).indexOf(normalize(q).split(/\\s+/)[0]);\n if (idx < 0) return label;\n const before = label.slice(0, idx);\n const match = label.slice(idx, idx + q.length);\n const after = label.slice(idx + q.length);\n return html`${before}<mark>${match}</mark>${after}`;\n }\n\n override render() {\n const filtered = this._filtered;\n const activeId = this._nav.activeDescendant;\n const activeIndex = this._nav.activeIndex;\n const invalid = this.required && !this.value && this.hasInteracted;\n const status = !this._open\n ? ''\n : filtered.length === 0\n ? this._localize.term('noResults')\n : `${filtered.length} ${this._localize.term('suggestions')}`;\n\n return html`\n <div\n class=\"base\"\n part=\"base\"\n @focusout=${this._onFocusOut}\n >\n <div\n class=\"control\"\n part=\"control\"\n @pointerdown=${this._onControlPointerDown}\n >\n <slot name=\"prefix\"></slot>\n <input\n class=\"input\"\n part=\"input\"\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n .placeholder=${this.placeholder}\n ?disabled=${this.disabled}\n aria-label=${this.label || nothing}\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-activedescendant=${activeId ?? nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @input=${this._onInput}\n @keydown=${this._onKeyDown}\n />\n ${this.withClear && (this.value || this._query)\n ? html`<button\n class=\"clear\"\n type=\"button\"\n tabindex=\"-1\"\n aria-label=${this._localize.term('clear')}\n @click=${this._clear}\n ></button>`\n : nothing}\n <span\n class=\"chevron\"\n aria-hidden=\"true\"\n ></span>\n </div>\n\n <div\n class=\"panel\"\n part=\"panel\"\n popover=\"manual\"\n >\n <ul\n class=\"listbox\"\n part=\"listbox\"\n role=\"listbox\"\n id=${this._listId}\n aria-label=${this.label || this._localize.term('suggestions')}\n >\n ${filtered.map(\n (item, i) => html`<li\n class=\"option\"\n part=\"option\"\n id=${this._nav.optionId(i)}\n role=\"option\"\n aria-selected=${i === activeIndex ? 'true' : 'false'}\n aria-disabled=${item.disabled ? 'true' : nothing}\n data-current=${item.value === this.value ? '' : nothing}\n @pointerdown=${(e: Event) => e.preventDefault()}\n @click=${() => this._commit(item)}\n >\n ${item.html\n ? html`<span class=\"option-rich\">${unsafeHTML(item.html)}</span>`\n : html`<span class=\"option-label\">${this._highlight(item.label)}</span>`}\n </li>`,\n )}\n </ul>\n ${filtered.length === 0\n ? html`<div\n class=\"empty\"\n part=\"empty\"\n >\n ${this._localize.term('noResults')}\n </div>`\n : nothing}\n </div>\n\n <div\n class=\"visually-hidden\"\n aria-live=\"polite\"\n >\n ${status}\n </div>\n </div>\n `;\n }\n}\n\n// Typed addEventListener for the colliding `change` / `input` names.\nexport interface Combobox {\n addEventListener<K extends keyof ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"combobox.js","names":[],"sources":["../../../src/html/elements/combobox/combobox.css?inline","../../../src/html/elements/combobox/combobox.ts"],"sourcesContent":[":host {\n --height: var(--l-form-control-height);\n --border-radius: var(--l-form-control-border-radius);\n --background: var(--l-color-surface-overlay);\n\n display: block;\n inline-size: 100%;\n}\n\n:host([size='xs']) {\n --height: var(--l-size-control-xs);\n}\n:host([size='sm']) {\n --height: var(--l-size-control-sm);\n}\n:host([size='lg']) {\n --height: var(--l-size-control-lg);\n}\n:host([size='xl']) {\n --height: var(--l-size-control-xl);\n}\n\n:host([disabled]) {\n opacity: 0.5;\n pointer-events: none;\n}\n\n.base {\n position: relative;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅲🅾🅽🆃🆁🅾🅻 (trigger)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.control {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n block-size: var(--height);\n padding-inline: var(--l-form-control-padding-inline);\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: var(--border-radius);\n background-color: var(--l-form-control-background-color);\n cursor: text;\n}\n\n@media (hover: hover) {\n .control:hover {\n border-color: var(--l-form-control-border-color-hover);\n }\n}\n\n.control:has(.input:focus-visible) {\n border-color: var(--l-focus-ring);\n box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);\n}\n\n.input {\n flex: 1;\n min-inline-size: 0;\n border: 0;\n background: transparent;\n color: var(--l-form-control-value-color);\n font: inherit;\n outline: none;\n}\n\n.input::placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.chevron {\n flex: none;\n inline-size: 1em;\n block-size: 1em;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_chevron-icon) center / contain no-repeat;\n transition: rotate 0.15s ease;\n}\n\n/* ≥24px hit area (WCAG 2.5.8) with a 1em icon centred inside. */\n.clear {\n flex: none;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border: 0;\n padding: 0;\n cursor: pointer;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_x-icon) center / 1em no-repeat;\n}\n\n@media (hover: hover) {\n .clear:hover {\n color: var(--l-form-control-value-color);\n }\n}\n\n.control:has(.input[aria-expanded='true']) .chevron {\n rotate: 180deg;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅿🅰🅽🅴🅻 (popover listbox)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.panel {\n inset: auto;\n margin: 0;\n padding: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius);\n background-color: var(--background);\n box-shadow: var(--l-shadow-md);\n overflow: hidden;\n opacity: 0;\n scale: 0.98;\n transition:\n opacity 0.12s ease,\n scale 0.12s ease,\n display 0.12s allow-discrete,\n overlay 0.12s allow-discrete;\n}\n\n.panel:popover-open {\n opacity: 1;\n scale: 1;\n}\n\n@starting-style {\n .panel:popover-open {\n opacity: 0;\n scale: 0.98;\n }\n}\n\n.listbox {\n margin: 0;\n padding: 0.25rem;\n list-style: none;\n max-block-size: 16rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n min-block-size: 2rem;\n padding-block: 0.25rem;\n padding-inline: 0.5rem var(--height);\n position: relative;\n border-radius: calc(var(--border-radius) - 2px);\n color: var(--l-color-text-primary);\n cursor: pointer;\n}\n\n.option[aria-selected='true'] {\n background-color: var(--l-color-bg-state-hover);\n}\n\n.option[aria-disabled='true'] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Checkmark on the currently committed value. */\n.option[data-current]::after {\n content: '';\n position: absolute;\n inset-inline-end: 0.5rem;\n inline-size: 1em;\n block-size: 1em;\n background-color: var(--l-form-control-activated-color);\n mask: var(--_check-icon) center / contain no-repeat;\n}\n\n.option-label {\n flex: 1;\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-label mark {\n background: transparent;\n color: inherit;\n font-weight: var(--l-font-weight-semibold);\n}\n\n/* Rich options — same authoring classes as l-select, styled inside the shadow. */\n.option-rich {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 1;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-media {\n flex: none;\n display: inline-flex;\n align-items: center;\n font-size: 1.25em;\n}\n\n.option-rich .l-select-item-text {\n display: flex;\n flex-direction: column;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-rich .l-select-item-description {\n font-size: 0.85em;\n color: var(--l-color-text-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.empty {\n padding: 0.5rem;\n color: var(--l-color-text-tertiary);\n text-align: center;\n}\n\n.visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅸🅲🅾🅽🆂\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n:host {\n --_chevron-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");\n --_x-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E\");\n --_check-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E\");\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel,\n .chevron {\n transition-duration: 0ms;\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport type { TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { LuxenFormAssociatedElement } from '../../shared/luxen-form-associated-element.js';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { ListboxNavController } from '../../shared/controllers/listbox-nav.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls, uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './combobox.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type ComboboxSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** A single option read from the `<datalist>`. */\nexport interface ComboboxItem {\n value: string;\n /** Plain text used for filtering and the input display. */\n label: string;\n disabled: boolean;\n /** Rich inner markup (from a non-empty `<option>`), projected into the row. */\n html?: string;\n}\n\n/** Default filter: accent/case-insensitive, every space-separated keyword must match. */\nexport type ComboboxFilter = (item: ComboboxItem, query: string) => boolean;\n\nfunction normalize(s: string): string {\n return s\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase();\n}\n\nconst defaultFilter: ComboboxFilter = (item, query) => {\n if (!query) return true;\n const label = normalize(item.label);\n return normalize(query)\n .split(/\\s+/)\n .filter(Boolean)\n .every((kw) => label.includes(kw));\n};\n\n/** Fired when an option is committed (selected). Bubbles; not composed. */\nexport class ComboboxChangeEvent extends Event {\n readonly value: string;\n constructor(value: string) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\n/** Fired as the user types (filter query changes). Bubbles; not composed. */\nexport class ComboboxInputEvent extends Event {\n /** The current query text. */\n readonly value: string;\n constructor(value: string) {\n super('input', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface ComboboxEventMap {\n change: ComboboxChangeEvent;\n input: ComboboxInputEvent;\n}\n\n/**\n * A text input paired with a filterable list of options (the ARIA combobox\n * pattern). Options are authored as a native `<datalist>` of `<option>` —\n * the same authoring surface as `l-select`. Form-associated.\n *\n * @summary A searchable text input with a filtered list of options.\n *\n * @example\n * ```html\n * <l-combobox label=\"Country\" name=\"country\" placeholder=\"Search…\">\n * <datalist>\n * <option value=\"us\">United States</option>\n * <option value=\"fr\">France</option>\n * </datalist>\n * </l-combobox>\n * ```\n *\n * @event change - Fired when an option is selected. Bubbles. Not cancelable. Properties: `value: string`.\n * @event input - Fired as the user types. Bubbles. Not cancelable. Properties: `value: string` (the query).\n * @event show - Fired before the list opens. Cancelable.\n * @event hide - Fired before the list closes. Cancelable.\n *\n * @csspart base - The host wrapper.\n * @csspart control - The input control row (input + clear + chevron).\n * @csspart input - The text input.\n * @csspart panel - The floating popover panel.\n * @csspart listbox - The options container.\n * @csspart option - Each option row.\n * @csspart empty - The \"no results\" message.\n *\n * @cssproperty [--height] - Control height. Defaults to the form-control height.\n * @cssproperty [--border-radius] - Control + panel radius.\n * @cssproperty [--background] - Panel background.\n *\n * @customElement l-combobox\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Combobox extends LuxenFormAssociatedElement {\n static override styles = [hostStyles, styles];\n\n private _localize = new LocalizeController(this);\n private readonly _listId = uniqueId('combobox-list');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._controlEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _nav = new ListboxNavController(this, {\n listboxId: this._listId,\n getCount: () => this._filtered.length,\n isDisabled: (i) => this._filtered[i]?.disabled ?? false,\n isOpen: () => this._open,\n open: () => this.show(),\n getOptionElements: () => this.shadowRoot?.querySelectorAll<HTMLElement>('.option'),\n });\n\n /** Selected value (the chosen option's `value`). */\n @property({ reflect: true })\n accessor value = '';\n\n /** Placeholder text in the input. */\n @property()\n accessor placeholder = '';\n\n /** Accessible label for the input. */\n @property()\n accessor label = '';\n\n /** Control size. */\n @property({ reflect: true })\n accessor size: ComboboxSize = 'md';\n\n /** Show a button to clear the value. */\n @property({ type: Boolean, reflect: true, attribute: 'with-clear' })\n accessor withClear = false;\n\n /** Accept a typed value that matches no option. */\n @property({ type: Boolean, reflect: true, attribute: 'allow-custom' })\n accessor allowCustom = false;\n\n /** Wrap the matching substring of each option in `<mark>`. */\n @property({ type: Boolean })\n accessor highlight = true;\n\n /** Panel placement relative to the control. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Override the option filter. `(item, query) => boolean`. */\n filter: ComboboxFilter = defaultFilter;\n\n @state() private accessor _open = false;\n @state() private accessor _query = '';\n @state() private accessor _items: ComboboxItem[] = [];\n\n private get _controlEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.control') ?? null;\n }\n private get _panelEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n }\n private get _inputEl() {\n return this.shadowRoot?.querySelector<HTMLInputElement>('.input') ?? null;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._readItems();\n this._defaultFormValue = this.value;\n this._syncFormValue(this.value);\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value')) {\n this._syncFormValue(this.value);\n this._updateValidity();\n if (!this._open) this._resetDisplay();\n }\n }\n\n override formResetCallback() {\n this.value = this._defaultFormValue;\n this._query = '';\n this._nav.reset();\n super.formResetCallback();\n this._resetDisplay();\n }\n\n override formStateRestoreCallback(state: string) {\n this.value = state;\n this._syncFormValue(state);\n }\n\n // --- Items (read from the light-DOM <datalist>) ---\n\n private _readItems() {\n const list = this.querySelector('datalist');\n const opts = list ? Array.from(list.options) : [];\n this._items = opts.map((o) => {\n const rich = o.childElementCount > 0;\n // Filter/display label: `label` attribute → rich title → flattened text.\n const title = o.querySelector(`.${cls('select-item-title')}`)?.textContent?.trim();\n const label = (o.getAttribute('label') || title || o.textContent || o.value).trim();\n return { value: o.value, label, disabled: o.disabled, html: rich ? o.innerHTML : undefined };\n });\n }\n\n private _labelForValue(value: string): string {\n return this._items.find((i) => i.value === value)?.label ?? (this.allowCustom ? value : '');\n }\n\n private get _filtered(): ComboboxItem[] {\n return this._items.filter((i) => this.filter(i, this._query));\n }\n\n private _resetDisplay() {\n const input = this._inputEl;\n if (input) input.value = this._labelForValue(this.value);\n }\n\n // --- Validity ---\n\n private _updateValidity() {\n if (this.required && !this.value) {\n this.setValidity(\n { valueMissing: true },\n this._localize.term('selectOption'),\n this._inputEl ?? undefined,\n );\n } else {\n this.setValidity({});\n }\n }\n\n // --- Open / close ---\n\n show() {\n if (this._open || this.disabled) return;\n this._readItems();\n if (!this.dispatchEvent(new Event('show', { cancelable: true }))) return;\n this._open = true;\n // Pre-activate the chosen option so a screen reader announces the current\n // selection when the list opens (only when not actively filtering).\n if (this.value && !this._query) {\n this._nav.setActive(this._filtered.findIndex((i) => i.value === this.value));\n }\n this.updateComplete.then(() => {\n const panel = this._panelEl as (HTMLElement & { showPopover?: () => void }) | null;\n panel?.showPopover?.();\n this._floating.startPositioning({\n placement: this.placement,\n distance: 4,\n minWidth: 'trigger',\n });\n });\n }\n\n hide() {\n if (!this._open) return;\n if (!this.dispatchEvent(new Event('hide', { cancelable: true }))) return;\n this._open = false;\n this._nav.reset();\n const panel = this._panelEl as (HTMLElement & { hidePopover?: () => void }) | null;\n if (panel?.matches?.(':popover-open')) (panel as { hidePopover?: () => void }).hidePopover?.();\n this._floating.stopPositioning();\n this._resetDisplay();\n }\n\n // --- Selection ---\n\n private _commit(item: ComboboxItem) {\n if (item.disabled) return;\n this.value = item.value;\n this._query = '';\n this.hasInteracted = true;\n const input = this._inputEl;\n if (input) input.value = item.label;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n input?.focus();\n }\n\n private _commitActiveOrCustom() {\n const filtered = this._filtered;\n const active = this._nav.activeIndex;\n if (active >= 0 && filtered[active]) {\n this._commit(filtered[active]);\n return;\n }\n const input = this._inputEl;\n if (this.allowCustom && input) {\n this.value = input.value;\n this._syncFormValue(this.value);\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(this.value));\n this.hide();\n }\n }\n\n private _clear() {\n this.value = '';\n this._query = '';\n this._nav.reset();\n if (this._inputEl) this._inputEl.value = '';\n this._syncFormValue('');\n this._updateValidity();\n this.dispatchEvent(new ComboboxChangeEvent(''));\n this._inputEl?.focus();\n }\n\n // --- Keyboard (navigation delegated to ListboxNavController) ---\n\n private _onKeyDown = (e: KeyboardEvent) => {\n // Arrow Up/Down move aria-activedescendant. Home/End are left to the input's\n // text cursor (APG editable combobox).\n if (this._nav.onKeyDown(e)) return;\n switch (e.key) {\n case 'Enter':\n if (this._open) {\n e.preventDefault();\n this._commitActiveOrCustom();\n }\n break;\n case 'Escape':\n if (this._open) {\n e.preventDefault();\n this.hide();\n } else if (this.value || this._inputEl?.value) {\n this._clear();\n }\n break;\n case 'Tab':\n if (this._open && this._nav.activeIndex >= 0) this._commitActiveOrCustom();\n break;\n default:\n break;\n }\n };\n\n private _onInput = (e: Event) => {\n this._query = (e.target as HTMLInputElement).value;\n this._nav.reset();\n if (!this._open) this.show();\n this.dispatchEvent(new ComboboxInputEvent(this._query));\n };\n\n private _onControlPointerDown = (e: PointerEvent) => {\n // Toggle on clicking the control chrome (not while typing).\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this._open) return;\n this.show();\n };\n\n private _onFocusOut = (e: FocusEvent) => {\n const next = e.relatedTarget as Node | null;\n if (next && this.shadowRoot?.contains(next)) return;\n this.hasInteracted = true;\n this._updateValidity();\n this.hide();\n };\n\n // --- Render ---\n\n private _highlight(label: string): TemplateResult | string {\n const q = this._query.trim();\n if (!this.highlight || !q) return label;\n const idx = normalize(label).indexOf(normalize(q).split(/\\s+/)[0]);\n if (idx < 0) return label;\n const before = label.slice(0, idx);\n const match = label.slice(idx, idx + q.length);\n const after = label.slice(idx + q.length);\n return html`${before}<mark>${match}</mark>${after}`;\n }\n\n override render() {\n const filtered = this._filtered;\n const activeId = this._nav.activeDescendant;\n const activeIndex = this._nav.activeIndex;\n const invalid = this.required && !this.value && this.hasInteracted;\n const status = !this._open\n ? ''\n : filtered.length === 0\n ? this._localize.term('noResults')\n : `${filtered.length} ${this._localize.term('suggestions')}`;\n\n return html`\n <div\n class=\"base\"\n part=\"base\"\n @focusout=${this._onFocusOut}\n >\n <div\n class=\"control\"\n part=\"control\"\n @pointerdown=${this._onControlPointerDown}\n >\n <slot name=\"prefix\"></slot>\n <input\n class=\"input\"\n part=\"input\"\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n .placeholder=${this.placeholder}\n ?disabled=${this.disabled}\n aria-label=${this.label || nothing}\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-activedescendant=${activeId ?? nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @input=${this._onInput}\n @keydown=${this._onKeyDown}\n />\n ${this.withClear && (this.value || this._query)\n ? html`<button\n class=\"clear\"\n type=\"button\"\n tabindex=\"-1\"\n aria-label=${this._localize.term('clear')}\n @click=${this._clear}\n ></button>`\n : nothing}\n <span\n class=\"chevron\"\n aria-hidden=\"true\"\n ></span>\n </div>\n\n <div\n class=\"panel\"\n part=\"panel\"\n popover=\"manual\"\n >\n <ul\n class=\"listbox\"\n part=\"listbox\"\n role=\"listbox\"\n id=${this._listId}\n aria-label=${this.label || this._localize.term('suggestions')}\n >\n ${filtered.map(\n (item, i) => html`<li\n class=\"option\"\n part=\"option\"\n id=${this._nav.optionId(i)}\n role=\"option\"\n aria-selected=${i === activeIndex ? 'true' : 'false'}\n aria-disabled=${item.disabled ? 'true' : nothing}\n data-current=${item.value === this.value ? '' : nothing}\n @pointerdown=${(e: Event) => e.preventDefault()}\n @click=${() => this._commit(item)}\n >\n ${item.html\n ? html`<span class=\"option-rich\">${unsafeHTML(item.html)}</span>`\n : html`<span class=\"option-label\">${this._highlight(item.label)}</span>`}\n </li>`,\n )}\n </ul>\n ${filtered.length === 0\n ? html`<div\n class=\"empty\"\n part=\"empty\"\n >\n ${this._localize.term('noResults')}\n </div>`\n : nothing}\n </div>\n\n <div\n class=\"visually-hidden\"\n aria-live=\"polite\"\n >\n ${status}\n </div>\n </div>\n `;\n }\n}\n\n// Typed addEventListener for the colliding `change` / `input` names.\nexport interface Combobox {\n addEventListener<K extends keyof ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof ComboboxEventMap>(\n type: K,\n listener: (this: Combobox, ev: ComboboxEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{i as e}from"../../chunks/lit.js";var t=e(`:host{--width:31rem;--border-radius:6px;--padding:1.5rem;--header-padding:var(--padding);--footer-padding:var(--padding);--show-duration:.2s;--hide-duration:.2s;--backdrop:var(--l-backdrop);--backdrop-blur:0;display:contents}dialog{box-sizing:border-box;width:var(--width);max-inline-size:min(90vw, var(--width));border-radius:var(--border-radius);background-color:var(--l-color-surface-overlay);max-block-size:min(80dvb,100%);color:var(--l-color-text-primary);opacity:0;transition-property:opacity,display,overlay;transition-duration:var(--hide-duration);transition-behavior:allow-discrete;border:0;margin:auto;padding:0;position:fixed;inset:0}dialog::backdrop{background:var(--backdrop);-webkit-backdrop-filter:blur(var(--backdrop-blur));backdrop-filter:blur(var(--backdrop-blur))}dialog[open]{opacity:1;transition-duration:var(--show-duration);grid-template-rows:auto minmax(0,1fr) auto;display:grid}@starting-style{dialog[open]{opacity:0}}[part=header]{padding:var(--header-padding);justify-content:space-between;align-items:center;gap:1rem;display:flex}[part=title]{
|
|
1
|
+
import{i as e}from"../../chunks/lit.js";var t=e(`:host{--width:31rem;--border-radius:6px;--padding:1.5rem;--header-padding:var(--padding);--footer-padding:var(--padding);--show-duration:.2s;--hide-duration:.2s;--backdrop:var(--l-backdrop);--backdrop-blur:0;display:contents}dialog{box-sizing:border-box;width:var(--width);max-inline-size:min(90vw, var(--width));border-radius:var(--border-radius);background-color:var(--l-color-surface-overlay);max-block-size:min(80dvb,100%);color:var(--l-color-text-primary);opacity:0;transition-property:opacity,display,overlay;transition-duration:var(--hide-duration);transition-behavior:allow-discrete;border:0;margin:auto;padding:0;position:fixed;inset:0}dialog::backdrop{background:var(--backdrop);-webkit-backdrop-filter:blur(var(--backdrop-blur));backdrop-filter:blur(var(--backdrop-blur))}dialog[open]{opacity:1;transition-duration:var(--show-duration);grid-template-rows:auto minmax(0,1fr) auto;display:grid}@starting-style{dialog[open]{opacity:0}}[part=header]{padding:var(--header-padding);justify-content:space-between;align-items:center;gap:1rem;display:flex}[part=title]{font-size:1.125rem;font-weight:var(--l-font-weight-semibold);margin:0;line-height:1.4}[part=body]{padding-inline:var(--padding);grid-row:2;overflow-y:auto}[part=footer]{padding:var(--footer-padding);grid-row:3;place-content:end;gap:.5rem;display:flex}[part=footer][data-empty]{display:none}[part=body]:has(~[part=footer][data-empty]){padding-block-end:var(--padding)}:host([without-header]) [part=body]{padding-block-start:var(--padding)}::slotted(menu[slot=footer]){display:contents}@media (prefers-reduced-motion:reduce){:host{--show-duration:0s;--hide-duration:0s}}`);export{t as default};
|
|
2
2
|
//# sourceMappingURL=dialog.styles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.styles.js","names":[],"sources":["../../../src/html/elements/dialog/dialog.css?inline","../../../src/html/elements/dialog/dialog.styles.ts"],"sourcesContent":[":host {\n --width: 31rem;\n --border-radius: 6px;\n --padding: 1.5rem;\n --header-padding: var(--padding);\n --footer-padding: var(--padding);\n --show-duration: 200ms;\n --hide-duration: 200ms;\n --backdrop: var(--l-backdrop);\n --backdrop-blur: 0;\n\n display: contents;\n}\n\ndialog {\n position: fixed;\n inset: 0;\n box-sizing: border-box;\n width: var(--width);\n max-inline-size: min(90vw, var(--width));\n max-block-size: min(80dvb, 100%);\n margin: auto;\n padding: 0;\n border: 0;\n border-radius: var(--border-radius);\n background-color: var(--l-color-surface-overlay);\n color: var(--l-color-text-primary);\n\n /* EXIT STATE */\n opacity: 0;\n\n transition-property: opacity, display, overlay;\n transition-duration: var(--hide-duration);\n transition-behavior: allow-discrete;\n\n &::backdrop {\n background: var(--backdrop);\n backdrop-filter: blur(var(--backdrop-blur));\n }\n\n /* OPEN STATE */\n /* grid layout pins header/footer; the middle row (body) scrolls via\n overflow on [part='body'] and minmax(0, 1fr) allowing it to shrink. */\n &[open] {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n opacity: 1;\n transition-duration: var(--show-duration);\n }\n\n /* BEFORE-OPEN STATE */\n @starting-style {\n &[open] {\n opacity: 0;\n }\n }\n}\n\n[part='header'] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 1rem;\n padding: var(--header-padding);\n}\n\n[part='title'] {\n margin: 0;\n font-size: 1.125rem;\n font-weight:
|
|
1
|
+
{"version":3,"file":"dialog.styles.js","names":[],"sources":["../../../src/html/elements/dialog/dialog.css?inline","../../../src/html/elements/dialog/dialog.styles.ts"],"sourcesContent":[":host {\n --width: 31rem;\n --border-radius: 6px;\n --padding: 1.5rem;\n --header-padding: var(--padding);\n --footer-padding: var(--padding);\n --show-duration: 200ms;\n --hide-duration: 200ms;\n --backdrop: var(--l-backdrop);\n --backdrop-blur: 0;\n\n display: contents;\n}\n\ndialog {\n position: fixed;\n inset: 0;\n box-sizing: border-box;\n width: var(--width);\n max-inline-size: min(90vw, var(--width));\n max-block-size: min(80dvb, 100%);\n margin: auto;\n padding: 0;\n border: 0;\n border-radius: var(--border-radius);\n background-color: var(--l-color-surface-overlay);\n color: var(--l-color-text-primary);\n\n /* EXIT STATE */\n opacity: 0;\n\n transition-property: opacity, display, overlay;\n transition-duration: var(--hide-duration);\n transition-behavior: allow-discrete;\n\n &::backdrop {\n background: var(--backdrop);\n backdrop-filter: blur(var(--backdrop-blur));\n }\n\n /* OPEN STATE */\n /* grid layout pins header/footer; the middle row (body) scrolls via\n overflow on [part='body'] and minmax(0, 1fr) allowing it to shrink. */\n &[open] {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n opacity: 1;\n transition-duration: var(--show-duration);\n }\n\n /* BEFORE-OPEN STATE */\n @starting-style {\n &[open] {\n opacity: 0;\n }\n }\n}\n\n[part='header'] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 1rem;\n padding: var(--header-padding);\n}\n\n[part='title'] {\n margin: 0;\n font-size: 1.125rem;\n font-weight: var(--l-font-weight-semibold);\n line-height: 1.4;\n}\n\n/* Pin body and footer so the layout stays correct when [without-header]\n removes the header and only two children auto-place into the grid. */\n[part='body'] {\n grid-row: 2;\n padding-inline: var(--padding);\n overflow-y: auto;\n}\n\n[part='footer'] {\n grid-row: 3;\n display: flex;\n place-content: end;\n gap: 0.5rem;\n padding: var(--footer-padding);\n}\n\n/* Collapse the footer row when nothing is slotted into it, so an empty\n dialog/drawer reserves no space at the bottom. `data-empty` is toggled by the\n HasSlotController in dialog.ts: `:host(:has(...))` can't be used because\n `:has()` is invalid inside `:host()` and the whole rule is dropped by the\n browser. */\n[part='footer'][data-empty] {\n display: none;\n}\n\n/* With the footer collapsed, the body has to provide its own block-end padding\n (normally inherited visually from the footer padding below it). */\n[part='body']:has(~ [part='footer'][data-empty]) {\n padding-block-end: var(--padding);\n}\n\n/* Without a header, the body has to provide its own block-start padding\n (normally inherited visually from the header padding above it). */\n:host([without-header]) [part='body'] {\n padding-block-start: var(--padding);\n}\n\n::slotted(menu[slot='footer']) {\n display: contents;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host {\n --show-duration: 0ms;\n --hide-duration: 0ms;\n }\n}\n","import { unsafeCSS } from 'lit';\nimport raw from './dialog.css?inline';\n\n/**\n * Wrapper module: imported by both `dialog.ts` and `drawer.ts`.\n * `unsafeCSS()` is called once here so both importers share the same\n * `CSSResult` instance (one constructed `CSSStyleSheet`, not two).\n */\nexport default unsafeCSS(raw);\n"],"mappings":"wCCQA,IAAA,EAAe,EAAU,ymDAAG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e}from"../../chunks/lit.js";import{a as t}from"../../chunks/lit-html.js";import{LuxenElement as n}from"../../shared/luxen-element.js";import r from"../../shared/styles/host.styles.js";var i=e(`:host{--color:var(--l-color-text-tertiary);display:block}.label{letter-spacing:.03em;text-transform:uppercase;color:var(--color);white-space:nowrap;-webkit-user-select:none;user-select:none;padding:.375rem .5rem .125rem;
|
|
1
|
+
import{i as e}from"../../chunks/lit.js";import{a as t}from"../../chunks/lit-html.js";import{LuxenElement as n}from"../../shared/luxen-element.js";import r from"../../shared/styles/host.styles.js";var i=e(`:host{--color:var(--l-color-text-tertiary);display:block}.label{font-size:.6875rem;font-weight:var(--l-font-weight-semibold);letter-spacing:.03em;text-transform:uppercase;color:var(--color);white-space:nowrap;-webkit-user-select:none;user-select:none;padding:.375rem .5rem .125rem;line-height:1.4}`),a=class extends n{static{this.styles=[r,i]}connectedCallback(){super.connectedCallback(),this.setAttribute(`role`,`presentation`)}render(){return t`<div class="label">
|
|
2
2
|
<slot></slot>
|
|
3
3
|
</div>`}};export{a as DropdownLabel};
|
|
4
4
|
//# sourceMappingURL=dropdown-label.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-label.js","names":[],"sources":["../../../src/html/elements/dropdown-label/dropdown-label.css?inline","../../../src/html/elements/dropdown-label/dropdown-label.ts"],"sourcesContent":[":host {\n display: block;\n --color: var(--l-color-text-tertiary);\n}\n\n.label {\n /* Inline padding aligns with dropdown-item's `0.375rem 0.5rem` so labels and\n items line up; extra top padding adds breathing room above each section. */\n padding: 0.375rem 0.5rem 0.125rem;\n font-size: 0.6875rem; /* 11px */\n font-weight:
|
|
1
|
+
{"version":3,"file":"dropdown-label.js","names":[],"sources":["../../../src/html/elements/dropdown-label/dropdown-label.css?inline","../../../src/html/elements/dropdown-label/dropdown-label.ts"],"sourcesContent":[":host {\n display: block;\n --color: var(--l-color-text-tertiary);\n}\n\n.label {\n /* Inline padding aligns with dropdown-item's `0.375rem 0.5rem` so labels and\n items line up; extra top padding adds breathing room above each section. */\n padding: 0.375rem 0.5rem 0.125rem;\n font-size: 0.6875rem; /* 11px */\n font-weight: var(--l-font-weight-semibold);\n letter-spacing: 0.03em;\n text-transform: uppercase;\n line-height: 1.4;\n color: var(--color);\n white-space: nowrap;\n user-select: none;\n}\n","import { html, unsafeCSS, type CSSResultGroup } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './dropdown-label.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A non-interactive section label for grouping items inside `<l-dropdown>`.\n *\n * @slot - Label text.\n *\n * @cssproperty --color - Text color. Defaults to `var(--l-color-text-tertiary)`.\n *\n * @customElement l-dropdown-label\n */\nexport class DropdownLabel extends LuxenElement {\n static override styles: CSSResultGroup = [hostStyles, styles];\n\n override connectedCallback() {\n super.connectedCallback();\n // Set the role on the host — that's the node that lands in the\n // `role=\"menu\"` light-DOM subtree (mirrors `l-divider`'s `role=\"separator\"`).\n // A role on the shadow-DOM `.label` <div> would not change how the host is\n // announced. `presentation` keeps the label out of menu-item navigation\n // while its text is still read as plain content.\n this.setAttribute('role', 'presentation');\n }\n\n override render() {\n return html`<div class=\"label\">\n <slot></slot>\n </div>`;\n }\n}\n"],"mappings":"oMCKA,IAAM,EAAS,EAAU,2SAAS,EAWrB,EAAb,cAAmC,CAAa,oBACL,CAAC,EAAY,CAAM,EAE5D,mBAA6B,CAC3B,MAAM,kBAAkB,EAMxB,KAAK,aAAa,OAAQ,cAAc,CAC1C,CAEA,QAAkB,CAChB,MAAO,EAAI;;WAGb,CACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cls as e,tagName as t,uniqueId as n}from"../../registry.js";import{i as r}from"../../chunks/lit.js";import{i,r as a,t as o}from"../../chunks/decorate.js";import{a as s,t as c}from"../../chunks/lit-html.js";import{LocalizeController as l}from"../../shared/localize.js";import{n as u,r as d}from"../../chunks/static-html.js";import f from"../../shared/styles/host.styles.js";import{t as p}from"../../chunks/unsafe-html.js";import{LuxenFormAssociatedElement as m}from"../../shared/luxen-form-associated-element.js";import{PopoverController as h}from"../../shared/controllers/popover.js";import{ListboxNavController as g}from"../../shared/controllers/listbox-nav.js";var _=r(`:host{--height:var(--l-form-control-height);--border-radius:var(--l-form-control-border-radius);--background:var(--l-color-surface-overlay);inline-size:100%;display:block}:host([size=xs]){--height:var(--l-size-control-xs)}:host([size=sm]){--height:var(--l-size-control-sm)}:host([size=lg]){--height:var(--l-size-control-lg)}:host([size=xl]){--height:var(--l-size-control-xl)}:host([disabled]){opacity:.5;pointer-events:none}.base{position:relative}.trigger{inline-size:100%;min-block-size:var(--height);padding-block:.25rem;padding-inline:var(--l-form-control-padding-inline);border:var(--l-form-control-border-width) solid var(--l-form-control-border-color);border-radius:var(--border-radius);background-color:var(--l-form-control-background-color);color:var(--l-form-control-value-color);font:inherit;text-align:start;cursor:pointer;align-items:center;gap:.5rem;display:flex}:host([multiple]) .trigger{padding-inline-start:.375rem}@media (hover:hover){.trigger:hover{border-color:var(--l-form-control-border-color-hover)}}.trigger:focus-visible{border-color:var(--l-focus-ring);box-shadow:0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);outline:none}.value{flex-wrap:wrap;flex:1;align-items:center;gap:.25rem;min-inline-size:0;display:flex;overflow:hidden}.value-text{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}[part=tag]{--height:initial;--border-radius:initial}.placeholder{color:var(--l-form-control-placeholder-color)}.chevron{block-size:1em;inline-size:1em;color:var(--l-form-control-placeholder-color);-webkit-mask:var(--_chevron-icon) center / contain no-repeat;mask:var(--_chevron-icon) center / contain no-repeat;background-color:currentColor;flex:none;transition:rotate .15s}.trigger[aria-expanded=true] .chevron{rotate:180deg}.clear{cursor:pointer;block-size:1.5rem;inline-size:1.5rem;color:var(--l-form-control-placeholder-color);-webkit-mask:var(--_x-icon) center / 1em no-repeat;mask:var(--_x-icon) center / 1em no-repeat;background-color:currentColor;border:0;margin-block:auto;padding:0;position:absolute;inset-block:0;inset-inline-end:calc(var(--l-form-control-padding-inline) + 1.25em)}@media (hover:hover){.clear:hover{color:var(--l-form-control-value-color)}}.clear:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px}.value .l-select-item-media{flex:none;align-items:center;font-size:1.25em;display:inline-flex}.value .l-select-item-text{flex-direction:column;min-inline-size:0;display:flex}.value .l-select-item-title{font-weight:inherit;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.value .l-select-item-description{display:none}.panel{border:1px solid var(--l-color-border-overlay);border-radius:var(--border-radius);background-color:var(--background);color:var(--l-color-text-primary);box-shadow:var(--l-shadow-md);opacity:0;transition:opacity .12s ease, scale .12s ease, display .12s allow-discrete, overlay .12s allow-discrete;margin:0;padding:0;font-size:.875rem;line-height:1.5;inset:auto;overflow:hidden;scale:.98}.panel:popover-open{opacity:1;scale:1}@starting-style{.panel:popover-open{opacity:0;scale:.98}}.search-row{border-block-end:1px solid var(--l-color-border-overlay);padding:.375rem}.search{inline-size:100%;block-size:var(--l-size-control-sm);border:var(--l-form-control-border-width) solid var(--l-form-control-border-color);border-radius:calc(var(--border-radius) - 2px);background-color:var(--l-form-control-background-color);color:var(--l-form-control-value-color);font:inherit;padding-inline:.5rem}.search:focus-visible{border-color:var(--l-focus-ring);outline:none}.search::placeholder{color:var(--l-form-control-placeholder-color)}.listbox{overscroll-behavior:contain;max-block-size:16rem;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.option{min-block-size:36px;color:var(--l-color-text-primary);cursor:pointer;border-radius:4px;align-items:start;gap:.5rem;padding-block:.5rem;padding-inline:1.75rem .5rem;display:flex;position:relative}@media (hover:hover){.option:hover{background-color:var(--l-color-bg-state-hover)}}.option[data-active]{background-color:var(--l-color-bg-state-hover)}.option[aria-disabled=true]{opacity:.4;cursor:not-allowed}.option[data-current]:after{content:"";block-size:1.3125rem;inline-size:1rem;-webkit-mask:var(--_check-icon) center / 1rem no-repeat;mask:var(--_check-icon) center / 1rem no-repeat;background-color:currentColor;position:absolute;inset-block-start:.5rem;inset-inline-start:.5rem}.option-label{text-overflow:ellipsis;white-space:nowrap;flex:1;min-inline-size:0;overflow:hidden}.option-label mark{color:inherit;background:0 0;font-weight:600}.option-rich{flex:1;align-items:start;gap:.5rem;min-inline-size:0;display:flex}.option-rich .l-select-item-media{object-fit:cover;border-radius:4px;flex:none;align-items:center;block-size:1.5rem;inline-size:1.5rem;font-size:1.25em;display:inline-flex}.option-rich .l-select-item-text{flex-direction:column;flex:auto;min-inline-size:0;display:flex}.option-rich .l-select-item-title{text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.option-rich .l-select-item-description{color:var(--l-color-text-tertiary);white-space:normal;text-wrap:pretty;font-size:.75rem;line-height:1.3}.empty{color:var(--l-color-text-tertiary);text-align:center;padding:.5rem}.visually-hidden{clip-path:inset(50%);white-space:nowrap;border:0;block-size:1px;inline-size:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}:host{--_chevron-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--_x-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E");--_check-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")}@media (prefers-reduced-motion:reduce){.panel,.chevron{transition-duration:0s}}`);function v(e){return e.normalize(`NFD`).replace(/[̀-ͯ]/g,``).toLowerCase()}var y=(e,t)=>{if(!t)return!0;let n=v(e.label);return v(t).split(/\s+/).filter(Boolean).every(e=>n.includes(e))},b=class extends Event{constructor(e){super(`change`,{bubbles:!0,composed:!1,cancelable:!1}),this.value=e}},x=class extends Event{constructor(e){super(`input`,{bubbles:!0,composed:!1,cancelable:!1}),this.value=e}},S=class extends m{constructor(...e){super(...e),this._localize=new l(this),this._listId=n(`select-list`),this._floating=new h(this,{getTriggerElement:()=>this._triggerEl,getFloatingElement:()=>this._panelEl,getArrowElement:()=>null}),this._nav=new g(this,{listboxId:this._listId,getCount:()=>this._filtered.length,isDisabled:e=>this._filtered[e]?.disabled??!1,isOpen:()=>this._open,open:()=>this.show(),getOptionElements:()=>this.shadowRoot?.querySelectorAll(`.option`)}),this.#e=``,this.#t=``,this.#n=`md`,this.#r=!1,this.#i=!1,this.#a=`bottom-start`,this.#o=!1,this.filter=y,this.#s=[],this.#c=!1,this.#l=``,this.#u=[],this._defaultValues=[],this._onDocumentPointerDown=e=>{e.composedPath().includes(this)||(this.hasInteracted=!0,this._updateValidity(),this.hide({focusTrigger:!1}))},this._onTriggerKeyDown=e=>{this._open||(e.key===`ArrowDown`||e.key===`ArrowUp`||e.key===`Enter`||e.key===` `)&&(e.preventDefault(),this.show())},this._onPanelKeyDown=e=>{if(!this._nav.onKeyDown(e))switch(e.key){case`Enter`:{e.preventDefault();let t=this._filtered[this._nav.activeIndex];t&&this._select(t);break}case`Escape`:e.preventDefault(),this.hide();break;case`Tab`:this.hide({focusTrigger:!1});break;default:break}},this._onSearchInput=e=>{this._query=e.target.value,this._nav.reset(),this.dispatchEvent(new x(this._query))}}static{this.styles=[f,_]}#e;get placeholder(){return this.#e}set placeholder(e){this.#e=e}#t;get label(){return this.#t}set label(e){this.#t=e}#n;get size(){return this.#n}set size(e){this.#n=e}#r;get searchable(){return this.#r}set searchable(e){this.#r=e}#i;get withClear(){return this.#i}set withClear(e){this.#i=e}#a;get placement(){return this.#a}set placement(e){this.#a=e}#o;get multiple(){return this.#o}set multiple(e){this.#o=e}#s;get _values(){return this.#s}set _values(e){this.#s=e}#c;get _open(){return this.#c}set _open(e){this.#c=e}#l;get _query(){return this.#l}set _query(e){this.#l=e}#u;get _items(){return this.#u}set _items(e){this.#u=e}get value(){return this.multiple?[...this._values]:this._values[0]??``}set value(e){this._values=Array.isArray(e)?[...e]:e?[e]:[]}get _triggerEl(){return this.shadowRoot?.querySelector(`.trigger`)??null}get _panelEl(){return this.shadowRoot?.querySelector(`.panel`)??null}get _searchEl(){return this.shadowRoot?.querySelector(`.search`)??null}get validationTarget(){return this._triggerEl??void 0}connectedCallback(){if(super.connectedCallback(),this._readItems(),this._values.length===0){let e=this.getAttribute(`value`);if(e)this._values=[e];else{let e=this._items.filter(e=>e.selected).map(e=>e.value);this._values=this.multiple?e:e.slice(0,1)}}this._defaultValues=[...this._values],this._syncForm()}updated(e){(e.has(`_values`)||e.has(`multiple`))&&(this._syncForm(),this._updateValidity())}formResetCallback(){this._values=[...this._defaultValues],this._query=``,this._nav.reset(),super.formResetCallback(),this._syncForm()}formStateRestoreCallback(e){this.value=e,this._syncForm()}_readItems(){let t=this.querySelector(`datalist`),n=t?Array.from(t.options):[];this._items=n.map(t=>{let n=t.childElementCount>0,r=t.querySelector(`.${e(`select-item-title`)}`)?.textContent?.trim(),i=(t.getAttribute(`label`)||r||t.textContent||t.value).trim();return{value:t.value,label:i,disabled:t.disabled,selected:t.hasAttribute(`selected`),html:n?t.innerHTML:void 0}})}_itemFor(e){return this._items.find(t=>t.value===e)}get _filtered(){return this._items.filter(e=>this.filter(e,this._query))}_syncForm(){if(this.multiple){let e=new FormData;for(let t of this._values)e.append(this.name,t);this._internals.setFormValue(e)}else this._internals.setFormValue(this._values[0]??``)}_updateValidity(){this.required&&this._values.length===0?this.setValidity({valueMissing:!0},this._localize.term(`selectOption`),this._triggerEl??void 0):this.setValidity({})}show(){this._open||this.disabled||(this._readItems(),this.dispatchEvent(new Event(`show`,{cancelable:!0}))&&(this._open=!0,this._query=``,this._values.length&&this._nav.setActive(this._filtered.findIndex(e=>e.value===this._values[0])),this.updateComplete.then(()=>{this._panelEl?.showPopover?.(),this._floating.startPositioning({placement:this.placement,distance:4,minWidth:`trigger`}),(this._searchEl??this._panelEl?.querySelector(`.listbox`))?.focus(),document.addEventListener(`pointerdown`,this._onDocumentPointerDown,!0)})))}hide({focusTrigger:e=!0}={}){if(!this._open||!this.dispatchEvent(new Event(`hide`,{cancelable:!0})))return;this._open=!1,this._query=``,this._nav.reset();let t=this._panelEl;t?.matches?.(`:popover-open`)&&t.hidePopover?.(),this._floating.stopPositioning(),document.removeEventListener(`pointerdown`,this._onDocumentPointerDown,!0),e&&this._triggerEl?.focus()}_select(e){e.disabled||(this.hasInteracted=!0,this.multiple?(this._values=this._values.includes(e.value)?this._values.filter(t=>t!==e.value):[...this._values,e.value],this.dispatchEvent(new b(this.value)),this._searchEl?.focus()):(this._values=[e.value],this.dispatchEvent(new b(this.value)),this.hide()))}_removeValue(e){this._values=this._values.filter(t=>t!==e),this.hasInteracted=!0,this.dispatchEvent(new b(this.value))}_clear(e){e?.stopPropagation(),this._values=[],this.hasInteracted=!0,this.dispatchEvent(new b(this.value)),this._triggerEl?.focus()}_highlight(e){let t=this._query.trim();if(!t)return e;let n=v(e).indexOf(v(t).split(/\s+/)[0]);return n<0?e:s`${e.slice(0,n)}<mark>${e.slice(n,n+t.length)}</mark>${e.slice(n+t.length)}`}_renderSingleValue(){let e=this._itemFor(this._values[0]);return e?e.html?s`${p(e.html)}`:s`<span class="value-text">${e.label}</span>`:s`<span class="value-text placeholder">${this.placeholder}</span>`}_renderChips(){if(this._values.length===0)return s`<span class="value-text placeholder">${this.placeholder}</span>`;let e=u(t(`tag`));return this._values.map(t=>d`<${e}
|
|
1
|
+
import{cls as e,tagName as t,uniqueId as n}from"../../registry.js";import{i as r}from"../../chunks/lit.js";import{i,r as a,t as o}from"../../chunks/decorate.js";import{a as s,t as c}from"../../chunks/lit-html.js";import{LocalizeController as l}from"../../shared/localize.js";import{n as u,r as d}from"../../chunks/static-html.js";import f from"../../shared/styles/host.styles.js";import{t as p}from"../../chunks/unsafe-html.js";import{LuxenFormAssociatedElement as m}from"../../shared/luxen-form-associated-element.js";import{PopoverController as h}from"../../shared/controllers/popover.js";import{ListboxNavController as g}from"../../shared/controllers/listbox-nav.js";var _=r(`:host{--height:var(--l-form-control-height);--border-radius:var(--l-form-control-border-radius);--background:var(--l-color-surface-overlay);inline-size:100%;display:block}:host([size=xs]){--height:var(--l-size-control-xs)}:host([size=sm]){--height:var(--l-size-control-sm)}:host([size=lg]){--height:var(--l-size-control-lg)}:host([size=xl]){--height:var(--l-size-control-xl)}:host([disabled]){opacity:.5;pointer-events:none}.base{position:relative}.trigger{inline-size:100%;min-block-size:var(--height);padding-block:.25rem;padding-inline:var(--l-form-control-padding-inline);border:var(--l-form-control-border-width) solid var(--l-form-control-border-color);border-radius:var(--border-radius);background-color:var(--l-form-control-background-color);color:var(--l-form-control-value-color);font:inherit;text-align:start;cursor:pointer;align-items:center;gap:.5rem;display:flex}:host([multiple]) .trigger{padding-inline-start:.375rem}@media (hover:hover){.trigger:hover{border-color:var(--l-form-control-border-color-hover)}}.trigger:focus-visible{border-color:var(--l-focus-ring);box-shadow:0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);outline:none}.value{flex-wrap:wrap;flex:1;align-items:center;gap:.25rem;min-inline-size:0;display:flex;overflow:hidden}.value-text{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}[part=tag]{--height:initial;--border-radius:initial}.placeholder{color:var(--l-form-control-placeholder-color)}.chevron{block-size:1em;inline-size:1em;color:var(--l-form-control-placeholder-color);-webkit-mask:var(--_chevron-icon) center / contain no-repeat;mask:var(--_chevron-icon) center / contain no-repeat;background-color:currentColor;flex:none;transition:rotate .15s}.trigger[aria-expanded=true] .chevron{rotate:180deg}.clear{cursor:pointer;block-size:1.5rem;inline-size:1.5rem;color:var(--l-form-control-placeholder-color);-webkit-mask:var(--_x-icon) center / 1em no-repeat;mask:var(--_x-icon) center / 1em no-repeat;background-color:currentColor;border:0;margin-block:auto;padding:0;position:absolute;inset-block:0;inset-inline-end:calc(var(--l-form-control-padding-inline) + 1.25em)}@media (hover:hover){.clear:hover{color:var(--l-form-control-value-color)}}.clear:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px}.value .l-select-item-media{flex:none;align-items:center;font-size:1.25em;display:inline-flex}.value .l-select-item-text{flex-direction:column;min-inline-size:0;display:flex}.value .l-select-item-title{font-weight:inherit;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.value .l-select-item-description{display:none}.panel{border:1px solid var(--l-color-border-overlay);border-radius:var(--border-radius);background-color:var(--background);color:var(--l-color-text-primary);box-shadow:var(--l-shadow-md);opacity:0;transition:opacity .12s ease, scale .12s ease, display .12s allow-discrete, overlay .12s allow-discrete;margin:0;padding:0;font-size:.875rem;line-height:1.5;inset:auto;overflow:hidden;scale:.98}.panel:popover-open{opacity:1;scale:1}@starting-style{.panel:popover-open{opacity:0;scale:.98}}.search-row{border-block-end:1px solid var(--l-color-border-overlay);padding:.375rem}.search{inline-size:100%;block-size:var(--l-size-control-sm);border:var(--l-form-control-border-width) solid var(--l-form-control-border-color);border-radius:calc(var(--border-radius) - 2px);background-color:var(--l-form-control-background-color);color:var(--l-form-control-value-color);font:inherit;padding-inline:.5rem}.search:focus-visible{border-color:var(--l-focus-ring);outline:none}.search::placeholder{color:var(--l-form-control-placeholder-color)}.listbox{overscroll-behavior:contain;max-block-size:16rem;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.option{min-block-size:36px;color:var(--l-color-text-primary);cursor:pointer;border-radius:4px;align-items:start;gap:.5rem;padding-block:.5rem;padding-inline:1.75rem .5rem;display:flex;position:relative}@media (hover:hover){.option:hover{background-color:var(--l-color-bg-state-hover)}}.option[data-active]{background-color:var(--l-color-bg-state-hover)}.option[aria-disabled=true]{opacity:.4;cursor:not-allowed}.option[data-current]:after{content:"";block-size:1.3125rem;inline-size:1rem;-webkit-mask:var(--_check-icon) center / 1rem no-repeat;mask:var(--_check-icon) center / 1rem no-repeat;background-color:currentColor;position:absolute;inset-block-start:.5rem;inset-inline-start:.5rem}.option-label{text-overflow:ellipsis;white-space:nowrap;flex:1;min-inline-size:0;overflow:hidden}.option-label mark{color:inherit;font-weight:var(--l-font-weight-semibold);background:0 0}.option-rich{flex:1;align-items:start;gap:.5rem;min-inline-size:0;display:flex}.option-rich .l-select-item-media{object-fit:cover;border-radius:4px;flex:none;align-items:center;block-size:1.5rem;inline-size:1.5rem;font-size:1.25em;display:inline-flex}.option-rich .l-select-item-text{flex-direction:column;flex:auto;min-inline-size:0;display:flex}.option-rich .l-select-item-title{text-overflow:ellipsis;white-space:nowrap;font-weight:var(--l-font-weight-semibold);overflow:hidden}.option-rich .l-select-item-description{color:var(--l-color-text-tertiary);white-space:normal;text-wrap:pretty;font-size:.75rem;line-height:1.3}.empty{color:var(--l-color-text-tertiary);text-align:center;padding:.5rem}.visually-hidden{clip-path:inset(50%);white-space:nowrap;border:0;block-size:1px;inline-size:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}:host{--_chevron-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--_x-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E");--_check-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")}@media (prefers-reduced-motion:reduce){.panel,.chevron{transition-duration:0s}}`);function v(e){return e.normalize(`NFD`).replace(/[̀-ͯ]/g,``).toLowerCase()}var y=(e,t)=>{if(!t)return!0;let n=v(e.label);return v(t).split(/\s+/).filter(Boolean).every(e=>n.includes(e))},b=class extends Event{constructor(e){super(`change`,{bubbles:!0,composed:!1,cancelable:!1}),this.value=e}},x=class extends Event{constructor(e){super(`input`,{bubbles:!0,composed:!1,cancelable:!1}),this.value=e}},S=class extends m{constructor(...e){super(...e),this._localize=new l(this),this._listId=n(`select-list`),this._floating=new h(this,{getTriggerElement:()=>this._triggerEl,getFloatingElement:()=>this._panelEl,getArrowElement:()=>null}),this._nav=new g(this,{listboxId:this._listId,getCount:()=>this._filtered.length,isDisabled:e=>this._filtered[e]?.disabled??!1,isOpen:()=>this._open,open:()=>this.show(),getOptionElements:()=>this.shadowRoot?.querySelectorAll(`.option`)}),this.#e=``,this.#t=``,this.#n=`md`,this.#r=!1,this.#i=!1,this.#a=`bottom-start`,this.#o=!1,this.filter=y,this.#s=[],this.#c=!1,this.#l=``,this.#u=[],this._defaultValues=[],this._onDocumentPointerDown=e=>{e.composedPath().includes(this)||(this.hasInteracted=!0,this._updateValidity(),this.hide({focusTrigger:!1}))},this._onTriggerKeyDown=e=>{this._open||(e.key===`ArrowDown`||e.key===`ArrowUp`||e.key===`Enter`||e.key===` `)&&(e.preventDefault(),this.show())},this._onPanelKeyDown=e=>{if(!this._nav.onKeyDown(e))switch(e.key){case`Enter`:{e.preventDefault();let t=this._filtered[this._nav.activeIndex];t&&this._select(t);break}case`Escape`:e.preventDefault(),this.hide();break;case`Tab`:this.hide({focusTrigger:!1});break;default:break}},this._onSearchInput=e=>{this._query=e.target.value,this._nav.reset(),this.dispatchEvent(new x(this._query))}}static{this.styles=[f,_]}#e;get placeholder(){return this.#e}set placeholder(e){this.#e=e}#t;get label(){return this.#t}set label(e){this.#t=e}#n;get size(){return this.#n}set size(e){this.#n=e}#r;get searchable(){return this.#r}set searchable(e){this.#r=e}#i;get withClear(){return this.#i}set withClear(e){this.#i=e}#a;get placement(){return this.#a}set placement(e){this.#a=e}#o;get multiple(){return this.#o}set multiple(e){this.#o=e}#s;get _values(){return this.#s}set _values(e){this.#s=e}#c;get _open(){return this.#c}set _open(e){this.#c=e}#l;get _query(){return this.#l}set _query(e){this.#l=e}#u;get _items(){return this.#u}set _items(e){this.#u=e}get value(){return this.multiple?[...this._values]:this._values[0]??``}set value(e){this._values=Array.isArray(e)?[...e]:e?[e]:[]}get _triggerEl(){return this.shadowRoot?.querySelector(`.trigger`)??null}get _panelEl(){return this.shadowRoot?.querySelector(`.panel`)??null}get _searchEl(){return this.shadowRoot?.querySelector(`.search`)??null}get validationTarget(){return this._triggerEl??void 0}connectedCallback(){if(super.connectedCallback(),this._readItems(),this._values.length===0){let e=this.getAttribute(`value`);if(e)this._values=[e];else{let e=this._items.filter(e=>e.selected).map(e=>e.value);this._values=this.multiple?e:e.slice(0,1)}}this._defaultValues=[...this._values],this._syncForm()}updated(e){(e.has(`_values`)||e.has(`multiple`))&&(this._syncForm(),this._updateValidity())}formResetCallback(){this._values=[...this._defaultValues],this._query=``,this._nav.reset(),super.formResetCallback(),this._syncForm()}formStateRestoreCallback(e){this.value=e,this._syncForm()}_readItems(){let t=this.querySelector(`datalist`),n=t?Array.from(t.options):[];this._items=n.map(t=>{let n=t.childElementCount>0,r=t.querySelector(`.${e(`select-item-title`)}`)?.textContent?.trim(),i=(t.getAttribute(`label`)||r||t.textContent||t.value).trim();return{value:t.value,label:i,disabled:t.disabled,selected:t.hasAttribute(`selected`),html:n?t.innerHTML:void 0}})}_itemFor(e){return this._items.find(t=>t.value===e)}get _filtered(){return this._items.filter(e=>this.filter(e,this._query))}_syncForm(){if(this.multiple){let e=new FormData;for(let t of this._values)e.append(this.name,t);this._internals.setFormValue(e)}else this._internals.setFormValue(this._values[0]??``)}_updateValidity(){this.required&&this._values.length===0?this.setValidity({valueMissing:!0},this._localize.term(`selectOption`),this._triggerEl??void 0):this.setValidity({})}show(){this._open||this.disabled||(this._readItems(),this.dispatchEvent(new Event(`show`,{cancelable:!0}))&&(this._open=!0,this._query=``,this._values.length&&this._nav.setActive(this._filtered.findIndex(e=>e.value===this._values[0])),this.updateComplete.then(()=>{this._panelEl?.showPopover?.(),this._floating.startPositioning({placement:this.placement,distance:4,minWidth:`trigger`}),(this._searchEl??this._panelEl?.querySelector(`.listbox`))?.focus(),document.addEventListener(`pointerdown`,this._onDocumentPointerDown,!0)})))}hide({focusTrigger:e=!0}={}){if(!this._open||!this.dispatchEvent(new Event(`hide`,{cancelable:!0})))return;this._open=!1,this._query=``,this._nav.reset();let t=this._panelEl;t?.matches?.(`:popover-open`)&&t.hidePopover?.(),this._floating.stopPositioning(),document.removeEventListener(`pointerdown`,this._onDocumentPointerDown,!0),e&&this._triggerEl?.focus()}_select(e){e.disabled||(this.hasInteracted=!0,this.multiple?(this._values=this._values.includes(e.value)?this._values.filter(t=>t!==e.value):[...this._values,e.value],this.dispatchEvent(new b(this.value)),this._searchEl?.focus()):(this._values=[e.value],this.dispatchEvent(new b(this.value)),this.hide()))}_removeValue(e){this._values=this._values.filter(t=>t!==e),this.hasInteracted=!0,this.dispatchEvent(new b(this.value))}_clear(e){e?.stopPropagation(),this._values=[],this.hasInteracted=!0,this.dispatchEvent(new b(this.value)),this._triggerEl?.focus()}_highlight(e){let t=this._query.trim();if(!t)return e;let n=v(e).indexOf(v(t).split(/\s+/)[0]);return n<0?e:s`${e.slice(0,n)}<mark>${e.slice(n,n+t.length)}</mark>${e.slice(n+t.length)}`}_renderSingleValue(){let e=this._itemFor(this._values[0]);return e?e.html?s`${p(e.html)}`:s`<span class="value-text">${e.label}</span>`:s`<span class="value-text placeholder">${this.placeholder}</span>`}_renderChips(){if(this._values.length===0)return s`<span class="value-text placeholder">${this.placeholder}</span>`;let e=u(t(`tag`));return this._values.map(t=>d`<${e}
|
|
2
2
|
size="sm"
|
|
3
3
|
removable
|
|
4
4
|
part="tag"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","names":[],"sources":["../../../src/html/elements/select/select.css?inline","../../../src/html/elements/select/select.ts"],"sourcesContent":[":host {\n --height: var(--l-form-control-height);\n --border-radius: var(--l-form-control-border-radius);\n --background: var(--l-color-surface-overlay);\n\n display: block;\n inline-size: 100%;\n}\n\n:host([size='xs']) {\n --height: var(--l-size-control-xs);\n}\n:host([size='sm']) {\n --height: var(--l-size-control-sm);\n}\n:host([size='lg']) {\n --height: var(--l-size-control-lg);\n}\n:host([size='xl']) {\n --height: var(--l-size-control-xl);\n}\n\n:host([disabled]) {\n opacity: 0.5;\n pointer-events: none;\n}\n\n.base {\n position: relative;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🆃🆁🅸🅶🅶🅴🆁 (button, opens the listbox popover)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.trigger {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n inline-size: 100%;\n min-block-size: var(--height);\n padding-block: 0.25rem;\n padding-inline: var(--l-form-control-padding-inline);\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: var(--border-radius);\n background-color: var(--l-form-control-background-color);\n color: var(--l-form-control-value-color);\n font: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n/* The form-control inline padding is tuned for text; before a chip row it reads\n as too much, so tighten the start edge in multiple mode (the chips bring their\n own spacing). */\n:host([multiple]) .trigger {\n padding-inline-start: 0.375rem;\n}\n\n@media (hover: hover) {\n .trigger:hover {\n border-color: var(--l-form-control-border-color-hover);\n }\n}\n\n.trigger:focus-visible {\n outline: none;\n border-color: var(--l-focus-ring);\n box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);\n}\n\n/* Selection display fills the trigger; the chevron (and overlaid clear) sit at the end. */\n.value {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0.25rem;\n overflow: hidden;\n}\n\n.value-text {\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* This element's public knobs and the chip's share two names — `--height` and\n `--border-radius` — and custom properties inherit straight through a shadow\n boundary. Left alone, every chip in the trigger renders at the *select's*\n height and corner rather than its own: a 36px chip with a 6px corner, when\n `size=\"sm\"` asked for a 24px pill. `initial` is the guaranteed-invalid value,\n so each `var(--x, …)` inside the chip falls back to its own default. Matched\n on the part, not on the tag name, so a renamed `elementPrefix` can't miss it.\n\n The fill used to collide too, back when the chip read `--background`; it now\n reads `--background-color`, which this element does not expose. */\n[part='tag'] {\n --height: initial;\n --border-radius: initial;\n}\n\n.placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.chevron {\n flex: none;\n inline-size: 1em;\n block-size: 1em;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_chevron-icon) center / contain no-repeat;\n transition: rotate 0.15s ease;\n}\n\n.trigger[aria-expanded='true'] .chevron {\n rotate: 180deg;\n}\n\n/* Clear is a SIBLING of the trigger (not a child — no nested buttons), overlaid\n * over the trigger's end, before the chevron. ≥24px hit area (WCAG 2.5.8). */\n.clear {\n position: absolute;\n inset-block: 0;\n inset-inline-end: calc(var(--l-form-control-padding-inline) + 1.25em);\n margin-block: auto;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border: 0;\n padding: 0;\n cursor: pointer;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_x-icon) center / 1em no-repeat;\n}\n\n@media (hover: hover) {\n .clear:hover {\n color: var(--l-form-control-value-color);\n }\n}\n\n.clear:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n}\n\n/* Rich content rendered in the trigger mirrors the option rows. */\n.value .l-select-item-media {\n flex: none;\n display: inline-flex;\n align-items: center;\n font-size: 1.25em;\n}\n.value .l-select-item-text {\n display: flex;\n flex-direction: column;\n min-inline-size: 0;\n}\n/* Mirror the native trigger: show only the title, drop the secondary description\n so the selected value stays compact and consistent across options. */\n.value .l-select-item-title {\n font-weight: inherit;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.value .l-select-item-description {\n display: none;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅿🅰🅽🅴🅻 (popover: search + listbox)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.panel {\n inset: auto;\n margin: 0;\n padding: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius);\n background-color: var(--background);\n color: var(--l-color-text-primary);\n box-shadow: var(--l-shadow-md);\n font-size: 0.875rem;\n line-height: 1.5;\n overflow: hidden;\n opacity: 0;\n scale: 0.98;\n transition:\n opacity 0.12s ease,\n scale 0.12s ease,\n display 0.12s allow-discrete,\n overlay 0.12s allow-discrete;\n}\n\n.panel:popover-open {\n opacity: 1;\n scale: 1;\n}\n\n@starting-style {\n .panel:popover-open {\n opacity: 0;\n scale: 0.98;\n }\n}\n\n.search-row {\n padding: 0.375rem;\n border-block-end: 1px solid var(--l-color-border-overlay);\n}\n\n.search {\n inline-size: 100%;\n block-size: var(--l-size-control-sm);\n padding-inline: 0.5rem;\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: calc(var(--border-radius) - 2px);\n background-color: var(--l-form-control-background-color);\n color: var(--l-form-control-value-color);\n font: inherit;\n}\n\n.search:focus-visible {\n outline: none;\n border-color: var(--l-focus-ring);\n}\n\n.search::placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.listbox {\n margin: 0;\n padding: 0.25rem;\n list-style: none;\n max-block-size: 16rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Mirrors the native `.l-select` picker (itself calé on l-dropdown): the\n * checkmark lives in a reserved inline-start column, only the hovered/active row\n * takes the tint, and a selected row carries no background (checkmark only). */\n.option {\n display: flex;\n position: relative;\n align-items: start;\n gap: 0.5rem;\n min-block-size: 36px;\n padding-block: 0.5rem;\n padding-inline: 1.75rem 0.5rem;\n border-radius: 4px;\n color: var(--l-color-text-primary);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .option:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n/* Keyboard-active row (moves with Arrow keys) — same tint as hover. */\n.option[data-active] {\n background-color: var(--l-color-bg-state-hover);\n}\n\n.option[aria-disabled='true'] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Selected indicator in the reserved inline-start column, pinned to the first\n * line so a wrapping description never shifts it (matches the native picker). */\n.option[data-current]::after {\n content: '';\n position: absolute;\n inset-inline-start: 0.5rem;\n inset-block-start: 0.5rem;\n inline-size: 1rem;\n block-size: 1.3125rem;\n background-color: currentColor;\n mask: var(--_check-icon) center / 1rem no-repeat;\n}\n\n.option-label {\n flex: 1;\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-label mark {\n background: transparent;\n color: inherit;\n font-weight: 600;\n}\n\n.option-rich {\n display: flex;\n align-items: start;\n gap: 0.5rem;\n flex: 1;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-media {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border-radius: 4px;\n object-fit: cover;\n font-size: 1.25em;\n}\n\n.option-rich .l-select-item-text {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.option-rich .l-select-item-description {\n color: var(--l-color-text-tertiary);\n font-size: 0.75rem;\n line-height: 1.3;\n white-space: normal;\n text-wrap: pretty;\n}\n\n.empty {\n padding: 0.5rem;\n color: var(--l-color-text-tertiary);\n text-align: center;\n}\n\n.visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅸🅲🅾🅽🆂\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n:host {\n --_chevron-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");\n --_x-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E\");\n --_check-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E\");\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel,\n .chevron {\n transition-duration: 0ms;\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport type { TemplateResult } from 'lit';\nimport { html as staticHtml, unsafeStatic } from 'lit/static-html.js';\nimport { property, state } from 'lit/decorators.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { LuxenFormAssociatedElement } from '../../shared/luxen-form-associated-element.js';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { ListboxNavController } from '../../shared/controllers/listbox-nav.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls, tagName, uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './select.css?inline';\n// Registers the chip element used to render selected values in multiple mode.\nimport '../tag/index.js';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type SelectSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** A single option read from the `<datalist>`. */\nexport interface SelectItem {\n value: string;\n /** Plain text used for filtering and the trigger display. */\n label: string;\n disabled: boolean;\n /** Authored `selected` attribute — seeds the initial value. */\n selected: boolean;\n /** Rich inner markup (from a non-empty `<option>`), projected into the row. */\n html?: string;\n}\n\n/** Default filter: accent/case-insensitive, every space-separated keyword must match. */\nexport type SelectFilter = (item: SelectItem, query: string) => boolean;\n\nfunction normalize(s: string): string {\n return s\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase();\n}\n\nconst defaultFilter: SelectFilter = (item, query) => {\n if (!query) return true;\n const label = normalize(item.label);\n return normalize(query)\n .split(/\\s+/)\n .filter(Boolean)\n .every((kw) => label.includes(kw));\n};\n\n/** Fired when the selection changes. Bubbles; not composed. */\nexport class SelectChangeEvent extends Event {\n /** The selected value (single mode) or values (multiple mode). */\n readonly value: string | string[];\n constructor(value: string | string[]) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\n/** Fired as the user types in the search box. Bubbles; not composed. */\nexport class SelectInputEvent extends Event {\n /** The current query text. */\n readonly value: string;\n constructor(value: string) {\n super('input', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface SelectEventMap {\n change: SelectChangeEvent;\n input: SelectInputEvent;\n}\n\n/**\n * A button-triggered, searchable select. A trigger shows the current selection,\n * opening a popover with a search box and a `role=\"listbox\"` of options authored\n * as a native `<datalist>` of `<option>` — the same authoring surface as\n * `l-combobox` and the native `.l-select`. Form-associated.\n *\n * @summary A searchable select with a button trigger and a popover listbox.\n *\n * @example\n * ```html\n * <l-select label=\"Country\" name=\"country\" placeholder=\"Select a country…\">\n * <datalist>\n * <option value=\"us\">United States</option>\n * <option value=\"fr\" selected>France</option>\n * </datalist>\n * </l-select>\n * ```\n *\n * @event change - Fired when the selection changes. Bubbles. Not cancelable. Properties: `value: string | string[]`.\n * @event input - Fired as the user types in the search box. Bubbles. Not cancelable. Properties: `value: string` (the query).\n * @event show - Fired before the listbox opens. Cancelable.\n * @event hide - Fired before the listbox closes. Cancelable.\n *\n * @csspart base - The host wrapper.\n * @csspart trigger - The button that opens the listbox.\n * @csspart value - The selection display inside the trigger.\n * @csspart chevron - The trigger chevron.\n * @csspart clear - The clear button.\n * @csspart panel - The floating popover panel.\n * @csspart search - The search input.\n * @csspart listbox - The options container.\n * @csspart option - Each option row.\n * @csspart empty - The \"no results\" message.\n *\n * @cssproperty [--height] - Control height. Defaults to the form-control height.\n * @cssproperty [--border-radius] - Trigger + panel radius.\n * @cssproperty [--background] - Panel background.\n *\n * @customElement l-select\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Select extends LuxenFormAssociatedElement {\n static override styles = [hostStyles, styles];\n\n private _localize = new LocalizeController(this);\n private readonly _listId = uniqueId('select-list');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._triggerEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _nav = new ListboxNavController(this, {\n listboxId: this._listId,\n getCount: () => this._filtered.length,\n isDisabled: (i) => this._filtered[i]?.disabled ?? false,\n isOpen: () => this._open,\n open: () => this.show(),\n getOptionElements: () => this.shadowRoot?.querySelectorAll<HTMLElement>('.option'),\n });\n\n /** Placeholder shown in the trigger when nothing is selected. */\n @property()\n accessor placeholder = '';\n\n /** Accessible label for the trigger. */\n @property()\n accessor label = '';\n\n /** Control size. */\n @property({ reflect: true })\n accessor size: SelectSize = 'md';\n\n /** Show a filter box inside the popover (opt-in, for long lists). */\n @property({ type: Boolean, reflect: true })\n accessor searchable = false;\n\n /** Show a button to clear the value. */\n @property({ type: Boolean, reflect: true, attribute: 'with-clear' })\n accessor withClear = false;\n\n /** Panel placement relative to the trigger. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Allow selecting multiple values — renders chips and submits one entry per value. */\n @property({ type: Boolean, reflect: true })\n accessor multiple = false;\n\n /** Override the option filter. `(item, query) => boolean`. */\n filter: SelectFilter = defaultFilter;\n\n @state() private accessor _values: string[] = [];\n @state() private accessor _open = false;\n @state() private accessor _query = '';\n @state() private accessor _items: SelectItem[] = [];\n\n private _defaultValues: string[] = [];\n\n /** The selected value (single mode) or array of values (multiple mode). */\n get value(): string | string[] {\n return this.multiple ? [...this._values] : (this._values[0] ?? '');\n }\n set value(v: string | string[]) {\n this._values = Array.isArray(v) ? [...v] : v ? [v] : [];\n }\n\n private get _triggerEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.trigger') ?? null;\n }\n private get _panelEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n }\n private get _searchEl() {\n return this.shadowRoot?.querySelector<HTMLInputElement>('.search') ?? null;\n }\n\n override get validationTarget() {\n return this._triggerEl ?? undefined;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._readItems();\n // Seed the initial selection: an explicit `value` attribute wins, else the\n // authored `<option selected>` (declarative pre-selection — markup-driven).\n if (this._values.length === 0) {\n const attr = this.getAttribute('value');\n if (attr) {\n this._values = [attr];\n } else {\n const selected = this._items.filter((i) => i.selected).map((i) => i.value);\n this._values = this.multiple ? selected : selected.slice(0, 1);\n }\n }\n this._defaultValues = [...this._values];\n this._syncForm();\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('_values') || changed.has('multiple')) {\n this._syncForm();\n this._updateValidity();\n }\n }\n\n override formResetCallback() {\n this._values = [...this._defaultValues];\n this._query = '';\n this._nav.reset();\n super.formResetCallback();\n this._syncForm();\n }\n\n override formStateRestoreCallback(state: string) {\n this.value = state;\n this._syncForm();\n }\n\n // --- Items (read from the light-DOM <datalist>) ---\n\n private _readItems() {\n const list = this.querySelector('datalist');\n const opts = list ? Array.from(list.options) : [];\n this._items = opts.map((o) => {\n const rich = o.childElementCount > 0;\n const title = o.querySelector(`.${cls('select-item-title')}`)?.textContent?.trim();\n const label = (o.getAttribute('label') || title || o.textContent || o.value).trim();\n return {\n value: o.value,\n label,\n disabled: o.disabled,\n selected: o.hasAttribute('selected'),\n html: rich ? o.innerHTML : undefined,\n };\n });\n }\n\n private _itemFor(value: string): SelectItem | undefined {\n return this._items.find((i) => i.value === value);\n }\n\n private get _filtered(): SelectItem[] {\n return this._items.filter((i) => this.filter(i, this._query));\n }\n\n // --- Validity ---\n\n /** Push the current selection to the form (single value, or one entry per value). */\n private _syncForm() {\n if (this.multiple) {\n const data = new FormData();\n for (const v of this._values) data.append(this.name, v);\n this._internals.setFormValue(data);\n } else {\n this._internals.setFormValue(this._values[0] ?? '');\n }\n }\n\n private _updateValidity() {\n if (this.required && this._values.length === 0) {\n this.setValidity(\n { valueMissing: true },\n this._localize.term('selectOption'),\n this._triggerEl ?? undefined,\n );\n } else {\n this.setValidity({});\n }\n }\n\n // --- Open / close ---\n\n show() {\n if (this._open || this.disabled) return;\n this._readItems();\n if (!this.dispatchEvent(new Event('show', { cancelable: true }))) return;\n this._open = true;\n this._query = '';\n // Pre-activate the (first) chosen option so a screen reader announces it on open.\n if (this._values.length) {\n this._nav.setActive(this._filtered.findIndex((i) => i.value === this._values[0]));\n }\n void this.updateComplete.then(() => {\n const panel = this._panelEl as (HTMLElement & { showPopover?: () => void }) | null;\n panel?.showPopover?.();\n this._floating.startPositioning({\n placement: this.placement,\n distance: 4,\n minWidth: 'trigger',\n });\n // Move focus into the search box (or the listbox when not searchable).\n (this._searchEl ?? this._panelEl?.querySelector<HTMLElement>('.listbox'))?.focus();\n document.addEventListener('pointerdown', this._onDocumentPointerDown, true);\n });\n }\n\n hide({ focusTrigger = true } = {}) {\n if (!this._open) return;\n if (!this.dispatchEvent(new Event('hide', { cancelable: true }))) return;\n this._open = false;\n this._query = '';\n this._nav.reset();\n const panel = this._panelEl as (HTMLElement & { hidePopover?: () => void }) | null;\n if (panel?.matches?.(':popover-open')) panel.hidePopover?.();\n this._floating.stopPositioning();\n document.removeEventListener('pointerdown', this._onDocumentPointerDown, true);\n if (focusTrigger) this._triggerEl?.focus();\n }\n\n private _onDocumentPointerDown = (e: PointerEvent) => {\n // popover=\"manual\" does not light-dismiss; do it ourselves.\n const path = e.composedPath();\n if (path.includes(this)) return;\n this.hasInteracted = true;\n this._updateValidity();\n this.hide({ focusTrigger: false });\n };\n\n // --- Selection ---\n\n /** Single: set the value and close. Multiple: toggle the value and stay open. */\n private _select(item: SelectItem) {\n if (item.disabled) return;\n this.hasInteracted = true;\n if (this.multiple) {\n this._values = this._values.includes(item.value)\n ? this._values.filter((v) => v !== item.value)\n : [...this._values, item.value];\n this.dispatchEvent(new SelectChangeEvent(this.value));\n this._searchEl?.focus();\n } else {\n this._values = [item.value];\n this.dispatchEvent(new SelectChangeEvent(this.value));\n this.hide();\n }\n }\n\n /** Deselect a single value (chip remove, multiple mode). */\n private _removeValue(value: string) {\n this._values = this._values.filter((v) => v !== value);\n this.hasInteracted = true;\n this.dispatchEvent(new SelectChangeEvent(this.value));\n }\n\n private _clear(e?: Event) {\n e?.stopPropagation();\n this._values = [];\n this.hasInteracted = true;\n this.dispatchEvent(new SelectChangeEvent(this.value));\n this._triggerEl?.focus();\n }\n\n // --- Keyboard ---\n\n private _onTriggerKeyDown = (e: KeyboardEvent) => {\n if (this._open) return;\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.show();\n }\n };\n\n private _onPanelKeyDown = (e: KeyboardEvent) => {\n if (this._nav.onKeyDown(e)) return;\n switch (e.key) {\n case 'Enter': {\n e.preventDefault();\n const active = this._filtered[this._nav.activeIndex];\n if (active) this._select(active);\n break;\n }\n case 'Escape':\n e.preventDefault();\n this.hide();\n break;\n case 'Tab':\n this.hide({ focusTrigger: false });\n break;\n default:\n break;\n }\n };\n\n private _onSearchInput = (e: Event) => {\n this._query = (e.target as HTMLInputElement).value;\n this._nav.reset();\n this.dispatchEvent(new SelectInputEvent(this._query));\n };\n\n // --- Render ---\n\n private _highlight(label: string): TemplateResult | string {\n const q = this._query.trim();\n if (!q) return label;\n const idx = normalize(label).indexOf(normalize(q).split(/\\s+/)[0]);\n if (idx < 0) return label;\n return html`${label.slice(0, idx)}<mark>${label.slice(idx, idx + q.length)}</mark>${label.slice(\n idx + q.length,\n )}`;\n }\n\n private _renderSingleValue() {\n const selected = this._itemFor(this._values[0]);\n if (!selected) {\n return html`<span class=\"value-text placeholder\">${this.placeholder}</span>`;\n }\n return selected.html\n ? html`${unsafeHTML(selected.html)}`\n : html`<span class=\"value-text\">${selected.label}</span>`;\n }\n\n private _renderChips() {\n if (this._values.length === 0) {\n return html`<span class=\"value-text placeholder\">${this.placeholder}</span>`;\n }\n // The chip tag is the renameable `l-tag`; it's a Shadow-DOM element so its\n // remove button renders correctly inside this element's shadow root.\n const chip = unsafeStatic(tagName('tag'));\n return this._values.map((value) => {\n const item = this._itemFor(value);\n return staticHtml`<${chip}\n size=\"sm\"\n removable\n part=\"tag\"\n @remove=${(e: Event) => {\n e.preventDefault();\n this._removeValue(value);\n }}\n @click=${(e: Event) => e.stopPropagation()}\n >${item?.label ?? value}</${chip}\n >`;\n });\n }\n\n private _renderTrigger(invalid: boolean) {\n const inner = html`\n <span\n class=\"value\"\n part=\"value\"\n >${this.multiple ? this._renderChips() : this._renderSingleValue()}</span\n >\n <span\n class=\"chevron\"\n aria-hidden=\"true\"\n ></span>\n `;\n const onClick = () => (this._open ? this.hide() : this.show());\n // Multiple mode uses a focusable div (role=\"combobox\") so the chips' remove\n // buttons are valid interactive children — a <button> trigger can't nest them.\n return this.multiple\n ? html`<div\n class=\"trigger\"\n part=\"trigger\"\n role=\"combobox\"\n tabindex=${this.disabled ? -1 : 0}\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-label=${this.label || nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @click=${onClick}\n @keydown=${this._onTriggerKeyDown}\n >\n ${inner}\n </div>`\n : html`<button\n class=\"trigger\"\n part=\"trigger\"\n type=\"button\"\n ?disabled=${this.disabled}\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-label=${this.label || nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @click=${onClick}\n @keydown=${this._onTriggerKeyDown}\n >\n ${inner}\n </button>`;\n }\n\n override render() {\n const filtered = this._filtered;\n const activeId = this._nav.activeDescendant;\n const activeIndex = this._nav.activeIndex;\n const invalid = this.required && this._values.length === 0 && this.hasInteracted;\n const hasValue = this._values.length > 0;\n const status = !this._open\n ? ''\n : filtered.length === 0\n ? this._localize.term('noResults')\n : `${filtered.length} ${this._localize.term('suggestions')}`;\n\n return html`\n <div\n class=\"base\"\n part=\"base\"\n >\n ${this._renderTrigger(invalid)}\n ${this.withClear && hasValue\n ? html`<button\n class=\"clear\"\n part=\"clear\"\n type=\"button\"\n aria-label=${this._localize.term('clear')}\n @click=${this._clear}\n ></button>`\n : nothing}\n\n <div\n class=\"panel\"\n part=\"panel\"\n popover=\"manual\"\n @keydown=${this._onPanelKeyDown}\n >\n ${this.searchable\n ? html`<div class=\"search-row\">\n <input\n class=\"search\"\n part=\"search\"\n type=\"text\"\n role=\"searchbox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n .placeholder=${this._localize.term('search') ?? ''}\n aria-controls=${this._listId}\n aria-activedescendant=${activeId ?? nothing}\n @input=${this._onSearchInput}\n />\n </div>`\n : nothing}\n\n <ul\n class=\"listbox\"\n part=\"listbox\"\n role=\"listbox\"\n id=${this._listId}\n tabindex=${this.searchable ? nothing : '0'}\n aria-label=${this.label || this._localize.term('suggestions')}\n aria-multiselectable=${this.multiple ? 'true' : nothing}\n aria-activedescendant=${!this.searchable ? (activeId ?? nothing) : nothing}\n >\n ${filtered.map(\n (item, i) => html`<li\n class=\"option\"\n part=\"option\"\n id=${this._nav.optionId(i)}\n role=\"option\"\n aria-selected=${this._values.includes(item.value) ? 'true' : 'false'}\n aria-disabled=${item.disabled ? 'true' : nothing}\n data-active=${i === activeIndex ? '' : nothing}\n data-current=${this._values.includes(item.value) ? '' : nothing}\n @pointerdown=${(e: Event) => e.preventDefault()}\n @click=${() => this._select(item)}\n >\n ${item.html\n ? html`<span class=\"option-rich\">${unsafeHTML(item.html)}</span>`\n : html`<span class=\"option-label\">${this._highlight(item.label)}</span>`}\n </li>`,\n )}\n </ul>\n ${filtered.length === 0\n ? html`<div\n class=\"empty\"\n part=\"empty\"\n >\n ${this._localize.term('noResults')}\n </div>`\n : nothing}\n </div>\n\n <div\n class=\"visually-hidden\"\n aria-live=\"polite\"\n >\n ${status}\n </div>\n </div>\n `;\n }\n}\n\n// Typed addEventListener for the colliding `change` / `input` names.\nexport interface Select {\n addEventListener<K extends keyof SelectEventMap>(\n type: K,\n listener: (this: Select, ev: SelectEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof SelectEventMap>(\n type: K,\n listener: (this: Select, ev: SelectEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"select.js","names":[],"sources":["../../../src/html/elements/select/select.css?inline","../../../src/html/elements/select/select.ts"],"sourcesContent":[":host {\n --height: var(--l-form-control-height);\n --border-radius: var(--l-form-control-border-radius);\n --background: var(--l-color-surface-overlay);\n\n display: block;\n inline-size: 100%;\n}\n\n:host([size='xs']) {\n --height: var(--l-size-control-xs);\n}\n:host([size='sm']) {\n --height: var(--l-size-control-sm);\n}\n:host([size='lg']) {\n --height: var(--l-size-control-lg);\n}\n:host([size='xl']) {\n --height: var(--l-size-control-xl);\n}\n\n:host([disabled]) {\n opacity: 0.5;\n pointer-events: none;\n}\n\n.base {\n position: relative;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🆃🆁🅸🅶🅶🅴🆁 (button, opens the listbox popover)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.trigger {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n inline-size: 100%;\n min-block-size: var(--height);\n padding-block: 0.25rem;\n padding-inline: var(--l-form-control-padding-inline);\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: var(--border-radius);\n background-color: var(--l-form-control-background-color);\n color: var(--l-form-control-value-color);\n font: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n/* The form-control inline padding is tuned for text; before a chip row it reads\n as too much, so tighten the start edge in multiple mode (the chips bring their\n own spacing). */\n:host([multiple]) .trigger {\n padding-inline-start: 0.375rem;\n}\n\n@media (hover: hover) {\n .trigger:hover {\n border-color: var(--l-form-control-border-color-hover);\n }\n}\n\n.trigger:focus-visible {\n outline: none;\n border-color: var(--l-focus-ring);\n box-shadow: 0 0 0 3px color-mix(in oklab, var(--l-focus-ring) 22%, transparent);\n}\n\n/* Selection display fills the trigger; the chevron (and overlaid clear) sit at the end. */\n.value {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0.25rem;\n overflow: hidden;\n}\n\n.value-text {\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* This element's public knobs and the chip's share two names — `--height` and\n `--border-radius` — and custom properties inherit straight through a shadow\n boundary. Left alone, every chip in the trigger renders at the *select's*\n height and corner rather than its own: a 36px chip with a 6px corner, when\n `size=\"sm\"` asked for a 24px pill. `initial` is the guaranteed-invalid value,\n so each `var(--x, …)` inside the chip falls back to its own default. Matched\n on the part, not on the tag name, so a renamed `elementPrefix` can't miss it.\n\n The fill used to collide too, back when the chip read `--background`; it now\n reads `--background-color`, which this element does not expose. */\n[part='tag'] {\n --height: initial;\n --border-radius: initial;\n}\n\n.placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.chevron {\n flex: none;\n inline-size: 1em;\n block-size: 1em;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_chevron-icon) center / contain no-repeat;\n transition: rotate 0.15s ease;\n}\n\n.trigger[aria-expanded='true'] .chevron {\n rotate: 180deg;\n}\n\n/* Clear is a SIBLING of the trigger (not a child — no nested buttons), overlaid\n * over the trigger's end, before the chevron. ≥24px hit area (WCAG 2.5.8). */\n.clear {\n position: absolute;\n inset-block: 0;\n inset-inline-end: calc(var(--l-form-control-padding-inline) + 1.25em);\n margin-block: auto;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border: 0;\n padding: 0;\n cursor: pointer;\n background-color: currentColor;\n color: var(--l-form-control-placeholder-color);\n mask: var(--_x-icon) center / 1em no-repeat;\n}\n\n@media (hover: hover) {\n .clear:hover {\n color: var(--l-form-control-value-color);\n }\n}\n\n.clear:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n}\n\n/* Rich content rendered in the trigger mirrors the option rows. */\n.value .l-select-item-media {\n flex: none;\n display: inline-flex;\n align-items: center;\n font-size: 1.25em;\n}\n.value .l-select-item-text {\n display: flex;\n flex-direction: column;\n min-inline-size: 0;\n}\n/* Mirror the native trigger: show only the title, drop the secondary description\n so the selected value stays compact and consistent across options. */\n.value .l-select-item-title {\n font-weight: inherit;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.value .l-select-item-description {\n display: none;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅿🅰🅽🅴🅻 (popover: search + listbox)\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n.panel {\n inset: auto;\n margin: 0;\n padding: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius);\n background-color: var(--background);\n color: var(--l-color-text-primary);\n box-shadow: var(--l-shadow-md);\n font-size: 0.875rem;\n line-height: 1.5;\n overflow: hidden;\n opacity: 0;\n scale: 0.98;\n transition:\n opacity 0.12s ease,\n scale 0.12s ease,\n display 0.12s allow-discrete,\n overlay 0.12s allow-discrete;\n}\n\n.panel:popover-open {\n opacity: 1;\n scale: 1;\n}\n\n@starting-style {\n .panel:popover-open {\n opacity: 0;\n scale: 0.98;\n }\n}\n\n.search-row {\n padding: 0.375rem;\n border-block-end: 1px solid var(--l-color-border-overlay);\n}\n\n.search {\n inline-size: 100%;\n block-size: var(--l-size-control-sm);\n padding-inline: 0.5rem;\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: calc(var(--border-radius) - 2px);\n background-color: var(--l-form-control-background-color);\n color: var(--l-form-control-value-color);\n font: inherit;\n}\n\n.search:focus-visible {\n outline: none;\n border-color: var(--l-focus-ring);\n}\n\n.search::placeholder {\n color: var(--l-form-control-placeholder-color);\n}\n\n.listbox {\n margin: 0;\n padding: 0.25rem;\n list-style: none;\n max-block-size: 16rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Mirrors the native `.l-select` picker (itself calé on l-dropdown): the\n * checkmark lives in a reserved inline-start column, only the hovered/active row\n * takes the tint, and a selected row carries no background (checkmark only). */\n.option {\n display: flex;\n position: relative;\n align-items: start;\n gap: 0.5rem;\n min-block-size: 36px;\n padding-block: 0.5rem;\n padding-inline: 1.75rem 0.5rem;\n border-radius: 4px;\n color: var(--l-color-text-primary);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .option:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n/* Keyboard-active row (moves with Arrow keys) — same tint as hover. */\n.option[data-active] {\n background-color: var(--l-color-bg-state-hover);\n}\n\n.option[aria-disabled='true'] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Selected indicator in the reserved inline-start column, pinned to the first\n * line so a wrapping description never shifts it (matches the native picker). */\n.option[data-current]::after {\n content: '';\n position: absolute;\n inset-inline-start: 0.5rem;\n inset-block-start: 0.5rem;\n inline-size: 1rem;\n block-size: 1.3125rem;\n background-color: currentColor;\n mask: var(--_check-icon) center / 1rem no-repeat;\n}\n\n.option-label {\n flex: 1;\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-label mark {\n background: transparent;\n color: inherit;\n font-weight: var(--l-font-weight-semibold);\n}\n\n.option-rich {\n display: flex;\n align-items: start;\n gap: 0.5rem;\n flex: 1;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-media {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n inline-size: 1.5rem;\n block-size: 1.5rem;\n border-radius: 4px;\n object-fit: cover;\n font-size: 1.25em;\n}\n\n.option-rich .l-select-item-text {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-inline-size: 0;\n}\n\n.option-rich .l-select-item-title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: var(--l-font-weight-semibold);\n}\n\n.option-rich .l-select-item-description {\n color: var(--l-color-text-tertiary);\n font-size: 0.75rem;\n line-height: 1.3;\n white-space: normal;\n text-wrap: pretty;\n}\n\n.empty {\n padding: 0.5rem;\n color: var(--l-color-text-tertiary);\n text-align: center;\n}\n\n.visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/*\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n 🅸🅲🅾🅽🆂\n▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n*/\n\n:host {\n --_chevron-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");\n --_x-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E\");\n --_check-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E\");\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel,\n .chevron {\n transition-duration: 0ms;\n }\n}\n","import { html, nothing, unsafeCSS } from 'lit';\nimport type { TemplateResult } from 'lit';\nimport { html as staticHtml, unsafeStatic } from 'lit/static-html.js';\nimport { property, state } from 'lit/decorators.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { LuxenFormAssociatedElement } from '../../shared/luxen-form-associated-element.js';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { ListboxNavController } from '../../shared/controllers/listbox-nav.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls, tagName, uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './select.css?inline';\n// Registers the chip element used to render selected values in multiple mode.\nimport '../tag/index.js';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type SelectSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** A single option read from the `<datalist>`. */\nexport interface SelectItem {\n value: string;\n /** Plain text used for filtering and the trigger display. */\n label: string;\n disabled: boolean;\n /** Authored `selected` attribute — seeds the initial value. */\n selected: boolean;\n /** Rich inner markup (from a non-empty `<option>`), projected into the row. */\n html?: string;\n}\n\n/** Default filter: accent/case-insensitive, every space-separated keyword must match. */\nexport type SelectFilter = (item: SelectItem, query: string) => boolean;\n\nfunction normalize(s: string): string {\n return s\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase();\n}\n\nconst defaultFilter: SelectFilter = (item, query) => {\n if (!query) return true;\n const label = normalize(item.label);\n return normalize(query)\n .split(/\\s+/)\n .filter(Boolean)\n .every((kw) => label.includes(kw));\n};\n\n/** Fired when the selection changes. Bubbles; not composed. */\nexport class SelectChangeEvent extends Event {\n /** The selected value (single mode) or values (multiple mode). */\n readonly value: string | string[];\n constructor(value: string | string[]) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\n/** Fired as the user types in the search box. Bubbles; not composed. */\nexport class SelectInputEvent extends Event {\n /** The current query text. */\n readonly value: string;\n constructor(value: string) {\n super('input', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface SelectEventMap {\n change: SelectChangeEvent;\n input: SelectInputEvent;\n}\n\n/**\n * A button-triggered, searchable select. A trigger shows the current selection,\n * opening a popover with a search box and a `role=\"listbox\"` of options authored\n * as a native `<datalist>` of `<option>` — the same authoring surface as\n * `l-combobox` and the native `.l-select`. Form-associated.\n *\n * @summary A searchable select with a button trigger and a popover listbox.\n *\n * @example\n * ```html\n * <l-select label=\"Country\" name=\"country\" placeholder=\"Select a country…\">\n * <datalist>\n * <option value=\"us\">United States</option>\n * <option value=\"fr\" selected>France</option>\n * </datalist>\n * </l-select>\n * ```\n *\n * @event change - Fired when the selection changes. Bubbles. Not cancelable. Properties: `value: string | string[]`.\n * @event input - Fired as the user types in the search box. Bubbles. Not cancelable. Properties: `value: string` (the query).\n * @event show - Fired before the listbox opens. Cancelable.\n * @event hide - Fired before the listbox closes. Cancelable.\n *\n * @csspart base - The host wrapper.\n * @csspart trigger - The button that opens the listbox.\n * @csspart value - The selection display inside the trigger.\n * @csspart chevron - The trigger chevron.\n * @csspart clear - The clear button.\n * @csspart panel - The floating popover panel.\n * @csspart search - The search input.\n * @csspart listbox - The options container.\n * @csspart option - Each option row.\n * @csspart empty - The \"no results\" message.\n *\n * @cssproperty [--height] - Control height. Defaults to the form-control height.\n * @cssproperty [--border-radius] - Trigger + panel radius.\n * @cssproperty [--background] - Panel background.\n *\n * @customElement l-select\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Select extends LuxenFormAssociatedElement {\n static override styles = [hostStyles, styles];\n\n private _localize = new LocalizeController(this);\n private readonly _listId = uniqueId('select-list');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._triggerEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _nav = new ListboxNavController(this, {\n listboxId: this._listId,\n getCount: () => this._filtered.length,\n isDisabled: (i) => this._filtered[i]?.disabled ?? false,\n isOpen: () => this._open,\n open: () => this.show(),\n getOptionElements: () => this.shadowRoot?.querySelectorAll<HTMLElement>('.option'),\n });\n\n /** Placeholder shown in the trigger when nothing is selected. */\n @property()\n accessor placeholder = '';\n\n /** Accessible label for the trigger. */\n @property()\n accessor label = '';\n\n /** Control size. */\n @property({ reflect: true })\n accessor size: SelectSize = 'md';\n\n /** Show a filter box inside the popover (opt-in, for long lists). */\n @property({ type: Boolean, reflect: true })\n accessor searchable = false;\n\n /** Show a button to clear the value. */\n @property({ type: Boolean, reflect: true, attribute: 'with-clear' })\n accessor withClear = false;\n\n /** Panel placement relative to the trigger. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Allow selecting multiple values — renders chips and submits one entry per value. */\n @property({ type: Boolean, reflect: true })\n accessor multiple = false;\n\n /** Override the option filter. `(item, query) => boolean`. */\n filter: SelectFilter = defaultFilter;\n\n @state() private accessor _values: string[] = [];\n @state() private accessor _open = false;\n @state() private accessor _query = '';\n @state() private accessor _items: SelectItem[] = [];\n\n private _defaultValues: string[] = [];\n\n /** The selected value (single mode) or array of values (multiple mode). */\n get value(): string | string[] {\n return this.multiple ? [...this._values] : (this._values[0] ?? '');\n }\n set value(v: string | string[]) {\n this._values = Array.isArray(v) ? [...v] : v ? [v] : [];\n }\n\n private get _triggerEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.trigger') ?? null;\n }\n private get _panelEl() {\n return this.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n }\n private get _searchEl() {\n return this.shadowRoot?.querySelector<HTMLInputElement>('.search') ?? null;\n }\n\n override get validationTarget() {\n return this._triggerEl ?? undefined;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._readItems();\n // Seed the initial selection: an explicit `value` attribute wins, else the\n // authored `<option selected>` (declarative pre-selection — markup-driven).\n if (this._values.length === 0) {\n const attr = this.getAttribute('value');\n if (attr) {\n this._values = [attr];\n } else {\n const selected = this._items.filter((i) => i.selected).map((i) => i.value);\n this._values = this.multiple ? selected : selected.slice(0, 1);\n }\n }\n this._defaultValues = [...this._values];\n this._syncForm();\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('_values') || changed.has('multiple')) {\n this._syncForm();\n this._updateValidity();\n }\n }\n\n override formResetCallback() {\n this._values = [...this._defaultValues];\n this._query = '';\n this._nav.reset();\n super.formResetCallback();\n this._syncForm();\n }\n\n override formStateRestoreCallback(state: string) {\n this.value = state;\n this._syncForm();\n }\n\n // --- Items (read from the light-DOM <datalist>) ---\n\n private _readItems() {\n const list = this.querySelector('datalist');\n const opts = list ? Array.from(list.options) : [];\n this._items = opts.map((o) => {\n const rich = o.childElementCount > 0;\n const title = o.querySelector(`.${cls('select-item-title')}`)?.textContent?.trim();\n const label = (o.getAttribute('label') || title || o.textContent || o.value).trim();\n return {\n value: o.value,\n label,\n disabled: o.disabled,\n selected: o.hasAttribute('selected'),\n html: rich ? o.innerHTML : undefined,\n };\n });\n }\n\n private _itemFor(value: string): SelectItem | undefined {\n return this._items.find((i) => i.value === value);\n }\n\n private get _filtered(): SelectItem[] {\n return this._items.filter((i) => this.filter(i, this._query));\n }\n\n // --- Validity ---\n\n /** Push the current selection to the form (single value, or one entry per value). */\n private _syncForm() {\n if (this.multiple) {\n const data = new FormData();\n for (const v of this._values) data.append(this.name, v);\n this._internals.setFormValue(data);\n } else {\n this._internals.setFormValue(this._values[0] ?? '');\n }\n }\n\n private _updateValidity() {\n if (this.required && this._values.length === 0) {\n this.setValidity(\n { valueMissing: true },\n this._localize.term('selectOption'),\n this._triggerEl ?? undefined,\n );\n } else {\n this.setValidity({});\n }\n }\n\n // --- Open / close ---\n\n show() {\n if (this._open || this.disabled) return;\n this._readItems();\n if (!this.dispatchEvent(new Event('show', { cancelable: true }))) return;\n this._open = true;\n this._query = '';\n // Pre-activate the (first) chosen option so a screen reader announces it on open.\n if (this._values.length) {\n this._nav.setActive(this._filtered.findIndex((i) => i.value === this._values[0]));\n }\n void this.updateComplete.then(() => {\n const panel = this._panelEl as (HTMLElement & { showPopover?: () => void }) | null;\n panel?.showPopover?.();\n this._floating.startPositioning({\n placement: this.placement,\n distance: 4,\n minWidth: 'trigger',\n });\n // Move focus into the search box (or the listbox when not searchable).\n (this._searchEl ?? this._panelEl?.querySelector<HTMLElement>('.listbox'))?.focus();\n document.addEventListener('pointerdown', this._onDocumentPointerDown, true);\n });\n }\n\n hide({ focusTrigger = true } = {}) {\n if (!this._open) return;\n if (!this.dispatchEvent(new Event('hide', { cancelable: true }))) return;\n this._open = false;\n this._query = '';\n this._nav.reset();\n const panel = this._panelEl as (HTMLElement & { hidePopover?: () => void }) | null;\n if (panel?.matches?.(':popover-open')) panel.hidePopover?.();\n this._floating.stopPositioning();\n document.removeEventListener('pointerdown', this._onDocumentPointerDown, true);\n if (focusTrigger) this._triggerEl?.focus();\n }\n\n private _onDocumentPointerDown = (e: PointerEvent) => {\n // popover=\"manual\" does not light-dismiss; do it ourselves.\n const path = e.composedPath();\n if (path.includes(this)) return;\n this.hasInteracted = true;\n this._updateValidity();\n this.hide({ focusTrigger: false });\n };\n\n // --- Selection ---\n\n /** Single: set the value and close. Multiple: toggle the value and stay open. */\n private _select(item: SelectItem) {\n if (item.disabled) return;\n this.hasInteracted = true;\n if (this.multiple) {\n this._values = this._values.includes(item.value)\n ? this._values.filter((v) => v !== item.value)\n : [...this._values, item.value];\n this.dispatchEvent(new SelectChangeEvent(this.value));\n this._searchEl?.focus();\n } else {\n this._values = [item.value];\n this.dispatchEvent(new SelectChangeEvent(this.value));\n this.hide();\n }\n }\n\n /** Deselect a single value (chip remove, multiple mode). */\n private _removeValue(value: string) {\n this._values = this._values.filter((v) => v !== value);\n this.hasInteracted = true;\n this.dispatchEvent(new SelectChangeEvent(this.value));\n }\n\n private _clear(e?: Event) {\n e?.stopPropagation();\n this._values = [];\n this.hasInteracted = true;\n this.dispatchEvent(new SelectChangeEvent(this.value));\n this._triggerEl?.focus();\n }\n\n // --- Keyboard ---\n\n private _onTriggerKeyDown = (e: KeyboardEvent) => {\n if (this._open) return;\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.show();\n }\n };\n\n private _onPanelKeyDown = (e: KeyboardEvent) => {\n if (this._nav.onKeyDown(e)) return;\n switch (e.key) {\n case 'Enter': {\n e.preventDefault();\n const active = this._filtered[this._nav.activeIndex];\n if (active) this._select(active);\n break;\n }\n case 'Escape':\n e.preventDefault();\n this.hide();\n break;\n case 'Tab':\n this.hide({ focusTrigger: false });\n break;\n default:\n break;\n }\n };\n\n private _onSearchInput = (e: Event) => {\n this._query = (e.target as HTMLInputElement).value;\n this._nav.reset();\n this.dispatchEvent(new SelectInputEvent(this._query));\n };\n\n // --- Render ---\n\n private _highlight(label: string): TemplateResult | string {\n const q = this._query.trim();\n if (!q) return label;\n const idx = normalize(label).indexOf(normalize(q).split(/\\s+/)[0]);\n if (idx < 0) return label;\n return html`${label.slice(0, idx)}<mark>${label.slice(idx, idx + q.length)}</mark>${label.slice(\n idx + q.length,\n )}`;\n }\n\n private _renderSingleValue() {\n const selected = this._itemFor(this._values[0]);\n if (!selected) {\n return html`<span class=\"value-text placeholder\">${this.placeholder}</span>`;\n }\n return selected.html\n ? html`${unsafeHTML(selected.html)}`\n : html`<span class=\"value-text\">${selected.label}</span>`;\n }\n\n private _renderChips() {\n if (this._values.length === 0) {\n return html`<span class=\"value-text placeholder\">${this.placeholder}</span>`;\n }\n // The chip tag is the renameable `l-tag`; it's a Shadow-DOM element so its\n // remove button renders correctly inside this element's shadow root.\n const chip = unsafeStatic(tagName('tag'));\n return this._values.map((value) => {\n const item = this._itemFor(value);\n return staticHtml`<${chip}\n size=\"sm\"\n removable\n part=\"tag\"\n @remove=${(e: Event) => {\n e.preventDefault();\n this._removeValue(value);\n }}\n @click=${(e: Event) => e.stopPropagation()}\n >${item?.label ?? value}</${chip}\n >`;\n });\n }\n\n private _renderTrigger(invalid: boolean) {\n const inner = html`\n <span\n class=\"value\"\n part=\"value\"\n >${this.multiple ? this._renderChips() : this._renderSingleValue()}</span\n >\n <span\n class=\"chevron\"\n aria-hidden=\"true\"\n ></span>\n `;\n const onClick = () => (this._open ? this.hide() : this.show());\n // Multiple mode uses a focusable div (role=\"combobox\") so the chips' remove\n // buttons are valid interactive children — a <button> trigger can't nest them.\n return this.multiple\n ? html`<div\n class=\"trigger\"\n part=\"trigger\"\n role=\"combobox\"\n tabindex=${this.disabled ? -1 : 0}\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-label=${this.label || nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @click=${onClick}\n @keydown=${this._onTriggerKeyDown}\n >\n ${inner}\n </div>`\n : html`<button\n class=\"trigger\"\n part=\"trigger\"\n type=\"button\"\n ?disabled=${this.disabled}\n aria-haspopup=\"listbox\"\n aria-expanded=${this._open ? 'true' : 'false'}\n aria-controls=${this._listId}\n aria-label=${this.label || nothing}\n aria-required=${this.required ? 'true' : nothing}\n aria-invalid=${invalid ? 'true' : nothing}\n @click=${onClick}\n @keydown=${this._onTriggerKeyDown}\n >\n ${inner}\n </button>`;\n }\n\n override render() {\n const filtered = this._filtered;\n const activeId = this._nav.activeDescendant;\n const activeIndex = this._nav.activeIndex;\n const invalid = this.required && this._values.length === 0 && this.hasInteracted;\n const hasValue = this._values.length > 0;\n const status = !this._open\n ? ''\n : filtered.length === 0\n ? this._localize.term('noResults')\n : `${filtered.length} ${this._localize.term('suggestions')}`;\n\n return html`\n <div\n class=\"base\"\n part=\"base\"\n >\n ${this._renderTrigger(invalid)}\n ${this.withClear && hasValue\n ? html`<button\n class=\"clear\"\n part=\"clear\"\n type=\"button\"\n aria-label=${this._localize.term('clear')}\n @click=${this._clear}\n ></button>`\n : nothing}\n\n <div\n class=\"panel\"\n part=\"panel\"\n popover=\"manual\"\n @keydown=${this._onPanelKeyDown}\n >\n ${this.searchable\n ? html`<div class=\"search-row\">\n <input\n class=\"search\"\n part=\"search\"\n type=\"text\"\n role=\"searchbox\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n .placeholder=${this._localize.term('search') ?? ''}\n aria-controls=${this._listId}\n aria-activedescendant=${activeId ?? nothing}\n @input=${this._onSearchInput}\n />\n </div>`\n : nothing}\n\n <ul\n class=\"listbox\"\n part=\"listbox\"\n role=\"listbox\"\n id=${this._listId}\n tabindex=${this.searchable ? nothing : '0'}\n aria-label=${this.label || this._localize.term('suggestions')}\n aria-multiselectable=${this.multiple ? 'true' : nothing}\n aria-activedescendant=${!this.searchable ? (activeId ?? nothing) : nothing}\n >\n ${filtered.map(\n (item, i) => html`<li\n class=\"option\"\n part=\"option\"\n id=${this._nav.optionId(i)}\n role=\"option\"\n aria-selected=${this._values.includes(item.value) ? 'true' : 'false'}\n aria-disabled=${item.disabled ? 'true' : nothing}\n data-active=${i === activeIndex ? '' : nothing}\n data-current=${this._values.includes(item.value) ? '' : nothing}\n @pointerdown=${(e: Event) => e.preventDefault()}\n @click=${() => this._select(item)}\n >\n ${item.html\n ? html`<span class=\"option-rich\">${unsafeHTML(item.html)}</span>`\n : html`<span class=\"option-label\">${this._highlight(item.label)}</span>`}\n </li>`,\n )}\n </ul>\n ${filtered.length === 0\n ? html`<div\n class=\"empty\"\n part=\"empty\"\n >\n ${this._localize.term('noResults')}\n </div>`\n : nothing}\n </div>\n\n <div\n class=\"visually-hidden\"\n aria-live=\"polite\"\n >\n ${status}\n </div>\n </div>\n `;\n }\n}\n\n// Typed addEventListener for the colliding `change` / `input` names.\nexport interface Select {\n addEventListener<K extends keyof SelectEventMap>(\n type: K,\n listener: (this: Select, ev: SelectEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof SelectEventMap>(\n type: K,\n listener: (this: Select, ev: SelectEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,n as r,r as i,t as a}from"../../chunks/decorate.js";import{t as o}from"../../chunks/lit-html.js";import{LuxenElement as s}from"../../shared/luxen-element.js";import{LocalizeController as c}from"../../shared/localize.js";import{ShowEvent as l}from"../../events/show.js";import{AfterShowEvent as u}from"../../events/after-show.js";import{HideEvent as d}from"../../events/hide.js";import{AfterHideEvent as f}from"../../events/after-hide.js";import{r as p}from"../../chunks/static-html.js";import{staticTag as m}from"../../static-tag.js";import h from"../../shared/styles/host.styles.js";import{t as g}from"../../chunks/map.js";var _=t(`:host{--width:min(420px, 100vw);--aspect-ratio:9 / 16;--progress-color:white;--progress-bg:#ffffff59;--progress-gap:4px;--show-duration:.2s;--hide-duration:.2s;--backdrop:var(--l-backdrop-strong);display:contents}dialog{color:#fff;opacity:0;width:100vw;max-width:none;height:100dvh;max-height:none;transition-property:opacity,display,overlay;transition-duration:var(--hide-duration);transition-behavior:allow-discrete;background:0 0;border:0;place-items:center;margin:0;padding:0;position:fixed;inset:0;overflow:clip}dialog::backdrop{background:var(--backdrop)}dialog[open]{opacity:1;transition-duration:var(--show-duration);display:grid}@starting-style{dialog[open]{opacity:0}}.frame{width:var(--width);max-width:100vw;max-height:100dvh;aspect-ratio:var(--aspect-ratio);isolation:isolate;-webkit-user-select:none;user-select:none;touch-action:pan-y;background:#000;border-radius:.75rem;position:relative;overflow:clip}@media (width<=480px){.frame{aspect-ratio:auto;border-radius:0;width:100vw;height:100dvh}}.video{object-fit:cover;background:#000;width:100%;height:100%;position:absolute;inset:0}.spinner{z-index:1;color:#fff;pointer-events:none;opacity:0;width:2rem;height:2rem;margin:auto;transition:opacity .15s;position:absolute;inset:0}.spinner[data-state=loading]{opacity:1;transition-delay:.2s}@media (prefers-reduced-motion:reduce){.spinner{transition:none}}.progress{z-index:2;gap:var(--progress-gap);pointer-events:none;display:flex;position:absolute;top:.5rem;left:.5rem;right:.5rem}.progress-segment{background:var(--progress-bg);border-radius:999px;flex:1 1 0;height:2.5px;overflow:clip}.progress-fill{background:var(--progress-color);width:100%;height:100%;transform:scaleX(var(--p,0));transform-origin:0;transition:transform 80ms linear;display:block}.progress-segment[data-state=past] .progress-fill{transform:scaleX(1)}@media (prefers-reduced-motion:reduce){.progress-fill{transition:none}:host{--show-duration:0s;--hide-duration:0s}}.header{z-index:2;pointer-events:none;align-items:center;gap:.625rem;min-width:0;display:flex;position:absolute;top:1.25rem;left:.75rem;right:4rem}.header>::slotted(*){pointer-events:auto}.header-thumb{object-fit:cover;pointer-events:auto;border-radius:50%;flex-shrink:0;width:2rem;height:2rem}.header-text{pointer-events:auto;flex-direction:column;min-width:0;display:flex}.header-label{color:#fff;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;font-weight:600;line-height:1.2;overflow:hidden}.actions{z-index:2;pointer-events:none;flex-direction:column;gap:.75rem;display:flex;position:absolute;top:1.25rem;right:.75rem}.actions>::slotted(*),.actions .btn{pointer-events:auto}.btn{color:#fff;cursor:pointer;background:#00000040;border:0;border-radius:999px;justify-content:center;align-items:center;width:2rem;height:2rem;padding:0;font-size:1rem;transition:background .15s;display:inline-flex;position:relative}.btn:hover{background:#00000073}.btn:focus-visible{outline:2px solid var(--progress-color);outline-offset:2px}.btn:before{content:"";position:absolute;inset:-6px}.btn-nav{z-index:2;width:2.25rem;height:2.25rem;font-size:1.25rem;position:absolute}.btn-nav:before{inset:-4px}.btn-previous,.btn-next{top:50%;transform:translateY(-50%)}@media (width>=768px){.btn-previous{left:calc(50% - var(--width) / 2 - 3rem)}.btn-next{right:calc(50% - var(--width) / 2 - 3rem)}}@media (width<=767px){.btn-previous,.btn-next{display:none}}.tap-zone{z-index:0;position:absolute;top:0;bottom:0}.tap-previous{width:30%;left:0}.tap-next{left:30%;right:0}.overlay{z-index:1;pointer-events:none;padding:1rem;position:absolute;inset:auto 0 0}.overlay>::slotted(*){pointer-events:auto}.sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}`),v=class extends Event{constructor(e,t){super(`story-change`,{bubbles:!1,composed:!1,cancelable:!1}),this.index=e,this.story=t}},y=class extends Event{constructor(e){super(`story-end`,{bubbles:!1,composed:!1,cancelable:!1}),this.index=e}},b=class extends Event{constructor(e,t){super(`chapter-change`,{bubbles:!1,composed:!1,cancelable:!1}),this.chapter=e,this.story=t}},x=class extends Event{constructor(e){super(`mute-change`,{bubbles:!1,composed:!1,cancelable:!1}),this.muted=e}},S=Symbol.for(`luxen-stories-viewer-scroll-lock`);if(typeof document<`u`&&!(S in document)){let t=new CSSStyleSheet;t.replaceSync(`html:has(${e(`stories-viewer`)}[data-modal]) { overflow: hidden; scrollbar-gutter: stable; }`),document.adoptedStyleSheets.push(t),Object.defineProperty(document,S,{value:t})}var C=class extends s{constructor(...e){super(...e),this._localize=new c(this),this.open=!1,this.index=0,this.muted=!0,this.loop=!1,this.autoAdvance=!0,this.lightDismiss=!0,this.chapter=0,this.stories=[],this.source=null,this._paused=!1,this._loading=!1,this._raf=0,this._holdTimer=0,this._pointerStart=null,this._segments=[],this._chapterStarts=[0],this._prefetchedSrc=null,this._onKeyDown=e=>{if(this.open)switch(e.key){case`ArrowLeft`:e.preventDefault(),this.previous();break;case`ArrowRight`:e.preventDefault(),this.next();break;case` `:e.preventDefault(),this._video.paused?this.play():this.pause();break;case`m`:case`M`:e.preventDefault(),this.muted=!this.muted,this.dispatchEvent(new x(this.muted));break}},this._onFramePointerDown=e=>{e.target.closest(`button, a, [data-no-gesture]`)||(this._pointerStart={x:e.clientX,y:e.clientY,t:performance.now()},this._holdTimer=window.setTimeout(()=>this.pause(),200))},this._onFramePointerUp=e=>{clearTimeout(this._holdTimer);let t=this._pointerStart;if(this._pointerStart=null,!t)return;let n=e.clientX-t.x,r=e.clientY-t.y,i=performance.now()-t.t,a=i<200&&Math.abs(n)<10&&Math.abs(r)<10;if(i>=200&&Math.abs(n)<10&&Math.abs(r)<10){this._video.paused&&this.play();return}if(a){e.target.closest(`[data-tap]`)?.dataset.tap===`previous`?this.previous():this.next();return}Math.abs(n)>Math.abs(r)&&Math.abs(n)>50?n<0?this.nextStory():this.previousStory():r>50&&this.close()},this._tick=()=>{if(!this.open)return;let e=this._video,t=this._segments[this.chapter];if(e&&t){let n=this._chapterStarts[this.chapter]??0,r=this._chapterStarts[this.chapter+1]??e.duration,i=Math.max(0,r-n),a=i?Math.max(0,Math.min((e.currentTime-n)/i,1)):0;t.style.setProperty(`--p`,String(a)),a>=1&&this.chapter<this._lastChapter&&this._advance(1)}this._raf=requestAnimationFrame(this._tick)}}static{this.styles=[h,_]}connectedCallback(){super.connectedCallback(),this.addEventListener(`keydown`,this._onKeyDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`keydown`,this._onKeyDown),cancelAnimationFrame(this._raf),clearTimeout(this._holdTimer),this._clearPrefetchHints()}firstUpdated(){this._dialog.addEventListener(`cancel`,e=>this._onCancel(e)),this._dialog.addEventListener(`close`,()=>this._onNativeClose()),this._dialog.addEventListener(`click`,e=>this._onDialogClick(e)),this._video.addEventListener(`ended`,()=>this._onEnded()),this._video.addEventListener(`loadedmetadata`,()=>this._tick()),this._video.addEventListener(`play`,()=>this._paused=!1),this._video.addEventListener(`pause`,()=>this._paused=!0),this._video.addEventListener(`loadstart`,()=>this._loading=!0),this._video.addEventListener(`waiting`,()=>this._loading=!0),this._video.addEventListener(`playing`,()=>this._loading=!1),this._video.addEventListener(`canplay`,()=>this._loading=!1),this._video.addEventListener(`error`,()=>this._loading=!1),this._video.addEventListener(`timeupdate`,()=>this._maybePrefetchNext())}willUpdate(e){let t=e.has(`open`)&&this.open,n=e.has(`index`);if(t||n){let e=this.stories[this.index];e&&(this._chapterStarts=e.getChapterStarts(),this.chapter=0)}}updated(e){if(e.has(`open`)){if(this.open&&!this._dialog.open){if(!this.dispatchEvent(new l)){queueMicrotask(()=>{this.open=!1});return}this.toggleAttribute(`data-modal`,!0),this._dialog.showModal(),queueMicrotask(()=>this._loadCurrent()),this._emitAfter(`after-show`)}else if(!this.open&&this._dialog.open){if(!this.dispatchEvent(new d)){queueMicrotask(()=>{this.open=!0});return}this._dialog.close()}}if(e.has(`index`)&&this.open){queueMicrotask(()=>this._loadCurrent());let e=this.stories[this.index];e&&this.dispatchEvent(new v(this.index,e))}e.has(`chapter`)&&this.open&&!e.has(`index`)&&this._applyChapterSeek(),e.has(`muted`)&&this._video&&(this._video.muted=this.muted)}_onCancel(e){this.dispatchEvent(new d)||e.preventDefault()}_onNativeClose(){cancelAnimationFrame(this._raf),this._video.pause(),this._video.removeAttribute(`src`),this._video.load(),this._clearPrefetchHints();for(let e=0;e<=this.index&&e<this.stories.length;e++)this.stories[e].seen=!0;this._restoreFocus(),this.open=!1,this.removeAttribute(`data-modal`),this._emitAfter(`after-hide`)}_onDialogClick(e){this.lightDismiss&&e.target===this._dialog&&(this.open=!1)}_refreshSegmentRefs(){this._segments=Array.from(this.shadowRoot.querySelectorAll(`[part~="progress-fill"]`))}_resetSegmentFills(){this._refreshSegmentRefs();for(let e=0;e<this._segments.length;e++){let t=+(e<this.chapter);this._segments[e].style.setProperty(`--p`,String(t))}}_loadCurrent(){if(!this.open||!this._dialog.open)return;let e=this.stories[this.index];e&&(cancelAnimationFrame(this._raf),this._prefetchedSrc=null,this._syncSlots(e),this._video.muted=this.muted,this._video.loop=this.loop,this._video.src===e.src?this._video.currentTime=0:this._video.src=e.src,requestAnimationFrame(()=>this._resetSegmentFills()),this._video.play().catch(()=>{this.muted=!0,this._video.muted=!0,this._video.play().catch(()=>{})}))}_maybePrefetchNext(){let e=this._video;if(!e.duration||e.currentTime/e.duration<.5)return;let t=this.stories[this.index+1];if(!t?.src||this._prefetchedSrc===t.src)return;this._prefetchedSrc=t.src;let n=document.createElement(`link`);n.rel=`preload`,n.as=`video`,n.href=t.src,n.crossOrigin=`anonymous`,n.dataset.luxenStoriesPrefetch=``,document.head.appendChild(n)}_clearPrefetchHints(){let e=document.head.querySelectorAll(`link[data-luxen-stories-prefetch]`);for(let t of e)t.remove();this._prefetchedSrc=null}_applyChapterSeek(){let e=this._chapterStarts[this.chapter]??0;this._video.currentTime=e,this._resetSegmentFills();let t=this.stories[this.index];t&&this.dispatchEvent(new b(this.chapter,t))}get _lastChapter(){return Math.max(0,this._chapterStarts.length-1)}_syncSlots(e){for(let e of Array.from(this.children))e.dataset.storySlot!==void 0&&e.remove();for(let t of Array.from(e.children)){if(!t.getAttribute?.(`slot`))continue;let e=t.cloneNode(!0);e.dataset.storySlot=``,this.appendChild(e)}}_onEnded(){if(this.stories[this.index]&&this.dispatchEvent(new y(this.index)),this.loop){this._video.currentTime=this._chapterStarts[0]??0,this.chapter=0,this._video.play().catch(()=>{});return}this.autoAdvance&&(this.index<this.stories.length-1?this.index+=1:this.close())}_advance(e){if(e===1){this.chapter<this._lastChapter?this.chapter+=1:this.index<this.stories.length-1?this.index+=1:this.close();return}let t=this._chapterStarts[this.chapter]??0;if(this._video.currentTime>t+1){this._video.currentTime=t,this._resetSegmentFills();return}if(this.chapter>0)--this.chapter;else if(this.index>0){let e=this.stories[this.index-1],t=Math.max(0,e.getChapterStarts().length-1);--this.index,queueMicrotask(()=>{this.chapter=t})}}openAt(e,t=0,n=null){this.stories=e,this.source=n,this.index=Math.max(0,Math.min(t,e.length-1)),this.open=!0}close(){this.open=!1}next(){this._advance(1)}previous(){this._advance(-1)}nextStory(){this.index<this.stories.length-1?this.index+=1:this.close()}previousStory(){this.index>0&&--this.index}play(){this._video?.play().catch(()=>{}),this._raf=requestAnimationFrame(this._tick)}pause(){this._video?.pause(),cancelAnimationFrame(this._raf)}async _emitAfter(e){await new Promise(e=>requestAnimationFrame(()=>e(null)));let t=this._dialog.getAnimations({subtree:!1});await Promise.all(t.map(e=>e.finished.catch(()=>{}))),e===`after-show`===this.open&&(this.dispatchEvent(e===`after-show`?new u:new f),e===`after-show`&&(this._raf=requestAnimationFrame(this._tick)))}_restoreFocus(){((this.source?.stories()??this.stories)[this.index]?.querySelector(`button[data-story-trigger]`))?.focus({preventScroll:!1})}render(){let e=this.stories.length,t=this.stories[this.index],n=this.index===0,r=this.index>=e-1,i=this._chapterStarts.length?this._chapterStarts:t?.getChapterStarts()??[0],a=m(`icon`),s=m(`spinner`);return p`
|
|
1
|
+
import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,n as r,r as i,t as a}from"../../chunks/decorate.js";import{t as o}from"../../chunks/lit-html.js";import{LuxenElement as s}from"../../shared/luxen-element.js";import{LocalizeController as c}from"../../shared/localize.js";import{ShowEvent as l}from"../../events/show.js";import{AfterShowEvent as u}from"../../events/after-show.js";import{HideEvent as d}from"../../events/hide.js";import{AfterHideEvent as f}from"../../events/after-hide.js";import{r as p}from"../../chunks/static-html.js";import{staticTag as m}from"../../static-tag.js";import h from"../../shared/styles/host.styles.js";import{t as g}from"../../chunks/map.js";var _=t(`:host{--width:min(420px, 100vw);--aspect-ratio:9 / 16;--progress-color:white;--progress-bg:#ffffff59;--progress-gap:4px;--show-duration:.2s;--hide-duration:.2s;--backdrop:var(--l-backdrop-strong);display:contents}dialog{color:#fff;opacity:0;width:100vw;max-width:none;height:100dvh;max-height:none;transition-property:opacity,display,overlay;transition-duration:var(--hide-duration);transition-behavior:allow-discrete;background:0 0;border:0;place-items:center;margin:0;padding:0;position:fixed;inset:0;overflow:clip}dialog::backdrop{background:var(--backdrop)}dialog[open]{opacity:1;transition-duration:var(--show-duration);display:grid}@starting-style{dialog[open]{opacity:0}}.frame{width:var(--width);max-width:100vw;max-height:100dvh;aspect-ratio:var(--aspect-ratio);isolation:isolate;-webkit-user-select:none;user-select:none;touch-action:pan-y;background:#000;border-radius:.75rem;position:relative;overflow:clip}@media (width<=480px){.frame{aspect-ratio:auto;border-radius:0;width:100vw;height:100dvh}}.video{object-fit:cover;background:#000;width:100%;height:100%;position:absolute;inset:0}.spinner{z-index:1;color:#fff;pointer-events:none;opacity:0;width:2rem;height:2rem;margin:auto;transition:opacity .15s;position:absolute;inset:0}.spinner[data-state=loading]{opacity:1;transition-delay:.2s}@media (prefers-reduced-motion:reduce){.spinner{transition:none}}.progress{z-index:2;gap:var(--progress-gap);pointer-events:none;display:flex;position:absolute;top:.5rem;left:.5rem;right:.5rem}.progress-segment{background:var(--progress-bg);border-radius:999px;flex:1 1 0;height:2.5px;overflow:clip}.progress-fill{background:var(--progress-color);width:100%;height:100%;transform:scaleX(var(--p,0));transform-origin:0;transition:transform 80ms linear;display:block}.progress-segment[data-state=past] .progress-fill{transform:scaleX(1)}@media (prefers-reduced-motion:reduce){.progress-fill{transition:none}:host{--show-duration:0s;--hide-duration:0s}}.header{z-index:2;pointer-events:none;align-items:center;gap:.625rem;min-width:0;display:flex;position:absolute;top:1.25rem;left:.75rem;right:4rem}.header>::slotted(*){pointer-events:auto}.header-thumb{object-fit:cover;pointer-events:auto;border-radius:50%;flex-shrink:0;width:2rem;height:2rem}.header-text{pointer-events:auto;flex-direction:column;min-width:0;display:flex}.header-label{font-size:.875rem;font-weight:var(--l-font-weight-semibold);color:#fff;text-overflow:ellipsis;white-space:nowrap;line-height:1.2;overflow:hidden}.actions{z-index:2;pointer-events:none;flex-direction:column;gap:.75rem;display:flex;position:absolute;top:1.25rem;right:.75rem}.actions>::slotted(*),.actions .btn{pointer-events:auto}.btn{color:#fff;cursor:pointer;background:#00000040;border:0;border-radius:999px;justify-content:center;align-items:center;width:2rem;height:2rem;padding:0;font-size:1rem;transition:background .15s;display:inline-flex;position:relative}.btn:hover{background:#00000073}.btn:focus-visible{outline:2px solid var(--progress-color);outline-offset:2px}.btn:before{content:"";position:absolute;inset:-6px}.btn-nav{z-index:2;width:2.25rem;height:2.25rem;font-size:1.25rem;position:absolute}.btn-nav:before{inset:-4px}.btn-previous,.btn-next{top:50%;transform:translateY(-50%)}@media (width>=768px){.btn-previous{left:calc(50% - var(--width) / 2 - 3rem)}.btn-next{right:calc(50% - var(--width) / 2 - 3rem)}}@media (width<=767px){.btn-previous,.btn-next{display:none}}.tap-zone{z-index:0;position:absolute;top:0;bottom:0}.tap-previous{width:30%;left:0}.tap-next{left:30%;right:0}.overlay{z-index:1;pointer-events:none;padding:1rem;position:absolute;inset:auto 0 0}.overlay>::slotted(*){pointer-events:auto}.sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}`),v=class extends Event{constructor(e,t){super(`story-change`,{bubbles:!1,composed:!1,cancelable:!1}),this.index=e,this.story=t}},y=class extends Event{constructor(e){super(`story-end`,{bubbles:!1,composed:!1,cancelable:!1}),this.index=e}},b=class extends Event{constructor(e,t){super(`chapter-change`,{bubbles:!1,composed:!1,cancelable:!1}),this.chapter=e,this.story=t}},x=class extends Event{constructor(e){super(`mute-change`,{bubbles:!1,composed:!1,cancelable:!1}),this.muted=e}},S=Symbol.for(`luxen-stories-viewer-scroll-lock`);if(typeof document<`u`&&!(S in document)){let t=new CSSStyleSheet;t.replaceSync(`html:has(${e(`stories-viewer`)}[data-modal]) { overflow: hidden; scrollbar-gutter: stable; }`),document.adoptedStyleSheets.push(t),Object.defineProperty(document,S,{value:t})}var C=class extends s{constructor(...e){super(...e),this._localize=new c(this),this.open=!1,this.index=0,this.muted=!0,this.loop=!1,this.autoAdvance=!0,this.lightDismiss=!0,this.chapter=0,this.stories=[],this.source=null,this._paused=!1,this._loading=!1,this._raf=0,this._holdTimer=0,this._pointerStart=null,this._segments=[],this._chapterStarts=[0],this._prefetchedSrc=null,this._onKeyDown=e=>{if(this.open)switch(e.key){case`ArrowLeft`:e.preventDefault(),this.previous();break;case`ArrowRight`:e.preventDefault(),this.next();break;case` `:e.preventDefault(),this._video.paused?this.play():this.pause();break;case`m`:case`M`:e.preventDefault(),this.muted=!this.muted,this.dispatchEvent(new x(this.muted));break}},this._onFramePointerDown=e=>{e.target.closest(`button, a, [data-no-gesture]`)||(this._pointerStart={x:e.clientX,y:e.clientY,t:performance.now()},this._holdTimer=window.setTimeout(()=>this.pause(),200))},this._onFramePointerUp=e=>{clearTimeout(this._holdTimer);let t=this._pointerStart;if(this._pointerStart=null,!t)return;let n=e.clientX-t.x,r=e.clientY-t.y,i=performance.now()-t.t,a=i<200&&Math.abs(n)<10&&Math.abs(r)<10;if(i>=200&&Math.abs(n)<10&&Math.abs(r)<10){this._video.paused&&this.play();return}if(a){e.target.closest(`[data-tap]`)?.dataset.tap===`previous`?this.previous():this.next();return}Math.abs(n)>Math.abs(r)&&Math.abs(n)>50?n<0?this.nextStory():this.previousStory():r>50&&this.close()},this._tick=()=>{if(!this.open)return;let e=this._video,t=this._segments[this.chapter];if(e&&t){let n=this._chapterStarts[this.chapter]??0,r=this._chapterStarts[this.chapter+1]??e.duration,i=Math.max(0,r-n),a=i?Math.max(0,Math.min((e.currentTime-n)/i,1)):0;t.style.setProperty(`--p`,String(a)),a>=1&&this.chapter<this._lastChapter&&this._advance(1)}this._raf=requestAnimationFrame(this._tick)}}static{this.styles=[h,_]}connectedCallback(){super.connectedCallback(),this.addEventListener(`keydown`,this._onKeyDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`keydown`,this._onKeyDown),cancelAnimationFrame(this._raf),clearTimeout(this._holdTimer),this._clearPrefetchHints()}firstUpdated(){this._dialog.addEventListener(`cancel`,e=>this._onCancel(e)),this._dialog.addEventListener(`close`,()=>this._onNativeClose()),this._dialog.addEventListener(`click`,e=>this._onDialogClick(e)),this._video.addEventListener(`ended`,()=>this._onEnded()),this._video.addEventListener(`loadedmetadata`,()=>this._tick()),this._video.addEventListener(`play`,()=>this._paused=!1),this._video.addEventListener(`pause`,()=>this._paused=!0),this._video.addEventListener(`loadstart`,()=>this._loading=!0),this._video.addEventListener(`waiting`,()=>this._loading=!0),this._video.addEventListener(`playing`,()=>this._loading=!1),this._video.addEventListener(`canplay`,()=>this._loading=!1),this._video.addEventListener(`error`,()=>this._loading=!1),this._video.addEventListener(`timeupdate`,()=>this._maybePrefetchNext())}willUpdate(e){let t=e.has(`open`)&&this.open,n=e.has(`index`);if(t||n){let e=this.stories[this.index];e&&(this._chapterStarts=e.getChapterStarts(),this.chapter=0)}}updated(e){if(e.has(`open`)){if(this.open&&!this._dialog.open){if(!this.dispatchEvent(new l)){queueMicrotask(()=>{this.open=!1});return}this.toggleAttribute(`data-modal`,!0),this._dialog.showModal(),queueMicrotask(()=>this._loadCurrent()),this._emitAfter(`after-show`)}else if(!this.open&&this._dialog.open){if(!this.dispatchEvent(new d)){queueMicrotask(()=>{this.open=!0});return}this._dialog.close()}}if(e.has(`index`)&&this.open){queueMicrotask(()=>this._loadCurrent());let e=this.stories[this.index];e&&this.dispatchEvent(new v(this.index,e))}e.has(`chapter`)&&this.open&&!e.has(`index`)&&this._applyChapterSeek(),e.has(`muted`)&&this._video&&(this._video.muted=this.muted)}_onCancel(e){this.dispatchEvent(new d)||e.preventDefault()}_onNativeClose(){cancelAnimationFrame(this._raf),this._video.pause(),this._video.removeAttribute(`src`),this._video.load(),this._clearPrefetchHints();for(let e=0;e<=this.index&&e<this.stories.length;e++)this.stories[e].seen=!0;this._restoreFocus(),this.open=!1,this.removeAttribute(`data-modal`),this._emitAfter(`after-hide`)}_onDialogClick(e){this.lightDismiss&&e.target===this._dialog&&(this.open=!1)}_refreshSegmentRefs(){this._segments=Array.from(this.shadowRoot.querySelectorAll(`[part~="progress-fill"]`))}_resetSegmentFills(){this._refreshSegmentRefs();for(let e=0;e<this._segments.length;e++){let t=+(e<this.chapter);this._segments[e].style.setProperty(`--p`,String(t))}}_loadCurrent(){if(!this.open||!this._dialog.open)return;let e=this.stories[this.index];e&&(cancelAnimationFrame(this._raf),this._prefetchedSrc=null,this._syncSlots(e),this._video.muted=this.muted,this._video.loop=this.loop,this._video.src===e.src?this._video.currentTime=0:this._video.src=e.src,requestAnimationFrame(()=>this._resetSegmentFills()),this._video.play().catch(()=>{this.muted=!0,this._video.muted=!0,this._video.play().catch(()=>{})}))}_maybePrefetchNext(){let e=this._video;if(!e.duration||e.currentTime/e.duration<.5)return;let t=this.stories[this.index+1];if(!t?.src||this._prefetchedSrc===t.src)return;this._prefetchedSrc=t.src;let n=document.createElement(`link`);n.rel=`preload`,n.as=`video`,n.href=t.src,n.crossOrigin=`anonymous`,n.dataset.luxenStoriesPrefetch=``,document.head.appendChild(n)}_clearPrefetchHints(){let e=document.head.querySelectorAll(`link[data-luxen-stories-prefetch]`);for(let t of e)t.remove();this._prefetchedSrc=null}_applyChapterSeek(){let e=this._chapterStarts[this.chapter]??0;this._video.currentTime=e,this._resetSegmentFills();let t=this.stories[this.index];t&&this.dispatchEvent(new b(this.chapter,t))}get _lastChapter(){return Math.max(0,this._chapterStarts.length-1)}_syncSlots(e){for(let e of Array.from(this.children))e.dataset.storySlot!==void 0&&e.remove();for(let t of Array.from(e.children)){if(!t.getAttribute?.(`slot`))continue;let e=t.cloneNode(!0);e.dataset.storySlot=``,this.appendChild(e)}}_onEnded(){if(this.stories[this.index]&&this.dispatchEvent(new y(this.index)),this.loop){this._video.currentTime=this._chapterStarts[0]??0,this.chapter=0,this._video.play().catch(()=>{});return}this.autoAdvance&&(this.index<this.stories.length-1?this.index+=1:this.close())}_advance(e){if(e===1){this.chapter<this._lastChapter?this.chapter+=1:this.index<this.stories.length-1?this.index+=1:this.close();return}let t=this._chapterStarts[this.chapter]??0;if(this._video.currentTime>t+1){this._video.currentTime=t,this._resetSegmentFills();return}if(this.chapter>0)--this.chapter;else if(this.index>0){let e=this.stories[this.index-1],t=Math.max(0,e.getChapterStarts().length-1);--this.index,queueMicrotask(()=>{this.chapter=t})}}openAt(e,t=0,n=null){this.stories=e,this.source=n,this.index=Math.max(0,Math.min(t,e.length-1)),this.open=!0}close(){this.open=!1}next(){this._advance(1)}previous(){this._advance(-1)}nextStory(){this.index<this.stories.length-1?this.index+=1:this.close()}previousStory(){this.index>0&&--this.index}play(){this._video?.play().catch(()=>{}),this._raf=requestAnimationFrame(this._tick)}pause(){this._video?.pause(),cancelAnimationFrame(this._raf)}async _emitAfter(e){await new Promise(e=>requestAnimationFrame(()=>e(null)));let t=this._dialog.getAnimations({subtree:!1});await Promise.all(t.map(e=>e.finished.catch(()=>{}))),e===`after-show`===this.open&&(this.dispatchEvent(e===`after-show`?new u:new f),e===`after-show`&&(this._raf=requestAnimationFrame(this._tick)))}_restoreFocus(){((this.source?.stories()??this.stories)[this.index]?.querySelector(`button[data-story-trigger]`))?.focus({preventScroll:!1})}render(){let e=this.stories.length,t=this.stories[this.index],n=this.index===0,r=this.index>=e-1,i=this._chapterStarts.length?this._chapterStarts:t?.getChapterStarts()??[0],a=m(`icon`),s=m(`spinner`);return p`
|
|
2
2
|
<dialog
|
|
3
3
|
part="dialog"
|
|
4
4
|
aria-label=${this._localize.term(`stories`)}
|