@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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # `@rcarls/rc-listbox`
2
+
3
+ A headless WAI-ARIA listbox component built with Lit 3. It renders option
4
+ elements into light DOM so parent components can use `aria-activedescendant`
5
+ with IDs that resolve in the same document or shadow root.
6
+
7
+ `rc-listbox` is primarily an infrastructure component for `rc-select` and
8
+ `rc-combobox`, but it can be used directly when an application controls option
9
+ state from JavaScript.
10
+
11
+ ## Installation
12
+
13
+ PowerShell:
14
+
15
+ ```powershell
16
+ yarn.cmd add @rcarls/rc-listbox
17
+ ```
18
+
19
+ Bash/zsh:
20
+
21
+ ```bash
22
+ yarn add @rcarls/rc-listbox
23
+ ```
24
+
25
+ ## Import
26
+
27
+ ```ts
28
+ import '@rcarls/rc-listbox/define';
29
+ ```
30
+
31
+ ## Basic Usage
32
+
33
+ ```ts
34
+ const listbox = document.querySelector('rc-listbox');
35
+
36
+ listbox.options = [
37
+ { value: 'apple', label: 'Apple' },
38
+ { value: 'banana', label: 'Banana' },
39
+ { value: 'cherry', label: 'Cherry', disabled: true },
40
+ ];
41
+
42
+ listbox.setSelectedValues(['apple']);
43
+ ```
44
+
45
+ ## API
46
+
47
+ | Property / method | Type | Description |
48
+ | --- | --- | --- |
49
+ | `multiple` | `boolean` | Enables multi-selection and reflects `aria-multiselectable`. |
50
+ | `filterStrategy` | `'prefix' \| 'contains' \| function` | Controls how `filterOptions()` matches labels. |
51
+ | `options` | `ListboxOption[]` | Replaces the rendered option list. |
52
+ | `allOptions` | `readonly ListboxOption[]` | All configured options. |
53
+ | `filteredOptions` | `readonly ListboxOption[]` | Options currently passing the filter. |
54
+ | `selectedValues` | `string[]` | Current selected values. |
55
+ | `appendOption(opt)` | `void` | Adds one option. |
56
+ | `setSelectedValues(values)` | `void` | Replaces selection without firing an event. |
57
+ | `toggleOption(value)` | `void` | Toggles selection and fires `rc-listbox-change`. |
58
+ | `clearSelection()` | `void` | Clears selected values. |
59
+ | `filterOptions(text)` | `void` | Filters visible options. |
60
+ | `clearFilter()` | `void` | Clears the active filter. |
61
+ | `navigableItems` | `Element[]` | Visible, enabled options for active-descendant navigation. |
62
+ | `setCreateOption(label)` | `void` | Shows or hides the synthetic create option. |
63
+
64
+ ## Events
65
+
66
+ | Event | Detail | Description |
67
+ | --- | --- | --- |
68
+ | `rc-listbox-change` | `{ value: string; selected: boolean }` | Fires when an option is activated. |
69
+
70
+ ## Accessibility
71
+
72
+ - Host role defaults to `listbox`.
73
+ - Options render with `role="option"` and `aria-selected`.
74
+ - Disabled options render with `aria-disabled="true"` and are omitted from
75
+ `navigableItems`.
76
+ - Multi-select mode reflects `aria-multiselectable="true"`.
@@ -0,0 +1,562 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/define.ts",
8
+ "declarations": [],
9
+ "exports": [
10
+ {
11
+ "kind": "custom-element-definition",
12
+ "name": "rc-listbox",
13
+ "declaration": {
14
+ "name": "RCListbox",
15
+ "module": "/src/index.js"
16
+ }
17
+ },
18
+ {
19
+ "kind": "js",
20
+ "name": "*",
21
+ "declaration": {
22
+ "name": "*",
23
+ "module": "src/index.js"
24
+ }
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "kind": "javascript-module",
30
+ "path": "src/index.ts",
31
+ "declarations": [],
32
+ "exports": [
33
+ {
34
+ "kind": "js",
35
+ "name": "*",
36
+ "declaration": {
37
+ "name": "*",
38
+ "module": "src/rc-listbox"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "kind": "javascript-module",
45
+ "path": "src/rc-listbox.ts",
46
+ "declarations": [
47
+ {
48
+ "kind": "class",
49
+ "description": "A headless listbox popup component following the WAI-ARIA listbox pattern.\n\nRenders into its own light DOM (no shadow root) so that when placed inside\nanother component's shadow root, option element IDs resolve within that same\nshadow root — enabling `aria-activedescendant` and `aria-controls` IDREFs to\nwork correctly from the parent trigger.\n\nConsumers 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`",
50
+ "name": "RCListbox",
51
+ "cssParts": [
52
+ {
53
+ "description": "Individual option elements",
54
+ "name": "option"
55
+ },
56
+ {
57
+ "description": "The checkmark indicator inside each option",
58
+ "name": "option-checkmark"
59
+ },
60
+ {
61
+ "description": "The label text span inside each option",
62
+ "name": "option-label"
63
+ },
64
+ {
65
+ "description": "The \"Create\" option when allow-create is active",
66
+ "name": "create-option"
67
+ }
68
+ ],
69
+ "slots": [
70
+ {
71
+ "description": "No slots; options are rendered programmatically from the `options` property.",
72
+ "name": "—"
73
+ }
74
+ ],
75
+ "members": [
76
+ {
77
+ "kind": "field",
78
+ "name": "multiple",
79
+ "type": {
80
+ "text": "boolean"
81
+ },
82
+ "default": "false",
83
+ "description": "Allow multiple selection. Reflected as `aria-multiselectable` on the host.",
84
+ "attribute": "multiple",
85
+ "reflects": true
86
+ },
87
+ {
88
+ "kind": "field",
89
+ "name": "checkmark",
90
+ "type": {
91
+ "text": "boolean"
92
+ },
93
+ "default": "false",
94
+ "description": "Render a checkmark indicator inside each option element.\nHidden by default; enable for combobox / select patterns where the\nconsumer's CSS shows it conditionally via `[aria-selected='true']`.",
95
+ "attribute": "checkmark",
96
+ "reflects": true
97
+ },
98
+ {
99
+ "kind": "field",
100
+ "name": "filterStrategy",
101
+ "type": {
102
+ "text": "FilterStrategy"
103
+ },
104
+ "default": "'contains'",
105
+ "description": "How option labels are matched against the active filter text.\nDefaults to `'contains'` (substring). Set to `'prefix'` for starts-with\nmatching, or pass a custom predicate for full control.\nFunction values are JS-only; string values may be set via the\n`filter-strategy` attribute.",
106
+ "attribute": "filter-strategy"
107
+ },
108
+ {
109
+ "kind": "field",
110
+ "name": "_options",
111
+ "type": {
112
+ "text": "ListboxOption[]"
113
+ },
114
+ "privacy": "private",
115
+ "default": "[]"
116
+ },
117
+ {
118
+ "kind": "field",
119
+ "name": "_selectedValues",
120
+ "type": {
121
+ "text": "Set<string>"
122
+ },
123
+ "privacy": "private",
124
+ "default": "new Set()"
125
+ },
126
+ {
127
+ "kind": "field",
128
+ "name": "_filterText",
129
+ "type": {
130
+ "text": "string"
131
+ },
132
+ "privacy": "private",
133
+ "default": "''"
134
+ },
135
+ {
136
+ "kind": "field",
137
+ "name": "_createLabel",
138
+ "type": {
139
+ "text": "string | null"
140
+ },
141
+ "privacy": "private",
142
+ "default": "null"
143
+ },
144
+ {
145
+ "kind": "field",
146
+ "name": "_defaultValue",
147
+ "type": {
148
+ "text": "string | string[] | undefined"
149
+ },
150
+ "privacy": "private"
151
+ },
152
+ {
153
+ "kind": "field",
154
+ "name": "_value",
155
+ "type": {
156
+ "text": "string | string[] | undefined"
157
+ },
158
+ "privacy": "private"
159
+ },
160
+ {
161
+ "kind": "field",
162
+ "name": "_selectionInitialized",
163
+ "type": {
164
+ "text": "boolean"
165
+ },
166
+ "privacy": "private",
167
+ "default": "false"
168
+ },
169
+ {
170
+ "kind": "field",
171
+ "name": "_uid",
172
+ "privacy": "private",
173
+ "readonly": true,
174
+ "default": "`rc-lb-${++_uid}`"
175
+ },
176
+ {
177
+ "kind": "field",
178
+ "name": "_adc",
179
+ "privacy": "private",
180
+ "readonly": true,
181
+ "default": "new ActiveDescendantController(this, { host: () => this, items: () => this.navigableItems, })"
182
+ },
183
+ {
184
+ "kind": "field",
185
+ "name": "allOptions",
186
+ "type": {
187
+ "text": "readonly ListboxOption[]"
188
+ },
189
+ "description": "All options regardless of filter state.",
190
+ "readonly": true
191
+ },
192
+ {
193
+ "kind": "field",
194
+ "name": "filteredOptions",
195
+ "type": {
196
+ "text": "readonly ListboxOption[]"
197
+ },
198
+ "description": "Options currently passing the active filter.",
199
+ "readonly": true
200
+ },
201
+ {
202
+ "kind": "field",
203
+ "name": "options",
204
+ "type": {
205
+ "text": "ListboxOption[]"
206
+ },
207
+ "description": "Replace the full options list. Triggers a re-render."
208
+ },
209
+ {
210
+ "kind": "method",
211
+ "name": "appendOption",
212
+ "return": {
213
+ "type": {
214
+ "text": "void"
215
+ }
216
+ },
217
+ "parameters": [
218
+ {
219
+ "name": "opt",
220
+ "type": {
221
+ "text": "ListboxOption"
222
+ }
223
+ }
224
+ ],
225
+ "description": "Append a single option without replacing the list."
226
+ },
227
+ {
228
+ "kind": "field",
229
+ "name": "selectedValues",
230
+ "type": {
231
+ "text": "string[]"
232
+ },
233
+ "readonly": true
234
+ },
235
+ {
236
+ "kind": "field",
237
+ "name": "value",
238
+ "type": {
239
+ "text": "string | string[]"
240
+ },
241
+ "description": "Current selection. Host writes update silently."
242
+ },
243
+ {
244
+ "kind": "field",
245
+ "name": "defaultValue",
246
+ "type": {
247
+ "text": "string | string[] | undefined"
248
+ },
249
+ "description": "Initial uncontrolled selection."
250
+ },
251
+ {
252
+ "kind": "method",
253
+ "name": "setSelectedValues",
254
+ "return": {
255
+ "type": {
256
+ "text": "void"
257
+ }
258
+ },
259
+ "parameters": [
260
+ {
261
+ "name": "values",
262
+ "type": {
263
+ "text": "string[]"
264
+ }
265
+ }
266
+ ],
267
+ "description": "Replace the selection set without firing `rc-listbox-change`."
268
+ },
269
+ {
270
+ "kind": "method",
271
+ "name": "toggleOption",
272
+ "return": {
273
+ "type": {
274
+ "text": "void"
275
+ }
276
+ },
277
+ "parameters": [
278
+ {
279
+ "name": "value",
280
+ "type": {
281
+ "text": "string"
282
+ }
283
+ }
284
+ ],
285
+ "description": "Toggle the selected state of the option with `value`.\nIn single-select mode, toggleing a selected item deselects it (and selects\nthe new item). Fires `rc-listbox-change`."
286
+ },
287
+ {
288
+ "kind": "method",
289
+ "name": "clearSelection",
290
+ "return": {
291
+ "type": {
292
+ "text": "void"
293
+ }
294
+ }
295
+ },
296
+ {
297
+ "kind": "method",
298
+ "name": "filterOptions",
299
+ "return": {
300
+ "type": {
301
+ "text": "void"
302
+ }
303
+ },
304
+ "parameters": [
305
+ {
306
+ "name": "text",
307
+ "type": {
308
+ "text": "string"
309
+ }
310
+ }
311
+ ],
312
+ "description": "Filter visible options to those whose label starts with `text` (case-insensitive)."
313
+ },
314
+ {
315
+ "kind": "method",
316
+ "name": "clearFilter",
317
+ "return": {
318
+ "type": {
319
+ "text": "void"
320
+ }
321
+ }
322
+ },
323
+ {
324
+ "kind": "field",
325
+ "name": "navigableItems",
326
+ "type": {
327
+ "text": "Element[]"
328
+ },
329
+ "description": "Ordered list of option elements currently navigable: visible and not disabled.\nFeed this to `ActiveDescendantController.items` in the parent component.\nIncludes the create option element when one is set.",
330
+ "readonly": true
331
+ },
332
+ {
333
+ "kind": "method",
334
+ "name": "setCreateOption",
335
+ "return": {
336
+ "type": {
337
+ "text": "void"
338
+ }
339
+ },
340
+ "parameters": [
341
+ {
342
+ "name": "label",
343
+ "type": {
344
+ "text": "string | null"
345
+ }
346
+ }
347
+ ],
348
+ "description": "Show or hide the \"Create\" option at the end of the list.\nPass `null` to hide it, or a non-empty string to show \"Create '{label}'\".\nFires `rc-listbox-change` with `value: '__create__'` when activated."
349
+ },
350
+ {
351
+ "kind": "field",
352
+ "name": "_onBlur",
353
+ "privacy": "private"
354
+ },
355
+ {
356
+ "kind": "field",
357
+ "name": "_onKeydown",
358
+ "privacy": "private"
359
+ },
360
+ {
361
+ "kind": "method",
362
+ "name": "_selectActiveItem",
363
+ "privacy": "private",
364
+ "return": {
365
+ "type": {
366
+ "text": "void"
367
+ }
368
+ },
369
+ "description": "Selects the active-descendant item without toggling (single-select safe).\nUsed by arrow-key navigation so the item under the cursor is always selected."
370
+ },
371
+ {
372
+ "kind": "method",
373
+ "name": "_optId",
374
+ "privacy": "private",
375
+ "return": {
376
+ "type": {
377
+ "text": "string"
378
+ }
379
+ },
380
+ "parameters": [
381
+ {
382
+ "name": "index",
383
+ "type": {
384
+ "text": "number"
385
+ }
386
+ }
387
+ ]
388
+ },
389
+ {
390
+ "kind": "method",
391
+ "name": "_isVisible",
392
+ "privacy": "private",
393
+ "return": {
394
+ "type": {
395
+ "text": "boolean"
396
+ }
397
+ },
398
+ "parameters": [
399
+ {
400
+ "name": "opt",
401
+ "type": {
402
+ "text": "ListboxOption"
403
+ }
404
+ }
405
+ ]
406
+ },
407
+ {
408
+ "kind": "method",
409
+ "name": "_applySelection",
410
+ "privacy": "private",
411
+ "return": {
412
+ "type": {
413
+ "text": "void"
414
+ }
415
+ },
416
+ "parameters": [
417
+ {
418
+ "name": "values",
419
+ "type": {
420
+ "text": "string[]"
421
+ }
422
+ }
423
+ ]
424
+ },
425
+ {
426
+ "kind": "method",
427
+ "name": "_applySelectionFromSource",
428
+ "privacy": "private",
429
+ "return": {
430
+ "type": {
431
+ "text": "void"
432
+ }
433
+ }
434
+ },
435
+ {
436
+ "kind": "method",
437
+ "name": "_normalizeValue",
438
+ "privacy": "private",
439
+ "return": {
440
+ "type": {
441
+ "text": "string[]"
442
+ }
443
+ },
444
+ "parameters": [
445
+ {
446
+ "name": "value",
447
+ "type": {
448
+ "text": "string | string[]"
449
+ }
450
+ }
451
+ ]
452
+ },
453
+ {
454
+ "kind": "method",
455
+ "name": "_dispatchChange",
456
+ "privacy": "private",
457
+ "return": {
458
+ "type": {
459
+ "text": "void"
460
+ }
461
+ },
462
+ "parameters": [
463
+ {
464
+ "name": "option",
465
+ "type": {
466
+ "text": "ListboxOption"
467
+ }
468
+ },
469
+ {
470
+ "name": "selected",
471
+ "type": {
472
+ "text": "boolean"
473
+ }
474
+ }
475
+ ]
476
+ },
477
+ {
478
+ "kind": "method",
479
+ "name": "_selectedOptionsFor",
480
+ "privacy": "private",
481
+ "return": {
482
+ "type": {
483
+ "text": "ListboxOption[]"
484
+ }
485
+ },
486
+ "parameters": [
487
+ {
488
+ "name": "values",
489
+ "type": {
490
+ "text": "string[]"
491
+ }
492
+ }
493
+ ]
494
+ }
495
+ ],
496
+ "events": [
497
+ {
498
+ "name": "rc-listbox-change",
499
+ "type": {
500
+ "text": "CustomEvent"
501
+ },
502
+ "description": "Fired when an option is activated (clicked or Enter/Space)"
503
+ }
504
+ ],
505
+ "attributes": [
506
+ {
507
+ "name": "multiple",
508
+ "type": {
509
+ "text": "boolean"
510
+ },
511
+ "default": "false",
512
+ "description": "Allow multiple selection. Reflected as `aria-multiselectable` on the host.",
513
+ "fieldName": "multiple"
514
+ },
515
+ {
516
+ "name": "checkmark",
517
+ "type": {
518
+ "text": "boolean"
519
+ },
520
+ "default": "false",
521
+ "description": "Render a checkmark indicator inside each option element.\nHidden by default; enable for combobox / select patterns where the\nconsumer's CSS shows it conditionally via `[aria-selected='true']`.",
522
+ "fieldName": "checkmark"
523
+ },
524
+ {
525
+ "name": "filter-strategy",
526
+ "type": {
527
+ "text": "FilterStrategy"
528
+ },
529
+ "default": "'contains'",
530
+ "description": "How option labels are matched against the active filter text.\nDefaults to `'contains'` (substring). Set to `'prefix'` for starts-with\nmatching, or pass a custom predicate for full control.\nFunction values are JS-only; string values may be set via the\n`filter-strategy` attribute.",
531
+ "fieldName": "filterStrategy"
532
+ }
533
+ ],
534
+ "superclass": {
535
+ "name": "LitElement",
536
+ "package": "lit"
537
+ },
538
+ "tagName": "rc-listbox",
539
+ "customElement": true
540
+ }
541
+ ],
542
+ "exports": [
543
+ {
544
+ "kind": "js",
545
+ "name": "RCListbox",
546
+ "declaration": {
547
+ "name": "RCListbox",
548
+ "module": "src/rc-listbox.ts"
549
+ }
550
+ },
551
+ {
552
+ "kind": "js",
553
+ "name": "default",
554
+ "declaration": {
555
+ "name": "RCListbox",
556
+ "module": "src/rc-listbox.ts"
557
+ }
558
+ }
559
+ ]
560
+ }
561
+ ]
562
+ }