@sken-ds/primitives 0.3.6 → 0.3.8
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/dist/index.js +14 -12
- package/dist/ix-bridge.css +20 -20
- package/dist/sken-button.js.map +1 -1
- package/dist/sken-checkbox.js.map +1 -1
- package/dist/sken-combobox.js +51 -42
- package/dist/sken-combobox.js.map +1 -1
- package/dist/{sken-datatable-BtR-NwRN.js → sken-datatable-ChDjB_T_.js} +7 -9
- package/dist/{sken-datatable-BtR-NwRN.js.map → sken-datatable-ChDjB_T_.js.map} +1 -1
- package/dist/sken-datatable.js +1 -1
- package/dist/sken-date-picker.js.map +1 -1
- package/dist/{sken-dialog-hRtML2jW.js → sken-dialog-CLqZu1WQ.js} +33 -18
- package/dist/sken-dialog-CLqZu1WQ.js.map +1 -0
- package/dist/sken-dialog.js +1 -1
- package/dist/sken-filter-chip.js +16 -10
- package/dist/sken-filter-chip.js.map +1 -1
- package/dist/sken-input.js.map +1 -1
- package/dist/sken-layout-region.js +30 -0
- package/dist/sken-layout-region.js.map +1 -0
- package/dist/sken-layout.js +295 -0
- package/dist/sken-layout.js.map +1 -0
- package/dist/sken-number-input.js +32 -42
- package/dist/sken-number-input.js.map +1 -1
- package/dist/sken-switch.js.map +1 -1
- package/dist/sken-textarea.js.map +1 -1
- package/package.json +10 -2
- package/dist/sken-dialog-hRtML2jW.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sken-combobox.js","names":[],"sources":["../src/components/sken-combobox.ts"],"sourcesContent":["/**\n * `<sken-combobox>` — Sken contract implementation.\n *\n * Single + multiple selection. Filterable. Groupable. Tokenised.\n * No third-party vendor in the runtime.\n *\n * The Sken contract (`SkenComboboxProps`, `SkenComboboxItemProps`,\n * `SkenComboboxGroupProps`, `SkenComboboxEmits`,\n * `SkenComboboxValue<M>`, `skenComboboxAria`) lives in\n * `@sken-ds/contracts`. This component is a Lit 3 implementation\n * that:\n *\n * 1. Accepts the Sken contract props (`mode`, `value`,\n * `filterable`, `label`, `helperText`, `placeholder`,\n * `disabled`, `invalid`, `required`, `name`, `size`,\n * `describedById`).\n * 2. Discovers the options at first render by introspecting\n * `<sken-combobox-item>` and `<sken-combobox-group>`\n * children. Each item knows its `value` and `label`; each\n * group knows its `label` and `disabled` state. Items\n * inside a group are associated with that group; items\n * outside any group belong to an implicit \"ungrouped\"\n * bucket rendered at the top of the listbox (no header).\n * 3. Renders a Shadow DOM with: a `<label>` for the visible\n * field label, an `<input role=\"combobox\">` for the\n * trigger, a clear button, a chevron, a listbox panel\n * containing the (optionally grouped, optionally filtered)\n * options, and helper / invalid / warning / valid text.\n * 4. Implements the WAI-ARIA combobox 1.2 pattern: the input\n * has `role=\"combobox\"`, `aria-expanded`, `aria-controls`\n * pointing to the listbox, and `aria-activedescendant`\n * pointing to the currently highlighted option. The listbox\n * has `role=\"listbox\"`, each option has `role=\"option\"` +\n * `aria-selected`, and each group has `role=\"group\"` +\n * `aria-labelledby` pointing to the group's header.\n * 5. Implements the keyboard model: ArrowUp / ArrowDown open\n * and navigate, Home / End jump to the ends, Enter selects,\n * Escape closes (or clears when already closed), Tab moves\n * focus out without selecting.\n * 6. Emits the Sken-shaped `sken-change` event with the new\n * value. The shape of the value depends on `mode`:\n * `string | null` for `single`, `string[]` for `multiple`.\n * Native `change` on the input is suppressed; consumers use\n * `sken-change` for portability.\n *\n * Implementation notes:\n *\n * - The children are introspected lazily on each render so\n * dynamic option lists (Vue v-for, conditional rendering)\n * work without manual registration.\n *\n * - The filter is a case-insensitive substring match against\n * `item.label`. Groups hide themselves when ALL their items\n * are filtered out. An empty filter restores every item.\n *\n * - The listbox is positioned absolutely below the input,\n * matching the input's width. The visual treatment is\n * tokenised via `--sken-*` custom properties; the consumer\n * imports `@sken-ds/theme/css` once at app boot.\n *\n * - The \"marker\" pattern for the children (items and groups\n * render nothing; the parent projects them) is documented in\n * the iX 5 reference and in the comments on the children\n * themselves. The trade-off is that `<sken-combobox-item>`\n * does not appear in the rendered DOM tree as itself.\n */\nimport { LitElement, html, css, type PropertyValues } from 'lit'\nimport { customElement, property, state, query } from 'lit/decorators.js'\n\nimport type {\n SkenComboboxMode,\n SkenComboboxValue,\n SkenComboboxProps,\n SkenComboboxDisplay,\n SkenComboboxChipOverflow,\n} from '@sken-ds/contracts'\n\n// Side-effect import: register the `<sken-filter-chip>`\n// custom element so the combobox can render chips inside its\n// shadow DOM without forcing the consumer to import the chip\n// module explicitly. The chip is a pure consumer-side primitive\n// of the combobox in `display=\"chips\"` mode, so it belongs in\n// the combobox's module graph.\nimport './sken-filter-chip.js'\n\nimport type { SkenComboboxItem } from './sken-combobox-item'\nimport type { SkenComboboxGroup } from './sken-combobox-group'\n\n/**\n * Internal flat model: each item is a row, optionally\n * associated with a group label. The listbox renders a flat\n * list; the group headers are rendered as separator rows when\n * the group becomes visible after filtering.\n */\ninterface ResolvedItem {\n value: string\n label: string\n disabled: boolean\n groupLabel: string | null\n groupDisabled: boolean\n}\n\n@customElement('sken-combobox')\nexport class SkenCombobox\n extends LitElement\n implements Omit<SkenComboboxProps, 'value' | 'slots'>\n{\n // The Sken contract declares `value` as `SkenComboboxValue`\n // (a generic defaulting to `string | null`). This\n // implementation uses `SkenComboboxValue<SkenComboboxMode>`\n // (the full union) so that single-mode and multi-mode\n // values can both flow through `_setValue` without TS\n // narrowing complaints. The runtime contract is\n // identical; the difference is only at the type level.\n // ── public API (mapped to Sken contract) ──────────────────────────\n\n @property({ reflect: true }) mode: SkenComboboxMode = 'single'\n\n /**\n * How the trigger renders the current selection in\n * `mode=\"multiple\"`. See `SkenComboboxDisplay`. No effect\n * in `mode=\"single\"`. @default \"count\"\n */\n @property() display: SkenComboboxDisplay = 'count'\n\n /**\n * How the chip row overflows when `display=\"chips\"` and\n * the chips do not fit on a single line. See\n * `SkenComboboxChipOverflow`. No effect in\n * `mode=\"single\"` or when `display=\"count\"`.\n * @default \"wrap\"\n */\n @property({ attribute: 'chip-overflow' }) chipOverflow: SkenComboboxChipOverflow = 'wrap'\n\n @property({ attribute: false })\n value: SkenComboboxValue<SkenComboboxMode> = null as SkenComboboxValue<SkenComboboxMode>\n\n @property({ type: Boolean }) filterable = true\n\n @property() label = ''\n\n @property({ attribute: 'helper-text' }) helperText = ''\n\n @property() placeholder = ''\n\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: Boolean, reflect: true }) invalid = false\n\n @property({ type: Boolean, reflect: true }) required = false\n\n @property() name = ''\n\n @property({ reflect: true }) size: 'sm' | 'md' | 'lg' = 'md'\n\n @property({ attribute: 'described-by-id' }) describedById = ''\n\n // ── internal state ────────────────────────────────────────────────\n\n /** Resolved flat model of all items, with their group labels. */\n @state() private _items: ResolvedItem[] = []\n\n /** Current filter query. Lowercased for case-insensitive match. */\n @state() private _query = ''\n\n /** Whether the listbox is open. */\n @state() private _open = false\n\n /** Index into the visible items (after filtering). -1 = no highlight. */\n @state() private _activeIndex = -1\n\n /**\n * Number of selected chips that fit on the first row of the\n * trigger. The rest are hidden behind the overflow chip in\n * `chipOverflow=\"count\"` mode. `-1` means \"no measurement\n * yet\" (or `chipOverflow` is not `\"count\"`).\n *\n * The measurement runs once per render in `updated()`. It\n * uses the actual rendered widths of the chip DOM nodes,\n * not a guess, so the overflow chip appears exactly when\n * the last visible chip would not fit. On window resize\n * the user re-renders (e.g. by typing, opening the\n * listbox, or any other state change) and the measurement\n * re-runs.\n */\n @state() private _visibleChipCount = -1\n\n @query('input') private _input!: HTMLInputElement\n\n // ── lifecycle ─────────────────────────────────────────────────────\n\n override connectedCallback(): void {\n super.connectedCallback()\n // Re-resolve on any DOM change inside the host. This is how\n // we pick up dynamic option lists (Vue v-for, etc.) without\n // the consumer having to call a register() method.\n this._observer = new MutationObserver(() => {\n this._resolveItems()\n // Force a re-render so the listbox reflects the new\n // items. Without this, mutations that happen after the\n // first render (e.g. Vue mounting children asynchronously)\n // would not trigger a render() pass.\n this.requestUpdate()\n })\n this._observer.observe(this, { childList: true, subtree: true })\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this._observer?.disconnect()\n }\n\n override willUpdate(_changed: PropertyValues): void {\n // Resolving on every update is cheap (a few querySelectorAll\n // calls) and guarantees the model is fresh by the time\n // render() runs.\n this._resolveItems()\n }\n\n override firstUpdated(_changed: PropertyValues): void {\n // Belt-and-braces: re-resolve after the first render. Some\n // frameworks (notably Vue's h() with custom elements) may\n // append children to the host AFTER the Lit lifecycle has\n // run its first render, so the initial willUpdate sees an\n // empty children list. By the time firstUpdated runs, the\n // DOM is settled and a fresh resolve is authoritative.\n this._resolveItems()\n // Some bundlers (Vite) and frameworks (Vue) mount custom\n // element children asynchronously after the host's\n // `connectedCallback`. We poll once on the next microtask\n // to catch any children that were attached between the\n // synchronous render and the next paint. The cost is one\n // extra querySelectorAll; the benefit is that dynamic v-for\n // lists in Vue render correctly on the first paint.\n queueMicrotask(() => {\n if (this._items.length === 0 && this.children.length > 0) {\n this._resolveItems()\n this.requestUpdate()\n }\n })\n }\n\n override updated(_changed: PropertyValues): void {\n super.updated(_changed)\n // Measure the chip row to compute the overflow budget. We\n // only run this in `chipOverflow=\"count\"` mode; for `wrap`\n // and `none`, CSS does the work and the state stays at\n // its initial `-1` (\"no measurement\").\n //\n // The measurement is intentionally simple: sum the\n // rendered widths of the chip DOM nodes (offsetWidth\n // includes padding + border) plus the gap, and stop when\n // the next chip would push the row past the trigger's\n // content box. We do not use ResizeObserver — a chip\n // add/remove, a value change, or any other state update\n // is enough to retrigger the measurement, and the trigger\n // height is constant in this mode so a single read per\n // render is correct.\n //\n // The overflow chip itself is excluded from the\n // measurement (it carries the `.overflow` class). If it\n // were included, the loop would re-add it to the count\n // every render and never converge on a stable `_visibleChipCount`.\n if (this.mode !== 'multiple' || this.display !== 'chips' || this.chipOverflow !== 'count') {\n // No measurement in this mode. Reset the budget so a\n // later switch to `\"count\"` re-measures from scratch.\n if (this._visibleChipCount !== -1) this._visibleChipCount = -1\n return\n }\n const row = this.renderRoot.querySelector<HTMLElement>('.chip-row')\n if (!row) return\n const trigger = this.renderRoot.querySelector<HTMLElement>('.trigger')\n if (!trigger) return\n // Only the real chips, not the overflow sentinel.\n const chips = Array.from(\n row.querySelectorAll<HTMLElement>('sken-filter-chip:not(.overflow)'),\n )\n if (chips.length === 0) {\n if (this._visibleChipCount !== 0) this._visibleChipCount = 0\n return\n }\n // Available width = trigger clientWidth minus the\n // non-chip siblings (clear + chevron + their gap). We\n // approximate by reading the trigger's content width and\n // subtracting the input's width as a safe lower bound\n // (the input has `flex: 1`, so it shrinks to whatever\n // space is left after the chips).\n const triggerStyle = getComputedStyle(trigger)\n const paddingX = (parseFloat(triggerStyle.paddingLeft) || 0)\n + (parseFloat(triggerStyle.paddingRight) || 0)\n const gap = parseFloat(triggerStyle.gap) || 0\n const triggerInner = trigger.clientWidth - paddingX\n // Walk the chips left-to-right. The row's content can\n // grow indefinitely in `flex-wrap: nowrap` rows; we sum\n // each chip's offsetWidth and stop when adding the next\n // would push us past `triggerInner`. We reserve space\n // for one overflow chip (32px) plus its gap so the budget\n // is honest.\n const OVERFLOW_CHIP_WIDTH = 32 + gap\n let acc = 0\n let visible = 0\n for (let i = 0; i < chips.length; i++) {\n const w = chips[i].offsetWidth\n const need = acc + w + (i > 0 ? gap : 0)\n // If we have to add an overflow chip at the end, we\n // need to reserve space for it. Only reserve if there\n // is at least one chip to hide.\n const remaining = chips.length - (i + 1)\n const reserved = remaining > 0 ? OVERFLOW_CHIP_WIDTH : 0\n if (need + reserved > triggerInner) break\n acc = need\n visible = i + 1\n }\n if (this._visibleChipCount !== visible) {\n this._visibleChipCount = visible\n }\n }\n\n private _observer?: MutationObserver\n\n // ── model: introspect children, build the flat list ──────────────\n\n /**\n * Walk the host's children. For each direct child, classify\n * it as a group (and recurse into it) or as an ungrouped item.\n * The result is a flat list of items with their group label\n * denormalised onto each row. The listbox render uses this\n * flat list directly, which keeps the rendering loop simple\n * and lets the filter operate on a single array.\n */\n private _resolveItems(): void {\n const items: ResolvedItem[] = []\n for (const child of Array.from(this.children)) {\n if (customElements.get('sken-combobox-group') && child instanceof customElements.get('sken-combobox-group')!) {\n const group = child as SkenComboboxGroup\n const groupLabel = group.label || ''\n const groupDisabled = group.disabled\n for (const itemEl of Array.from(group.children)) {\n if (customElements.get('sken-combobox-item') && itemEl instanceof customElements.get('sken-combobox-item')!) {\n const item = itemEl as SkenComboboxItem\n items.push({\n value: item.value,\n label: item.label,\n disabled: item.disabled || groupDisabled,\n groupLabel,\n groupDisabled,\n })\n }\n }\n } else if (customElements.get('sken-combobox-item') && child instanceof customElements.get('sken-combobox-item')!) {\n const item = child as SkenComboboxItem\n items.push({\n value: item.value,\n label: item.label,\n disabled: item.disabled,\n groupLabel: null,\n groupDisabled: false,\n })\n }\n }\n this._items = items\n }\n\n // ── filter ────────────────────────────────────────────────────────\n\n /**\n * Apply the current query to the resolved items. An item is\n * visible when its label contains the query (case-insensitive),\n * or when the query is empty. A group is visible when at\n * least one of its items is visible. Ungrouped items are\n * always at the top.\n */\n private get _visibleItems(): ResolvedItem[] {\n if (!this._query) return this._items\n const q = this._query.toLowerCase()\n return this._items.filter((it) => !it.disabled || true) // disabled items are still shown, just not selectable\n .filter((it) => it.label.toLowerCase().includes(q))\n }\n\n /**\n * The visible items, grouped by `groupLabel`. Each group\n * becomes a `{ label, items }` entry; ungrouped items go\n * into a synthetic group with `label = null`.\n */\n private get _visibleGroups(): { label: string | null; items: ResolvedItem[] }[] {\n const visible = this._visibleItems\n const groups = new Map<string | null, ResolvedItem[]>()\n for (const it of visible) {\n const key = it.groupLabel || null\n if (!groups.has(key)) groups.set(key, [])\n groups.get(key)!.push(it)\n }\n // Preserve declaration order. Items inside each group are\n // already in order (we walked the DOM in order). Groups are\n // returned in the order their first item appears.\n const ordered: { label: string | null; items: ResolvedItem[] }[] = []\n for (const [label, items] of groups) {\n ordered.push({ label, items })\n }\n return ordered\n }\n\n // ── selection ─────────────────────────────────────────────────────\n\n private _setValue(next: SkenComboboxValue<SkenComboboxMode>): void {\n if (next === this.value) return\n this.value = next\n this.dispatchEvent(\n new CustomEvent<{ value: SkenComboboxValue<SkenComboboxMode> }>('sken-change', {\n detail: { value: next },\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n /** Click handler on a listbox option. */\n private _selectItem(item: ResolvedItem): void {\n if (item.disabled) return\n if (this.mode === 'multiple') {\n const current: string[] = Array.isArray(this.value) ? [...this.value] : []\n const idx = current.indexOf(item.value)\n if (idx >= 0) current.splice(idx, 1)\n else current.push(item.value)\n this._setValue(current)\n // Stay open in multiple mode. The user may want to pick more.\n this._input.focus()\n } else {\n this._setValue(item.value)\n this._close()\n }\n }\n\n /**\n * Remove a single chip in `multiple` + `display=\"chips\"`\n * mode. Called by the chip's own `sken-close` event. The\n * chip itself does not remove from the DOM — we do, by\n * updating `value`, which triggers a re-render.\n */\n private _removeChip(value: string, e: CustomEvent): void {\n e.stopPropagation()\n if (this.mode !== 'multiple') return\n const current: string[] = Array.isArray(this.value) ? [...this.value] : []\n const idx = current.indexOf(value)\n if (idx < 0) return\n current.splice(idx, 1)\n this._setValue(current)\n // Return focus to the input so the user can keep typing.\n this._input.focus()\n }\n\n // ── open / close ──────────────────────────────────────────────────\n\n private _open_(): void {\n if (this._open || this.disabled) return\n this._open = true\n this._activeIndex = this._firstSelectableIndex()\n }\n\n private _close(): void {\n if (!this._open) return\n this._open = false\n this._activeIndex = -1\n }\n\n // ── keyboard ──────────────────────────────────────────────────────\n\n private _firstSelectableIndex(): number {\n return this._visibleItems.findIndex((it) => !it.disabled)\n }\n\n private _nextSelectableIndex(from: number, dir: 1 | -1): number {\n const items = this._visibleItems\n if (items.length === 0) return -1\n let i = from + dir\n while (i >= 0 && i < items.length) {\n if (!items[i]!.disabled) return i\n i += dir\n }\n return from\n }\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (this.disabled) return\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n if (!this._open) {\n this._open_()\n } else {\n this._activeIndex = this._nextSelectableIndex(this._activeIndex, 1)\n }\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n if (!this._open) {\n this._open_()\n } else {\n this._activeIndex = this._nextSelectableIndex(this._activeIndex, -1)\n }\n break\n }\n case 'Home': {\n if (!this._open) return\n e.preventDefault()\n this._activeIndex = this._firstSelectableIndex()\n break\n }\n case 'End': {\n if (!this._open) return\n e.preventDefault()\n const items = this._visibleItems\n for (let i = items.length - 1; i >= 0; i--) {\n if (!items[i]!.disabled) {\n this._activeIndex = i\n break\n }\n }\n break\n }\n case 'Enter': {\n if (!this._open) {\n e.preventDefault()\n this._open_()\n return\n }\n const items = this._visibleItems\n const active = items[this._activeIndex]\n if (active && !active.disabled) {\n e.preventDefault()\n this._selectItem(active)\n }\n break\n }\n case 'Escape': {\n if (this._open) {\n e.preventDefault()\n this._close()\n } else if (this.value !== null && this.value !== undefined) {\n // Clear the value when the listbox is already closed.\n // Mirrors the upstream iX 5 pattern (combobox with\n // Escape clears the value when closed).\n e.preventDefault()\n const cleared: SkenComboboxValue<SkenComboboxMode> = this.mode === 'multiple' ? [] : null\n this._setValue(cleared)\n }\n break\n }\n case 'Tab': {\n // Tab moves focus out without selecting. Standard\n // combobox 1.2 pattern.\n this._close()\n break\n }\n }\n }\n\n private _onInput = (e: InputEvent): void => {\n if (!this.filterable) return\n const target = e.target as HTMLInputElement\n this._query = target.value\n if (!this._open) this._open_()\n this._activeIndex = this._firstSelectableIndex()\n }\n\n private _onFocus = (): void => {\n this.dispatchEvent(new FocusEvent('sken-focus', { bubbles: true, composed: true }))\n }\n\n private _onBlur = (): void => {\n // Defer close so a click on an option still registers.\n setTimeout(() => {\n this._close()\n this.dispatchEvent(new FocusEvent('sken-blur', { bubbles: true, composed: true }))\n }, 100)\n }\n\n // ── display value in the trigger ─────────────────────────────────\n\n /**\n * Text shown in the `<input>` element of the trigger.\n * Three branches, in priority order:\n *\n * 1. If the user is typing (a query is active), the input\n * shows the query text — even in `display=\"chips\"` mode.\n * This is the same priority as the original behavior.\n * 2. In `display=\"count\"` mode (default), the trigger shows\n * `\"N selected\"` for a non-empty multi-value. The\n * primitive uses this as a stable-height alternative to\n * chips, useful in dense forms.\n * 3. In `display=\"chips\"` mode, the input itself is empty;\n * the chip row is what conveys the selection. The chips\n * are rendered as separate elements next to the input\n * in the same flex row.\n *\n * Single mode falls through to the existing \"label of the\n * chosen option\" behavior.\n */\n private get _displayValue(): string {\n if (this._query) return this._query\n if (this.mode === 'multiple') {\n const arr = Array.isArray(this.value) ? this.value : []\n if (arr.length === 0) return ''\n // In `display=\"chips\"`, the chip row (rendered in the\n // template) is what the user sees; the input itself\n // stays empty so the cursor can still land at the end of\n // the row for further typing. In `display=\"count\"`, the\n // input shows the count, keeping the trigger height\n // stable across selections.\n if (this.display === 'chips') return ''\n return `${arr.length} selected`\n }\n if (this.value && typeof this.value === 'string') {\n const it = this._items.find((i) => i.value === this.value)\n return it?.label ?? ''\n }\n return ''\n }\n\n // ── render ────────────────────────────────────────────────────────\n\n static override styles = css`\n :host {\n display: inline-block;\n position: relative;\n font-family: var(--sken-family-sans, inherit);\n color: var(--sken-foreground, #152b48);\n inline-size: 100%;\n }\n .field {\n display: grid;\n gap: 0.375rem;\n }\n .label {\n font-size: 0.8125rem;\n font-weight: 500;\n }\n .trigger {\n position: relative;\n display: flex;\n /* The trigger wraps its children: the chip row (if any)\n * goes on its own line, then the input row. Without\n * flex-wrap: wrap here, the chip row would push the input\n * horizontally and the clear + chevron would float at\n * the end of the first chip line. */\n flex-wrap: wrap;\n align-items: center;\n gap: 0.25rem;\n padding-inline: 0.75rem;\n padding-block: var(--sken-2, 0.5rem);\n min-block-size: 2.25rem;\n box-sizing: border-box;\n inline-size: 100%;\n border: 1px solid var(--sken-border, #dfdfe3);\n border-radius: var(--sken-md, 0.5rem);\n background: var(--sken-input, #fff);\n color: var(--sken-foreground, #152b48);\n font-size: var(--sken-size-md, 0.875rem);\n cursor: pointer;\n transition: border-color 120ms ease, box-shadow 120ms ease;\n }\n .trigger:hover:not([data-disabled='true']) {\n border-color: var(--sken-foreground, #152b48);\n }\n .trigger[data-focused='true'] {\n border-color: var(--sken-primary, #2e6fc6);\n box-shadow: 0 0 0 2px var(--sken-ring, #2e6fc640);\n outline: none;\n }\n .trigger[data-disabled='true'] {\n cursor: not-allowed;\n opacity: var(--sken-disabled, 0.5);\n }\n :host([invalid]) .trigger {\n border-color: var(--sken-destructive, #d4181d);\n }\n input {\n flex: 1;\n min-inline-size: 0;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n outline: none;\n }\n input::placeholder {\n color: var(--sken-muted-foreground, #4f6076);\n }\n .chevron,\n .clear {\n inline-size: 1rem;\n block-size: 1rem;\n flex-shrink: 0;\n color: var(--sken-muted-foreground, #4f6076);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n /* The trigger sets font-size: 0.875rem (--sken-size-md)\n * and inherits Inter. Inter renders block glyphs like\n * the down-chevron (▼, U+25BC) at near full em-box,\n * which makes the chevron look huge next to the\n * narrower clear (×, U+D7) which Arial renders as a\n * thin cross. We pick a system sans explicitly so the\n * two glyphs have similar visual weight, and drop to\n * 0.75rem so the chevron's block shape does not eat\n * the input's vertical padding. */\n font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n font-size: 0.75rem;\n user-select: none;\n }\n .chevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: 1rem;\n block-size: 1rem;\n flex-shrink: 0;\n color: var(--sken-muted-foreground, #4f6076);\n transition: transform 120ms ease;\n }\n .chevron svg {\n inline-size: 100%;\n block-size: 100%;\n /* Default stroke-width is fine for 16x16; if the consumer\n * needs a different size, the consumer can set\n * --sken-icon-stroke-width on the host. */\n }\n .chevron[data-open='true'] {\n transform: rotate(180deg);\n }\n .clear {\n cursor: pointer;\n /* Outlined at rest: a 1px border on a transparent\n * background, matching the Nord Design System's tag\n * pattern (see nordhealth.design/components/tag).\n * The outline signals affordance without filling\n * the trigger with a gray pill, so the combobox\n * stays visually calm when the clear is the only\n * thing on the right edge. */\n background: transparent;\n border: 1px solid var(--sken-border, #dfdfe3);\n padding: 0;\n border-radius: 999px;\n }\n .clear:hover {\n background: var(--sken-muted, #f4f4f5);\n border-color: var(--sken-muted-foreground, #4f6076);\n color: var(--sken-foreground, #152b48);\n }\n .listbox {\n position: absolute;\n inset-block-start: calc(100% + 0.25rem);\n inset-inline: 0;\n max-block-size: 16rem;\n overflow-y: auto;\n background: var(--sken-card, #fff);\n border: 1px solid var(--sken-border, #dfdfe3);\n border-radius: var(--sken-md, 0.5rem);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n z-index: 10;\n list-style: none;\n margin: 0;\n padding: 0.25rem 0;\n /* Token-driven scrollbar so the panel matches the design\n * language instead of inheriting the host OS default. The\n * WebKit pseudo-elements cover Safari + older Chromium;\n * scrollbar-width / scrollbar-color cover Firefox +\n * modern Chromium. Both are needed because each engine\n * ignores the other API. */\n scrollbar-width: thin;\n scrollbar-color: var(--sken-border, #dfdfe3) transparent;\n }\n .listbox::-webkit-scrollbar {\n inline-size: 0.5rem;\n block-size: 0.5rem;\n }\n .listbox::-webkit-scrollbar-track {\n background: transparent;\n }\n .listbox::-webkit-scrollbar-thumb {\n background: var(--sken-border, #dfdfe3);\n border-radius: 999px;\n border: 2px solid var(--sken-card, #fff);\n background-clip: padding-box;\n }\n .listbox::-webkit-scrollbar-thumb:hover {\n background: var(--sken-muted-foreground, #4f6076);\n background-clip: padding-box;\n }\n .group-header {\n padding: 0.5rem 0.75rem 0.25rem;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--sken-muted-foreground, #4f6076);\n }\n .option {\n padding: 0.5rem 0.75rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: var(--sken-size-md, 0.875rem);\n }\n .option[data-active='true'] {\n background: var(--sken-muted, #f4f4f5);\n }\n .option[data-selected='true'] {\n font-weight: 500;\n }\n .option[data-disabled='true'] {\n cursor: not-allowed;\n opacity: var(--sken-disabled, 0.5);\n }\n .check {\n inline-size: 1rem;\n block-size: 1rem;\n flex-shrink: 0;\n color: var(--sken-primary, #2e6fc6);\n opacity: 0;\n }\n .option[data-selected='true'] .check {\n opacity: 1;\n }\n .helper {\n font-size: 0.75rem;\n color: var(--sken-muted-foreground, #4f6076);\n }\n .chip-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.25rem;\n min-inline-size: 0;\n /* The chip row wraps to multiple lines when the chips\n * do not fit. The trigger's own height grows accordingly;\n * for display:chips + chip-overflow:count, the row uses\n * nowrap + overflow:hidden instead, so the trigger\n * height stays constant. */\n flex-basis: 100%;\n row-gap: 0.25rem;\n }\n .chip-row[data-overflow='none'] {\n flex-wrap: nowrap;\n overflow-x: auto;\n /* Hide the scrollbar visually; consumers who need to\n * see overflow can override. Same scrollbar pattern as\n * the listbox. */\n scrollbar-width: none;\n }\n .chip-row[data-overflow='none']::-webkit-scrollbar {\n display: none;\n }\n .chip-row[data-overflow='count'] {\n flex-wrap: nowrap;\n overflow-x: hidden;\n }\n .chip-row sken-filter-chip.overflow {\n /* The overflow chip reuses the same visual treatment as\n * a regular chip but with no close button (set via the\n * hide-close-button attribute on the primitive). */\n background: var(--sken-primary, #2e6fc6);\n color: var(--sken-primary-foreground, #fff);\n }\n .chip-row sken-filter-chip.overflow:hover {\n background: var(--sken-primary, #2e6fc6);\n opacity: 0.92;\n }\n /* The input row (input + clear + chevron) sits on its own\n * line below the chip row. Without this wrapper, the\n * clear + chevron would float to the end of the first\n * chip line in display:chips mode (visually wrong).\n * The input row is always present, even when there are\n * no chips — that way the layout is stable across the\n * \"empty\" and \"with chips\" states. */\n .input-row {\n display: flex;\n align-items: center;\n /* Slightly wider gap than the trigger's general one, so\n * the clear button and the chevron do not feel\n * glued together at the right edge of the input. The\n * extra 4px matches the visual weight of the icons\n * (16x16 each) without making the layout feel sparse. */\n gap: 0.5rem;\n flex-basis: 100%;\n min-inline-size: 0;\n }\n `\n\n protected override render() {\n const visible = this._visibleItems\n const groups = this._visibleGroups\n const isMulti = this.mode === 'multiple'\n const selectedSet = isMulti\n ? new Set(Array.isArray(this.value) ? this.value : [])\n : new Set(this.value ? [this.value as string] : [])\n const listboxId = `sken-combobox-listbox-${Math.random().toString(36).slice(2, 8)}`\n\n // The chip row is only meaningful in `multiple` + `display=\"chips\"`.\n // Single mode never renders chips; the trigger shows the chosen\n // option's label in the input itself.\n const showChips = isMulti && this.display === 'chips'\n const selectedItems = showChips\n ? this._items.filter((it) => selectedSet.has(it.value))\n : []\n\n // Apply the overflow budget. The `_visibleChipCount` is set in\n // `updated()` once the chip row has been rendered; until then\n // it is `-1`, which means \"show them all\" (no measurement yet).\n // After the first measurement, the first `_visibleChipCount`\n // items render as full chips; the rest are hidden behind the\n // overflow chip (only relevant in `chipOverflow=\"count\"`).\n const visibleChipSlice = showChips && this._visibleChipCount >= 0\n ? selectedItems.slice(0, this._visibleChipCount)\n : selectedItems\n const hiddenChips = showChips && this._visibleChipCount >= 0\n ? selectedItems.slice(this._visibleChipCount)\n : []\n\n return html`\n <div class=\"field\">\n ${this.label\n ? html`<label class=\"label\" for=\"trigger\">${this.label}</label>`\n : null}\n <div\n class=\"trigger\"\n data-focused=${this._open}\n data-disabled=${this.disabled}\n @click=${() => !this.disabled && (this._open ? this._close() : this._open_())}\n >\n ${showChips && selectedItems.length > 0\n ? html`<div\n class=\"chip-row\"\n part=\"chip-row\"\n data-overflow=${this.chipOverflow}\n >\n ${visibleChipSlice.map((it) => html`\n <sken-filter-chip\n label=${it.label}\n data-sken-combobox-chip-value=${it.value}\n @sken-close=${(e: CustomEvent) => this._removeChip(it.value, e)}\n ></sken-filter-chip>\n `)}\n ${hiddenChips.length > 0 && this.chipOverflow === 'count'\n ? html`<sken-filter-chip\n class=\"overflow\"\n label=\"+${hiddenChips.length}\"\n data-sken-combobox-overflow=${hiddenChips.length}\n hide-close-button\n @click=${(e: MouseEvent) => { e.stopPropagation(); this._open = true }}\n ></sken-filter-chip>`\n : null}\n </div>`\n : null}\n <div class=\"input-row\">\n <input\n id=\"trigger\"\n role=\"combobox\"\n aria-expanded=${this._open}\n aria-controls=${listboxId}\n aria-activedescendant=${this._activeIndex >= 0 ? `${listboxId}-opt-${this._activeIndex}` : ''}\n aria-autocomplete=${this.filterable ? 'list' : 'none'}\n aria-required=${this.required}\n aria-invalid=${this.invalid}\n aria-describedby=${this.describedById}\n ?disabled=${this.disabled}\n .value=${this._displayValue}\n placeholder=${this.placeholder}\n @input=${this._onInput}\n @keydown=${this._onKeydown}\n @focus=${this._onFocus}\n @blur=${this._onBlur}\n />\n ${!this.disabled && this.value !== null && (isMulti ? (selectedSet.size > 0) : true)\n ? html`<button\n type=\"button\"\n class=\"clear\"\n aria-label=\"Clear selection\"\n @click=${(e: Event) => {\n e.stopPropagation()\n const cleared: SkenComboboxValue<SkenComboboxMode> = isMulti ? [] : null\n this._setValue(cleared)\n this._query = ''\n this._input.focus()\n }}\n >×</button>`\n : null}\n <span class=\"chevron\" data-open=${this._open} aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3.5 6.5 L8 11 L12.5 6.5\"></path>\n </svg>\n </span>\n </div>\n </div>\n\n ${this._open\n ? html`<ul\n id=${listboxId}\n class=\"listbox\"\n role=\"listbox\"\n aria-multiselectable=${isMulti}\n >\n ${visible.length === 0\n ? html`<li class=\"option\" data-disabled=\"true\">No matches</li>`\n : groups.map((g) => html`\n ${g.label\n ? html`<li\n class=\"group-header\"\n role=\"presentation\"\n >${g.label}</li>`\n : null}\n ${g.items.map((it) => {\n const idx = visible.indexOf(it)\n const isActive = idx === this._activeIndex\n const isSelected = selectedSet.has(it.value)\n return html`<li\n id=${`${listboxId}-opt-${idx}`}\n class=\"option\"\n role=\"option\"\n data-active=${isActive}\n data-selected=${isSelected}\n data-disabled=${it.disabled}\n aria-selected=${isSelected}\n aria-disabled=${it.disabled}\n @mouseenter=${() => { this._activeIndex = idx }}\n @click=${() => this._selectItem(it)}\n >\n ${isMulti\n ? html`<span class=\"check\" aria-hidden=\"true\">✓</span>`\n : null}\n <span>${it.label}</span>\n </li>`\n })}\n `)}\n </ul>`\n : null}\n\n ${this.helperText\n ? html`<div class=\"helper\">${this.helperText}</div>`\n : null}\n </div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-combobox': SkenCombobox\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sken-combobox.js","names":[],"sources":["../src/components/sken-combobox.ts"],"sourcesContent":["/**\n * `<sken-combobox>` — Sken contract implementation.\n *\n * Single + multiple selection. Filterable. Groupable. Tokenised.\n * No third-party vendor in the runtime.\n *\n * The Sken contract (`SkenComboboxProps`, `SkenComboboxItemProps`,\n * `SkenComboboxGroupProps`, `SkenComboboxEmits`,\n * `SkenComboboxValue<M>`, `skenComboboxAria`) lives in\n * `@sken-ds/contracts`. This component is a Lit 3 implementation\n * that:\n *\n * 1. Accepts the Sken contract props (`mode`, `value`,\n * `filterable`, `label`, `helperText`, `placeholder`,\n * `disabled`, `invalid`, `required`, `name`, `size`,\n * `describedById`).\n * 2. Discovers the options at first render by introspecting\n * `<sken-combobox-item>` and `<sken-combobox-group>`\n * children. Each item knows its `value` and `label`; each\n * group knows its `label` and `disabled` state. Items\n * inside a group are associated with that group; items\n * outside any group belong to an implicit \"ungrouped\"\n * bucket rendered at the top of the listbox (no header).\n * 3. Renders a Shadow DOM with: a `<label>` for the visible\n * field label, an `<input role=\"combobox\">` for the\n * trigger, a clear button, a chevron, a listbox panel\n * containing the (optionally grouped, optionally filtered)\n * options, and helper / invalid / warning / valid text.\n * 4. Implements the WAI-ARIA combobox 1.2 pattern: the input\n * has `role=\"combobox\"`, `aria-expanded`, `aria-controls`\n * pointing to the listbox, and `aria-activedescendant`\n * pointing to the currently highlighted option. The listbox\n * has `role=\"listbox\"`, each option has `role=\"option\"` +\n * `aria-selected`, and each group has `role=\"group\"` +\n * `aria-labelledby` pointing to the group's header.\n * 5. Implements the keyboard model: ArrowUp / ArrowDown open\n * and navigate, Home / End jump to the ends, Enter selects,\n * Escape closes (or clears when already closed), Tab moves\n * focus out without selecting.\n * 6. Emits the Sken-shaped `sken-change` event with the new\n * value. The shape of the value depends on `mode`:\n * `string | null` for `single`, `string[]` for `multiple`.\n * Native `change` on the input is suppressed; consumers use\n * `sken-change` for portability.\n *\n * Implementation notes:\n *\n * - The children are introspected lazily on each render so\n * dynamic option lists (Vue v-for, conditional rendering)\n * work without manual registration.\n *\n * - The filter is a case-insensitive substring match against\n * `item.label`. Groups hide themselves when ALL their items\n * are filtered out. An empty filter restores every item.\n *\n * - The listbox is positioned absolutely below the input,\n * matching the input's width. The visual treatment is\n * tokenised via `--sken-*` custom properties; the consumer\n * imports `@sken-ds/theme/css` once at app boot.\n *\n * - The \"marker\" pattern for the children (items and groups\n * render nothing; the parent projects them) is documented in\n * the iX 5 reference and in the comments on the children\n * themselves. The trade-off is that `<sken-combobox-item>`\n * does not appear in the rendered DOM tree as itself.\n */\nimport { LitElement, css, html, type PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport type {\n SkenComboboxChipOverflow,\n SkenComboboxDisplay,\n SkenComboboxMode,\n SkenComboboxProps,\n SkenComboboxValue,\n} from '@sken-ds/contracts'\n\n// Side-effect import: register the `<sken-filter-chip>`\n// custom element so the combobox can render chips inside its\n// shadow DOM without forcing the consumer to import the chip\n// module explicitly. The chip is a pure consumer-side primitive\n// of the combobox in `display=\"chips\"` mode, so it belongs in\n// the combobox's module graph.\nimport './sken-filter-chip.js'\n\nimport type { SkenComboboxGroup } from './sken-combobox-group'\nimport type { SkenComboboxItem } from './sken-combobox-item'\n\n/**\n * Internal flat model: each item is a row, optionally\n * associated with a group label. The listbox renders a flat\n * list; the group headers are rendered as separator rows when\n * the group becomes visible after filtering.\n */\ninterface ResolvedItem {\n value: string\n label: string\n disabled: boolean\n groupLabel: string | null\n groupDisabled: boolean\n}\n\n@customElement('sken-combobox')\nexport class SkenCombobox extends LitElement implements Omit<SkenComboboxProps, 'value' | 'slots'> {\n // The Sken contract declares `value` as `SkenComboboxValue`\n // (a generic defaulting to `string | null`). This\n // implementation uses `SkenComboboxValue<SkenComboboxMode>`\n // (the full union) so that single-mode and multi-mode\n // values can both flow through `_setValue` without TS\n // narrowing complaints. The runtime contract is\n // identical; the difference is only at the type level.\n // ── public API (mapped to Sken contract) ──────────────────────────\n\n @property({ reflect: true }) mode: SkenComboboxMode = 'single'\n\n /**\n * How the trigger renders the current selection in\n * `mode=\"multiple\"`. See `SkenComboboxDisplay`. No effect\n * in `mode=\"single\"`. @default \"count\"\n */\n @property() display: SkenComboboxDisplay = 'count'\n\n /**\n * How the chip row overflows when `display=\"chips\"` and\n * the chips do not fit on a single line. See\n * `SkenComboboxChipOverflow`. No effect in\n * `mode=\"single\"` or when `display=\"count\"`.\n * @default \"wrap\"\n */\n @property({ attribute: 'chip-overflow' }) chipOverflow: SkenComboboxChipOverflow = 'wrap'\n\n @property({ attribute: false })\n value: SkenComboboxValue<SkenComboboxMode> = null as SkenComboboxValue<SkenComboboxMode>\n\n @property({ type: Boolean }) filterable = true\n\n @property() label = ''\n\n @property({ attribute: 'helper-text' }) helperText = ''\n\n @property() placeholder = ''\n\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: Boolean, reflect: true }) invalid = false\n\n @property({ type: Boolean, reflect: true }) required = false\n\n @property() name = ''\n\n @property({ reflect: true }) size: 'sm' | 'md' | 'lg' = 'md'\n\n @property({ attribute: 'described-by-id' }) describedById = ''\n\n // ── internal state ────────────────────────────────────────────────\n\n /** Resolved flat model of all items, with their group labels. */\n @state() private _items: ResolvedItem[] = []\n\n /** Current filter query. Lowercased for case-insensitive match. */\n @state() private _query = ''\n\n /** Whether the listbox is open. */\n @state() private _open = false\n\n /** Index into the visible items (after filtering). -1 = no highlight. */\n @state() private _activeIndex = -1\n\n /**\n * Number of selected chips that fit on the first row of the\n * trigger. The rest are hidden behind the overflow chip in\n * `chipOverflow=\"count\"` mode. `-1` means \"no measurement\n * yet\" (or `chipOverflow` is not `\"count\"`).\n *\n * The measurement runs once per render in `updated()`. It\n * uses the actual rendered widths of the chip DOM nodes,\n * not a guess, so the overflow chip appears exactly when\n * the last visible chip would not fit. On window resize\n * the user re-renders (e.g. by typing, opening the\n * listbox, or any other state change) and the measurement\n * re-runs.\n */\n @state() private _visibleChipCount = -1\n\n @query('input') private _input!: HTMLInputElement\n\n // ── lifecycle ─────────────────────────────────────────────────────\n\n override connectedCallback(): void {\n super.connectedCallback()\n // Re-resolve on any DOM change inside the host. This is how\n // we pick up dynamic option lists (Vue v-for, etc.) without\n // the consumer having to call a register() method.\n this._observer = new MutationObserver(() => {\n this._resolveItems()\n // Force a re-render so the listbox reflects the new\n // items. Without this, mutations that happen after the\n // first render (e.g. Vue mounting children asynchronously)\n // would not trigger a render() pass.\n this.requestUpdate()\n })\n this._observer.observe(this, { childList: true, subtree: true })\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this._observer?.disconnect()\n }\n\n override willUpdate(_changed: PropertyValues): void {\n // Resolving on every update is cheap (a few querySelectorAll\n // calls) and guarantees the model is fresh by the time\n // render() runs.\n this._resolveItems()\n }\n\n override firstUpdated(_changed: PropertyValues): void {\n // Belt-and-braces: re-resolve after the first render. Some\n // frameworks (notably Vue's h() with custom elements) may\n // append children to the host AFTER the Lit lifecycle has\n // run its first render, so the initial willUpdate sees an\n // empty children list. By the time firstUpdated runs, the\n // DOM is settled and a fresh resolve is authoritative.\n this._resolveItems()\n // Some bundlers (Vite) and frameworks (Vue) mount custom\n // element children asynchronously after the host's\n // `connectedCallback`. We poll once on the next microtask\n // to catch any children that were attached between the\n // synchronous render and the next paint. The cost is one\n // extra querySelectorAll; the benefit is that dynamic v-for\n // lists in Vue render correctly on the first paint.\n queueMicrotask(() => {\n if (this._items.length === 0 && this.children.length > 0) {\n this._resolveItems()\n this.requestUpdate()\n }\n })\n }\n\n override updated(_changed: PropertyValues): void {\n super.updated(_changed)\n // Measure the chip row to compute the overflow budget. We\n // only run this in `chipOverflow=\"count\"` mode; for `wrap`\n // and `none`, CSS does the work and the state stays at\n // its initial `-1` (\"no measurement\").\n //\n // The measurement is intentionally simple: sum the\n // rendered widths of the chip DOM nodes (offsetWidth\n // includes padding + border) plus the gap, and stop when\n // the next chip would push the row past the trigger's\n // content box. We do not use ResizeObserver — a chip\n // add/remove, a value change, or any other state update\n // is enough to retrigger the measurement, and the trigger\n // height is constant in this mode so a single read per\n // render is correct.\n //\n // The overflow chip itself is excluded from the\n // measurement (it carries the `.overflow` class). If it\n // were included, the loop would re-add it to the count\n // every render and never converge on a stable `_visibleChipCount`.\n if (this.mode !== 'multiple' || this.display !== 'chips' || this.chipOverflow !== 'count') {\n // No measurement in this mode. Reset the budget so a\n // later switch to `\"count\"` re-measures from scratch.\n if (this._visibleChipCount !== -1) this._visibleChipCount = -1\n return\n }\n const row = this.renderRoot.querySelector<HTMLElement>('.chip-row')\n if (!row) return\n const trigger = this.renderRoot.querySelector<HTMLElement>('.trigger')\n if (!trigger) return\n // Only the real chips, not the overflow sentinel.\n const chips = Array.from(row.querySelectorAll<HTMLElement>('sken-filter-chip:not(.overflow)'))\n if (chips.length === 0) {\n if (this._visibleChipCount !== 0) this._visibleChipCount = 0\n return\n }\n // Available width = trigger clientWidth minus the\n // non-chip siblings (clear + chevron + their gap). We\n // approximate by reading the trigger's content width and\n // subtracting the input's width as a safe lower bound\n // (the input has `flex: 1`, so it shrinks to whatever\n // space is left after the chips).\n const triggerStyle = getComputedStyle(trigger)\n const paddingX =\n (parseFloat(triggerStyle.paddingLeft) || 0) + (parseFloat(triggerStyle.paddingRight) || 0)\n const gap = parseFloat(triggerStyle.gap) || 0\n const triggerInner = trigger.clientWidth - paddingX\n // Walk the chips left-to-right. The row's content can\n // grow indefinitely in `flex-wrap: nowrap` rows; we sum\n // each chip's offsetWidth and stop when adding the next\n // would push us past `triggerInner`. We reserve space\n // for one overflow chip (32px) plus its gap so the budget\n // is honest.\n const OVERFLOW_CHIP_WIDTH = 32 + gap\n let acc = 0\n let visible = 0\n for (let i = 0; i < chips.length; i++) {\n const w = chips[i].offsetWidth\n const need = acc + w + (i > 0 ? gap : 0)\n // If we have to add an overflow chip at the end, we\n // need to reserve space for it. Only reserve if there\n // is at least one chip to hide.\n const remaining = chips.length - (i + 1)\n const reserved = remaining > 0 ? OVERFLOW_CHIP_WIDTH : 0\n if (need + reserved > triggerInner) break\n acc = need\n visible = i + 1\n }\n if (this._visibleChipCount !== visible) {\n this._visibleChipCount = visible\n }\n }\n\n private _observer?: MutationObserver\n\n // ── model: introspect children, build the flat list ──────────────\n\n /**\n * Walk the host's children. For each direct child, classify\n * it as a group (and recurse into it) or as an ungrouped item.\n * The result is a flat list of items with their group label\n * denormalised onto each row. The listbox render uses this\n * flat list directly, which keeps the rendering loop simple\n * and lets the filter operate on a single array.\n */\n private _resolveItems(): void {\n const items: ResolvedItem[] = []\n for (const child of Array.from(this.children)) {\n if (\n customElements.get('sken-combobox-group') &&\n child instanceof customElements.get('sken-combobox-group')!\n ) {\n const group = child as SkenComboboxGroup\n const groupLabel = group.label || ''\n const groupDisabled = group.disabled\n for (const itemEl of Array.from(group.children)) {\n if (\n customElements.get('sken-combobox-item') &&\n itemEl instanceof customElements.get('sken-combobox-item')!\n ) {\n const item = itemEl as SkenComboboxItem\n items.push({\n value: item.value,\n label: item.label,\n disabled: item.disabled || groupDisabled,\n groupLabel,\n groupDisabled,\n })\n }\n }\n } else if (\n customElements.get('sken-combobox-item') &&\n child instanceof customElements.get('sken-combobox-item')!\n ) {\n const item = child as SkenComboboxItem\n items.push({\n value: item.value,\n label: item.label,\n disabled: item.disabled,\n groupLabel: null,\n groupDisabled: false,\n })\n }\n }\n this._items = items\n }\n\n // ── filter ────────────────────────────────────────────────────────\n\n /**\n * Apply the current query to the resolved items. An item is\n * visible when its label contains the query (case-insensitive),\n * or when the query is empty. A group is visible when at\n * least one of its items is visible. Ungrouped items are\n * always at the top.\n */\n private get _visibleItems(): ResolvedItem[] {\n if (!this._query) return this._items\n const q = this._query.toLowerCase()\n return this._items\n .filter((it) => !it.disabled || true) // disabled items are still shown, just not selectable\n .filter((it) => it.label.toLowerCase().includes(q))\n }\n\n /**\n * The visible items, grouped by `groupLabel`. Each group\n * becomes a `{ label, items }` entry; ungrouped items go\n * into a synthetic group with `label = null`.\n */\n private get _visibleGroups(): { label: string | null; items: ResolvedItem[] }[] {\n const visible = this._visibleItems\n const groups = new Map<string | null, ResolvedItem[]>()\n for (const it of visible) {\n const key = it.groupLabel || null\n if (!groups.has(key)) groups.set(key, [])\n groups.get(key)!.push(it)\n }\n // Preserve declaration order. Items inside each group are\n // already in order (we walked the DOM in order). Groups are\n // returned in the order their first item appears.\n const ordered: { label: string | null; items: ResolvedItem[] }[] = []\n for (const [label, items] of groups) {\n ordered.push({ label, items })\n }\n return ordered\n }\n\n // ── selection ─────────────────────────────────────────────────────\n\n private _setValue(next: SkenComboboxValue<SkenComboboxMode>): void {\n if (next === this.value) return\n this.value = next\n this.dispatchEvent(\n new CustomEvent<{ value: SkenComboboxValue<SkenComboboxMode> }>('sken-change', {\n detail: { value: next },\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n /** Click handler on a listbox option. */\n private _selectItem(item: ResolvedItem): void {\n if (item.disabled) return\n if (this.mode === 'multiple') {\n const current: string[] = Array.isArray(this.value) ? [...this.value] : []\n const idx = current.indexOf(item.value)\n if (idx >= 0) current.splice(idx, 1)\n else current.push(item.value)\n this._setValue(current)\n // Stay open in multiple mode. The user may want to pick more.\n this._input.focus()\n } else {\n this._setValue(item.value)\n this._close()\n }\n }\n\n /**\n * Remove a single chip in `multiple` + `display=\"chips\"`\n * mode. Called by the chip's own `sken-close` event. The\n * chip itself does not remove from the DOM — we do, by\n * updating `value`, which triggers a re-render.\n */\n private _removeChip(value: string, e: CustomEvent): void {\n e.stopPropagation()\n if (this.mode !== 'multiple') return\n const current: string[] = Array.isArray(this.value) ? [...this.value] : []\n const idx = current.indexOf(value)\n if (idx < 0) return\n current.splice(idx, 1)\n this._setValue(current)\n // Return focus to the input so the user can keep typing.\n this._input.focus()\n }\n\n // ── open / close ──────────────────────────────────────────────────\n\n private _open_(): void {\n if (this._open || this.disabled) return\n this._open = true\n this._activeIndex = this._firstSelectableIndex()\n }\n\n private _close(): void {\n if (!this._open) return\n this._open = false\n this._activeIndex = -1\n }\n\n // ── keyboard ──────────────────────────────────────────────────────\n\n private _firstSelectableIndex(): number {\n return this._visibleItems.findIndex((it) => !it.disabled)\n }\n\n private _nextSelectableIndex(from: number, dir: 1 | -1): number {\n const items = this._visibleItems\n if (items.length === 0) return -1\n let i = from + dir\n while (i >= 0 && i < items.length) {\n if (!items[i]!.disabled) return i\n i += dir\n }\n return from\n }\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (this.disabled) return\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n if (!this._open) {\n this._open_()\n } else {\n this._activeIndex = this._nextSelectableIndex(this._activeIndex, 1)\n }\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n if (!this._open) {\n this._open_()\n } else {\n this._activeIndex = this._nextSelectableIndex(this._activeIndex, -1)\n }\n break\n }\n case 'Home': {\n if (!this._open) return\n e.preventDefault()\n this._activeIndex = this._firstSelectableIndex()\n break\n }\n case 'End': {\n if (!this._open) return\n e.preventDefault()\n const items = this._visibleItems\n for (let i = items.length - 1; i >= 0; i--) {\n if (!items[i]!.disabled) {\n this._activeIndex = i\n break\n }\n }\n break\n }\n case 'Enter': {\n if (!this._open) {\n e.preventDefault()\n this._open_()\n return\n }\n const items = this._visibleItems\n const active = items[this._activeIndex]\n if (active && !active.disabled) {\n e.preventDefault()\n this._selectItem(active)\n }\n break\n }\n case 'Escape': {\n if (this._open) {\n e.preventDefault()\n this._close()\n } else if (this.value !== null && this.value !== undefined) {\n // Clear the value when the listbox is already closed.\n // Mirrors the upstream iX 5 pattern (combobox with\n // Escape clears the value when closed).\n e.preventDefault()\n const cleared: SkenComboboxValue<SkenComboboxMode> = this.mode === 'multiple' ? [] : null\n this._setValue(cleared)\n }\n break\n }\n case 'Tab': {\n // Tab moves focus out without selecting. Standard\n // combobox 1.2 pattern.\n this._close()\n break\n }\n }\n }\n\n private _onInput = (e: InputEvent): void => {\n if (!this.filterable) return\n const target = e.target as HTMLInputElement\n this._query = target.value\n if (!this._open) this._open_()\n this._activeIndex = this._firstSelectableIndex()\n }\n\n private _onFocus = (): void => {\n this.dispatchEvent(new FocusEvent('sken-focus', { bubbles: true, composed: true }))\n }\n\n private _onBlur = (): void => {\n // Defer close so a click on an option still registers.\n setTimeout(() => {\n this._close()\n this.dispatchEvent(new FocusEvent('sken-blur', { bubbles: true, composed: true }))\n }, 100)\n }\n\n // ── display value in the trigger ─────────────────────────────────\n\n /**\n * Text shown in the `<input>` element of the trigger.\n * Three branches, in priority order:\n *\n * 1. If the user is typing (a query is active), the input\n * shows the query text — even in `display=\"chips\"` mode.\n * This is the same priority as the original behavior.\n * 2. In `display=\"count\"` mode (default), the trigger shows\n * `\"N selected\"` for a non-empty multi-value. The\n * primitive uses this as a stable-height alternative to\n * chips, useful in dense forms.\n * 3. In `display=\"chips\"` mode, the input itself is empty;\n * the chip row is what conveys the selection. The chips\n * are rendered as separate elements next to the input\n * in the same flex row.\n *\n * Single mode falls through to the existing \"label of the\n * chosen option\" behavior.\n */\n private get _displayValue(): string {\n if (this._query) return this._query\n if (this.mode === 'multiple') {\n const arr = Array.isArray(this.value) ? this.value : []\n if (arr.length === 0) return ''\n // In `display=\"chips\"`, the chip row (rendered in the\n // template) is what the user sees; the input itself\n // stays empty so the cursor can still land at the end of\n // the row for further typing. In `display=\"count\"`, the\n // input shows the count, keeping the trigger height\n // stable across selections.\n if (this.display === 'chips') return ''\n return `${arr.length} selected`\n }\n if (this.value && typeof this.value === 'string') {\n const it = this._items.find((i) => i.value === this.value)\n return it?.label ?? ''\n }\n return ''\n }\n\n // ── render ────────────────────────────────────────────────────────\n\n static override styles = css`\n :host {\n display: inline-block;\n position: relative;\n font-family: var(--sken-family-sans, inherit);\n color: var(--sken-foreground, #152b48);\n inline-size: 100%;\n }\n .field {\n display: grid;\n gap: 0.375rem;\n }\n .label {\n font-size: 0.8125rem;\n font-weight: 500;\n }\n .trigger {\n position: relative;\n display: flex;\n /* The trigger wraps its children: the chip row (if any)\n * goes on its own line, then the input row. Without\n * flex-wrap: wrap here, the chip row would push the input\n * horizontally and the clear + chevron would float at\n * the end of the first chip line. */\n flex-wrap: wrap;\n align-items: center;\n gap: 0.25rem;\n padding-inline: 0.75rem;\n padding-block: var(--sken-2, 0.5rem);\n min-block-size: 2.25rem;\n box-sizing: border-box;\n inline-size: 100%;\n border: 1px solid var(--sken-border, #dfdfe3);\n border-radius: var(--sken-md, 0.5rem);\n background: var(--sken-input, #fff);\n color: var(--sken-foreground, #152b48);\n font-size: var(--sken-size-md, 0.875rem);\n cursor: pointer;\n transition:\n border-color 120ms ease,\n box-shadow 120ms ease;\n }\n .trigger:hover:not([data-disabled='true']) {\n border-color: var(--sken-foreground, #152b48);\n }\n .trigger[data-focused='true'] {\n border-color: var(--sken-primary, #2e6fc6);\n box-shadow: 0 0 0 2px var(--sken-ring, #2e6fc640);\n outline: none;\n }\n .trigger[data-disabled='true'] {\n cursor: not-allowed;\n opacity: var(--sken-disabled, 0.5);\n }\n :host([invalid]) .trigger {\n border-color: var(--sken-destructive, #d4181d);\n }\n input {\n flex: 1;\n min-inline-size: 0;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n outline: none;\n }\n input::placeholder {\n color: var(--sken-muted-foreground, #4f6076);\n }\n .chevron,\n .clear {\n inline-size: 1rem;\n block-size: 1rem;\n flex-shrink: 0;\n color: var(--sken-muted-foreground, #4f6076);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n /* The trigger sets font-size: 0.875rem (--sken-size-md)\n * and inherits Inter. Inter renders block glyphs like\n * the down-chevron (▼, U+25BC) at near full em-box,\n * which makes the chevron look huge next to the\n * narrower clear (×, U+D7) which Arial renders as a\n * thin cross. We pick a system sans explicitly so the\n * two glyphs have similar visual weight, and drop to\n * 0.75rem so the chevron's block shape does not eat\n * the input's vertical padding. */\n font-family:\n ui-sans-serif,\n system-ui,\n -apple-system,\n 'Segoe UI',\n Roboto,\n sans-serif;\n font-size: 0.75rem;\n user-select: none;\n }\n .chevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: 1rem;\n block-size: 1rem;\n flex-shrink: 0;\n color: var(--sken-muted-foreground, #4f6076);\n transition: transform 120ms ease;\n }\n .chevron svg {\n inline-size: 100%;\n block-size: 100%;\n /* Default stroke-width is fine for 16x16; if the consumer\n * needs a different size, the consumer can set\n * --sken-icon-stroke-width on the host. */\n }\n .chevron[data-open='true'] {\n transform: rotate(180deg);\n }\n .clear {\n cursor: pointer;\n /* Outlined at rest: a 1px border on a transparent\n * background, matching the Nord Design System's tag\n * pattern (see nordhealth.design/components/tag).\n * The outline signals affordance without filling\n * the trigger with a gray pill, so the combobox\n * stays visually calm when the clear is the only\n * thing on the right edge. */\n background: transparent;\n border: 1px solid var(--sken-border, #dfdfe3);\n padding: 0;\n border-radius: 999px;\n }\n .clear:hover {\n background: var(--sken-muted, #f4f4f5);\n border-color: var(--sken-muted-foreground, #4f6076);\n color: var(--sken-foreground, #152b48);\n }\n .listbox {\n position: absolute;\n inset-block-start: calc(100% + 0.25rem);\n inset-inline: 0;\n max-block-size: 16rem;\n overflow-y: auto;\n background: var(--sken-card, #fff);\n border: 1px solid var(--sken-border, #dfdfe3);\n border-radius: var(--sken-md, 0.5rem);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n z-index: 10;\n list-style: none;\n margin: 0;\n padding: 0.25rem 0;\n /* Token-driven scrollbar so the panel matches the design\n * language instead of inheriting the host OS default. The\n * WebKit pseudo-elements cover Safari + older Chromium;\n * scrollbar-width / scrollbar-color cover Firefox +\n * modern Chromium. Both are needed because each engine\n * ignores the other API. */\n scrollbar-width: thin;\n scrollbar-color: var(--sken-border, #dfdfe3) transparent;\n }\n .listbox::-webkit-scrollbar {\n inline-size: 0.5rem;\n block-size: 0.5rem;\n }\n .listbox::-webkit-scrollbar-track {\n background: transparent;\n }\n .listbox::-webkit-scrollbar-thumb {\n background: var(--sken-border, #dfdfe3);\n border-radius: 999px;\n border: 2px solid var(--sken-card, #fff);\n background-clip: padding-box;\n }\n .listbox::-webkit-scrollbar-thumb:hover {\n background: var(--sken-muted-foreground, #4f6076);\n background-clip: padding-box;\n }\n .group-header {\n padding: 0.5rem 0.75rem 0.25rem;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--sken-muted-foreground, #4f6076);\n }\n .option {\n padding: 0.5rem 0.75rem;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: var(--sken-size-md, 0.875rem);\n }\n .option[data-active='true'] {\n background: var(--sken-muted, #f4f4f5);\n }\n .option[data-selected='true'] {\n font-weight: 500;\n }\n .option[data-disabled='true'] {\n cursor: not-allowed;\n opacity: var(--sken-disabled, 0.5);\n }\n .check {\n inline-size: 1rem;\n block-size: 1rem;\n flex-shrink: 0;\n color: var(--sken-primary, #2e6fc6);\n opacity: 0;\n }\n .option[data-selected='true'] .check {\n opacity: 1;\n }\n .helper {\n font-size: 0.75rem;\n color: var(--sken-muted-foreground, #4f6076);\n }\n .chip-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.25rem;\n min-inline-size: 0;\n /* The chip row wraps to multiple lines when the chips\n * do not fit. The trigger's own height grows accordingly;\n * for display:chips + chip-overflow:count, the row uses\n * nowrap + overflow:hidden instead, so the trigger\n * height stays constant. */\n flex-basis: 100%;\n row-gap: 0.25rem;\n }\n .chip-row[data-overflow='none'] {\n flex-wrap: nowrap;\n overflow-x: auto;\n /* Hide the scrollbar visually; consumers who need to\n * see overflow can override. Same scrollbar pattern as\n * the listbox. */\n scrollbar-width: none;\n }\n .chip-row[data-overflow='none']::-webkit-scrollbar {\n display: none;\n }\n .chip-row[data-overflow='count'] {\n flex-wrap: nowrap;\n overflow-x: hidden;\n }\n .chip-row sken-filter-chip.overflow {\n /* The overflow chip reuses the same visual treatment as\n * a regular chip but with no close button (set via the\n * hide-close-button attribute on the primitive). */\n background: var(--sken-primary, #2e6fc6);\n color: var(--sken-primary-foreground, #fff);\n }\n .chip-row sken-filter-chip.overflow:hover {\n background: var(--sken-primary, #2e6fc6);\n opacity: 0.92;\n }\n /* The input row (input + clear + chevron) sits on its own\n * line below the chip row. Without this wrapper, the\n * clear + chevron would float to the end of the first\n * chip line in display:chips mode (visually wrong).\n * The input row is always present, even when there are\n * no chips — that way the layout is stable across the\n * \"empty\" and \"with chips\" states. */\n .input-row {\n display: flex;\n align-items: center;\n /* Slightly wider gap than the trigger's general one, so\n * the clear button and the chevron do not feel\n * glued together at the right edge of the input. The\n * extra 4px matches the visual weight of the icons\n * (16x16 each) without making the layout feel sparse. */\n gap: 0.5rem;\n flex-basis: 100%;\n min-inline-size: 0;\n }\n `\n\n protected override render() {\n const visible = this._visibleItems\n const groups = this._visibleGroups\n const isMulti = this.mode === 'multiple'\n const selectedSet = isMulti\n ? new Set(Array.isArray(this.value) ? this.value : [])\n : new Set(this.value ? [this.value as string] : [])\n const listboxId = `sken-combobox-listbox-${Math.random().toString(36).slice(2, 8)}`\n\n // The chip row is only meaningful in `multiple` + `display=\"chips\"`.\n // Single mode never renders chips; the trigger shows the chosen\n // option's label in the input itself.\n const showChips = isMulti && this.display === 'chips'\n const selectedItems = showChips ? this._items.filter((it) => selectedSet.has(it.value)) : []\n\n // Apply the overflow budget. The `_visibleChipCount` is set in\n // `updated()` once the chip row has been rendered; until then\n // it is `-1`, which means \"show them all\" (no measurement yet).\n // After the first measurement, the first `_visibleChipCount`\n // items render as full chips; the rest are hidden behind the\n // overflow chip (only relevant in `chipOverflow=\"count\"`).\n const visibleChipSlice =\n showChips && this._visibleChipCount >= 0\n ? selectedItems.slice(0, this._visibleChipCount)\n : selectedItems\n const hiddenChips =\n showChips && this._visibleChipCount >= 0 ? selectedItems.slice(this._visibleChipCount) : []\n\n return html`\n <div class=\"field\">\n ${this.label ? html`<label class=\"label\" for=\"trigger\">${this.label}</label>` : null}\n <div\n class=\"trigger\"\n data-focused=${this._open}\n data-disabled=${this.disabled}\n @click=${() => !this.disabled && (this._open ? this._close() : this._open_())}\n >\n ${\n showChips && selectedItems.length > 0\n ? html`<div class=\"chip-row\" part=\"chip-row\" data-overflow=${this.chipOverflow}>\n ${visibleChipSlice.map(\n (it) => html`\n <sken-filter-chip\n label=${it.label}\n data-sken-combobox-chip-value=${it.value}\n @sken-close=${(e: CustomEvent) => this._removeChip(it.value, e)}\n ></sken-filter-chip>\n `,\n )}\n ${\n hiddenChips.length > 0 && this.chipOverflow === 'count'\n ? html`<sken-filter-chip\n class=\"overflow\"\n label=\"+${hiddenChips.length}\"\n data-sken-combobox-overflow=${hiddenChips.length}\n hide-close-button\n @click=${(e: MouseEvent) => {\n e.stopPropagation()\n this._open = true\n }}\n ></sken-filter-chip>`\n : null\n }\n </div>`\n : null\n }\n <div class=\"input-row\">\n <input\n id=\"trigger\"\n role=\"combobox\"\n aria-expanded=${this._open}\n aria-controls=${listboxId}\n aria-activedescendant=${this._activeIndex >= 0 ? `${listboxId}-opt-${this._activeIndex}` : ''}\n aria-autocomplete=${this.filterable ? 'list' : 'none'}\n aria-required=${this.required}\n aria-invalid=${this.invalid}\n aria-describedby=${this.describedById}\n ?disabled=${this.disabled}\n .value=${this._displayValue}\n placeholder=${this.placeholder}\n @input=${this._onInput}\n @keydown=${this._onKeydown}\n @focus=${this._onFocus}\n @blur=${this._onBlur}\n />\n ${\n !this.disabled && this.value !== null && (isMulti ? selectedSet.size > 0 : true)\n ? html`<button\n type=\"button\"\n class=\"clear\"\n aria-label=\"Clear selection\"\n @click=${(e: Event) => {\n e.stopPropagation()\n const cleared: SkenComboboxValue<SkenComboboxMode> = isMulti ? [] : null\n this._setValue(cleared)\n this._query = ''\n this._input.focus()\n }}\n >\n ×\n </button>`\n : null\n }\n <span class=\"chevron\" data-open=${this._open} aria-hidden=\"true\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M3.5 6.5 L8 11 L12.5 6.5\"></path>\n </svg>\n </span>\n </div>\n </div>\n\n ${\n this._open\n ? html`<ul\n id=${listboxId}\n class=\"listbox\"\n role=\"listbox\"\n aria-multiselectable=${isMulti}\n >\n ${\n visible.length === 0\n ? html`<li class=\"option\" data-disabled=\"true\">No matches</li>`\n : groups.map(\n (g) => html`\n ${\n g.label\n ? html`<li class=\"group-header\" role=\"presentation\">${g.label}</li>`\n : null\n }\n ${g.items.map((it) => {\n const idx = visible.indexOf(it)\n const isActive = idx === this._activeIndex\n const isSelected = selectedSet.has(it.value)\n return html`<li\n id=${`${listboxId}-opt-${idx}`}\n class=\"option\"\n role=\"option\"\n data-active=${isActive}\n data-selected=${isSelected}\n data-disabled=${it.disabled}\n aria-selected=${isSelected}\n aria-disabled=${it.disabled}\n @mouseenter=${() => {\n this._activeIndex = idx\n }}\n @click=${() => this._selectItem(it)}\n >\n ${isMulti ? html`<span class=\"check\" aria-hidden=\"true\">✓</span>` : null}\n <span>${it.label}</span>\n </li>`\n })}\n `,\n )\n }\n </ul>`\n : null\n }\n ${this.helperText ? html`<div class=\"helper\">${this.helperText}</div>` : null}\n </div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-combobox': SkenCombobox\n }\n}\n"],"mappings":"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"}
|
|
@@ -1322,11 +1322,11 @@ var $ = class extends a {
|
|
|
1322
1322
|
>
|
|
1323
1323
|
<span class="header-text">${n}</span>
|
|
1324
1324
|
${r ? s`<span
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1325
|
+
class="sort-indicator"
|
|
1326
|
+
data-state=${i || "none"}
|
|
1327
|
+
aria-hidden="true"
|
|
1328
|
+
>${a}</span
|
|
1329
|
+
>` : l}
|
|
1330
1330
|
</th>
|
|
1331
1331
|
`;
|
|
1332
1332
|
})}
|
|
@@ -1366,9 +1366,7 @@ var $ = class extends a {
|
|
|
1366
1366
|
>
|
|
1367
1367
|
← Prev
|
|
1368
1368
|
</button>
|
|
1369
|
-
<span class="page-info">
|
|
1370
|
-
Page ${t + 1} of ${Math.max(i, 1)}
|
|
1371
|
-
</span>
|
|
1369
|
+
<span class="page-info"> Page ${t + 1} of ${Math.max(i, 1)} </span>
|
|
1372
1370
|
<button
|
|
1373
1371
|
type="button"
|
|
1374
1372
|
class="page-btn"
|
|
@@ -1560,4 +1558,4 @@ e([d({ attribute: !1 })], $.prototype, "rows", void 0), e([d({ attribute: !1 })]
|
|
|
1560
1558
|
//#endregion
|
|
1561
1559
|
export { $ as t };
|
|
1562
1560
|
|
|
1563
|
-
//# sourceMappingURL=sken-datatable-
|
|
1561
|
+
//# sourceMappingURL=sken-datatable-ChDjB_T_.js.map
|