@sken-ds/primitives 0.4.6 → 0.5.0

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 CHANGED
@@ -1,16 +1,16 @@
1
- import { t as e } from "./sken-datatable-CEXcvhM-.js";
2
- import { SkenButton as t } from "./sken-button.js";
3
- import { SkenCheckbox as n } from "./sken-checkbox.js";
4
- import { SkenComboboxGroup as r } from "./sken-combobox-group.js";
5
- import { SkenComboboxItem as i } from "./sken-combobox-item.js";
6
- import { SkenFilterChip as a } from "./sken-filter-chip.js";
7
- import { SkenCombobox as o } from "./sken-combobox.js";
1
+ import { SkenButton as e } from "./sken-button.js";
2
+ import { SkenCheckbox as t } from "./sken-checkbox.js";
3
+ import { SkenComboboxGroup as n } from "./sken-combobox-group.js";
4
+ import { SkenComboboxItem as r } from "./sken-combobox-item.js";
5
+ import { SkenFilterChip as i } from "./sken-filter-chip.js";
6
+ import { SkenCombobox as a } from "./sken-combobox.js";
7
+ import { t as o } from "./sken-datatable-jx_jh1Fu.js";
8
8
  import { SkenDatePicker as s } from "./sken-date-picker.js";
9
- import { t as c } from "./sken-dialog-BvRZxwSk.js";
9
+ import { SkenDialog as c } from "./sken-dialog.js";
10
10
  import { SkenInput as l } from "./sken-input.js";
11
11
  import { SkenLayoutRegion as u } from "./sken-layout-region.js";
12
12
  import { SkenLayout as d } from "./sken-layout.js";
13
13
  import { SkenNumberInput as f } from "./sken-number-input.js";
14
14
  import { SkenSwitch as p } from "./sken-switch.js";
15
15
  import { SkenTextarea as m } from "./sken-textarea.js";
16
- export { t as SkenButton, n as SkenCheckbox, o as SkenCombobox, r as SkenComboboxGroup, i as SkenComboboxItem, e as SkenDataTable, s as SkenDatePicker, c as SkenDialog, a as SkenFilterChip, l as SkenInput, d as SkenLayout, u as SkenLayoutRegion, f as SkenNumberInput, p as SkenSwitch, m as SkenTextarea };
16
+ export { e as SkenButton, t as SkenCheckbox, a as SkenCombobox, n as SkenComboboxGroup, r as SkenComboboxItem, o as SkenDataTable, s as SkenDatePicker, c as SkenDialog, i as SkenFilterChip, l as SkenInput, d as SkenLayout, u as SkenLayoutRegion, f as SkenNumberInput, p as SkenSwitch, m as SkenTextarea };
@@ -0,0 +1,17 @@
1
+ //#region \0rolldown/runtime.js
2
+ var e = Object.defineProperty, t = Object.getOwnPropertyDescriptor, n = Object.getOwnPropertyNames, r = Object.prototype.hasOwnProperty, i = (t, n) => {
3
+ let r = {};
4
+ for (var i in t) e(r, i, {
5
+ get: t[i],
6
+ enumerable: !0
7
+ });
8
+ return n || e(r, Symbol.toStringTag, { value: "Module" }), r;
9
+ }, a = (i, a, o, s) => {
10
+ if (a && typeof a == "object" || typeof a == "function") for (var c = n(a), l = 0, u = c.length, d; l < u; l++) d = c[l], !r.call(i, d) && d !== o && e(i, d, {
11
+ get: ((e) => a[e]).bind(null, d),
12
+ enumerable: !(s = t(a, d)) || s.enumerable
13
+ });
14
+ return i;
15
+ }, o = (e, t, n) => (a(e, t, "default"), n && a(n, t, "default"));
16
+ //#endregion
17
+ export { o as n, i as t };
@@ -8,7 +8,7 @@ var o = class extends t {
8
8
  }
9
9
  #e;
