@rcarls/rc-select 0.1.0 → 0.3.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.
@@ -1,7 +1,7 @@
1
- import { css as v, LitElement as f, nothing as _, html as c } from "lit";
2
- import { property as n, query as h, state as p } from "lit/decorators.js";
3
- import { ActiveDescendantController as b, AnchorController as m } from "@rcarls/rc-common";
4
- const g = v`
1
+ import { css as f, LitElement as b, nothing as _, html as h } from "lit";
2
+ import { property as n, query as p, state as d } from "lit/decorators.js";
3
+ import { NativeChildController as g, ActiveDescendantController as m, AnchorController as y } from "@rcarls/rc-common";
4
+ const x = f`
5
5
  :host {
6
6
  display: inline-block;
7
7
  }
@@ -19,12 +19,15 @@ const g = v`
19
19
  flex-wrap: wrap;
20
20
  gap: var(--rc-select-gap, var(--rc-control-gap, 0.25em));
21
21
  min-block-size: var(--rc-select-control-block-size, var(--rc-control-block-size, auto));
22
- padding: var(--rc-select-padding-block, var(--rc-control-padding-block, 0.25em))
23
- var(--rc-select-padding-inline, var(--rc-control-padding-inline, 0.5em));
22
+ padding: var(--rc-select-padding-block, var(--rc-control-padding-block, 1px))
23
+ var(--rc-select-padding-inline, var(--rc-control-padding-inline, 4px));
24
24
  min-width: 8em;
25
25
  cursor: default;
26
26
  user-select: none;
27
- border: var(--rc-select-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));
27
+ border: var(
28
+ --rc-select-border,
29
+ var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder))
30
+ );
28
31
  border-radius: var(--rc-select-radius, var(--rc-control-radius, var(--rc-radius-sm, 0.125em)));
29
32
  background: var(--rc-field, Field);
30
33
  color: var(--rc-field-text, FieldText);
@@ -51,9 +54,12 @@ const g = v`
51
54
  white-space: nowrap;
52
55
  }
53
56
 
54
- [part='toggle-icon'] {
57
+ [part='toggle-indicator'] {
55
58
  flex-shrink: 0;
56
- font-size: 0.75em;
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ inline-size: var(--rc-select-toggle-indicator-size, 1.1em);
57
63
  }
58
64
 
59
65
  /* Chips — the whole chip is the remove button for a larger touch target */
@@ -61,9 +67,11 @@ const g = v`
61
67
  display: inline-flex;
62
68
  align-items: center;
63
69
  gap: var(--rc-select-chip-gap, calc(var(--rc-control-gap, 0.25em) * 0.8));
64
- padding: var(--rc-select-chip-padding-block, 0.1em)
65
- var(--rc-select-chip-padding-inline, 0.3em);
66
- border: var(--rc-select-chip-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));
70
+ padding: var(--rc-select-chip-padding-block, 0.1em) var(--rc-select-chip-padding-inline, 0.3em);
71
+ border: var(
72
+ --rc-select-chip-border,
73
+ var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder))
74
+ );
67
75
  border-radius: var(--rc-select-chip-radius, var(--rc-radius-md, 0.25em));
68
76
  background: var(--rc-button-bg, ButtonFace);
69
77
  color: var(--rc-button-text, ButtonText);
@@ -92,8 +100,8 @@ const g = v`
92
100
  pointer-events: none;
93
101
  }
94
102
 
