@sken-ds/primitives 0.4.6 → 0.6.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.
Files changed (52) hide show
  1. package/dist/catalog-y40FMic1-DY2BFupW.js +153 -0
  2. package/dist/catalog-y40FMic1-DY2BFupW.js.map +1 -0
  3. package/dist/{decorate-D4FKzyDw.js → decorate-C1e2sg49.js} +1 -1
  4. package/dist/index.js +16 -12
  5. package/dist/ix-bridge.css +53 -191
  6. package/dist/primitives.css +2 -0
  7. package/dist/rolldown-runtime-BRfZIaNm.js +17 -0
  8. package/dist/sken-button.js +3 -3
  9. package/dist/sken-button.js.map +1 -1
  10. package/dist/sken-checkbox.js +14 -4
  11. package/dist/sken-checkbox.js.map +1 -1
  12. package/dist/sken-combobox-group.js +1 -1
  13. package/dist/sken-combobox-group.js.map +1 -1
  14. package/dist/sken-combobox-item.js +1 -1
  15. package/dist/sken-combobox-item.js.map +1 -1
  16. package/dist/sken-combobox.js +120 -99
  17. package/dist/sken-combobox.js.map +1 -1
  18. package/dist/{sken-datatable-CEXcvhM-.js → sken-datatable-DSXSyrNf.js} +219 -251
  19. package/dist/sken-datatable-DSXSyrNf.js.map +1 -0
  20. package/dist/sken-datatable.js +1 -1
  21. package/dist/sken-date-picker.js +1 -1
  22. package/dist/sken-date-picker.js.map +1 -1
  23. package/dist/sken-dialog.js +251 -2
  24. package/dist/sken-dialog.js.map +1 -0
  25. package/dist/sken-filter-chip.js +1 -1
  26. package/dist/sken-filter-chip.js.map +1 -1
  27. package/dist/sken-input.js +69 -44
  28. package/dist/sken-input.js.map +1 -1
  29. package/dist/sken-layout-region.js +1 -1
  30. package/dist/sken-layout-region.js.map +1 -1
  31. package/dist/sken-layout.js +35 -32
  32. package/dist/sken-layout.js.map +1 -1
  33. package/dist/sken-nav-group.js +306 -0
  34. package/dist/sken-nav-group.js.map +1 -0
  35. package/dist/sken-nav-item.js +224 -0
  36. package/dist/sken-nav-item.js.map +1 -0
  37. package/dist/sken-nav.js +233 -0
  38. package/dist/sken-nav.js.map +1 -0
  39. package/dist/sken-number-input.js +15 -7
  40. package/dist/sken-number-input.js.map +1 -1
  41. package/dist/sken-switch.js +27 -7
  42. package/dist/sken-switch.js.map +1 -1
  43. package/dist/sken-textarea.js +108 -52
  44. package/dist/sken-textarea.js.map +1 -1
  45. package/dist/sken-tooltip.js +70 -0
  46. package/dist/sken-tooltip.js.map +1 -0
  47. package/package.json +35 -16
  48. package/dist/directive-BcLtPdVO.js +0 -263
  49. package/dist/directive-BcLtPdVO.js.map +0 -1
  50. package/dist/sken-datatable-CEXcvhM-.js.map +0 -1
  51. package/dist/sken-dialog-BvRZxwSk.js +0 -258
  52. package/dist/sken-dialog-BvRZxwSk.js.map +0 -1
@@ -1,11 +1,17 @@
1
- import { t as e } from "./decorate-D4FKzyDw.js";
1
+ import { t as e } from "./decorate-C1e2sg49.js";
2
2
  import "./sken-filter-chip.js";
3
3
  import { LitElement as t, css as n, html as r } from "lit";
4
4
  import { customElement as i, property as a, query as o, state as s } from "lit/decorators.js";
5
5
  //#region src/components/sken-combobox.ts