10
10
  constructor() {
11
- super(), this.#e = null, this.variant = "primary", this.size = "md", this.disabled = !1, this.type = "button", this.ariaLabel = null, this.#t = (e) => {
11
+ super(), this.#e = void 0, this.variant = "primary", this.size = "md", this.disabled = !1, this.type = "button", this.ariaLabel = null, this.#t = (e) => {
12
12
  if (this.disabled) {
13
13
  e.preventDefault(), e.stopImmediatePropagation();
14
14
  return;
@@ -23,7 +23,7 @@ var o = class extends t {
23
23
  try {
24
24
  this.#e = this.attachInternals();
25
25
  } catch {
26
- this.#e = null;
26
+ this.#e = void 0;
27
27
  }
28
28
  }
29
29
  static {
@@ -1 +1 @@
1
- {"version":3,"file":"sken-button.js","names":["#internals","#handleClick"],"sources":["../src/components/sken-button.ts"],"sourcesContent":["// ── <sken-button> — framework-ready Web Component ────────────────────\n// Wraps a native <button> with token-driven styles. Contract lives in\n// @sken-ds/contracts; this file is the implementation.\n//\n// Requires @sken-ds/theme/css loaded: every --sken-* variable this\n// component reads comes from the theme. If the theme is missing,\n// styles fall back to initial / inherited values and the button\n// looks broken — intentional, not silent. ADR-0006 / ADR-0004.\n\nimport type { SkenButtonSize, SkenButtonVariant } from '@sken-ds/contracts'\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\n@customElement('sken-button')\nexport class SkenButton extends LitElement {\n // Form-associated custom element: opt in to the ElementInternals\n // API so this primitive can reach its surrounding <form> via\n // `internals.form`. We do NOT publish a value to FormData\n // (this is a button, not an input), but we DO need form\n // access for the `submit` and `reset` click bridges below.\n // https://lit.dev/docs/components/form/\n static formAssociated = true\n\n // The ElementInternals instance. Created in the constructor\n // via attachInternals(), which the browser only makes\n // available because `formAssociated` is true. Nullable to\n // gracefully degrade in test environments without\n // ElementInternals.\n #internals: ElementInternals | null = null\n\n constructor() {\n super()\n try {\n this.#internals = this.attachInternals()\n } catch {\n this.#internals = null\n }\n }\n\n @property({ reflect: true }) variant: SkenButtonVariant = 'primary'\n @property({ reflect: true }) size: SkenButtonSize = 'md'\n @property({ reflect: true, type: Boolean }) disabled = false\n @property() type: 'button' | 'submit' | 'reset' = 'button'\n\n // `string | null` matches `lib.dom.d.ts`; the contract normalizes\n // to `string | undefined` for the public API.\n @property({ attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n static styles = css`\n :host {\n display: inline-block;\n }\n\n button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--sken-2);\n box-sizing: border-box;\n border: 1px solid transparent;\n border-radius: var(--sken-md);\n background: transparent;\n color: inherit;\n font-family: var(--sken-family-sans);\n font-size: var(--sken-size-md);\n font-weight: 500;\n line-height: 1.25;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background-color 120ms ease,\n border-color 120ms ease,\n color 120ms ease;\n padding: var(--sken-2) var(--sken-4);\n min-height: 2.25rem;\n }\n\n /* ── Sizes ────────────────────────────────────────────────── */\n :host([size='sm']) button {\n padding: var(--sken-1) var(--sken-3);\n font-size: var(--sken-size-sm);\n min-height: 1.75rem;\n }\n :host([size='md']) button {\n /* Default styles above. */\n }\n :host([size='lg']) button {\n padding: var(--sken-3) var(--sken-5);\n font-size: var(--sken-size-lg);\n min-height: 2.75rem;\n }\n\n /* ── Variants ─────────────────────────────────────────────── */\n :host([variant='primary']) button {\n background: var(--sken-primary);\n color: var(--sken-primary-foreground);\n }\n :host([variant='primary']) button:hover:not(:disabled) {\n background: var(--sken-primary-hover);\n }\n :host([variant='primary']) button:active:not(:disabled) {\n background: var(--sken-primary-active);\n }\n\n :host([variant='secondary']) button {\n background: var(--sken-transparent);\n color: var(--sken-foreground);\n border-color: var(--sken-border);\n }\n :host([variant='secondary']) button:hover:not(:disabled) {\n background: var(--sken-muted);\n }\n\n :host([variant='ghost']) button {\n color: var(--sken-foreground);\n }\n :host([variant='ghost']) button:hover:not(:disabled) {\n background: var(--sken-muted);\n }\n\n /* ── States ───────────────────────────────────────────────── */\n button:focus-visible {\n outline: 2px solid var(--sken-ring, currentColor);\n outline-offset: 2px;\n }\n button:disabled {\n cursor: not-allowed;\n opacity: var(--sken-disabled);\n }\n `\n\n protected override render() {\n return html`\n <button\n part=\"button\"\n ?disabled=${this.disabled}\n type=${this.type}\n @click=${this.#handleClick}\n >\n <slot></slot>\n </button>\n `\n }\n\n // `composed: true` is required for the `sken-click` event to\n // cross the shadow boundary; `preventDefault` +\n // `stopImmediatePropagation` on a disabled button suppresses the\n // native submit-on-Enter behavior.\n //\n // For `type=\"submit\"` and `type=\"reset\"` we ALSO bridge the\n // shadow boundary so the surrounding <form> receives the\n // submit / reset. Without this, the inner <button type=\"submit\">'s\n // default action submits the (notional) form inside the shadow\n // root, which doesn't exist — no `submit` event ever reaches the\n // outer light-DOM form. `internals.form.requestSubmit()` is the\n // modern, non-deprecated API; it dispatches a real `submit` event\n // on the form and runs constraint validation. `form.reset()` is\n // the standard form reset method. We `preventDefault()` to\n // suppress the inner button's default action (submit/reset the\n // shadow form) so the bridge is the only path. Without it we\n // would get a phantom submit to a nonexistent form AND a\n // requestSubmit to the real one. (Tested with happy-dom: phantom\n // submit is a no-op there because the shadow form doesn't exist,\n // but on a real browser it would dispatch a submit event to a\n // formless button — harmless but noisy.)\n //\n // The `sken-click` event is dispatched for every type so\n // consumers can listen to it regardless of whether they put the\n // handler on the button or on the form. Consumers that want\n // submit-only handling should listen to the form's `submit`\n // event, not the button's click — same as native HTML.\n #handleClick = (event: MouseEvent | KeyboardEvent) => {\n if (this.disabled) {\n event.preventDefault()\n event.stopImmediatePropagation()\n return\n }\n const form = this.#internals?.form\n if (this.type === 'submit' && form) {\n event.preventDefault()\n form.requestSubmit()\n } else if (this.type === 'reset' && form) {\n event.preventDefault()\n form.reset()\n }\n this.dispatchEvent(\n new CustomEvent('sken-click', {\n detail: { originalEvent: event },\n bubbles: true,\n composed: true,\n }),\n )\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-button': SkenButton\n }\n}\n"],"mappings":";;;;AAcO,IAAM,IAAN,cAAyB,EAAW;;EAOjB,KAAA,iBAAA;;CAOxB;CAEA,cAAc;EA8IE,AA7Id,MAAM,GAH8B,KAAA,KAAA,MAWoB,KAAA,UAAA,WACN,KAAA,OAAA,MACG,KAAA,WAAA,IACL,KAAA,OAAA,UAIyB,KAAA,YAAA,MA8H3D,KAAA,MAAA,MAAsC;GACpD,IAAI,KAAK,UAAU;IAEjB,AADA,EAAM,eAAe,GACrB,EAAM,yBAAyB;IAC/B;GACF;GACA,IAAM,IAAO,KAAKA,IAAY;GAQ9B,AAPI,KAAK,SAAS,YAAY,KAC5B,EAAM,eAAe,GACrB,EAAK,cAAc,KACV,KAAK,SAAS,WAAW,MAClC,EAAM,eAAe,GACrB,EAAK,MAAM,IAEb,KAAK,cACH,IAAI,YAAY,cAAc;IAC5B,QAAQ,EAAE,eAAe,EAAM;IAC/B,SAAS;IACT,UAAU;GACZ,CAAC,CACH;EACF;EAjKE,IAAI;GACF,KAAKA,KAAa,KAAK,gBAAgB;EACzC,QAAQ;GACN,KAAKA,KAAa;EACpB;CACF;;EAWgB,KAAA,SAAA,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFnB,SAA4B;EAC1B,OAAO,CAAI;;;oBAGK,KAAK,SAAS;eACnB,KAAK,KAAK;iBACR,KAAKC,GAAa;;;;;CAKjC;CA6BA;AAsBF;AA3JG,EAAA,CAAA,EAAS,EAAE,SAAS,GAAK,CAAC,CAAA,GAAA,EAAA,WAAA,WAAA,KAAA,CAAA,GAC1B,EAAA,CAAA,EAAS,EAAE,SAAS,GAAK,CAAC,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GAC1B,EAAA,CAAA,EAAS;CAAE,SAAS;CAAM,MAAM;AAAQ,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GACzC,EAAA,CAAA,EAAS,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GAIT,EAAA,CAAA,EAAS,EAAE,WAAW,aAAa,CAAC,CAAA,GAAA,EAAA,WAAA,aAAA,KAAA,CAAA,GAjCtC,IAAA,EAAA,CAAA,EAAc,aAAa,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"sken-button.js","names":["#internals","#handleClick"],"sources":["../src/components/sken-button.ts"],"sourcesContent":["// ── <sken-button> — framework-ready Web Component ────────────────────\n// Wraps a native <button> with token-driven styles. Contract lives in\n// @sken-ds/contracts; this file is the implementation.\n//\n// Requires @sken-ds/theme/css loaded: every --sken-* variable this\n// component reads comes from the theme. If the theme is missing,\n// styles fall back to initial / inherited values and the button\n// looks broken — intentional, not silent. ADR-0006 / ADR-0004.\n\nimport type { SkenButtonSize, SkenButtonVariant } from '@sken-ds/contracts'\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\n@customElement('sken-button')\nexport class SkenButton extends LitElement {\n // Form-associated custom element: opt in to the ElementInternals\n // API so this primitive can reach its surrounding <form> via\n // `internals.form`. We do NOT publish a value to FormData\n // (this is a button, not an input), but we DO need form\n // access for the `submit` and `reset` click bridges below.\n // https://lit.dev/docs/components/form/\n static formAssociated = true\n\n // The ElementInternals instance. Created in the constructor\n // via attachInternals(), which the browser only makes\n // available because `formAssociated` is true. Nullable to\n // gracefully degrade in test environments without\n // ElementInternals.\n #internals: ElementInternals | undefined = undefined\n\n constructor() {\n super()\n try {\n this.#internals = this.attachInternals()\n } catch {\n this.#internals = undefined\n }\n }\n\n @property({ reflect: true }) variant: SkenButtonVariant = 'primary'\n @property({ reflect: true }) size: SkenButtonSize = 'md'\n @property({ reflect: true, type: Boolean }) disabled = false\n @property() type: 'button' | 'submit' | 'reset' = 'button'\n\n // `string | null` matches `lib.dom.d.ts`; the contract normalizes\n // to `string | undefined` for the public API.\n @property({ attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n static styles = css`\n :host {\n display: inline-block;\n }\n\n button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--sken-2);\n box-sizing: border-box;\n border: 1px solid transparent;\n border-radius: var(--sken-md);\n background: transparent;\n color: inherit;\n font-family: var(--sken-family-sans);\n font-size: var(--sken-size-md);\n font-weight: 500;\n line-height: 1.25;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background-color 120ms ease,\n border-color 120ms ease,\n color 120ms ease;\n padding: var(--sken-2) var(--sken-4);\n min-height: 2.25rem;\n }\n\n /* ── Sizes ────────────────────────────────────────────────── */\n :host([size='sm']) button {\n padding: var(--sken-1) var(--sken-3);\n font-size: var(--sken-size-sm);\n min-height: 1.75rem;\n }\n :host([size='md']) button {\n /* Default styles above. */\n }\n :host([size='lg']) button {\n padding: var(--sken-3) var(--sken-5);\n font-size: var(--sken-size-lg);\n min-height: 2.75rem;\n }\n\n /* ── Variants ─────────────────────────────────────────────── */\n :host([variant='primary']) button {\n background: var(--sken-primary);\n color: var(--sken-primary-foreground);\n }\n :host([variant='primary']) button:hover:not(:disabled) {\n background: var(--sken-primary-hover);\n }\n :host([variant='primary']) button:active:not(:disabled) {\n background: var(--sken-primary-active);\n }\n\n :host([variant='secondary']) button {\n background: var(--sken-transparent);\n color: var(--sken-foreground);\n border-color: var(--sken-border);\n }\n :host([variant='secondary']) button:hover:not(:disabled) {\n background: var(--sken-muted);\n }\n\n :host([variant='ghost']) button {\n color: var(--sken-foreground);\n }\n :host([variant='ghost']) button:hover:not(:disabled) {\n background: var(--sken-muted);\n }\n\n /* ── States ───────────────────────────────────────────────── */\n button:focus-visible {\n outline: 2px solid var(--sken-ring, currentColor);\n outline-offset: 2px;\n }\n button:disabled {\n cursor: not-allowed;\n opacity: var(--sken-disabled);\n }\n `\n\n protected override render() {\n return html`\n <button\n part=\"button\"\n ?disabled=${this.disabled}\n type=${this.type}\n @click=${this.#handleClick}\n >\n <slot></slot>\n </button>\n `\n }\n\n // `composed: true` is required for the `sken-click` event to\n // cross the shadow boundary; `preventDefault` +\n // `stopImmediatePropagation` on a disabled button suppresses the\n // native submit-on-Enter behavior.\n //\n // For `type=\"submit\"` and `type=\"reset\"` we ALSO bridge the\n // shadow boundary so the surrounding <form> receives the\n // submit / reset. Without this, the inner <button type=\"submit\">'s\n // default action submits the (notional) form inside the shadow\n // root, which doesn't exist — no `submit` event ever reaches the\n // outer light-DOM form. `internals.form.requestSubmit()` is the\n // modern, non-deprecated API; it dispatches a real `submit` event\n // on the form and runs constraint validation. `form.reset()` is\n // the standard form reset method. We `preventDefault()` to\n // suppress the inner button's default action (submit/reset the\n // shadow form) so the bridge is the only path. Without it we\n // would get a phantom submit to a nonexistent form AND a\n // requestSubmit to the real one. (Tested with happy-dom: phantom\n // submit is a no-op there because the shadow form doesn't exist,\n // but on a real browser it would dispatch a submit event to a\n // formless button — harmless but noisy.)\n //\n // The `sken-click` event is dispatched for every type so\n // consumers can listen to it regardless of whether they put the\n // handler on the button or on the form. Consumers that want\n // submit-only handling should listen to the form's `submit`\n // event, not the button's click — same as native HTML.\n #handleClick = (event: MouseEvent | KeyboardEvent) => {\n if (this.disabled) {\n event.preventDefault()\n event.stopImmediatePropagation()\n return\n }\n const form = this.#internals?.form\n if (this.type === 'submit' && form) {\n event.preventDefault()\n form.requestSubmit()\n } else if (this.type === 'reset' && form) {\n event.preventDefault()\n form.reset()\n }\n this.dispatchEvent(\n new CustomEvent('sken-click', {\n detail: { originalEvent: event },\n bubbles: true,\n composed: true,\n }),\n )\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-button': SkenButton\n }\n}\n"],"mappings":";;;;AAcO,IAAM,IAAN,cAAyB,EAAW;;EAOjB,KAAA,iBAAA;;CAOxB;CAEA,cAAc;EA8IE,AA7Id,MAAM,GAHmC,KAAA,KAAA,KAAA,GAWe,KAAA,UAAA,WACN,KAAA,OAAA,MACG,KAAA,WAAA,IACL,KAAA,OAAA,UAIyB,KAAA,YAAA,MA8H3D,KAAA,MAAA,MAAsC;GACpD,IAAI,KAAK,UAAU;IAEjB,AADA,EAAM,eAAe,GACrB,EAAM,yBAAyB;IAC/B;GACF;GACA,IAAM,IAAO,KAAKA,IAAY;GAQ9B,AAPI,KAAK,SAAS,YAAY,KAC5B,EAAM,eAAe,GACrB,EAAK,cAAc,KACV,KAAK,SAAS,WAAW,MAClC,EAAM,eAAe,GACrB,EAAK,MAAM,IAEb,KAAK,cACH,IAAI,YAAY,cAAc;IAC5B,QAAQ,EAAE,eAAe,EAAM;IAC/B,SAAS;IACT,UAAU;GACZ,CAAC,CACH;EACF;EAjKE,IAAI;GACF,KAAKA,KAAa,KAAK,gBAAgB;EACzC,QAAQ;GACN,KAAKA,KAAa,KAAA;EACpB;CACF;;EAWgB,KAAA,SAAA,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFnB,SAA4B;EAC1B,OAAO,CAAI;;;oBAGK,KAAK,SAAS;eACnB,KAAK,KAAK;iBACR,KAAKC,GAAa;;;;;CAKjC;CA6BA;AAsBF;AA3JG,EAAA,CAAA,EAAS,EAAE,SAAS,GAAK,CAAC,CAAA,GAAA,EAAA,WAAA,WAAA,KAAA,CAAA,GAC1B,EAAA,CAAA,EAAS,EAAE,SAAS,GAAK,CAAC,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GAC1B,EAAA,CAAA,EAAS;CAAE,SAAS;CAAM,MAAM;AAAQ,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GACzC,EAAA,CAAA,EAAS,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GAIT,EAAA,CAAA,EAAS,EAAE,WAAW,aAAa,CAAC,CAAA,GAAA,EAAA,WAAA,aAAA,KAAA,CAAA,GAjCtC,IAAA,EAAA,CAAA,EAAc,aAAa,CAAA,GAAA,CAAA"}
@@ -5,7 +5,7 @@ import { customElement as i, property as a, query as o, state as s } from "lit/d
5
5
  //#region src/components/sken-combobox.ts
6
6
  var c = class extends t {
7
7
  constructor(...e) {
8
- super(...e), this.mode = "single", this.display = "count", this.chipOverflow = "wrap", this.value = null, this.filterable = !0, this.label = "", this.helperText = "", this.placeholder = "", this.disabled = !1, this.invalid = !1, this.required = !1, this.name = "", this.size = "md", this.describedById = "", this._items = [], this._query = "", this._open = !1, this._activeIndex = -1, this._visibleChipCount = -1, this._onKeydown = (e) => {
8
+ super(...e), this.mode = "single", this.display = "count", this.chipOverflow = "wrap", this.value = void 0, this.filterable = !0, this.label = "", this.helperText = "", this.placeholder = "", this.disabled = !1, this.invalid = !1, this.required = !1, this.name = "", this.size = "md", this.describedById = "", this._items = [], this._query = "", this._open = !1, this._activeIndex = -1, this._visibleChipCount = -1, this._listboxId = `sken-combobox-listbox-${Math.random().toString(36).slice(2, 8)}`, this._onKeydown = (e) => {
9
9
  if (!this.disabled) switch (e.key) {
10
10
  case "ArrowDown":
11
11
  e.preventDefault(), this._open ? this._activeIndex = this._nextSelectableIndex(this._activeIndex, 1) : this._open_();
@@ -38,9 +38,9 @@ var c = class extends t {
38
38
  }
39
39
  case "Escape":
40
40
  if (this._open) e.preventDefault(), this._close();
41
- else if (this.value !== null && this.value !== void 0) {
41
+ else if (this._hasSelection) {
42
42
  e.preventDefault();
43
- let t = this.mode === "multiple" ? [] : null;
43
+ let t = this.mode === "multiple" ? [] : void 0;
44
44
  this._setValue(t);
45
45
  }
46
46
  break;
@@ -50,18 +50,19 @@ var c = class extends t {
50
50
  if (!this.filterable) return;
51
51
  let t = e.target;
52
52
  this._query = t.value, this._open || this._open_(), this._activeIndex = this._firstSelectableIndex();
53
- }, this._onFocus = () => {
54
- this.dispatchEvent(new FocusEvent("sken-focus", {
53
+ }, this._onFocus = (e) => {
54
+ this.dispatchEvent(new CustomEvent("sken-focus", {
55
+ detail: e,
55
56
  bubbles: !0,
56
57
  composed: !0
57
58
  }));
58
- }, this._onBlur = () => {
59
- setTimeout(() => {
60
- this._close(), this.dispatchEvent(new FocusEvent("sken-blur", {
61
- bubbles: !0,
62
- composed: !0
63
- }));
64
- }, 100);
59
+ }, this._onBlur = (e) => {
60
+ let t = e.relatedTarget;
61
+ t && this.renderRoot.contains(t) || (this._close(), this.dispatchEvent(new CustomEvent("sken-blur", {
62
+ detail: e,
63
+ bubbles: !0,
64
+ composed: !0
65
+ })));
65
66
  };
66
67
  }
67
68
  connectedCallback() {
@@ -134,7 +135,10 @@ var c = class extends t {
134
135
  get _visibleItems() {
135
136
  if (!this._query) return this._items;
136
137
  let e = this._query.toLowerCase();
137
- return this._items.filter((e) => !e.disabled || !0).filter((t) => t.label.toLowerCase().includes(e));
138
+ return this._items.filter((t) => t.label.toLowerCase().includes(e));
139
+ }
140
+ get _hasSelection() {
141
+ return this.mode === "multiple" ? Array.isArray(this.value) && this.value.length > 0 : this.value !== void 0 && this.value !== "";
138
142
  }
139
143
  get _visibleGroups() {
140
144
  let e = this._visibleItems, t = /* @__PURE__ */ new Map();
@@ -163,7 +167,7 @@ var c = class extends t {
163
167
  } else this._setValue(e.value), this._close();
164
168
  }
165
169
  _removeChip(e, t) {
166
- if (t.stopPropagation(), this.mode !== "multiple") return;
170
+ if (t.stopPropagation(), this.disabled || this.mode !== "multiple") return;
167
171
  let n = Array.isArray(this.value) ? [...this.value] : [], r = n.indexOf(e);
168
172
  r < 0 || (n.splice(r, 1), this._setValue(n), this._input.focus());
169
173
  }
@@ -214,15 +218,11 @@ var c = class extends t {
214
218
  .trigger {
215
219
  position: relative;
216
220
  display: flex;
217
- /* The trigger wraps its children: the chip row (if any)
218
- * goes on its own line, then the input row. Without
219
- * flex-wrap: wrap here, the chip row would push the input
220
- * horizontally and the clear + chevron would float at
221
- * the end of the first chip line. */
222
221
  flex-wrap: wrap;
223
222
  align-items: center;
224
223
  gap: 0.25rem;
225
224
  padding-inline: 0.75rem;
225
+ padding-inline-end: 3.25rem;
226
226
  padding-block: var(--sken-2, 0.5rem);
227
227
  min-block-size: 2.25rem;
228
228
  box-sizing: border-box;
@@ -273,7 +273,6 @@ var c = class extends t {
273
273
  display: inline-flex;
274
274
  align-items: center;
275
275
  justify-content: center;
276
- line-height: 1;
277
276
  /* The trigger sets font-size: 0.875rem (--sken-size-md)
278
277
  * and inherits Inter. Inter renders block glyphs like
279
278
  * the down-chevron (▼, U+25BC) at near full em-box,
@@ -418,12 +417,6 @@ var c = class extends t {
418
417
  align-items: center;
419
418
  gap: 0.25rem;
420
419
  min-inline-size: 0;
421
- /* The chip row wraps to multiple lines when the chips
422
- * do not fit. The trigger's own height grows accordingly;
423
- * for display:chips + chip-overflow:count, the row uses
424
- * nowrap + overflow:hidden instead, so the trigger
425
- * height stays constant. */
426
- flex-basis: 100%;
427
420
  row-gap: 0.25rem;
428
421
  }
429
422
  .chip-row[data-overflow='none'] {
@@ -452,29 +445,19 @@ var c = class extends t {
452
445
  background: var(--sken-primary, #2e6fc6);
453
446
  opacity: 0.92;
454
447
  }
455
- /* The input row (input + clear + chevron) sits on its own
456
- * line below the chip row. Without this wrapper, the
457
- * clear + chevron would float to the end of the first
458
- * chip line in display:chips mode (visually wrong).
459
- * The input row is always present, even when there are
460
- * no chips — that way the layout is stable across the
461
- * "empty" and "with chips" states. */
462
- .input-row {
448
+ .icons {
449
+ position: absolute;
450
+ inset-inline-end: 0.75rem;
451
+ inset-block-start: var(--sken-2, 0.5rem);
452
+ min-block-size: 1.25rem;
463
453
  display: flex;
464
454
  align-items: center;
465
- /* Slightly wider gap than the trigger's general one, so
466
- * the clear button and the chevron do not feel
467
- * glued together at the right edge of the input. The
468
- * extra 4px matches the visual weight of the icons
469
- * (16x16 each) without making the layout feel sparse. */
470
455
  gap: 0.5rem;
471
- flex-basis: 100%;
472
- min-inline-size: 0;
473
456
  }
474
457
  `;
475
458
  }
476
459
  render() {
477
- let e = this._visibleItems, t = this._visibleGroups, n = this.mode === "multiple", i = n ? new Set(Array.isArray(this.value) ? this.value : []) : new Set(this.value ? [this.value] : []), a = `sken-combobox-listbox-${Math.random().toString(36).slice(2, 8)}`, o = n && this.display === "chips", s = o ? this._items.filter((e) => i.has(e.value)) : [], c = o && this._visibleChipCount >= 0 ? s.slice(0, this._visibleChipCount) : s, l = o && this._visibleChipCount >= 0 ? s.slice(this._visibleChipCount) : [];
460
+ let e = this._visibleItems, t = this._visibleGroups, n = this.mode === "multiple", i = n ? new Set(Array.isArray(this.value) ? this.value : []) : new Set(this.value ? [this.value] : []), a = this._listboxId, o = n && this.display === "chips", s = o ? this._items.filter((e) => i.has(e.value)) : [], c = o && this._visibleChipCount >= 0 ? s.slice(0, this._visibleChipCount) : s, l = o && this._visibleChipCount >= 0 ? s.slice(this._visibleChipCount) : [];
478
461
  return r`
479
462
  <div class="field">
480
463
  ${this.label ? r`<label class="label" for="trigger">${this.label}</label>` : null}
@@ -484,11 +467,17 @@ var c = class extends t {
484
467
  data-disabled=${this.disabled}
485
468
  @click=${() => !this.disabled && (this._open ? this._close() : this._open_())}
486
469
  >
487
- ${o && s.length > 0 ? r`<div class="chip-row" part="chip-row" data-overflow=${this.chipOverflow}>
470
+ ${o && s.length > 0 ? r`<div
471
+ class="chip-row"
472
+ part="chip-row"
473
+ data-overflow=${this.chipOverflow}
474
+ @mousedown=${(e) => e.preventDefault()}
475
+ >
488
476
  ${c.map((e) => r`
489
477
  <sken-filter-chip
490
478
  label=${e.label}
491
479
  data-sken-combobox-chip-value=${e.value}
480
+ ?disabled=${this.disabled}
492
481
  @sken-close=${(t) => this._removeChip(e.value, t)}
493
482
  ></sken-filter-chip>
494
483
  `)}
@@ -502,32 +491,33 @@ var c = class extends t {
502
491
  }}
503
492
  ></sken-filter-chip>` : null}
504
493
  </div>` : null}
505
- <div class="input-row">
506
- <input
507
- id="trigger"
508
- role="combobox"
509
- aria-expanded=${this._open}
510
- aria-controls=${a}
511
- aria-activedescendant=${this._activeIndex >= 0 ? `${a}-opt-${this._activeIndex}` : ""}
512
- aria-autocomplete=${this.filterable ? "list" : "none"}
513
- aria-required=${this.required}
514
- aria-invalid=${this.invalid}
515
- aria-describedby=${this.describedById}
516
- ?disabled=${this.disabled}
517
- .value=${this._displayValue}
518
- placeholder=${this.placeholder}
519
- @input=${this._onInput}
520
- @keydown=${this._onKeydown}
521
- @focus=${this._onFocus}
522
- @blur=${this._onBlur}
523
- />
524
- ${!this.disabled && this.value !== null && (!n || i.size > 0) ? r`<button
494
+ <input
495
+ id="trigger"
496
+ role="combobox"
497
+ aria-expanded=${this._open}
498
+ aria-controls=${a}
499
+ aria-activedescendant=${this._activeIndex >= 0 ? `${a}-opt-${this._activeIndex}` : ""}
500
+ aria-autocomplete=${this.filterable ? "list" : "none"}
501
+ aria-required=${this.required}
502
+ aria-invalid=${this.invalid}
503
+ aria-describedby=${this.describedById}
504
+ ?disabled=${this.disabled}
505
+ .value=${this._displayValue}
506
+ placeholder=${this.placeholder}
507
+ @input=${this._onInput}
508
+ @keydown=${this._onKeydown}
509
+ @focus=${this._onFocus}
510
+ @blur=${this._onBlur}
511
+ />
512
+ <div class="icons">
513
+ ${!this.disabled && this._hasSelection ? r`<button
525
514
  type="button"
526
515
  class="clear"
527
516
  aria-label="Clear selection"
517
+ @mousedown=${(e) => e.preventDefault()}
528
518
  @click=${(e) => {
529
519
  e.stopPropagation();
530
- let t = n ? [] : null;
520
+ let t = n ? [] : void 0;
531
521
  this._setValue(t), this._query = "", this._input.focus();
532
522
  }}
533
523
  >
@@ -553,6 +543,7 @@ var c = class extends t {
553
543
  class="listbox"
554
544
  role="listbox"
555
545
  aria-multiselectable=${n}
546
+ @mousedown=${(e) => e.preventDefault()}
556
547
  >
557
548
  ${e.length === 0 ? r`<li class="option" data-disabled="true">No matches</li>` : t.map((t) => r`
558
549
  ${t.label ? r`<li class="group-header" role="presentation">${t.label}</li>` : null}
@@ -568,7 +559,7 @@ var c = class extends t {
568
559
  aria-selected=${c}
569
560
  aria-disabled=${t.disabled}
570
561
  @mouseenter=${() => {
571
- this._activeIndex = o;
562
+ t.disabled || (this._activeIndex = o);
572
563
  }}
573
564
  @click=${() => this._selectItem(t)}
574
565
  >
@@ -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, 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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"}
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} from '@sken-ds/contracts'\n\n/**\n * Internal value type used by the primitive. The Sken public\n * contract exposes `value: string | string[]` (no `null`, no\n * `undefined`); the primitive keeps a slightly wider type\n * internally because `undefined` is the \"uninitialised\"\n * sentinel before the consumer passes a value, and `''` is\n * the \"cleared\" sentinel after. Both flow through the same\n * `_setValue` code path. The adapter translates `undefined`\n * to `''` before forwarding to the primitive, so consumers\n * never see `undefined` either.\n */\ntype SkenComboboxValue<M extends SkenComboboxMode = 'single'> = M extends 'multiple'\n ? string[]\n : string | undefined\n\ntype PrimitiveValue = SkenComboboxValue<SkenComboboxMode>\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> = undefined 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 private readonly _listboxId = `sken-combobox-listbox-${Math.random().toString(36).slice(2, 8)}`\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.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 /**\n * Whether there is anything to clear. Shared by the clear\n * button's visibility (in `render()`) and the `Escape`\n * \"clear when closed\" shortcut, so both agree on what\n * counts as \"empty\" regardless of mode.\n */\n private get _hasSelection(): boolean {\n return this.mode === 'multiple'\n ? Array.isArray(this.value) && this.value.length > 0\n : this.value !== undefined && this.value !== ''\n }\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.disabled || 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._hasSelection) {\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> =\n this.mode === 'multiple' ? [] : undefined\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 = (event: FocusEvent): void => {\n this.dispatchEvent(\n new CustomEvent<FocusEvent>('sken-focus', { detail: event, bubbles: true, composed: true }),\n )\n }\n\n private _onBlur = (event: FocusEvent): void => {\n const related = event.relatedTarget as Node | null\n if (related && this.renderRoot.contains(related)) return\n this._close()\n this.dispatchEvent(\n new CustomEvent<FocusEvent>('sken-blur', { detail: event, bubbles: true, composed: true }),\n )\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 flex-wrap: wrap;\n align-items: center;\n gap: 0.25rem;\n padding-inline: 0.75rem;\n padding-inline-end: 3.25rem;\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 /* 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 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 .icons {\n position: absolute;\n inset-inline-end: 0.75rem;\n inset-block-start: var(--sken-2, 0.5rem);\n min-block-size: 1.25rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\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 = this._listboxId\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\n class=\"chip-row\"\n part=\"chip-row\"\n data-overflow=${this.chipOverflow}\n @mousedown=${(e: MouseEvent) => e.preventDefault()}\n >\n ${visibleChipSlice.map(\n (it) => html`\n <sken-filter-chip\n label=${it.label}\n data-sken-combobox-chip-value=${it.value}\n ?disabled=${this.disabled}\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 <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 <div class=\"icons\">\n ${\n !this.disabled && this._hasSelection\n ? html`<button\n type=\"button\"\n class=\"clear\"\n aria-label=\"Clear selection\"\n @mousedown=${(e: MouseEvent) => e.preventDefault()}\n @click=${(e: Event) => {\n e.stopPropagation()\n const cleared: SkenComboboxValue<SkenComboboxMode> = isMulti ? [] : undefined\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 @mousedown=${(e: MouseEvent) => e.preventDefault()}\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 if (!it.disabled) 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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"}