95
- /* Hidden native select slot */
96
- slot[name='select'] {
103
+ /* Hide the default slot — visually suppresses the slotted native <select> */
104
+ slot:not([name]) {
97
105
  display: none;
98
106
  }
99
107
 
@@ -102,44 +110,44 @@ const g = v`
102
110
  max-height: var(--rc-select-max-height, 20em);
103
111
  overflow-y: auto;
104
112
  background: var(--rc-surface, Canvas);
105
- border: var(--rc-select-listbox-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));
113
+ border: var(
114
+ --rc-select-listbox-border,
115
+ var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder))
116
+ );
106
117
  border-radius: var(--rc-select-listbox-radius, var(--rc-control-radius, 0));
107
- box-shadow: var(--rc-select-shadow, var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)));
118
+ box-shadow: var(
119
+ --rc-select-shadow,
120
+ var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent))
121
+ );
108
122
  color: var(--rc-field-text, FieldText);
109
123
  padding-block: var(--rc-select-listbox-padding-block, var(--rc-control-padding-block, 0.25em));
124
+ --rc-listbox-option-gap: var(--rc-item-gap, 0.4em);
125
+ --rc-listbox-option-padding-block: var(--rc-item-padding-block, 0.3em);
126
+ --rc-listbox-option-padding-inline: var(--rc-item-padding-inline, 0.75em);
127
+ --rc-listbox-hover-bg: var(--rc-highlight, Highlight);
128
+ --rc-listbox-hover-color: var(--rc-highlight-text, HighlightText);
129
+ --rc-listbox-active-bg: var(--rc-highlight, Highlight);
130
+ --rc-listbox-active-color: var(--rc-highlight-text, HighlightText);
131
+ --rc-listbox-selected-bg: var(--rc-highlight, Highlight);
132
+ --rc-listbox-selected-color: var(--rc-highlight-text, HighlightText);
133
+ --rc-listbox-disabled-opacity: var(--rc-disabled-opacity, 0.5);
110
134
 
111
135
  &:not(:popover-open) {
112
136
  display: none;
113
137
  }
114
138
  }
115
139
 
116
- /* Options within the listbox */
117
- rc-listbox [part~='option'] {
118
- display: flex;
119
- align-items: center;
120
- gap: var(--rc-item-gap, 0.4em);
121
- padding: var(--rc-item-padding-block, 0.3em) var(--rc-item-padding-inline, 0.75em);
122
- cursor: default;
123
-
124
- /* display: flex overrides [hidden]'s browser-default display:none — restore it explicitly */
125
- &[hidden] { display: none; }
126
-
127
- &:not([hidden]):not([aria-disabled='true']):hover {
128
- background: var(--rc-highlight, Highlight);
129
- color: var(--rc-highlight-text, HighlightText);
130
- }
140
+ rc-listbox [part~='option'][hidden] {
141
+ display: none;
142
+ }
131
143
 
132
- &[data-active]:not([aria-disabled='true']) {
133
- background: var(--rc-highlight, Highlight);
134
- color: var(--rc-highlight-text, HighlightText);
135
- outline: var(--rc-focus-ring, 2px solid var(--rc-accent, Highlight));
136
- outline-offset: -2px;
137
- }
144
+ rc-listbox [part~='option'][data-active]:not([aria-disabled='true']) {
145
+ outline: var(--rc-focus-ring, 2px solid var(--rc-accent, Highlight));
146
+ outline-offset: -2px;
147
+ }
138
148
 
139
- &[aria-disabled='true'] {
140
- opacity: var(--rc-disabled-opacity, 0.5);
141
- cursor: not-allowed;
142
- }
149
+ rc-listbox [part~='option'][aria-disabled='true'] {
150
+ cursor: not-allowed;
143
151
  }
144
152
 
145
153
  /* Selection checkmark — reserves space whether visible or not */
@@ -162,17 +170,23 @@ const g = v`
162
170
  padding-top: 0.3em;
163
171
  }
