@rcarls/rc-listbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/demo.css ADDED
@@ -0,0 +1,85 @@
1
+ *, *::before, *::after {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ :root {
6
+ color-scheme: light dark;
7
+ --rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
8
+ }
9
+
10
+ @supports (color: AccentColor) {
11
+ :root {
12
+ --rc-accent: AccentColor;
13
+ }
14
+ }
15
+
16
+ [data-theme="light"] { color-scheme: light; }
17
+ [data-theme="dark"] { color-scheme: dark; }
18
+
19
+ [data-theme="solarized-light"] {
20
+ color-scheme: light;
21
+ --rc-surface: #fdf6e3;
22
+ --rc-text: #657b83;
23
+ --rc-border: 1px solid #93a1a1;
24
+ --rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
25
+ }
26
+
27
+ [data-theme="solarized-dark"] {
28
+ color-scheme: dark;
29
+ --rc-surface: #002b36;
30
+ --rc-text: #839496;
31
+ --rc-border: 1px solid #586e75;
32
+ --rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
33
+ }
34
+
35
+ body {
36
+ font-family: system-ui, sans-serif;
37
+ margin: 0;
38
+ }
39
+
40
+ .demo-page {
41
+ max-width: 60rem;
42
+ margin: 0 auto;
43
+ padding: 2rem 1.5rem;
44
+ }
45
+
46
+ .demo-controls {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 1rem;
50
+ margin-bottom: 2rem;
51
+ padding-bottom: 1rem;
52
+ border-bottom: 1px solid ButtonBorder;
53
+ }
54
+
55
+ .demo-controls .demo-title {
56
+ flex: 1;
57
+ }
58
+
59
+ .demo-controls h1 {
60
+ margin: 0 0 0.15rem;
61
+ font-size: 1.5rem;
62
+ }
63
+
64
+ .demo-controls a {
65
+ font-size: 0.8rem;
66
+ }
67
+
68
+ .demo-section {
69
+ margin-bottom: 2rem;
70
+ }
71
+
72
+ .demo-section h2 {
73
+ margin: 0 0 0.5rem;
74
+ }
75
+
76
+ .theme-picker {
77
+ padding: 0.35em 0.75em;
78
+ font-family: inherit;
79
+ font-size: 0.8rem;
80
+ cursor: pointer;
81
+ border: 1px solid ButtonBorder;
82
+ border-radius: 4px;
83
+ background: Canvas;
84
+ color: CanvasText;
85
+ }
package/dist/demo.js ADDED
@@ -0,0 +1,40 @@
1
+ const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
2
+ const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
3
+
4
+ const stored = localStorage.getItem('rc-demo-theme') ?? '';
5
+ applyTheme(stored);
6
+
7
+ function applyTheme(theme) {
8
+ if (theme) {
9
+ document.documentElement.dataset.theme = theme;
10
+ } else {
11
+ delete document.documentElement.dataset.theme;
12
+ }
13
+ }
14
+
15
+ function currentIndex() {
16
+ const current = document.documentElement.dataset.theme ?? '';
17
+ const idx = THEMES.indexOf(current);
18
+ return idx === -1 ? 0 : idx;
19
+ }
20
+
21
+ function updateButtons() {
22
+ const label = LABELS[currentIndex()];
23
+ document.querySelectorAll('.theme-picker').forEach((btn) => {
24
+ btn.textContent = label;
25
+ });
26
+ }
27
+
28
+ window.cycleTheme = function () {
29
+ const next = THEMES[(currentIndex() + 1) % THEMES.length];
30
+ applyTheme(next);
31
+ localStorage.setItem('rc-demo-theme', next);
32
+ updateButtons();
33
+ };
34
+
35
+ document.addEventListener('DOMContentLoaded', () => {
36
+ updateButtons();
37
+ document.querySelectorAll('.theme-picker').forEach((btn) => {
38
+ btn.addEventListener('click', window.cycleTheme);
39
+ });
40
+ });
@@ -0,0 +1,291 @@
1
+ import { LitElement as p, nothing as u, html as r } from "lit";
2
+ import { property as c, state as n } from "lit/decorators.js";
3
+ import { ActiveDescendantController as _ } from "@rcarls/rc-common";
4
+ var f = Object.defineProperty, l = (o, e, t, i) => {
5
+ for (var s = void 0, h = o.length - 1, d; h >= 0; h--)
6
+ (d = o[h]) && (s = d(e, t, s) || s);
7
+ return s && f(e, t, s), s;
8
+ };
9
+ let v = 0;
10
+ class a extends p {
11
+ constructor() {
12
+ super(...arguments), this.multiple = !1, this.checkmark = !1, this.filterStrategy = "contains", this._options = [], this._selectedValues = /* @__PURE__ */ new Set(), this._filterText = "", this._createLabel = null, this._selectionInitialized = !1, this._uid = `rc-lb-${++v}`, this._adc = new _(this, {
13
+ host: () => this,
14
+ items: () => this.navigableItems
15
+ }), this._onBlur = () => {
16
+ this._adc.clear();
17
+ }, this._onKeydown = (e) => {
18
+ if (!(e.altKey || e.ctrlKey || e.metaKey))
19
+ switch (e.key) {
20
+ case "ArrowDown": {
21
+ e.preventDefault(), this._adc.activeItem ? this._adc.navigate(1) : this._adc.navigateToFirst(), this.multiple || this._selectActiveItem();
22
+ break;
23
+ }
24
+ case "ArrowUp": {
25
+ e.preventDefault(), this._adc.activeItem ? this._adc.navigate(-1) : this._adc.navigateToLast(), this.multiple || this._selectActiveItem();
26
+ break;
27
+ }
28
+ case "Home": {
29
+ e.preventDefault(), this._adc.navigateToFirst(), this.multiple || this._selectActiveItem();
30
+ break;
31
+ }
32
+ case "End": {
33
+ e.preventDefault(), this._adc.navigateToLast(), this.multiple || this._selectActiveItem();
34
+ break;
35
+ }
36
+ case " ":
37
+ case "Enter": {
38
+ e.preventDefault();
39
+ const t = this._adc.activeItem?.getAttribute("data-value");
40
+ if (t == null) break;
41
+ this.multiple ? this.toggleOption(t) : this._selectActiveItem();
42
+ break;
43
+ }
44
+ }
45
+ };
46
+ }
47
+ /** Renders into the host element — no shadow root — so option IDs resolve in the parent shadow root. */
48
+ createRenderRoot() {
49
+ return this;
50
+ }
51
+ connectedCallback() {
52
+ super.connectedCallback(), this.hasAttribute("role") || this.setAttribute("role", "listbox"), this.addEventListener("keydown", this._onKeydown), this.addEventListener("blur", this._onBlur);
53
+ }
54
+ disconnectedCallback() {
55
+ super.disconnectedCallback(), this.removeEventListener("keydown", this._onKeydown), this.removeEventListener("blur", this._onBlur);
56
+ }
57
+ updated() {
58
+ this.multiple ? this.setAttribute("aria-multiselectable", "true") : this.removeAttribute("aria-multiselectable");
59
+ }
60
+ // ── Options ─────────────────────────────────────────────────────────────────
61
+ /** All options regardless of filter state. */
62
+ get allOptions() {
63
+ return this._options;
64
+ }
65
+ /** Options currently passing the active filter. */
66
+ get filteredOptions() {
67
+ return this._options.filter((e) => this._isVisible(e));
68
+ }
69
+ /** Replace the full options list. Triggers a re-render. */
70
+ get options() {
71
+ return [...this._options];
72
+ }
73
+ /** Replace the full options list. Triggers a re-render. */
74
+ set options(e) {
75
+ this._options = [...e], this._applySelectionFromSource();
76
+ }
77
+ /** Append a single option without replacing the list. */
78
+ appendOption(e) {
79
+ this._options = [...this._options, e];
80
+ }
81
+ // ── Selection ────────────────────────────────────────────────────────────────
82
+ get selectedValues() {
83
+ return [...this._selectedValues];
84
+ }
85
+ /** Current selection. Host writes update silently. */
86
+ get value() {
87
+ return this.multiple ? this.selectedValues : this.selectedValues[0] ?? "";
88
+ }
89
+ /** Current selection. Host writes update silently. */
90
+ set value(e) {
91
+ const t = this._value;
92
+ if (this._value = e, e === void 0) {
93
+ this.requestUpdate("value", t);
94
+ return;
95
+ }
96
+ this._selectionInitialized = !0, this._applySelection(this._normalizeValue(e)), this.requestUpdate("value", t);
97
+ }
98
+ /** Initial uncontrolled selection. */
99
+ get defaultValue() {
100
+ return this._defaultValue;
101
+ }
102
+ /** Initial uncontrolled selection. */
103
+ set defaultValue(e) {
104
+ const t = this._defaultValue;
105
+ this._defaultValue = e, !this._selectionInitialized && this._value === void 0 && e !== void 0 && this._applySelection(this._normalizeValue(e)), this.requestUpdate("defaultValue", t);
106
+ }
107
+ /** Replace the selection set without firing `rc-listbox-change`. */
108
+ setSelectedValues(e) {
109
+ this._applySelection(e);
110
+ }
111
+ /**
112
+ * Toggle the selected state of the option with `value`.
113
+ * In single-select mode, toggleing a selected item deselects it (and selects
114
+ * the new item). Fires `rc-listbox-change`.
115
+ */
116
+ toggleOption(e) {
117
+ const t = this._options.find((s) => s.value === e);
118
+ if (!t || t.disabled) return;
119
+ const i = this._selectedValues.has(e);
120
+ if (this.multiple) {
121
+ const s = new Set(this._selectedValues);
122
+ i ? s.delete(e) : s.add(e), this._selectedValues = s;
123
+ } else
124
+ this._selectedValues = i ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([e]);
125
+ this._selectionInitialized = !0, this._dispatchChange(t, !i);
126
+ }
127
+ clearSelection() {
128
+ this._applySelection([]);
129
+ }
130
+ // ── Filtering ────────────────────────────────────────────────────────────────
131
+ /** Filter visible options to those whose label starts with `text` (case-insensitive). */
132
+ filterOptions(e) {
133
+ this._filterText = e;
134
+ }
135
+ clearFilter() {
136
+ this._filterText = "";
137
+ }
138
+ // ── Navigation target ────────────────────────────────────────────────────────
139
+ /**
140
+ * Ordered list of option elements currently navigable: visible and not disabled.
141
+ * Feed this to `ActiveDescendantController.items` in the parent component.
142
+ * Includes the create option element when one is set.
143
+ */
144
+ get navigableItems() {
145
+ const e = [];
146
+ for (let t = 0; t < this._options.length; t++) {
147
+ const i = this._options[t];
148
+ if (i.disabled || !this._isVisible(i)) continue;
149
+ const s = this.querySelector(`#${this._optId(t)}`);
150
+ s && e.push(s);
151
+ }
152
+ if (this._createLabel !== null) {
153
+ const t = this.querySelector(`#${this._uid}-create`);
154
+ t && e.push(t);
155
+ }
156
+ return e;
157
+ }
158
+ // ── Allow-create ─────────────────────────────────────────────────────────────
159
+ /**
160
+ * Show or hide the "Create" option at the end of the list.
161
+ * Pass `null` to hide it, or a non-empty string to show "Create '{label}'".
162
+ * Fires `rc-listbox-change` with `value: '__create__'` when activated.
163
+ */
164
+ setCreateOption(e) {
165
+ this._createLabel = e;
166
+ }
167
+ // ── Rendering ────────────────────────────────────────────────────────────────
168
+ render() {
169
+ return r`
170
+ ${this._options.map(
171
+ (e, t) => r`
172
+ <div
173
+ id=${this._optId(t)}
174
+ part="option"
175
+ role="option"
176
+ aria-selected=${this._selectedValues.has(e.value) ? "true" : "false"}
177
+ aria-disabled=${e.disabled ? "true" : "false"}
178
+ data-value=${e.value}
179
+ ?hidden=${!this._isVisible(e)}
180
+ @pointerdown=${(i) => {
181
+ i.preventDefault(), this.toggleOption(e.value);
182
+ }}
183
+ >${this.checkmark ? r`<span part="option-checkmark" aria-hidden="true">&#x2713;</span>` : u}<span part="option-label">${e.label}</span></div>
184
+ `
185
+ )}
186
+ ${this._createLabel !== null ? r`
187
+ <div
188
+ id=${`${this._uid}-create`}
189
+ part="option create-option"
190
+ role="option"
191
+ aria-selected="false"
192
+ data-value="__create__"
193
+ @pointerdown=${(e) => {
194
+ e.preventDefault(), this.dispatchEvent(
195
+ new CustomEvent("rc-listbox-change", {
196
+ bubbles: !0,
197
+ composed: !0,
198
+ detail: {
199
+ value: this.value,
200
+ selected: !0,
201
+ optionValue: "__create__",
202
+ option: null,
203
+ selectedValues: this.selectedValues,
204
+ selectedOptions: this._selectedOptionsFor(this.selectedValues)
205
+ }
206
+ })
207
+ );
208
+ }}
209
+ >${this.checkmark ? r`<span part="option-checkmark" aria-hidden="true">&#x2713;</span>` : u}<span part="option-label">Create "${this._createLabel}"</span></div>
210
+ ` : u}
211
+ `;
212
+ }
213
+ /**
214
+ * Selects the active-descendant item without toggling (single-select safe).
215
+ * Used by arrow-key navigation so the item under the cursor is always selected.
216
+ */
217
+ _selectActiveItem() {
218
+ const e = this._adc.activeItem?.getAttribute("data-value");
219
+ if (e == null) return;
220
+ const t = this._options.find((i) => i.value === e);
221
+ !t || t.disabled || this._selectedValues.has(e) && this._selectedValues.size === 1 || (this._selectedValues = /* @__PURE__ */ new Set([e]), this._selectionInitialized = !0, this._dispatchChange(t, !0));
222
+ }
223
+ _optId(e) {
224
+ return `${this._uid}-${e}`;
225
+ }
226
+ _isVisible(e) {
227
+ if (!this._filterText) return !0;
228
+ const t = e.label.toLowerCase(), i = this._filterText.toLowerCase();
229
+ return typeof this.filterStrategy == "function" ? this.filterStrategy(t, i) : this.filterStrategy === "contains" ? t.includes(i) : t.startsWith(i);
230
+ }
231
+ _applySelection(e) {
232
+ this._selectedValues = new Set(this.multiple ? e : e.slice(0, 1));
233
+ }
234
+ _applySelectionFromSource() {
235
+ if (this._value !== void 0) {
236
+ this._applySelection(this._normalizeValue(this._value));
237
+ return;
238
+ }
239
+ !this._selectionInitialized && this._defaultValue !== void 0 && (this._selectionInitialized = !0, this._applySelection(this._normalizeValue(this._defaultValue)));
240
+ }
241
+ _normalizeValue(e) {
242
+ return Array.isArray(e) ? e : e ? [e] : [];
243
+ }
244
+ _dispatchChange(e, t) {
245
+ this.dispatchEvent(
246
+ new CustomEvent("rc-listbox-change", {
247
+ bubbles: !0,
248
+ composed: !0,
249
+ detail: {
250
+ value: this.value,
251
+ selected: t,
252
+ optionValue: e.value,
253
+ option: e,
254
+ selectedValues: this.selectedValues,
255
+ selectedOptions: this._selectedOptionsFor(this.selectedValues)
256
+ }
257
+ })
258
+ );
259
+ }
260
+ _selectedOptionsFor(e) {
261
+ return e.map((t) => this._options.find((i) => i.value === t) ?? {
262
+ value: t,
263
+ label: t
264
+ });
265
+ }
266
+ }
267
+ l([
268
+ c({ type: Boolean, reflect: !0 })
269
+ ], a.prototype, "multiple");
270
+ l([
271
+ c({ type: Boolean, reflect: !0 })
272
+ ], a.prototype, "checkmark");
273
+ l([
274
+ c({ attribute: "filter-strategy", reflect: !1 })
275
+ ], a.prototype, "filterStrategy");
276
+ l([
277
+ n()
278
+ ], a.prototype, "_options");
279
+ l([
280
+ n()
281
+ ], a.prototype, "_selectedValues");
282
+ l([
283
+ n()
284
+ ], a.prototype, "_filterText");
285
+ l([
286
+ n()
287
+ ], a.prototype, "_createLabel");
288
+ export {
289
+ a as R
290
+ };
291
+ //# sourceMappingURL=rc-listbox-D1vHntE2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-listbox-D1vHntE2.js","sources":["../src/rc-listbox.ts"],"sourcesContent":["import { LitElement, html, nothing } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { ActiveDescendantController } from '@rcarls/rc-common';\n\nexport interface ListboxOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\n/**\n * Determines how `filterOptions()` matches option labels against the query string.\n *\n * - `'prefix'` — label must start with the query (default, matches native `<select>` type-ahead).\n * - `'contains'` — label must contain the query anywhere.\n * - `function` — custom predicate; receives lowercased label and query, return `true` to show the option.\n */\nexport type FilterStrategy =\n | 'prefix'\n | 'contains'\n | ((label: string, query: string) => boolean);\n\nexport interface RCListboxChangeEvent {\n /** Canonical selected value for single mode, or selected values for multi mode. */\n value: string | string[];\n /** Whether the option was selected (false means it was deselected). */\n selected: boolean;\n /** The option value that was activated. `'__create__'` for the create option. */\n optionValue: string;\n /** The option that was activated. */\n option: ListboxOption | null;\n /** Selected values as a consistently-array-shaped convenience value. */\n selectedValues: string[];\n /** Selected option objects. */\n selectedOptions: ListboxOption[];\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-listbox': RCListbox;\n }\n}\n\nlet _uid = 0;\n\n/**\n * A headless listbox popup component following the WAI-ARIA listbox pattern.\n *\n * Renders into its own light DOM (no shadow root) so that when placed inside\n * another component's shadow root, option element IDs resolve within that same\n * shadow root — enabling `aria-activedescendant` and `aria-controls` IDREFs to\n * work correctly from the parent trigger.\n *\n * Consumers drive this component via its JS API:\n * - Set `options` to populate the list\n * - Set `value` / `defaultValue` to manage selection\n * - Call `toggleOption()` for internal keyboard/pointer activation\n * - Call `filterOptions()` to filter visible options\n * - Read `navigableItems` to feed `ActiveDescendantController`\n *\n * @slot — No slots; options are rendered programmatically from the `options` property.\n * @fires rc-listbox-change - Fired when an option is activated (clicked or Enter/Space)\n * @csspart option - Individual option elements\n * @csspart option-checkmark - The checkmark indicator inside each option\n * @csspart option-label - The label text span inside each option\n * @csspart create-option - The \"Create\" option when allow-create is active\n */\nexport class RCListbox extends LitElement {\n /** Renders into the host element — no shadow root — so option IDs resolve in the parent shadow root. */\n override createRenderRoot() {\n return this;\n }\n\n /** Allow multiple selection. Reflected as `aria-multiselectable` on the host. */\n @property({ type: Boolean, reflect: true })\n multiple = false;\n\n /**\n * Render a checkmark indicator inside each option element.\n * Hidden by default; enable for combobox / select patterns where the\n * consumer's CSS shows it conditionally via `[aria-selected='true']`.\n */\n @property({ type: Boolean, reflect: true })\n checkmark = false;\n\n /**\n * How option labels are matched against the active filter text.\n * Defaults to `'contains'` (substring). Set to `'prefix'` for starts-with\n * matching, or pass a custom predicate for full control.\n * Function values are JS-only; string values may be set via the\n * `filter-strategy` attribute.\n */\n @property({ attribute: 'filter-strategy', reflect: false })\n filterStrategy: FilterStrategy = 'contains';\n\n @state() private _options: ListboxOption[] = [];\n @state() private _selectedValues: Set<string> = new Set();\n @state() private _filterText = '';\n @state() private _createLabel: string | null = null;\n private _defaultValue: string | string[] | undefined;\n private _value: string | string[] | undefined;\n private _selectionInitialized = false;\n\n private readonly _uid = `rc-lb-${++_uid}`;\n\n private readonly _adc = new ActiveDescendantController(this, {\n host: () => this,\n items: () => this.navigableItems,\n });\n\n override connectedCallback() {\n super.connectedCallback();\n if (!this.hasAttribute('role')) this.setAttribute('role', 'listbox');\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('blur', this._onBlur);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('blur', this._onBlur);\n }\n\n override updated() {\n if (this.multiple) {\n this.setAttribute('aria-multiselectable', 'true');\n } else {\n this.removeAttribute('aria-multiselectable');\n }\n }\n\n // ── Options ─────────────────────────────────────────────────────────────────\n\n /** All options regardless of filter state. */\n get allOptions(): readonly ListboxOption[] {\n return this._options;\n }\n\n /** Options currently passing the active filter. */\n get filteredOptions(): readonly ListboxOption[] {\n return this._options.filter((o) => this._isVisible(o));\n }\n\n /** Replace the full options list. Triggers a re-render. */\n get options(): ListboxOption[] {\n return [...this._options];\n }\n\n /** Replace the full options list. Triggers a re-render. */\n set options(opts: ListboxOption[]) {\n this._options = [...opts];\n this._applySelectionFromSource();\n }\n\n /** Append a single option without replacing the list. */\n appendOption(opt: ListboxOption): void {\n this._options = [...this._options, opt];\n }\n\n // ── Selection ────────────────────────────────────────────────────────────────\n\n get selectedValues(): string[] {\n return [...this._selectedValues];\n }\n\n /** Current selection. Host writes update silently. */\n get value(): string | string[] {\n return this.multiple\n ? this.selectedValues\n : (this.selectedValues[0] ?? '');\n }\n\n /** Current selection. Host writes update silently. */\n set value(value: string | string[] | undefined) {\n const oldValue = this._value;\n\n this._value = value;\n\n if (value === undefined) {\n this.requestUpdate('value', oldValue);\n return;\n }\n\n this._selectionInitialized = true;\n this._applySelection(this._normalizeValue(value));\n this.requestUpdate('value', oldValue);\n }\n\n /** Initial uncontrolled selection. */\n get defaultValue(): string | string[] | undefined {\n return this._defaultValue;\n }\n\n /** Initial uncontrolled selection. */\n set defaultValue(value: string | string[] | undefined) {\n const oldValue = this._defaultValue;\n\n this._defaultValue = value;\n\n if (!this._selectionInitialized && this._value === undefined && value !== undefined) {\n this._applySelection(this._normalizeValue(value));\n }\n\n this.requestUpdate('defaultValue', oldValue);\n }\n\n /** Replace the selection set without firing `rc-listbox-change`. */\n setSelectedValues(values: string[]): void {\n this._applySelection(values);\n }\n\n /**\n * Toggle the selected state of the option with `value`.\n * In single-select mode, toggleing a selected item deselects it (and selects\n * the new item). Fires `rc-listbox-change`.\n */\n toggleOption(value: string): void {\n const opt = this._options.find((o) => o.value === value);\n if (!opt || opt.disabled) return;\n\n const wasSelected = this._selectedValues.has(value);\n\n if (this.multiple) {\n const next = new Set(this._selectedValues);\n if (wasSelected) next.delete(value); else next.add(value);\n this._selectedValues = next;\n } else {\n this._selectedValues = wasSelected ? new Set() : new Set([value]);\n }\n\n this._selectionInitialized = true;\n this._dispatchChange(opt, !wasSelected);\n }\n\n clearSelection(): void {\n this._applySelection([]);\n }\n\n // ── Filtering ────────────────────────────────────────────────────────────────\n\n /** Filter visible options to those whose label starts with `text` (case-insensitive). */\n filterOptions(text: string): void {\n this._filterText = text;\n }\n\n clearFilter(): void {\n this._filterText = '';\n }\n\n // ── Navigation target ────────────────────────────────────────────────────────\n\n /**\n * Ordered list of option elements currently navigable: visible and not disabled.\n * Feed this to `ActiveDescendantController.items` in the parent component.\n * Includes the create option element when one is set.\n */\n get navigableItems(): Element[] {\n const results: Element[] = [];\n for (let i = 0; i < this._options.length; i++) {\n const opt = this._options[i];\n if (opt.disabled || !this._isVisible(opt)) continue;\n const el = this.querySelector<Element>(`#${this._optId(i)}`);\n if (el) results.push(el);\n }\n if (this._createLabel !== null) {\n const el = this.querySelector<Element>(`#${this._uid}-create`);\n if (el) results.push(el);\n }\n return results;\n }\n\n // ── Allow-create ─────────────────────────────────────────────────────────────\n\n /**\n * Show or hide the \"Create\" option at the end of the list.\n * Pass `null` to hide it, or a non-empty string to show \"Create '{label}'\".\n * Fires `rc-listbox-change` with `value: '__create__'` when activated.\n */\n setCreateOption(label: string | null): void {\n this._createLabel = label;\n }\n\n // ── Rendering ────────────────────────────────────────────────────────────────\n\n protected override render() {\n return html`\n ${this._options.map(\n (opt, i) => html`\n <div\n id=${this._optId(i)}\n part=\"option\"\n role=\"option\"\n aria-selected=${this._selectedValues.has(opt.value) ? 'true' : 'false'}\n aria-disabled=${opt.disabled ? 'true' : 'false'}\n data-value=${opt.value}\n ?hidden=${!this._isVisible(opt)}\n @pointerdown=${(e: PointerEvent) => {\n e.preventDefault();\n this.toggleOption(opt.value);\n }}\n >${this.checkmark ? html`<span part=\"option-checkmark\" aria-hidden=\"true\">&#x2713;</span>` : nothing}<span part=\"option-label\">${opt.label}</span></div>\n `,\n )}\n ${this._createLabel !== null\n ? html`\n <div\n id=${`${this._uid}-create`}\n part=\"option create-option\"\n role=\"option\"\n aria-selected=\"false\"\n data-value=\"__create__\"\n @pointerdown=${(e: PointerEvent) => {\n e.preventDefault();\n this.dispatchEvent(\n new CustomEvent<RCListboxChangeEvent>('rc-listbox-change', {\n bubbles: true,\n composed: true,\n detail: {\n value: this.value,\n selected: true,\n optionValue: '__create__',\n option: null,\n selectedValues: this.selectedValues,\n selectedOptions: this._selectedOptionsFor(this.selectedValues),\n },\n }),\n );\n }}\n >${this.checkmark ? html`<span part=\"option-checkmark\" aria-hidden=\"true\">&#x2713;</span>` : nothing}<span part=\"option-label\">Create \"${this._createLabel}\"</span></div>\n `\n : nothing}\n `;\n }\n\n private _onBlur = (): void => {\n this._adc.clear();\n };\n\n private _onKeydown = (e: KeyboardEvent): void => {\n // Pass modifier-key combos to parent handlers (e.g. Alt+Arrow in rc-transfer-list).\n if (e.altKey || e.ctrlKey || e.metaKey) return;\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n if (this._adc.activeItem) this._adc.navigate(1);\n else this._adc.navigateToFirst();\n if (!this.multiple) this._selectActiveItem();\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n if (this._adc.activeItem) this._adc.navigate(-1);\n else this._adc.navigateToLast();\n if (!this.multiple) this._selectActiveItem();\n break;\n }\n case 'Home': {\n e.preventDefault();\n this._adc.navigateToFirst();\n if (!this.multiple) this._selectActiveItem();\n break;\n }\n case 'End': {\n e.preventDefault();\n this._adc.navigateToLast();\n if (!this.multiple) this._selectActiveItem();\n break;\n }\n case ' ':\n case 'Enter': {\n e.preventDefault();\n const value = this._adc.activeItem?.getAttribute('data-value');\n if (value == null) break;\n if (this.multiple) {\n this.toggleOption(value);\n } else {\n this._selectActiveItem();\n }\n break;\n }\n }\n };\n\n /**\n * Selects the active-descendant item without toggling (single-select safe).\n * Used by arrow-key navigation so the item under the cursor is always selected.\n */\n private _selectActiveItem(): void {\n const value = this._adc.activeItem?.getAttribute('data-value');\n if (value == null) return;\n\n const opt = this._options.find((o) => o.value === value);\n if (!opt || opt.disabled) return;\n\n if (this._selectedValues.has(value) && this._selectedValues.size === 1) return;\n\n this._selectedValues = new Set([value]);\n this._selectionInitialized = true;\n this._dispatchChange(opt, true);\n }\n\n private _optId(index: number): string {\n return `${this._uid}-${index}`;\n }\n\n private _isVisible(opt: ListboxOption): boolean {\n if (!this._filterText) return true;\n\n const label = opt.label.toLowerCase();\n const query = this._filterText.toLowerCase();\n\n if (typeof this.filterStrategy === 'function') return this.filterStrategy(label, query);\n if (this.filterStrategy === 'contains') return label.includes(query);\n\n return label.startsWith(query); // 'prefix' default\n }\n\n private _applySelection(values: string[]): void {\n this._selectedValues = new Set(this.multiple ? values : values.slice(0, 1));\n }\n\n private _applySelectionFromSource(): void {\n if (this._value !== undefined) {\n this._applySelection(this._normalizeValue(this._value));\n return;\n }\n\n if (!this._selectionInitialized && this._defaultValue !== undefined) {\n this._selectionInitialized = true;\n this._applySelection(this._normalizeValue(this._defaultValue));\n }\n }\n\n private _normalizeValue(value: string | string[]): string[] {\n return Array.isArray(value) ? value : value ? [value] : [];\n }\n\n private _dispatchChange(option: ListboxOption, selected: boolean): void {\n this.dispatchEvent(\n new CustomEvent<RCListboxChangeEvent>('rc-listbox-change', {\n bubbles: true,\n composed: true,\n detail: {\n value: this.value,\n selected,\n optionValue: option.value,\n option,\n selectedValues: this.selectedValues,\n selectedOptions: this._selectedOptionsFor(this.selectedValues),\n },\n }),\n );\n }\n\n private _selectedOptionsFor(values: string[]): ListboxOption[] {\n return values.map((value) => {\n return (\n this._options.find((opt) => opt.value === value) ?? {\n value,\n label: value,\n }\n );\n });\n }\n}\n\nexport default RCListbox;\n"],"names":["_uid","RCListbox","LitElement","ActiveDescendantController","value","o","opts","opt","oldValue","values","wasSelected","next","text","results","i","el","label","html","e","nothing","index","query","option","selected","__decorateClass","property","state"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { R as t } from "./rc-listbox-D1vHntE2.js";
2
+ customElements.get("rc-listbox") || customElements.define("rc-listbox", t);
3
+ export {
4
+ t as RCListbox
5
+ };
6
+ //# sourceMappingURL=rc-listbox-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-listbox-define.js","sources":["../src/define.ts"],"sourcesContent":["import { RCListbox } from './index.js';\n\ncustomElements.get('rc-listbox') || customElements.define('rc-listbox', RCListbox);\n\nexport * from './index.js';\n"],"names":["RCListbox"],"mappings":";AAEA,eAAe,IAAI,YAAY,KAAK,eAAe,OAAO,cAAcA,CAAS;"}
@@ -0,0 +1,5 @@
1
+ import { R as s } from "./rc-listbox-D1vHntE2.js";
2
+ export {
3
+ s as RCListbox
4
+ };
5
+ //# sourceMappingURL=rc-listbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-listbox.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-listbox';
@@ -0,0 +1,146 @@
1
+ import { LitElement } from 'lit';
2
+ export interface ListboxOption {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+ /**
8
+ * Determines how `filterOptions()` matches option labels against the query string.
9
+ *
10
+ * - `'prefix'` — label must start with the query (default, matches native `<select>` type-ahead).
11
+ * - `'contains'` — label must contain the query anywhere.
12
+ * - `function` — custom predicate; receives lowercased label and query, return `true` to show the option.
13
+ */
14
+ export type FilterStrategy = 'prefix' | 'contains' | ((label: string, query: string) => boolean);
15
+ export interface RCListboxChangeEvent {
16
+ /** Canonical selected value for single mode, or selected values for multi mode. */
17
+ value: string | string[];
18
+ /** Whether the option was selected (false means it was deselected). */
19
+ selected: boolean;
20
+ /** The option value that was activated. `'__create__'` for the create option. */
21
+ optionValue: string;
22
+ /** The option that was activated. */
23
+ option: ListboxOption | null;
24
+ /** Selected values as a consistently-array-shaped convenience value. */
25
+ selectedValues: string[];
26
+ /** Selected option objects. */
27
+ selectedOptions: ListboxOption[];
28
+ }
29
+ declare global {
30
+ interface HTMLElementTagNameMap {
31
+ 'rc-listbox': RCListbox;
32
+ }
33
+ }
34
+ /**
35
+ * A headless listbox popup component following the WAI-ARIA listbox pattern.
36
+ *
37
+ * Renders into its own light DOM (no shadow root) so that when placed inside
38
+ * another component's shadow root, option element IDs resolve within that same
39
+ * shadow root — enabling `aria-activedescendant` and `aria-controls` IDREFs to
40
+ * work correctly from the parent trigger.
41
+ *
42
+ * Consumers drive this component via its JS API:
43
+ * - Set `options` to populate the list
44
+ * - Set `value` / `defaultValue` to manage selection
45
+ * - Call `toggleOption()` for internal keyboard/pointer activation
46
+ * - Call `filterOptions()` to filter visible options
47
+ * - Read `navigableItems` to feed `ActiveDescendantController`
48
+ *
49
+ * @slot — No slots; options are rendered programmatically from the `options` property.
50
+ * @fires rc-listbox-change - Fired when an option is activated (clicked or Enter/Space)
51
+ * @csspart option - Individual option elements
52
+ * @csspart option-checkmark - The checkmark indicator inside each option
53
+ * @csspart option-label - The label text span inside each option
54
+ * @csspart create-option - The "Create" option when allow-create is active
55
+ */
56
+ export declare class RCListbox extends LitElement {
57
+ /** Renders into the host element — no shadow root — so option IDs resolve in the parent shadow root. */
58
+ createRenderRoot(): this;
59
+ /** Allow multiple selection. Reflected as `aria-multiselectable` on the host. */
60
+ multiple: boolean;
61
+ /**
62
+ * Render a checkmark indicator inside each option element.
63
+ * Hidden by default; enable for combobox / select patterns where the
64
+ * consumer's CSS shows it conditionally via `[aria-selected='true']`.
65
+ */
66
+ checkmark: boolean;
67
+ /**
68
+ * How option labels are matched against the active filter text.
69
+ * Defaults to `'contains'` (substring). Set to `'prefix'` for starts-with
70
+ * matching, or pass a custom predicate for full control.
71
+ * Function values are JS-only; string values may be set via the
72
+ * `filter-strategy` attribute.
73
+ */
74
+ filterStrategy: FilterStrategy;
75
+ private _options;
76
+ private _selectedValues;
77
+ private _filterText;
78
+ private _createLabel;
79
+ private _defaultValue;
80
+ private _value;
81
+ private _selectionInitialized;
82
+ private readonly _uid;
83
+ private readonly _adc;
84
+ connectedCallback(): void;
85
+ disconnectedCallback(): void;
86
+ updated(): void;
87
+ /** All options regardless of filter state. */
88
+ get allOptions(): readonly ListboxOption[];
89
+ /** Options currently passing the active filter. */
90
+ get filteredOptions(): readonly ListboxOption[];
91
+ /** Replace the full options list. Triggers a re-render. */
92
+ get options(): ListboxOption[];
93
+ /** Replace the full options list. Triggers a re-render. */
94
+ set options(opts: ListboxOption[]);
95
+ /** Append a single option without replacing the list. */
96
+ appendOption(opt: ListboxOption): void;
97
+ get selectedValues(): string[];
98
+ /** Current selection. Host writes update silently. */
99
+ get value(): string | string[];
100
+ /** Current selection. Host writes update silently. */
101
+ set value(value: string | string[] | undefined);
102
+ /** Initial uncontrolled selection. */
103
+ get defaultValue(): string | string[] | undefined;
104
+ /** Initial uncontrolled selection. */
105
+ set defaultValue(value: string | string[] | undefined);
106
+ /** Replace the selection set without firing `rc-listbox-change`. */
107
+ setSelectedValues(values: string[]): void;
108
+ /**
109
+ * Toggle the selected state of the option with `value`.
110
+ * In single-select mode, toggleing a selected item deselects it (and selects
111
+ * the new item). Fires `rc-listbox-change`.
112
+ */
113
+ toggleOption(value: string): void;
114
+ clearSelection(): void;
115
+ /** Filter visible options to those whose label starts with `text` (case-insensitive). */
116
+ filterOptions(text: string): void;
117
+ clearFilter(): void;
118
+ /**
119
+ * Ordered list of option elements currently navigable: visible and not disabled.
120
+ * Feed this to `ActiveDescendantController.items` in the parent component.
121
+ * Includes the create option element when one is set.
122
+ */
123
+ get navigableItems(): Element[];
124
+ /**
125
+ * Show or hide the "Create" option at the end of the list.
126
+ * Pass `null` to hide it, or a non-empty string to show "Create '{label}'".
127
+ * Fires `rc-listbox-change` with `value: '__create__'` when activated.
128
+ */
129
+ setCreateOption(label: string | null): void;
130
+ protected render(): import('lit').TemplateResult<1>;
131
+ private _onBlur;
132
+ private _onKeydown;
133
+ /**
134
+ * Selects the active-descendant item without toggling (single-select safe).
135
+ * Used by arrow-key navigation so the item under the cursor is always selected.
136
+ */
137
+ private _selectActiveItem;
138
+ private _optId;
139
+ private _isVisible;
140
+ private _applySelection;
141
+ private _applySelectionFromSource;
142
+ private _normalizeValue;
143
+ private _dispatchChange;
144
+ private _selectedOptionsFor;
145
+ }
146
+ export default RCListbox;