6
- var c = class extends t {
6
+ function c(e, t) {
7
+ return e.length === t.length && e.every((e, n) => {
8
+ let r = t[n];
9
+ return e.value === r.value && e.label === r.label && e.disabled === r.disabled && e.groupLabel === r.groupLabel && e.groupDisabled === r.groupDisabled;
10
+ });
11
+ }
12
+ var l = class extends t {
7
13
  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) => {
14
+ 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.ariaLabel = null, 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
15
  if (!this.disabled) switch (e.key) {
10
16
  case "ArrowDown":
11
17
  e.preventDefault(), this._open ? this._activeIndex = this._nextSelectableIndex(this._activeIndex, 1) : this._open_();
@@ -38,9 +44,9 @@ var c = class extends t {
38
44
  }
39
45
  case "Escape":
40
46
  if (this._open) e.preventDefault(), this._close();
41
- else if (this.value !== null && this.value !== void 0) {
47
+ else if (this._hasSelection) {
42
48
  e.preventDefault();
43
- let t = this.mode === "multiple" ? [] : null;
49
+ let t = this.mode === "multiple" ? [] : void 0;
44
50
  this._setValue(t);
45
51
  }
46
52
  break;
@@ -50,18 +56,19 @@ var c = class extends t {
50
56
  if (!this.filterable) return;
51
57
  let t = e.target;
52
58
  this._query = t.value, this._open || this._open_(), this._activeIndex = this._firstSelectableIndex();
53
- }, this._onFocus = () => {
54
- this.dispatchEvent(new FocusEvent("sken-focus", {
59
+ }, this._onFocus = (e) => {
60
+ this.dispatchEvent(new CustomEvent("sken-focus", {
61
+ detail: e,
55
62
  bubbles: !0,
56
63
  composed: !0
57
64
  }));
58
- }, this._onBlur = () => {
59
- setTimeout(() => {
60
- this._close(), this.dispatchEvent(new FocusEvent("sken-blur", {
61
- bubbles: !0,
62
- composed: !0
63
- }));
64
- }, 100);
65
+ }, this._onBlur = (e) => {
66
+ let t = e.relatedTarget;
67
+ t && this.renderRoot.contains(t) || (this._close(), this.dispatchEvent(new CustomEvent("sken-blur", {
68
+ detail: e,
69
+ bubbles: !0,
70
+ composed: !0
71
+ })));
65
72
  };
66
73
  }
67
74
  connectedCallback() {
@@ -134,7 +141,10 @@ var c = class extends t {
134
141
  get _visibleItems() {
135
142
  if (!this._query) return this._items;
136
143
  let e = this._query.toLowerCase();
137
- return this._items.filter((e) => !e.disabled || !0).filter((t) => t.label.toLowerCase().includes(e));
144
+ return this._items.filter((t) => t.label.toLowerCase().includes(e));
145
+ }
146
+ get _hasSelection() {
147
+ return this.mode === "multiple" ? Array.isArray(this.value) && this.value.length > 0 : this.value !== void 0 && this.value !== "";
138
148
  }
139
149
  get _visibleGroups() {
140
150
  let e = this._visibleItems, t = /* @__PURE__ */ new Map();
@@ -157,13 +167,15 @@ var c = class extends t {
157
167
  })));
158
168
  }
159
169
  _selectItem(e) {
160
- if (!e.disabled) if (this.mode === "multiple") {
161
- let t = Array.isArray(this.value) ? [...this.value] : [], n = t.indexOf(e.value);
162
- n >= 0 ? t.splice(n, 1) : t.push(e.value), this._setValue(t), this._input.focus();
163
- } else this._setValue(e.value), this._close();
170
+ if (!e.disabled) {
171
+ if (this.mode === "multiple") {
172
+ let t = Array.isArray(this.value) ? [...this.value] : [], n = t.indexOf(e.value);
173
+ n >= 0 ? t.splice(n, 1) : t.push(e.value), this._setValue(t), this._input.focus();
174
+ } else this._setValue(e.value), this._close();
175
+ }
164
176
  }
165
177
  _removeChip(e, t) {
166
- if (t.stopPropagation(), this.mode !== "multiple") return;
178
+ if (t.stopPropagation(), this.disabled || this.mode !== "multiple") return;
167
179
  let n = Array.isArray(this.value) ? [...this.value] : [], r = n.indexOf(e);
168
180
  r < 0 || (n.splice(r, 1), this._setValue(n), this._input.focus());
169
181
  }
@@ -205,24 +217,27 @@ var c = class extends t {
205
217
  }
206
218
  .field {
207
219
  display: grid;
208
- gap: 0.375rem;
220
+ gap: var(--sken-1, 0.25rem);
209
221
  }
210
222
  .label {
211
- font-size: 0.8125rem;
223
+ font-size: var(--sken-size-xs, 0.75rem);
212
224
  font-weight: 500;
213
225
  }
226
+ :host([required]) .label::after {
227
+ content: ' *';
228
+ }
229
+ :host([required][invalid]) .label,
230
+ :host([required][invalid]) .label::after {
231
+ color: var(--sken-destructive);
232
+ }
214
233
  .trigger {
215
234
  position: relative;
216
235
  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
236
  flex-wrap: wrap;
223
237
  align-items: center;
224
238
  gap: 0.25rem;
225
239
  padding-inline: 0.75rem;
240
+ padding-inline-end: 3.25rem;
226
241
  padding-block: var(--sken-2, 0.5rem);
227
242
  min-block-size: 2.25rem;
228
243
  box-sizing: border-box;
@@ -273,7 +288,6 @@ var c = class extends t {
273
288
  display: inline-flex;
274
289
  align-items: center;
275
290
  justify-content: center;
276
- line-height: 1;
277
291
  /* The trigger sets font-size: 0.875rem (--sken-size-md)
278
292
  * and inherits Inter. Inter renders block glyphs like
279
293
  * the down-chevron (▼, U+25BC) at near full em-box,
@@ -380,6 +394,11 @@ var c = class extends t {
380
394
  letter-spacing: 0.04em;
381
395
  color: var(--sken-muted-foreground, #4f6076);
382
396
  }
397
+ .group-list {
398
+ list-style: none;
399
+ margin: 0;
400
+ padding: 0;
401
+ }
383
402
  .option {
384
403
  padding: 0.5rem 0.75rem;
385
404
  cursor: pointer;
@@ -418,12 +437,6 @@ var c = class extends t {
418
437
  align-items: center;
419
438
  gap: 0.25rem;
420
439
  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
440
  row-gap: 0.25rem;
428
441
  }
429
442
  .chip-row[data-overflow='none'] {
@@ -452,29 +465,19 @@ var c = class extends t {
452
465
  background: var(--sken-primary, #2e6fc6);
453
466
  opacity: 0.92;
454
467
  }
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 {
468
+ .icons {
469
+ position: absolute;
470
+ inset-inline-end: 0.75rem;
471
+ inset-block-start: var(--sken-2, 0.5rem);
472
+ min-block-size: 1.25rem;
463
473
  display: flex;
464
474
  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
475
  gap: 0.5rem;
471
- flex-basis: 100%;
472
- min-inline-size: 0;
473
476
  }
474
477
  `;
475
478
  }
476
479
  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) : [];
480
+ 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
481
  return r`
479
482
  <div class="field">
480
483
  ${this.label ? r`<label class="label" for="trigger">${this.label}</label>` : null}
@@ -484,11 +487,17 @@ var c = class extends t {
484
487
  data-disabled=${this.disabled}
485
488
  @click=${() => !this.disabled && (this._open ? this._close() : this._open_())}
486
489
  >
487
- ${o && s.length > 0 ? r`<div class="chip-row" part="chip-row" data-overflow=${this.chipOverflow}>
490
+ ${o && s.length > 0 ? r`<div
491
+ class="chip-row"
492
+ part="chip-row"
493
+ data-overflow=${this.chipOverflow}
494
+ @mousedown=${(e) => e.preventDefault()}
495
+ >
488
496
  ${c.map((e) => r`
489
497
  <sken-filter-chip
490
498
  label=${e.label}
491
499
  data-sken-combobox-chip-value=${e.value}
500
+ ?disabled=${this.disabled}
492
501
  @sken-close=${(t) => this._removeChip(e.value, t)}
493
502
  ></sken-filter-chip>
494
503
  `)}
@@ -502,32 +511,34 @@ var c = class extends t {
502
511
  }}
503
512
  ></sken-filter-chip>` : null}
504
513
  </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
514
+ <input
515
+ id="trigger"
516
+ role="combobox"
517
+ aria-expanded=${this._open}
518
+ aria-controls=${a}
519
+ aria-activedescendant=${this._activeIndex >= 0 ? `${a}-opt-${this._activeIndex}` : ""}
520
+ aria-autocomplete=${this.filterable ? "list" : "none"}
521
+ aria-required=${this.required}
522
+ aria-invalid=${this.invalid}
523
+ aria-describedby=${this.describedById}
524
+ aria-label=${this.ariaLabel ?? ""}
525
+ ?disabled=${this.disabled}
526
+ .value=${this._displayValue}
527
+ placeholder=${this.placeholder}
528
+ @input=${this._onInput}
529
+ @keydown=${this._onKeydown}
530
+ @focus=${this._onFocus}
531
+ @blur=${this._onBlur}
532
+ />
533
+ <div class="icons">
534
+ ${!this.disabled && this._hasSelection ? r`<button
525
535
  type="button"
526
536
  class="clear"
527
537
  aria-label="Clear selection"
538
+ @mousedown=${(e) => e.preventDefault()}
528
539
  @click=${(e) => {
529
540
  e.stopPropagation();
530
- let t = n ? [] : null;
541
+ let t = n ? [] : void 0;
531
542
  this._setValue(t), this._query = "", this._input.focus();
532
543
  }}
533
544
  >
@@ -553,47 +564,57 @@ var c = class extends t {
553
564
  class="listbox"
554
565
  role="listbox"
555
566
  aria-multiselectable=${n}
567
+ @mousedown=${(e) => e.preventDefault()}
556
568
  >
557
- ${e.length === 0 ? r`<li class="option" data-disabled="true">No matches</li>` : t.map((t) => r`
558
- ${t.label ? r`<li class="group-header" role="presentation">${t.label}</li>` : null}
559
- ${t.items.map((t) => {
560
- let o = e.indexOf(t), s = o === this._activeIndex, c = i.has(t.value);
561
- return r`<li
562
- id=${`${a}-opt-${o}`}
563
- class="option"
564
- role="option"
565
- data-active=${s}
566
- data-selected=${c}
567
- data-disabled=${t.disabled}
568
- aria-selected=${c}
569
- aria-disabled=${t.disabled}
570
- @mouseenter=${() => {
571
- this._activeIndex = o;
572
- }}
573
- @click=${() => this._selectItem(t)}
574
- >
575
- ${n ? r`<span class="check" aria-hidden="true">✓</span>` : null}
576
- <span>${t.label}</span>
577
- </li>`;
569
+ ${e.length === 0 ? r`<li class="option" data-disabled="true">No matches</li>` : t.map((t, o) => {
570
+ let s = `${a}-group-${o}`, c = t.items.map((t) => {
571
+ let o = e.indexOf(t), s = o === this._activeIndex, c = i.has(t.value);
572
+ return r`<li
573
+ id=${`${a}-opt-${o}`}
574
+ class="option"
575
+ role="option"
576
+ data-active=${s}
577
+ data-selected=${c}
578
+ data-disabled=${t.disabled}
579
+ aria-selected=${c}
580
+ aria-disabled=${t.disabled}
581
+ @mouseenter=${() => {
582
+ t.disabled || (this._activeIndex = o);
583
+ }}
584
+ @click=${() => this._selectItem(t)}
585
+ >
586
+ ${n ? r`<span class="check" aria-hidden="true">✓</span>` : null}
587
+ <span>${t.label}</span>
588
+ </li>`;
589
+ });
590
+ return t.label ? r`
591
+ <li id=${s} class="group-header" role="presentation">
592
+ ${t.label}
593
+ </li>
594
+ <li role="presentation">
595
+ <ul class="group-list" role="group" aria-labelledby=${s}>
596
+ ${c}
597
+ </ul>
598
+ </li>
599
+ ` : c;
578
600
  })}
579
- `)}
580
601
  </ul>` : null}
581
602
  ${this.helperText ? r`<div class="helper">${this.helperText}</div>` : null}
582
603
  </div>
583
604
  `;
584
605
  }
585
606
  };
586
- e([a({ reflect: !0 })], c.prototype, "mode", void 0), e([a()], c.prototype, "display", void 0), e([a({ attribute: "chip-overflow" })], c.prototype, "chipOverflow", void 0), e([a({ attribute: !1 })], c.prototype, "value", void 0), e([a({ type: Boolean })], c.prototype, "filterable", void 0), e([a()], c.prototype, "label", void 0), e([a({ attribute: "helper-text" })], c.prototype, "helperText", void 0), e([a()], c.prototype, "placeholder", void 0), e([a({
607
+ e([a({ reflect: !0 })], l.prototype, "mode", void 0), e([a()], l.prototype, "display", void 0), e([a({ attribute: "chip-overflow" })], l.prototype, "chipOverflow", void 0), e([a({ attribute: !1 })], l.prototype, "value", void 0), e([a({ type: Boolean })], l.prototype, "filterable", void 0), e([a()], l.prototype, "label", void 0), e([a({ attribute: "helper-text" })], l.prototype, "helperText", void 0), e([a()], l.prototype, "placeholder", void 0), e([a({
587
608
  type: Boolean,
588
609
  reflect: !0
589
- })], c.prototype, "disabled", void 0), e([a({
610
+ })], l.prototype, "disabled", void 0), e([a({
590
611
  type: Boolean,
591
612
  reflect: !0
592
- })], c.prototype, "invalid", void 0), e([a({
613
+ })], l.prototype, "invalid", void 0), e([a({
593
614
  type: Boolean,
594
615
  reflect: !0
595
- })], c.prototype, "required", void 0), e([a()], c.prototype, "name", void 0), e([a({ reflect: !0 })], c.prototype, "size", void 0), e([a({ attribute: "described-by-id" })], c.prototype, "describedById", void 0), e([s()], c.prototype, "_items", void 0), e([s()], c.prototype, "_query", void 0), e([s()], c.prototype, "_open", void 0), e([s()], c.prototype, "_activeIndex", void 0), e([s()], c.prototype, "_visibleChipCount", void 0), e([o("input")], c.prototype, "_input", void 0), c = e([i("sken-combobox")], c);
616
+ })], l.prototype, "required", void 0), e([a()], l.prototype, "name", void 0), e([a({ reflect: !0 })], l.prototype, "size", void 0), e([a({ attribute: "described-by-id" })], l.prototype, "describedById", void 0), e([a({ attribute: "aria-label" })], l.prototype, "ariaLabel", void 0), e([s({ hasChanged: (e, t) => t === void 0 || !c(e, t) })], l.prototype, "_items", void 0), e([s()], l.prototype, "_query", void 0), e([s()], l.prototype, "_open", void 0), e([s()], l.prototype, "_activeIndex", void 0), e([s()], l.prototype, "_visibleChipCount", void 0), e([o("input")], l.prototype, "_input", void 0), l = e([i("sken-combobox")], l);
596
617
  //#endregion
597
- export { c as SkenCombobox };
618
+ export { l as SkenCombobox };
598
619
 
599
620
  //# sourceMappingURL=sken-combobox.js.map
@@ -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":["import { 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\ntype SkenComboboxValue<M extends SkenComboboxMode = 'single'> = M extends 'multiple'\n ? string[]\n : string | undefined\n\ntype PrimitiveValue = SkenComboboxValue<SkenComboboxMode>\n\nimport './sken-filter-chip.js'\n\nimport type { SkenComboboxGroup } from './sken-combobox-group'\nimport type { SkenComboboxItem } from './sken-combobox-item'\n\ninterface ResolvedItem {\n value: string\n label: string\n disabled: boolean\n groupLabel: string | null\n groupDisabled: boolean\n}\n\nfunction resolvedItemsEqual(a: ResolvedItem[], b: ResolvedItem[]): boolean {\n if (a.length !== b.length) return false\n return a.every((item, i) => {\n const other = b[i]!\n return (\n item.value === other.value &&\n item.label === other.label &&\n item.disabled === other.disabled &&\n item.groupLabel === other.groupLabel &&\n item.groupDisabled === other.groupDisabled\n )\n })\n}\n\n@customElement('sken-combobox')\nexport class SkenCombobox\n extends LitElement\n implements Omit<SkenComboboxProps, 'value' | 'slots' | 'ariaLabel'>\n{\n // ── public API (mapped to Sken contract) ──────────────────────────\n\n @property({ reflect: true }) mode: SkenComboboxMode = 'single'\n\n @property() display: SkenComboboxDisplay = 'count'\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 @property({ attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n // ── internal state ────────────────────────────────────────────────\n\n @state({\n hasChanged: (a, b) =>\n b === undefined || !resolvedItemsEqual(a as ResolvedItem[], b as ResolvedItem[]),\n })\n private _items: ResolvedItem[] = []\n\n @state() private _query = ''\n\n @state() private _open = false\n\n @state() private _activeIndex = -1\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 this._observer = new MutationObserver(() => {\n this._resolveItems()\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 this._resolveItems()\n }\n\n override firstUpdated(_changed: PropertyValues): void {\n this._resolveItems()\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 if (this.mode !== 'multiple' || this.display !== 'chips' || this.chipOverflow !== 'count') {\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 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 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 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 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 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 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 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 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 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 this._input.focus()\n } else {\n this._setValue(item.value)\n this._close()\n }\n }\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 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 e.preventDefault()\n const cleared: SkenComboboxValue<SkenComboboxMode> =\n this.mode === 'multiple' ? [] : undefined\n this._setValue(cleared)\n }\n break\n }\n case 'Tab': {\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 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 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: var(--sken-1, 0.25rem);\n }\n .label {\n font-size: var(--sken-size-xs, 0.75rem);\n font-weight: 500;\n }\n :host([required]) .label::after {\n content: ' *';\n }\n :host([required][invalid]) .label,\n :host([required][invalid]) .label::after {\n color: var(--sken-destructive);\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 .group-list {\n list-style: none;\n margin: 0;\n padding: 0;\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 const showChips = isMulti && this.display === 'chips'\n const selectedItems = showChips ? this._items.filter((it) => selectedSet.has(it.value)) : []\n\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 aria-label=${this.ariaLabel ?? ''}\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((g, gi) => {\n const groupHeaderId = `${listboxId}-group-${gi}`\n const optionItems = 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 if (!g.label) return optionItems\n return html`\n <li id=${groupHeaderId} class=\"group-header\" role=\"presentation\">\n ${g.label}\n </li>\n <li role=\"presentation\">\n <ul class=\"group-list\" role=\"group\" aria-labelledby=${groupHeaderId}>\n ${optionItems}\n </ul>\n </li>\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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"}