164
172
  `;
165
- var y = Object.defineProperty, a = (u, e, t, i) => {
166
- for (var s = void 0, r = u.length - 1, l; r >= 0; r--)
167
- (l = u[r]) && (s = l(e, t, s) || s);
168
- return s && y(e, t, s), s;
173
+ var k = Object.defineProperty, a = (v, e, t, i) => {
174
+ for (var s = void 0, r = v.length - 1, l; r >= 0; r--)
175
+ (l = v[r]) && (s = l(e, t, s) || s);
176
+ return s && k(e, t, s), s;
169
177
  };
170
- const d = class d extends f {
178
+ const u = class u extends b {
171
179
  constructor() {
172
- super(...arguments), this.open = !1, this.multiple = !1, this.disabled = !1, this.placeholder = "", this.display = "auto", this._selectedValues = /* @__PURE__ */ new Set(), this._chipNavIndex = -1, this._options = [], this._selectRef = null, this._mutationObserver = null, this._selectionInitialized = !1, this._typeAheadBuffer = "", this._typeAheadTimer = 0, this._adc = new b(this, {
180
+ super(...arguments), this.open = !1, this.multiple = !1, this.disabled = !1, this.placeholder = "", this.display = "auto", this._selectedValues = /* @__PURE__ */ new Set(), this._chipNavIndex = -1, this._options = [], this._selectRef = null, this._selectController = new g(this, {
181
+ selector: ":scope > select",
182
+ observe: !0,
183
+ onChange: (e) => this._setupSelect(e),
184
+ onMissing: () => {
185
+ }
186
+ }), this._mutationObserver = null, this._selectionInitialized = !1, this._typeAheadBuffer = "", this._typeAheadTimer = 0, this._activeDescendantCtrl = new m(this, {
173
187
  host: () => this._$trigger ?? null,
174
188
  items: () => this._$listbox?.navigableItems ?? []
175
- }), this._anchorCtrl = new m(this, {
189
+ }), this._anchorCtrl = new y(this, {
176
190
  anchor: () => this._$anchor ?? null,
177
191
  floating: () => this._$listbox ?? null,
178
192
  shadowHost: () => this,
@@ -192,16 +206,20 @@ const d = class d extends f {
192
206
  capture: !0
193
207
  }), this._mutationObserver?.disconnect();
194
208
  }
195
- // ── Popup ────────────────────────────────────────────────────────────────────
209
+ updated(e) {
210
+ super.updated(e), this.toggleAttribute("has-value", this._selectedValues.size > 0);
211
+ }
212
+ /** Opens the popup listbox if not already open or disabled. */
196
213
  openPopup() {
197
- this.open || this.disabled || (this.open = !0, this._$listbox.showPopover(), this._anchorCtrl.update(), this.dispatchEvent(
198
- new CustomEvent("rc-select-open", { bubbles: !0, composed: !0 })
199
- ), this.requestUpdate());
214
+ this.open || this.disabled || (this.open = !0, this._$listbox.showPopover(), this._anchorCtrl.update(), this.dispatchEvent(new CustomEvent("rc-select-open", { bubbles: !0, composed: !0 })), this.requestUpdate());
200
215
  }
216
+ /**
217
+ * Closes the popup listbox.
218
+ *
219
+ * @param returnFocus - When `true` (default), returns focus to the trigger.
220
+ */
201
221
  closePopup(e = !0) {
202
- this.open && (this.open = !1, this._$listbox.hidePopover(), this._adc.clear(), e && this._$trigger?.focus(), this.dispatchEvent(
203
- new CustomEvent("rc-select-close", { bubbles: !0, composed: !0 })
204
- ));
222
+ this.open && (this.open = !1, this._$listbox.hidePopover(), this._activeDescendantCtrl.clear(), e && this._$trigger?.focus(), this.dispatchEvent(new CustomEvent("rc-select-close", { bubbles: !0, composed: !0 })));
205
223
  }
206
224
  /**
207
225
  * Current selection. Host writes update selection silently; user interaction
@@ -238,35 +256,49 @@ const d = class d extends f {
238
256
  get selectedValues() {
239
257
  return [...this._selectedValues];
240
258
  }
259
+ /** `ListboxOption` objects for the current selection; falls back to label-only stubs for unknown values. */
241
260
  get selectedOptions() {
242
261
  return this._selectedOptionsFor(this.selectedValues);
243
262
  }
263
+ /**
264
+ * Applies an already-normalized array of values as the current selection.
265
+ *
266
+ * In single-select mode only the first value is kept. Syncs `_$listbox` and the
267
+ * native `<select>` but does NOT dispatch `rc-select-change`.
268
+ */
244
269
  _applySelection(e) {
245
270
  const t = this.multiple ? e : e.slice(0, 1);
246
271
  this._selectedValues = new Set(t), this._$listbox?.setSelectedValues(t), this._syncNativeSelect(), this.requestUpdate();
247
272
  }
248
- // ── Slot: native <select> ────────────────────────────────────────────────────
249
- _handleSelectSlotChange(e) {
250
- const i = e.target.assignedElements().find(
251
- (s) => s instanceof HTMLSelectElement
252
- ) ?? null;
253
- this._mutationObserver?.disconnect(), this._mutationObserver = null, this._selectRef = i ? new WeakRef(i) : null, i && queueMicrotask(() => {
254
- i.isConnected && (this.multiple = i.multiple, this.disabled = i.disabled, this._syncOptionsFromSelect(i), this._mutationObserver = new MutationObserver(() => {
255
- const s = this._selectRef?.deref();
256
- if (s) {
257
- if (this.multiple = s.multiple, this.disabled = s.disabled, this._propertyOptions !== void 0) {
273
+ /**
274
+ * Resolves the slotted `<select>` on every `slotchange`, wires `_mutationObserver`,
275
+ * and seeds initial state via `queueMicrotask` to stay safe inside framework reactive passes.
276
+ */
277
+ _handleSelectSlotChange() {
278
+ this._selectController.sync();
279
+ }
280
+ _setupSelect(e) {
281
+ this._mutationObserver?.disconnect(), this._mutationObserver = null, this._selectRef = e ? new WeakRef(e) : null, e && queueMicrotask(() => {
282
+ e.isConnected && (this.multiple = e.multiple, this.disabled = e.disabled, this._syncOptionsFromSelect(e), this._mutationObserver = new MutationObserver(() => {
283
+ const t = this._selectRef?.deref();
284
+ if (t) {
285
+ if (this.multiple = t.multiple, this.disabled = t.disabled, this._propertyOptions !== void 0) {
258
286
  this._applySelectionFromCurrentSource();
259
287
  return;
260
288
  }
261
- this._syncOptionsFromSelect(s);
289
+ this._syncOptionsFromSelect(t);
262
290
  }
263
- }), this._mutationObserver.observe(i, {
291
+ }), this._mutationObserver.observe(e, {
264
292
  childList: !0,
265
293
  subtree: !0,
266
294
  attributes: !0
267
- }), this._syncAccessibleName(i));
295
+ }), this._syncAccessibleName(e));
268
296
  });
269
297
  }
298
+ /**
299
+ * Derives options from `$select` (or defers to `_propertyOptions` when set)
300
+ * and re-applies selection from the current authoritative source.
301
+ */
270
302
  _syncOptionsFromSelect(e) {
271
303
  if (this._propertyOptions !== void 0) {
272
304
  this._syncOptions(this._propertyOptions), this._mirrorOptionsToNativeSelect(), this._applySelectionFromCurrentSource();
@@ -275,6 +307,10 @@ const d = class d extends f {
275
307
  const t = this._optionsFromSelect(e);
276
308
  this._syncOptions(t), this._applySelectionFromCurrentSource(), this._syncAccessibleName(e);
277
309
  }
310
+ /**
311
+ * Picks the correct selection source in priority order:
312
+ * controlled `_value` → `_defaultValue` → native `<select>` default/current state.
313
+ */
278
314
  _applySelectionFromCurrentSource() {
279
315
  if (this._value !== void 0) {
280
316
  this._applySelection(this._normalizeValue(this._value));
@@ -287,14 +323,22 @@ const d = class d extends f {
287
323
  const e = this._selectionInitialized ? this._selectedValuesFromNativeSelect() : this._defaultSelectedValuesFromNativeSelect();
288
324
  this._selectionInitialized = !0, this._applySelection(e);
289
325
  }
326
+ /** Writes `_options` and pushes the list to `_$listbox` when it is mounted. */
290
327
  _syncOptions(e) {
291
328
  this._options = [...e], this._$listbox && (this._$listbox.options = this._options);
292
329
  }
330
+ /** Returns `_propertyOptions` when set; otherwise reads live from the slotted `<select>`. */
293
331
  _currentOptions() {
294
- if (this._propertyOptions !== void 0) return this._propertyOptions;
332
+ if (this._propertyOptions !== void 0)
333
+ return this._propertyOptions;
295
334
  const e = this._selectRef?.deref();
296
335
  return e ? this._optionsFromSelect(e) : [];
297
336
  }
337
+ /**
338
+ * Maps non-empty native `<option>` nodes to `ListboxOption` objects.
339
+ *
340
+ * Options with empty `value` are skipped (placeholder guard).
341
+ */
298
342
  _optionsFromSelect(e) {
299
343
  const t = [];
300
344
  for (const i of e.options)
@@ -305,14 +349,22 @@ const d = class d extends f {
305
349
  });
306
350
  return t;
307
351
  }
352
+ /** Returns values of currently selected `<option>` nodes; falls back to `selectedValues` when no native select is present. */
308
353
  _selectedValuesFromNativeSelect() {
309
354
  const e = this._selectRef?.deref();
310
355
  return e ? Array.from(e.selectedOptions).map((t) => t.value).filter(Boolean) : this.selectedValues;
311
356
  }
357
+ /** Returns values of `defaultSelected` `<option>` nodes (author `selected` attribute); falls back to `selectedValues`. */
312
358
  _defaultSelectedValuesFromNativeSelect() {
313
359
  const e = this._selectRef?.deref();
314
360
  return e ? Array.from(e.options).filter((t) => t.defaultSelected).map((t) => t.value).filter(Boolean) : this.selectedValues;
315
361
  }
362
+ /**
363
+ * Rebuilds native `<select>` options from `_propertyOptions`.
364
+ *
365
+ * Only runs when `_propertyOptions` is set; pauses then resumes `_mutationObserver`
366
+ * while writing to avoid re-entrant sync.
367
+ */
316
368
  _mirrorOptionsToNativeSelect() {
317
369
  const e = this._selectRef?.deref();
318
370
  if (!(!e || this._propertyOptions === void 0)) {
@@ -328,44 +380,69 @@ const d = class d extends f {
328
380
  });
329
381
  }
330
382
  }
383
+ /**
384
+ * Upserts a single option into `_options` (and `_propertyOptions` when set)
385
+ * and adds the corresponding `<option>` to the native `<select>` if absent.
386
+ */
331
387
  _addOption(e) {
332
388
  const t = this._options.some((r) => r.value === e.value) ? this._options.map((r) => r.value === e.value ? e : r) : [...this._options, e];
333
389
  this._propertyOptions !== void 0 && (this._propertyOptions = t), this._syncOptions(t);
334
390
  const i = this._selectRef?.deref();
335
- if (!i || Array.from(i.options).some((r) => r.value === e.value)) return;
391
+ if (!i || Array.from(i.options).some((r) => r.value === e.value))
392
+ return;
336
393
  const s = document.createElement("option");
337
394
  s.value = e.value, s.text = e.label, s.disabled = e.disabled ?? !1, s.selected = this._selectedValues.has(e.value), i.add(s);
338
395
  }
396
+ /** Coerces `RCSelectValue` (string or string[]) to `string[]`; returns `[]` for empty-string singles. */
339
397
  _normalizeValue(e) {
340
398
  return Array.isArray(e) ? e : e ? [e] : [];
341
399
  }
400
+ /**
401
+ * Copies `aria-label` or first-label text from the native `<select>` to `_$trigger`.
402
+ *
403
+ * No-op when the trigger already has an explicit `aria-label`.
404
+ */
342
405
  _syncAccessibleName(e) {
343
- if (!this._$trigger || this._$trigger.hasAttribute("aria-label")) return;
406
+ if (!this._$trigger || this._$trigger.hasAttribute("aria-label"))
407
+ return;
344
408
  const t = e.getAttribute("aria-label") ?? e.labels?.[0]?.textContent?.trim() ?? null;
345
409
  t ? this._$trigger.setAttribute("aria-label", t) : this._$trigger.removeAttribute("aria-label");
346
410
  }
347
- // ── Selection ────────────────────────────────────────────────────────────────
411
+ /**
412
+ * Handles `rc-listbox-change` from the popup.
413
+ *
414
+ * Stops propagation, mutates `_selectedValues`, syncs state, and fires
415
+ * `rc-select-change`.
416
+ */
348
417
  _handleListboxChange(e) {
349
- const { optionValue: t, value: i, selected: s } = e.detail, r = t ?? (Array.isArray(i) ? i.at(-1) : i);
350
- if (r) {
418
+ const t = e.detail;
419
+ if (t.reason === "action") {
420
+ e.stopPropagation();
421
+ return;
422
+ }
423
+ const { optionValue: i, value: s, selected: r } = t, l = i ?? (Array.isArray(s) ? s.at(-1) : s);
424
+ if (l) {
351
425
  if (e.stopPropagation(), this.multiple) {
352
- const l = new Set(this._selectedValues);
353
- s ? l.add(r) : l.delete(r), this._selectedValues = l;
426
+ const c = new Set(this._selectedValues);
427
+ r ? c.add(l) : c.delete(l), this._selectedValues = c;
354
428
  } else
355
- this._selectedValues = s ? /* @__PURE__ */ new Set([r]) : /* @__PURE__ */ new Set(), s && this.closePopup();
429
+ this._selectedValues = r ? /* @__PURE__ */ new Set([l]) : /* @__PURE__ */ new Set(), r && this.closePopup();
356
430
  this._selectionInitialized = !0, this._syncNativeSelect(), this._$listbox.setSelectedValues(this.selectedValues), this._dispatchChange();
357
431
  }
358
432
  }
433
+ /** Removes a single value from `_selectedValues`, syncs the listbox and native select, and dispatches `rc-select-change`. */
359
434
  _removeValue(e) {
360
435
  const t = new Set(this._selectedValues);
361
436
  t.delete(e), this._selectedValues = t, this._$listbox.setSelectedValues([...t]), this._selectionInitialized = !0, this._syncNativeSelect(), this._dispatchChange();
362
437
  }
438
+ /** Flips `selected` on each `<option>` in the native `<select>` to match `_selectedValues`; no-op when no native select is present. */
363
439
  _syncNativeSelect() {
364
440
  const e = this._selectRef?.deref();
365
441
  if (e)
366
442
  for (const t of e.options)
367
443
  t.selected = this._selectedValues.has(t.value);
368
444
  }
445
+ /** Constructs and dispatches `rc-select-change` with current `value`, `selectedValues`, and `selectedOptions`. */
369
446
  _dispatchChange() {
370
447
  this.dispatchEvent(
371
448
  new CustomEvent("rc-select-change", {
@@ -379,26 +456,33 @@ const d = class d extends f {
379
456
  })
380
457
  );
381
458
  }
459
+ /** Maps value strings to `ListboxOption` objects from `_options`; creates label-only fallback stubs for unknown values. */
382
460
  _selectedOptionsFor(e) {
383
- return e.map((t) => this._options.find((i) => i.value === t) ?? {
461
+ return e.map((t) => this._options.find(
462
+ (i) => i.kind !== "action" && i.value === t
463
+ ) ?? {
384
464
  value: t,
385
465
  label: t
386
466
  });
387
467
  }
388
- // ── Display helpers ──────────────────────────────────────────────────────────
468
+ /** Resolved display mode after `auto` pointer-detection: `'chips'` on fine-pointer devices, `'compact'` on coarse. */
389
469
  get _effectiveDisplay() {
390
470
  return this.display === "chips" ? "chips" : this.display === "compact" || window.matchMedia("(pointer: coarse)").matches ? "compact" : "chips";
391
471
  }
472
+ /** Looks up the display label for `value` from native `<select>` option text; returns `value` itself as a fallback. */
392
473
  _labelFor(e) {
393
474
  const t = this._selectRef?.deref();
394
475
  if (t) {
395
476
  for (const i of t.options)
396
- if (i.value === e) return i.text;
477
+ if (i.value === e)
478
+ return i.text;
397
479
  }
398
480
  return e;
399
481
  }
482
+ /** Text shown in the trigger's value-display area; returns placeholder when chips are active or nothing is selected. */
400
483
  get _displayLabel() {
401
- if (this._selectedValues.size === 0) return this.placeholder;
484
+ if (this._selectedValues.size === 0)
485
+ return this.placeholder;
402
486
  if (this._effectiveDisplay === "chips" && this.multiple)
403
487
  return this.placeholder;
404
488
  if (this._selectedValues.size === 1)
@@ -406,24 +490,29 @@ const d = class d extends f {
406
490
  const e = [...this._selectedValues];
407
491
  return `${this._labelFor(e[0])}, +${e.length - 1} more`;
408
492
  }
409
- // ── Keyboard: trigger ────────────────────────────────────────────────────────
493
+ /**
494
+ * APG Combobox keyboard handler on the trigger.
495
+ *
496
+ * Arrow keys navigate/open, Space/Enter activate, Tab closes without focus return,
497
+ * ArrowLeft enters chip navigation, and printable chars forward to type-ahead.
498
+ */
410
499
  _handleTriggerKeyDown(e) {
411
500
  switch (e.key) {
412
501
  case "ArrowDown":
413
- e.preventDefault(), this.open ? this._adc.navigate(1) : (this.openPopup(), this._adc.navigateToFirst());
502
+ e.preventDefault(), this.open ? this._activeDescendantCtrl.navigate(1) : (this.openPopup(), this._activeDescendantCtrl.navigateToFirst());
414
503
  break;
415
504
  case "ArrowUp":
416
- e.preventDefault(), this.open ? this._adc.navigate(-1) : (this.openPopup(), this._adc.navigateToLast());
505
+ e.preventDefault(), this.open ? this._activeDescendantCtrl.navigate(-1) : (this.openPopup(), this._activeDescendantCtrl.navigateToLast());
417
506
  break;
418
507
  case "Home":
419
- this.open && (e.preventDefault(), this._adc.navigateToFirst());
508
+ this.open && (e.preventDefault(), this._activeDescendantCtrl.navigateToFirst());
420
509
  break;
421
510
  case "End":
422
- this.open && (e.preventDefault(), this._adc.navigateToLast());
511
+ this.open && (e.preventDefault(), this._activeDescendantCtrl.navigateToLast());
423
512
  break;
424
513
  case " ":
425
514
  case "Enter":
426
- e.preventDefault(), this.open ? this._activateActive() : (this.openPopup(), this._adc.navigateToFirst());
515
+ e.preventDefault(), this.open ? this._activateActive() : (this.openPopup(), this._activeDescendantCtrl.navigateToFirst());
427
516
  break;
428
517
  case "Tab":
429
518
  this.open && this.closePopup(!1);
@@ -435,13 +524,20 @@ const d = class d extends f {
435
524
  e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && this._handleTypeAhead(e.key);
436
525
  }
437
526
  }
527
+ /**
528
+ * Dispatches a synthetic `pointerdown` on the active listbox item to activate it.
529
+ *
530
+ * Prefers `pointerdown` over a direct call so listbox item click logic
531
+ * (which is bound to `pointerdown`) fires through its normal path.
532
+ */
438
533
  _activateActive() {
439
- const e = this._adc.activeItem;
440
- e && e.dispatchEvent(
441
- new PointerEvent("pointerdown", { bubbles: !0, cancelable: !0 })
442
- );
534
+ const e = this._activeDescendantCtrl.activeItem;
535
+ e && e.dispatchEvent(new PointerEvent("pointerdown", { bubbles: !0, cancelable: !0 }));
443
536
  }
444
- // ── Type-ahead ───────────────────────────────────────────────────────────────
537
+ /**
538
+ * Accumulates characters in `_typeAheadBuffer` and either immediately selects
539
+ * the first match (closed single-select) or moves the virtual cursor (open popup).
540
+ */
445
541
  _handleTypeAhead(e) {
446
542
  clearTimeout(this._typeAheadTimer), this._typeAheadBuffer += e.toLowerCase(), this._typeAheadTimer = window.setTimeout(() => {
447
543
  this._typeAheadBuffer = "";
@@ -453,26 +549,28 @@ const d = class d extends f {
453
549
  if (!this.open)
454
550
  this.multiple || (this._selectionInitialized = !0, this._applySelection([i.value]), this._dispatchChange());
455
551
  else {
456
- const r = this._$listbox.navigableItems.find(
457
- (l) => l.getAttribute("data-value") === i.value
458
- );
459
- r && this._adc.navigateToItem(r);
552
+ const r = this._$listbox.navigableItems.find((l) => l.getAttribute("data-value") === i.value);
553
+ r && this._activeDescendantCtrl.navigateToItem(r);
460
554
  }
461
555
  }
462
- // ── Chip keyboard navigation ─────────────────────────────────────────────────
556
+ /** Focuses the last chip button and sets `_chipNavIndex` to begin roving-tabindex navigation within the chip group. */
463
557
  _enterChipNav() {
464
- const e = this._getChipButtons();
558
+ const e = this._$chipButtons();
465
559
  e.length !== 0 && (this._chipNavIndex = e.length - 1, e[this._chipNavIndex].focus());
466
560
  }
467
- _getChipButtons() {
468
- return Array.from(
469
- this.renderRoot.querySelectorAll(
470
- 'button[part~="chip"]'
471
- )
472
- );
561
+ /** Queries all `button[part~="chip"]` elements in `renderRoot`; order matches DOM order. */
562
+ _$chipButtons() {
563
+ return Array.from(this.renderRoot.querySelectorAll('button[part~="chip"]'));
473
564
  }
565
+ /**
566
+ * Keyboard handler for chip buttons.
567
+ *
568
+ * ArrowLeft/Right rove chip focus, Escape returns to trigger,
569
+ * Delete/Backspace/Enter/Space remove the chip's value
570
+ * and return focus to the trigger.
571
+ */
474
572
  _handleChipKeyDown(e, t) {
475
- const i = this._getChipButtons();
573
+ const i = this._$chipButtons();
476
574
  switch (e.key) {
477
575
  case "ArrowLeft":
478
576
  e.preventDefault(), this._chipNavIndex > 0 && (this._chipNavIndex--, i[this._chipNavIndex]?.focus());
@@ -491,14 +589,13 @@ const d = class d extends f {
491
589
  break;
492
590
  }
493
591
  }
494
- // ── Trigger click ────────────────────────────────────────────────────────────
592
+ /** Toggles the popup open or closed on trigger click. */
495
593
  _handleTriggerClick() {
496
594
  this.open ? this.closePopup() : this.openPopup();
497
595
  }
498
- // ── Render ───────────────────────────────────────────────────────────────────
499
596
  render() {
500
597
  const e = this.multiple && this._effectiveDisplay === "chips" && this._selectedValues.size > 0;
501
- return c`
598
+ return h`
502
599
  <div id="anchor" part="anchor">
503
600
  <div
504
601
  id="trigger"
@@ -514,9 +611,24 @@ const d = class d extends f {
514
611
  @keydown=${this._handleTriggerKeyDown}
515
612
  >
516
613
  ${e ? this._renderChips() : _}
614
+
517
615
  <span part="value-display">${this._displayLabel}</span>
518
- <span part="toggle-icon" aria-hidden="true">
519
- <slot name="toggle-icon">&#9660;</slot>
616
+
617
+ <span part="toggle-indicator" aria-hidden="true">
618
+ <slot name="toggle-indicator">
619
+ <svg
620
+ width="10"
621
+ height="6"
622
+ viewBox="0 0 10 6"
623
+ fill="none"
624
+ stroke="currentColor"
625
+ stroke-width="1.5"
626
+ stroke-linecap="round"
627
+ stroke-linejoin="round"
628
+ >
629
+ <polyline points="1,1 5,5 9,1" />
630
+ </svg>
631
+ </slot>
520
632
  </span>
521
633
  </div>
522
634
  </div>
@@ -530,15 +642,16 @@ const d = class d extends f {
530
642
  @rc-listbox-change=${this._handleListboxChange}
531
643
  ></rc-listbox>
532
644
 
533
- <slot name="select" @slotchange=${this._handleSelectSlotChange}></slot>
645
+ <slot @slotchange=${this._handleSelectSlotChange}></slot>
534
646
  `;
