@chromvoid/uikit 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/LICENSE +8 -0
- package/README.md +96 -0
- package/dist/components/cv-accordion-item.d.ts +69 -0
- package/dist/components/cv-accordion-item.js +176 -0
- package/dist/components/cv-accordion.d.ts +79 -0
- package/dist/components/cv-accordion.js +310 -0
- package/dist/components/cv-alert-dialog.d.ts +86 -0
- package/dist/components/cv-alert-dialog.js +393 -0
- package/dist/components/cv-alert.d.ts +48 -0
- package/dist/components/cv-alert.js +156 -0
- package/dist/components/cv-badge.d.ts +56 -0
- package/dist/components/cv-badge.js +280 -0
- package/dist/components/cv-breadcrumb-item.d.ts +35 -0
- package/dist/components/cv-breadcrumb-item.js +64 -0
- package/dist/components/cv-breadcrumb.d.ts +39 -0
- package/dist/components/cv-breadcrumb.js +160 -0
- package/dist/components/cv-button.d.ts +83 -0
- package/dist/components/cv-button.js +541 -0
- package/dist/components/cv-callout.d.ts +32 -0
- package/dist/components/cv-callout.js +221 -0
- package/dist/components/cv-card.d.ts +48 -0
- package/dist/components/cv-card.js +269 -0
- package/dist/components/cv-carousel-slide.d.ts +25 -0
- package/dist/components/cv-carousel-slide.js +51 -0
- package/dist/components/cv-carousel.d.ts +96 -0
- package/dist/components/cv-carousel.js +457 -0
- package/dist/components/cv-checkbox.d.ts +84 -0
- package/dist/components/cv-checkbox.js +274 -0
- package/dist/components/cv-combobox-group.d.ts +15 -0
- package/dist/components/cv-combobox-group.js +34 -0
- package/dist/components/cv-combobox-option.d.ts +30 -0
- package/dist/components/cv-combobox-option.js +66 -0
- package/dist/components/cv-combobox.d.ts +135 -0
- package/dist/components/cv-combobox.js +834 -0
- package/dist/components/cv-command-item.d.ts +30 -0
- package/dist/components/cv-command-item.js +68 -0
- package/dist/components/cv-command-palette.d.ts +105 -0
- package/dist/components/cv-command-palette.js +578 -0
- package/dist/components/cv-context-menu.d.ts +98 -0
- package/dist/components/cv-context-menu.js +515 -0
- package/dist/components/cv-copy-button.d.ts +61 -0
- package/dist/components/cv-copy-button.js +318 -0
- package/dist/components/cv-date-picker.d.ts +161 -0
- package/dist/components/cv-date-picker.js +803 -0
- package/dist/components/cv-dialog.d.ts +89 -0
- package/dist/components/cv-dialog.js +459 -0
- package/dist/components/cv-disclosure.d.ts +57 -0
- package/dist/components/cv-disclosure.js +241 -0
- package/dist/components/cv-drawer.d.ts +102 -0
- package/dist/components/cv-drawer.js +595 -0
- package/dist/components/cv-feed-article.d.ts +26 -0
- package/dist/components/cv-feed-article.js +52 -0
- package/dist/components/cv-feed.d.ts +62 -0
- package/dist/components/cv-feed.js +310 -0
- package/dist/components/cv-grid-cell.d.ts +30 -0
- package/dist/components/cv-grid-cell.js +57 -0
- package/dist/components/cv-grid-column.d.ts +30 -0
- package/dist/components/cv-grid-column.js +43 -0
- package/dist/components/cv-grid-row.d.ts +30 -0
- package/dist/components/cv-grid-row.js +42 -0
- package/dist/components/cv-grid.d.ts +119 -0
- package/dist/components/cv-grid.js +567 -0
- package/dist/components/cv-icon.d.ts +57 -0
- package/dist/components/cv-icon.js +352 -0
- package/dist/components/cv-input.d.ts +127 -0
- package/dist/components/cv-input.js +482 -0
- package/dist/components/cv-landmark.d.ts +32 -0
- package/dist/components/cv-landmark.js +62 -0
- package/dist/components/cv-link.d.ts +22 -0
- package/dist/components/cv-link.js +99 -0
- package/dist/components/cv-listbox-group.d.ts +15 -0
- package/dist/components/cv-listbox-group.js +42 -0
- package/dist/components/cv-listbox.d.ts +81 -0
- package/dist/components/cv-listbox.js +388 -0
- package/dist/components/cv-menu-button.d.ts +118 -0
- package/dist/components/cv-menu-button.js +822 -0
- package/dist/components/cv-menu-group.d.ts +20 -0
- package/dist/components/cv-menu-group.js +48 -0
- package/dist/components/cv-menu-item.d.ts +52 -0
- package/dist/components/cv-menu-item.js +105 -0
- package/dist/components/cv-menu.d.ts +62 -0
- package/dist/components/cv-menu.js +414 -0
- package/dist/components/cv-meter.d.ts +66 -0
- package/dist/components/cv-meter.js +154 -0
- package/dist/components/cv-number.d.ts +139 -0
- package/dist/components/cv-number.js +553 -0
- package/dist/components/cv-option.d.ts +30 -0
- package/dist/components/cv-option.js +84 -0
- package/dist/components/cv-popover.d.ts +87 -0
- package/dist/components/cv-popover.js +373 -0
- package/dist/components/cv-progress-ring.d.ts +45 -0
- package/dist/components/cv-progress-ring.js +169 -0
- package/dist/components/cv-progress.d.ts +45 -0
- package/dist/components/cv-progress.js +148 -0
- package/dist/components/cv-radio-group.d.ts +79 -0
- package/dist/components/cv-radio-group.js +398 -0
- package/dist/components/cv-radio.d.ts +36 -0
- package/dist/components/cv-radio.js +123 -0
- package/dist/components/cv-select-group.d.ts +15 -0
- package/dist/components/cv-select-group.js +44 -0
- package/dist/components/cv-select-option.d.ts +30 -0
- package/dist/components/cv-select-option.js +66 -0
- package/dist/components/cv-select.d.ts +128 -0
- package/dist/components/cv-select.js +666 -0
- package/dist/components/cv-sidebar-item.d.ts +26 -0
- package/dist/components/cv-sidebar-item.js +142 -0
- package/dist/components/cv-sidebar.d.ts +171 -0
- package/dist/components/cv-sidebar.js +767 -0
- package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
- package/dist/components/cv-slider-multi-thumb.js +374 -0
- package/dist/components/cv-slider.d.ts +84 -0
- package/dist/components/cv-slider.js +328 -0
- package/dist/components/cv-spinbutton.d.ts +121 -0
- package/dist/components/cv-spinbutton.js +486 -0
- package/dist/components/cv-spinner.d.ts +18 -0
- package/dist/components/cv-spinner.js +95 -0
- package/dist/components/cv-switch.d.ts +81 -0
- package/dist/components/cv-switch.js +285 -0
- package/dist/components/cv-tab-panel.d.ts +20 -0
- package/dist/components/cv-tab-panel.js +37 -0
- package/dist/components/cv-tab.d.ts +40 -0
- package/dist/components/cv-tab.js +132 -0
- package/dist/components/cv-table-cell.d.ts +31 -0
- package/dist/components/cv-table-cell.js +49 -0
- package/dist/components/cv-table-column.d.ts +37 -0
- package/dist/components/cv-table-column.js +63 -0
- package/dist/components/cv-table-row.d.ts +30 -0
- package/dist/components/cv-table-row.js +45 -0
- package/dist/components/cv-table.d.ts +147 -0
- package/dist/components/cv-table.js +607 -0
- package/dist/components/cv-tabs.d.ts +70 -0
- package/dist/components/cv-tabs.js +524 -0
- package/dist/components/cv-textarea.d.ts +108 -0
- package/dist/components/cv-textarea.js +328 -0
- package/dist/components/cv-toast-region.d.ts +39 -0
- package/dist/components/cv-toast-region.js +162 -0
- package/dist/components/cv-toast.d.ts +67 -0
- package/dist/components/cv-toast.js +315 -0
- package/dist/components/cv-toolbar-item.d.ts +25 -0
- package/dist/components/cv-toolbar-item.js +72 -0
- package/dist/components/cv-toolbar-separator.d.ts +25 -0
- package/dist/components/cv-toolbar-separator.js +45 -0
- package/dist/components/cv-toolbar.d.ts +63 -0
- package/dist/components/cv-toolbar.js +295 -0
- package/dist/components/cv-tooltip.d.ts +83 -0
- package/dist/components/cv-tooltip.js +455 -0
- package/dist/components/cv-treegrid-cell.d.ts +30 -0
- package/dist/components/cv-treegrid-cell.js +57 -0
- package/dist/components/cv-treegrid-column.d.ts +37 -0
- package/dist/components/cv-treegrid-column.js +53 -0
- package/dist/components/cv-treegrid-row.d.ts +55 -0
- package/dist/components/cv-treegrid-row.js +90 -0
- package/dist/components/cv-treegrid.d.ts +96 -0
- package/dist/components/cv-treegrid.js +632 -0
- package/dist/components/cv-treeitem.d.ts +58 -0
- package/dist/components/cv-treeitem.js +144 -0
- package/dist/components/cv-treeview.d.ts +70 -0
- package/dist/components/cv-treeview.js +396 -0
- package/dist/components/cv-window-splitter.d.ts +79 -0
- package/dist/components/cv-window-splitter.js +316 -0
- package/dist/components/index.d.ts +94 -0
- package/dist/components/index.js +79 -0
- package/dist/dialog/create-dialog-controller.d.ts +31 -0
- package/dist/dialog/create-dialog-controller.js +320 -0
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +1 -0
- package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
- package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
- package/dist/form-associated/withFormAssociated.d.ts +5 -0
- package/dist/form-associated/withFormAssociated.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
- package/dist/reatom-lit/ReatomLitElement.js +118 -0
- package/dist/reatom-lit/html.d.ts +4 -0
- package/dist/reatom-lit/html.js +10 -0
- package/dist/reatom-lit/index.d.ts +4 -0
- package/dist/reatom-lit/index.js +4 -0
- package/dist/reatom-lit/watch.d.ts +15 -0
- package/dist/reatom-lit/watch.js +40 -0
- package/dist/reatom-lit/withReatomElement.d.ts +4 -0
- package/dist/reatom-lit/withReatomElement.js +57 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +84 -0
- package/dist/styles/component-styles.d.ts +4 -0
- package/dist/styles/component-styles.js +78 -0
- package/dist/theme/cv-theme-provider.d.ts +32 -0
- package/dist/theme/cv-theme-provider.js +110 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/theme-engine.d.ts +4 -0
- package/dist/theme/theme-engine.js +67 -0
- package/dist/theme/tokens.css +265 -0
- package/dist/theme/types.d.ts +7 -0
- package/dist/theme/types.js +1 -0
- package/dist/toast/create-toast-controller.d.ts +12 -0
- package/dist/toast/create-toast-controller.js +12 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +1 -0
- package/package.json +146 -0
- package/specs/_template.md +110 -0
- package/specs/components/accordion.md +207 -0
- package/specs/components/alert.md +83 -0
- package/specs/components/badge.md +183 -0
- package/specs/components/breadcrumb.md +152 -0
- package/specs/components/button.md +227 -0
- package/specs/components/callout.md +153 -0
- package/specs/components/card.md +192 -0
- package/specs/components/carousel.md +232 -0
- package/specs/components/checkbox.md +141 -0
- package/specs/components/combobox.md +427 -0
- package/specs/components/context-menu.md +375 -0
- package/specs/components/copy-button.md +236 -0
- package/specs/components/date-picker.md +290 -0
- package/specs/components/dialog.md +184 -0
- package/specs/components/disclosure.md +151 -0
- package/specs/components/drawer.md +216 -0
- package/specs/components/feed.md +266 -0
- package/specs/components/grid.md +423 -0
- package/specs/components/input.md +237 -0
- package/specs/components/landmark.md +92 -0
- package/specs/components/link.md +117 -0
- package/specs/components/listbox.md +327 -0
- package/specs/components/menu.md +508 -0
- package/specs/components/meter.md +148 -0
- package/specs/components/number.md +268 -0
- package/specs/components/option.md +167 -0
- package/specs/components/popover.md +207 -0
- package/specs/components/progress-ring.md +134 -0
- package/specs/components/progress.md +110 -0
- package/specs/components/radio.md +208 -0
- package/specs/components/select.md +305 -0
- package/specs/components/sidebar.md +204 -0
- package/specs/components/spinbutton.md +157 -0
- package/specs/components/spinner.md +83 -0
- package/specs/components/switch.md +145 -0
- package/specs/components/table.md +372 -0
- package/specs/components/tabs.md +242 -0
- package/specs/components/textarea.md +166 -0
- package/specs/components/theme.md +364 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +258 -0
- package/specs/components/tooltip.md +152 -0
- package/specs/components/treegrid.md +363 -0
- package/specs/components/treeview.md +263 -0
- package/specs/components/window-splitter.md +225 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# cv-listbox
|
|
2
|
+
|
|
3
|
+
Standalone listbox widget for single or multiple selection from a list of options, with keyboard navigation, typeahead, optional grouping, and virtual scroll support.
|
|
4
|
+
|
|
5
|
+
**Headless:** [`createListbox`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/listbox.md)
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<cv-listbox> (host)
|
|
11
|
+
└── <div part="base" role="listbox">
|
|
12
|
+
└── <slot> ← accepts <cv-option> and <cv-listbox-group> children
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Attributes
|
|
16
|
+
|
|
17
|
+
| Attribute | Type | Default | Description |
|
|
18
|
+
| ------------------------- | ------- | ------------------------- | ----------------------------------------------------------------------- |
|
|
19
|
+
| `selection-mode` | String | `"single"` | Selection mode: `"single"` \| `"multiple"` |
|
|
20
|
+
| `orientation` | String | `"vertical"` | Layout orientation: `"vertical"` \| `"horizontal"` |
|
|
21
|
+
| `focus-strategy` | String | `"aria-activedescendant"` | Focus management: `"aria-activedescendant"` \| `"roving-tabindex"` |
|
|
22
|
+
| `selection-follows-focus` | Boolean | `false` | Auto-select focused option in single mode |
|
|
23
|
+
| `range-selection` | Boolean | `false` | Enable Shift+Arrow and Shift+Space range selection (multiple mode only) |
|
|
24
|
+
| `typeahead` | Boolean | `true` | Enable typeahead character navigation |
|
|
25
|
+
| `aria-label` | String | `""` | Accessible label for the listbox |
|
|
26
|
+
|
|
27
|
+
Non-reflected properties:
|
|
28
|
+
|
|
29
|
+
| Property | Type | Default | Description |
|
|
30
|
+
| ---------------- | ---------------- | ------- | ----------------------------------------------------- |
|
|
31
|
+
| `value` | `string \| null` | `null` | First selected option value (single-select shorthand) |
|
|
32
|
+
| `selectedValues` | `string[]` | `[]` | Array of all selected option values |
|
|
33
|
+
|
|
34
|
+
## Slots
|
|
35
|
+
|
|
36
|
+
| Slot | Description |
|
|
37
|
+
| ----------- | ---------------------------------------------------------- |
|
|
38
|
+
| `(default)` | One or more `<cv-option>` or `<cv-listbox-group>` children |
|
|
39
|
+
|
|
40
|
+
## CSS Parts
|
|
41
|
+
|
|
42
|
+
| Part | Element | Description |
|
|
43
|
+
| ------ | ------- | ------------------------------------------ |
|
|
44
|
+
| `base` | `<div>` | Root listbox element with `role="listbox"` |
|
|
45
|
+
|
|
46
|
+
## CSS Custom Properties
|
|
47
|
+
|
|
48
|
+
| Property | Default | Description |
|
|
49
|
+
| ---------------------------------- | ---------------------------------- | -------------------------------------- |
|
|
50
|
+
| `--cv-listbox-gap` | `var(--cv-space-1, 4px)` | Gap between options |
|
|
51
|
+
| `--cv-listbox-padding` | `var(--cv-space-1, 4px)` | Inner padding of the listbox container |
|
|
52
|
+
| `--cv-listbox-border-radius` | `var(--cv-radius-md, 10px)` | Border radius of the listbox container |
|
|
53
|
+
| `--cv-listbox-border-color` | `var(--cv-color-border, #2a3245)` | Border color |
|
|
54
|
+
| `--cv-listbox-background` | `var(--cv-color-surface, #141923)` | Background color |
|
|
55
|
+
| `--cv-listbox-focus-outline-color` | `var(--cv-color-primary, #65d7ff)` | Focus-visible outline color |
|
|
56
|
+
|
|
57
|
+
## Visual States
|
|
58
|
+
|
|
59
|
+
| Host selector | Description |
|
|
60
|
+
| ------------------------------------------- | ----------------------------------------- |
|
|
61
|
+
| `:host([orientation="horizontal"])` | Horizontal layout (flexbox row direction) |
|
|
62
|
+
| `:host([selection-mode="multiple"])` | Multiple selection mode active |
|
|
63
|
+
| `:host([focus-strategy="roving-tabindex"])` | Options receive DOM focus directly |
|
|
64
|
+
|
|
65
|
+
## Events
|
|
66
|
+
|
|
67
|
+
| Event | Detail | Description |
|
|
68
|
+
| ----------- | --------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
69
|
+
| `cv-input` | `{selectedValues: string[], activeValue: string \| null}` | Fires when active option or selection changes via user interaction |
|
|
70
|
+
| `cv-change` | `{selectedValues: string[], activeValue: string \| null}` | Fires when selected option(s) change via user interaction |
|
|
71
|
+
|
|
72
|
+
## Keyboard Interaction
|
|
73
|
+
|
|
74
|
+
All keyboard handling is delegated to headless `actions.handleKeyDown`. The following is the resulting behavior:
|
|
75
|
+
|
|
76
|
+
| Key | Context | Action |
|
|
77
|
+
| ---------------------------- | -------------------------- | --------------------------------------- |
|
|
78
|
+
| `ArrowDown` / `ArrowRight`\* | any | Move to next enabled option |
|
|
79
|
+
| `ArrowUp` / `ArrowLeft`\* | any | Move to previous enabled option |
|
|
80
|
+
| `Home` | any | Move to first enabled option |
|
|
81
|
+
| `End` | any | Move to last enabled option |
|
|
82
|
+
| `Space` / `Enter` | single mode | Select active option exclusively |
|
|
83
|
+
| `Space` / `Enter` | multiple mode | Toggle active option selection |
|
|
84
|
+
| `Escape` | any | Close (for composite patterns) |
|
|
85
|
+
| `Ctrl/Cmd + A` | multiple mode | Select all enabled options |
|
|
86
|
+
| `Shift + Arrow` | multiple + range-selection | Extend range selection |
|
|
87
|
+
| `Shift + Space` | multiple + range-selection | Select range from anchor to active |
|
|
88
|
+
| printable char | typeahead enabled | Typeahead navigation to matching option |
|
|
89
|
+
|
|
90
|
+
\*Arrow key mapping depends on orientation: vertical uses Up/Down, horizontal uses Left/Right.
|
|
91
|
+
|
|
92
|
+
## Reactive State Mapping
|
|
93
|
+
|
|
94
|
+
`cv-listbox` is a visual adapter over headless `createListbox`.
|
|
95
|
+
|
|
96
|
+
### Attribute to Headless (UIKit -> Headless)
|
|
97
|
+
|
|
98
|
+
| UIKit Property | Direction | Headless Binding |
|
|
99
|
+
| ------------------------- | -------------- | ------------------------------------------------------------- |
|
|
100
|
+
| `selection-mode` | attr -> option | passed as `selectionMode` in `createListbox(options)` |
|
|
101
|
+
| `orientation` | attr -> option | passed as `orientation` in `createListbox(options)` |
|
|
102
|
+
| `focus-strategy` | attr -> option | passed as `focusStrategy` in `createListbox(options)` |
|
|
103
|
+
| `selection-follows-focus` | attr -> option | passed as `selectionFollowsFocus` in `createListbox(options)` |
|
|
104
|
+
| `range-selection` | attr -> option | passed as `rangeSelection` in `createListbox(options)` |
|
|
105
|
+
| `typeahead` | attr -> option | passed as `typeahead` in `createListbox(options)` |
|
|
106
|
+
| `aria-label` | attr -> option | passed as `ariaLabel` in `createListbox(options)` |
|
|
107
|
+
| `value` (setter) | prop -> action | `actions.selectOnly(id)` / `actions.clearSelected()` |
|
|
108
|
+
|
|
109
|
+
When any configuration attribute changes, the headless model is rebuilt via `createListbox` with updated options, preserving current selection and active state where still valid.
|
|
110
|
+
|
|
111
|
+
### Headless to DOM (Headless -> UIKit)
|
|
112
|
+
|
|
113
|
+
| Headless State | Direction | DOM Reflection |
|
|
114
|
+
| --------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `state.activeId()` | state -> render | `aria-activedescendant` on `[part="base"]` (activedescendant strategy); DOM focus on active option (roving-tabindex strategy) |
|
|
116
|
+
| `state.selectedIds()` | state -> render | `[aria-selected]` on each `cv-option`; `selectedValues` property; `value` property |
|
|
117
|
+
| `state.selectionMode` | state -> attr | `[selection-mode]` host attribute |
|
|
118
|
+
| `state.focusStrategy` | state -> attr | `[focus-strategy]` host attribute |
|
|
119
|
+
| `state.orientation` | state -> attr | `[orientation]` host attribute |
|
|
120
|
+
| `state.optionCount` | state -> render | `aria-setsize` on each option via `getOptionProps` |
|
|
121
|
+
|
|
122
|
+
### Contract Spreading
|
|
123
|
+
|
|
124
|
+
- `contracts.getRootProps()` is spread onto `[part="base"]` -- applies `role`, `tabindex`, `aria-orientation`, `aria-label`, `aria-multiselectable`, `aria-activedescendant`
|
|
125
|
+
- `contracts.getOptionProps(id)` is spread onto each `cv-option` -- applies `id`, `role`, `tabindex`, `aria-selected`, `aria-disabled`, `aria-setsize`, `aria-posinset`, `data-active`
|
|
126
|
+
- `contracts.getGroupProps(groupId)` is spread onto each `cv-listbox-group` shadow root group container -- applies `id`, `role`, `aria-labelledby`
|
|
127
|
+
- `contracts.getGroupLabelProps(groupId)` is spread onto each group label element -- applies `id`, `role`
|
|
128
|
+
- `contracts.getGroupOptions(groupId)` drives which options render within a group
|
|
129
|
+
- `contracts.getUngroupedOptions()` drives which options render outside any group
|
|
130
|
+
|
|
131
|
+
### UIKit-Only Concerns (NOT in headless)
|
|
132
|
+
|
|
133
|
+
- Option visual styling (active highlight, selected highlight, disabled opacity)
|
|
134
|
+
- Group visual styling (label header, indentation)
|
|
135
|
+
- Virtual scroll viewport management and option recycling
|
|
136
|
+
- `cv-input` and `cv-change` event dispatch based on state diffing after user interactions
|
|
137
|
+
- `preventDefault` on navigation keys to prevent page scroll
|
|
138
|
+
- Slot change detection to rebuild the headless model when child options are added/removed
|
|
139
|
+
|
|
140
|
+
## Behavioral Contract
|
|
141
|
+
|
|
142
|
+
### Option Collection
|
|
143
|
+
|
|
144
|
+
- `cv-listbox` scans its light DOM children (direct `cv-option` and `cv-option` within `cv-listbox-group`) to build the options array for the headless model
|
|
145
|
+
- Each `cv-option` must have a `value` attribute; if omitted, an auto-generated fallback `option-{n}` is assigned
|
|
146
|
+
- The `textContent` of each `cv-option` is used as the option label for typeahead matching
|
|
147
|
+
- Initial selection is read from `cv-option[selected]` attributes at first render
|
|
148
|
+
- On `slotchange`, the model is rebuilt with the updated option list, preserving still-valid selection and active state
|
|
149
|
+
|
|
150
|
+
### Pointer Interaction
|
|
151
|
+
|
|
152
|
+
- Clicking a `cv-option` calls `actions.setActive(id)` followed by `actions.selectOnly(id)` (single) or `actions.toggleSelected(id)` (multiple)
|
|
153
|
+
- Pointer interactions dispatch `cv-input` and `cv-change` events based on state diffing
|
|
154
|
+
|
|
155
|
+
### Focus Management
|
|
156
|
+
|
|
157
|
+
- When `focus-strategy="aria-activedescendant"` (default): `[part="base"]` has `tabindex="0"` and receives DOM focus; `aria-activedescendant` points to the active option; all options have `tabindex="-1"`
|
|
158
|
+
- When `focus-strategy="roving-tabindex"`: `[part="base"]` has `tabindex="-1"`; the active option has `tabindex="0"` and receives DOM focus; other options have `tabindex="-1"`
|
|
159
|
+
|
|
160
|
+
### Virtual Scroll Support
|
|
161
|
+
|
|
162
|
+
- `aria-setsize` and `aria-posinset` from `getOptionProps` support virtual scrolling
|
|
163
|
+
- When using virtual scrolling, only a subset of options is rendered, but each carries correct setsize/posinset reflecting the full option list
|
|
164
|
+
- Virtual scroll viewport management is a UIKit concern, not headless
|
|
165
|
+
|
|
166
|
+
## Usage
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<!-- Basic single-select listbox -->
|
|
170
|
+
<cv-listbox aria-label="Fruits">
|
|
171
|
+
<cv-option value="apple">Apple</cv-option>
|
|
172
|
+
<cv-option value="banana">Banana</cv-option>
|
|
173
|
+
<cv-option value="cherry">Cherry</cv-option>
|
|
174
|
+
</cv-listbox>
|
|
175
|
+
|
|
176
|
+
<!-- Multi-select listbox -->
|
|
177
|
+
<cv-listbox selection-mode="multiple" aria-label="Toppings">
|
|
178
|
+
<cv-option value="cheese">Cheese</cv-option>
|
|
179
|
+
<cv-option value="peppers">Peppers</cv-option>
|
|
180
|
+
<cv-option value="onions">Onions</cv-option>
|
|
181
|
+
<cv-option value="olives" disabled>Olives</cv-option>
|
|
182
|
+
</cv-listbox>
|
|
183
|
+
|
|
184
|
+
<!-- With pre-selected options -->
|
|
185
|
+
<cv-listbox selection-mode="multiple" aria-label="Languages">
|
|
186
|
+
<cv-option value="js" selected>JavaScript</cv-option>
|
|
187
|
+
<cv-option value="ts" selected>TypeScript</cv-option>
|
|
188
|
+
<cv-option value="py">Python</cv-option>
|
|
189
|
+
<cv-option value="rs">Rust</cv-option>
|
|
190
|
+
</cv-listbox>
|
|
191
|
+
|
|
192
|
+
<!-- Roving tabindex focus strategy -->
|
|
193
|
+
<cv-listbox focus-strategy="roving-tabindex" aria-label="Colors">
|
|
194
|
+
<cv-option value="red">Red</cv-option>
|
|
195
|
+
<cv-option value="green">Green</cv-option>
|
|
196
|
+
<cv-option value="blue">Blue</cv-option>
|
|
197
|
+
</cv-listbox>
|
|
198
|
+
|
|
199
|
+
<!-- Horizontal orientation -->
|
|
200
|
+
<cv-listbox orientation="horizontal" aria-label="Alignment">
|
|
201
|
+
<cv-option value="left">Left</cv-option>
|
|
202
|
+
<cv-option value="center">Center</cv-option>
|
|
203
|
+
<cv-option value="right">Right</cv-option>
|
|
204
|
+
</cv-listbox>
|
|
205
|
+
|
|
206
|
+
<!-- With range selection -->
|
|
207
|
+
<cv-listbox selection-mode="multiple" range-selection aria-label="Files">
|
|
208
|
+
<cv-option value="file1">document.pdf</cv-option>
|
|
209
|
+
<cv-option value="file2">image.png</cv-option>
|
|
210
|
+
<cv-option value="file3">notes.txt</cv-option>
|
|
211
|
+
<cv-option value="file4">data.csv</cv-option>
|
|
212
|
+
</cv-listbox>
|
|
213
|
+
|
|
214
|
+
<!-- With option groups -->
|
|
215
|
+
<cv-listbox aria-label="City">
|
|
216
|
+
<cv-listbox-group label="North America">
|
|
217
|
+
<cv-option value="nyc">New York</cv-option>
|
|
218
|
+
<cv-option value="la">Los Angeles</cv-option>
|
|
219
|
+
<cv-option value="tor">Toronto</cv-option>
|
|
220
|
+
</cv-listbox-group>
|
|
221
|
+
<cv-listbox-group label="Europe">
|
|
222
|
+
<cv-option value="lon">London</cv-option>
|
|
223
|
+
<cv-option value="par">Paris</cv-option>
|
|
224
|
+
<cv-option value="ber">Berlin</cv-option>
|
|
225
|
+
</cv-listbox-group>
|
|
226
|
+
</cv-listbox>
|
|
227
|
+
|
|
228
|
+
<!-- Mixed grouped and ungrouped options -->
|
|
229
|
+
<cv-listbox aria-label="Items">
|
|
230
|
+
<cv-option value="misc1">Miscellaneous A</cv-option>
|
|
231
|
+
<cv-listbox-group label="Category 1">
|
|
232
|
+
<cv-option value="cat1a">Item 1A</cv-option>
|
|
233
|
+
<cv-option value="cat1b">Item 1B</cv-option>
|
|
234
|
+
</cv-listbox-group>
|
|
235
|
+
<cv-option value="misc2">Miscellaneous B</cv-option>
|
|
236
|
+
</cv-listbox>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Child Elements
|
|
240
|
+
|
|
241
|
+
### cv-option
|
|
242
|
+
|
|
243
|
+
Individual selectable option within a `cv-listbox` or `cv-listbox-group`. The parent `cv-listbox` manages all ARIA attributes on this element via headless contracts.
|
|
244
|
+
|
|
245
|
+
#### Anatomy
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
<cv-option> (host)
|
|
249
|
+
└── <div part="base">
|
|
250
|
+
└── <slot>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### Attributes
|
|
254
|
+
|
|
255
|
+
| Attribute | Type | Default | Description |
|
|
256
|
+
| ---------- | ------- | ------- | ----------------------------------------------------------------------------- |
|
|
257
|
+
| `value` | String | `""` | Unique identifier for this option. Auto-generated as `option-{n}` if omitted. |
|
|
258
|
+
| `disabled` | Boolean | `false` | Whether the option is disabled |
|
|
259
|
+
| `selected` | Boolean | `false` | Whether the option is selected. Managed by parent. |
|
|
260
|
+
| `active` | Boolean | `false` | Whether the option is the active (highlighted) option. Managed by parent. |
|
|
261
|
+
|
|
262
|
+
#### Slots
|
|
263
|
+
|
|
264
|
+
| Slot | Description |
|
|
265
|
+
| ----------- | -------------------- |
|
|
266
|
+
| `(default)` | Option label content |
|
|
267
|
+
|
|
268
|
+
#### CSS Parts
|
|
269
|
+
|
|
270
|
+
| Part | Element | Description |
|
|
271
|
+
| ------ | ------- | ----------------------------------- |
|
|
272
|
+
| `base` | `<div>` | Root wrapper for the option content |
|
|
273
|
+
|
|
274
|
+
#### Visual States
|
|
275
|
+
|
|
276
|
+
| Host selector | Description |
|
|
277
|
+
| ----------------------- | -------------------------------------------------------------------- |
|
|
278
|
+
| `:host([selected])` | Option is currently selected (primary tint at 34%) |
|
|
279
|
+
| `:host([active])` | Option is the active/highlighted option (primary tint at 22%) |
|
|
280
|
+
| `:host([disabled])` | Option is disabled (opacity 0.55) |
|
|
281
|
+
| `:host(:focus-visible)` | Focus ring when option receives DOM focus (roving-tabindex strategy) |
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### cv-listbox-group
|
|
286
|
+
|
|
287
|
+
Groups related options under a visible label header. Must be a direct child of `cv-listbox`.
|
|
288
|
+
|
|
289
|
+
#### Anatomy
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
<cv-listbox-group> (host)
|
|
293
|
+
├── <div part="label"> ← group label text
|
|
294
|
+
└── <slot> ← accepts <cv-option> children
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Attributes
|
|
298
|
+
|
|
299
|
+
| Attribute | Type | Default | Description |
|
|
300
|
+
| --------- | ------ | ------- | ----------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| `label` | String | `""` | Visible group header text. Also used for `aria-labelledby` linkage via headless `getGroupLabelProps`. |
|
|
302
|
+
|
|
303
|
+
#### Slots
|
|
304
|
+
|
|
305
|
+
| Slot | Description |
|
|
306
|
+
| ----------- | ---------------------------------- |
|
|
307
|
+
| `(default)` | One or more `<cv-option>` children |
|
|
308
|
+
|
|
309
|
+
#### CSS Parts
|
|
310
|
+
|
|
311
|
+
| Part | Element | Description |
|
|
312
|
+
| ------- | ------- | ------------------------ |
|
|
313
|
+
| `label` | `<div>` | Group label text element |
|
|
314
|
+
|
|
315
|
+
#### CSS Custom Properties
|
|
316
|
+
|
|
317
|
+
| Property | Default | Description |
|
|
318
|
+
| ------------------------------------ | ------------------------------------- | ----------------------------------- |
|
|
319
|
+
| `--cv-listbox-group-label-color` | `var(--cv-color-text-muted, #8892a6)` | Group label text color |
|
|
320
|
+
| `--cv-listbox-group-label-font-size` | `0.85em` | Group label font size |
|
|
321
|
+
| `--cv-listbox-group-gap` | `var(--cv-space-1, 4px)` | Gap between group label and options |
|
|
322
|
+
|
|
323
|
+
#### Visual States
|
|
324
|
+
|
|
325
|
+
| Host selector | Description |
|
|
326
|
+
| ------------- | ------------------------------------------------- |
|
|
327
|
+
| `:host` | Block display with group role and aria-labelledby |
|