535
647
  }
648
+ /** Renders the chip group `<span>` with one `<button>` per selected value for multi-select chip display mode. */
536
649
  _renderChips() {
537
- return c`
650
+ return h`
538
651
  <span part="chips" role="group" aria-label="Selected items">
539
652
  ${[...this._selectedValues].map((e) => {
540
653
  const t = this._labelFor(e);
541
- return c`
654
+ return h`
542
655
  <button
543
656
  type="button"
544
657
  part="chip"
@@ -559,8 +672,8 @@ const d = class d extends f {
559
672
  `;
560
673
  }
561
674
  };
562
- d.styles = g;
563
- let o = d;
675
+ u.styles = x;
676
+ let o = u;
564
677
  a([
565
678
  n({ type: Boolean, reflect: !0 })
566
679
  ], o.prototype, "open");
@@ -577,24 +690,24 @@ a([
577
690
  n()
578
691
  ], o.prototype, "display");
579
692
  a([
580
- h("#anchor")
693
+ p("#anchor")
581
694
  ], o.prototype, "_$anchor");
582
695
  a([
583
- h("#trigger")
696
+ p("#trigger")
584
697
  ], o.prototype, "_$trigger");
585
698
  a([
586
- h("#listbox")
699
+ p("#listbox")
587
700
  ], o.prototype, "_$listbox");
588
701
  a([
589
- p()
702
+ d()
590
703
  ], o.prototype, "_selectedValues");
591
704
  a([
592
- p()
705
+ d()
593
706
  ], o.prototype, "_chipNavIndex");
594
707
  a([
595
- p()
708
+ d()
596
709
  ], o.prototype, "_options");
597
710
  export {
598
711
  o as R
599
712
  };
600
- //# sourceMappingURL=rc-select-BumY2uQl.js.map
713
+ //# sourceMappingURL=rc-select-Bu4ukw92.js.map