@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.
Files changed (246) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +96 -0
  3. package/dist/components/cv-accordion-item.d.ts +69 -0
  4. package/dist/components/cv-accordion-item.js +176 -0
  5. package/dist/components/cv-accordion.d.ts +79 -0
  6. package/dist/components/cv-accordion.js +310 -0
  7. package/dist/components/cv-alert-dialog.d.ts +86 -0
  8. package/dist/components/cv-alert-dialog.js +393 -0
  9. package/dist/components/cv-alert.d.ts +48 -0
  10. package/dist/components/cv-alert.js +156 -0
  11. package/dist/components/cv-badge.d.ts +56 -0
  12. package/dist/components/cv-badge.js +280 -0
  13. package/dist/components/cv-breadcrumb-item.d.ts +35 -0
  14. package/dist/components/cv-breadcrumb-item.js +64 -0
  15. package/dist/components/cv-breadcrumb.d.ts +39 -0
  16. package/dist/components/cv-breadcrumb.js +160 -0
  17. package/dist/components/cv-button.d.ts +83 -0
  18. package/dist/components/cv-button.js +541 -0
  19. package/dist/components/cv-callout.d.ts +32 -0
  20. package/dist/components/cv-callout.js +221 -0
  21. package/dist/components/cv-card.d.ts +48 -0
  22. package/dist/components/cv-card.js +269 -0
  23. package/dist/components/cv-carousel-slide.d.ts +25 -0
  24. package/dist/components/cv-carousel-slide.js +51 -0
  25. package/dist/components/cv-carousel.d.ts +96 -0
  26. package/dist/components/cv-carousel.js +457 -0
  27. package/dist/components/cv-checkbox.d.ts +84 -0
  28. package/dist/components/cv-checkbox.js +274 -0
  29. package/dist/components/cv-combobox-group.d.ts +15 -0
  30. package/dist/components/cv-combobox-group.js +34 -0
  31. package/dist/components/cv-combobox-option.d.ts +30 -0
  32. package/dist/components/cv-combobox-option.js +66 -0
  33. package/dist/components/cv-combobox.d.ts +135 -0
  34. package/dist/components/cv-combobox.js +834 -0
  35. package/dist/components/cv-command-item.d.ts +30 -0
  36. package/dist/components/cv-command-item.js +68 -0
  37. package/dist/components/cv-command-palette.d.ts +105 -0
  38. package/dist/components/cv-command-palette.js +578 -0
  39. package/dist/components/cv-context-menu.d.ts +98 -0
  40. package/dist/components/cv-context-menu.js +515 -0
  41. package/dist/components/cv-copy-button.d.ts +61 -0
  42. package/dist/components/cv-copy-button.js +318 -0
  43. package/dist/components/cv-date-picker.d.ts +161 -0
  44. package/dist/components/cv-date-picker.js +803 -0
  45. package/dist/components/cv-dialog.d.ts +89 -0
  46. package/dist/components/cv-dialog.js +459 -0
  47. package/dist/components/cv-disclosure.d.ts +57 -0
  48. package/dist/components/cv-disclosure.js +241 -0
  49. package/dist/components/cv-drawer.d.ts +102 -0
  50. package/dist/components/cv-drawer.js +595 -0
  51. package/dist/components/cv-feed-article.d.ts +26 -0
  52. package/dist/components/cv-feed-article.js +52 -0
  53. package/dist/components/cv-feed.d.ts +62 -0
  54. package/dist/components/cv-feed.js +310 -0
  55. package/dist/components/cv-grid-cell.d.ts +30 -0
  56. package/dist/components/cv-grid-cell.js +57 -0
  57. package/dist/components/cv-grid-column.d.ts +30 -0
  58. package/dist/components/cv-grid-column.js +43 -0
  59. package/dist/components/cv-grid-row.d.ts +30 -0
  60. package/dist/components/cv-grid-row.js +42 -0
  61. package/dist/components/cv-grid.d.ts +119 -0
  62. package/dist/components/cv-grid.js +567 -0
  63. package/dist/components/cv-icon.d.ts +57 -0
  64. package/dist/components/cv-icon.js +352 -0
  65. package/dist/components/cv-input.d.ts +127 -0
  66. package/dist/components/cv-input.js +482 -0
  67. package/dist/components/cv-landmark.d.ts +32 -0
  68. package/dist/components/cv-landmark.js +62 -0
  69. package/dist/components/cv-link.d.ts +22 -0
  70. package/dist/components/cv-link.js +99 -0
  71. package/dist/components/cv-listbox-group.d.ts +15 -0
  72. package/dist/components/cv-listbox-group.js +42 -0
  73. package/dist/components/cv-listbox.d.ts +81 -0
  74. package/dist/components/cv-listbox.js +388 -0
  75. package/dist/components/cv-menu-button.d.ts +118 -0
  76. package/dist/components/cv-menu-button.js +822 -0
  77. package/dist/components/cv-menu-group.d.ts +20 -0
  78. package/dist/components/cv-menu-group.js +48 -0
  79. package/dist/components/cv-menu-item.d.ts +52 -0
  80. package/dist/components/cv-menu-item.js +105 -0
  81. package/dist/components/cv-menu.d.ts +62 -0
  82. package/dist/components/cv-menu.js +414 -0
  83. package/dist/components/cv-meter.d.ts +66 -0
  84. package/dist/components/cv-meter.js +154 -0
  85. package/dist/components/cv-number.d.ts +139 -0
  86. package/dist/components/cv-number.js +553 -0
  87. package/dist/components/cv-option.d.ts +30 -0
  88. package/dist/components/cv-option.js +84 -0
  89. package/dist/components/cv-popover.d.ts +87 -0
  90. package/dist/components/cv-popover.js +373 -0
  91. package/dist/components/cv-progress-ring.d.ts +45 -0
  92. package/dist/components/cv-progress-ring.js +169 -0
  93. package/dist/components/cv-progress.d.ts +45 -0
  94. package/dist/components/cv-progress.js +148 -0
  95. package/dist/components/cv-radio-group.d.ts +79 -0
  96. package/dist/components/cv-radio-group.js +398 -0
  97. package/dist/components/cv-radio.d.ts +36 -0
  98. package/dist/components/cv-radio.js +123 -0
  99. package/dist/components/cv-select-group.d.ts +15 -0
  100. package/dist/components/cv-select-group.js +44 -0
  101. package/dist/components/cv-select-option.d.ts +30 -0
  102. package/dist/components/cv-select-option.js +66 -0
  103. package/dist/components/cv-select.d.ts +128 -0
  104. package/dist/components/cv-select.js +666 -0
  105. package/dist/components/cv-sidebar-item.d.ts +26 -0
  106. package/dist/components/cv-sidebar-item.js +142 -0
  107. package/dist/components/cv-sidebar.d.ts +171 -0
  108. package/dist/components/cv-sidebar.js +767 -0
  109. package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
  110. package/dist/components/cv-slider-multi-thumb.js +374 -0
  111. package/dist/components/cv-slider.d.ts +84 -0
  112. package/dist/components/cv-slider.js +328 -0
  113. package/dist/components/cv-spinbutton.d.ts +121 -0
  114. package/dist/components/cv-spinbutton.js +486 -0
  115. package/dist/components/cv-spinner.d.ts +18 -0
  116. package/dist/components/cv-spinner.js +95 -0
  117. package/dist/components/cv-switch.d.ts +81 -0
  118. package/dist/components/cv-switch.js +285 -0
  119. package/dist/components/cv-tab-panel.d.ts +20 -0
  120. package/dist/components/cv-tab-panel.js +37 -0
  121. package/dist/components/cv-tab.d.ts +40 -0
  122. package/dist/components/cv-tab.js +132 -0
  123. package/dist/components/cv-table-cell.d.ts +31 -0
  124. package/dist/components/cv-table-cell.js +49 -0
  125. package/dist/components/cv-table-column.d.ts +37 -0
  126. package/dist/components/cv-table-column.js +63 -0
  127. package/dist/components/cv-table-row.d.ts +30 -0
  128. package/dist/components/cv-table-row.js +45 -0
  129. package/dist/components/cv-table.d.ts +147 -0
  130. package/dist/components/cv-table.js +607 -0
  131. package/dist/components/cv-tabs.d.ts +70 -0
  132. package/dist/components/cv-tabs.js +524 -0
  133. package/dist/components/cv-textarea.d.ts +108 -0
  134. package/dist/components/cv-textarea.js +328 -0
  135. package/dist/components/cv-toast-region.d.ts +39 -0
  136. package/dist/components/cv-toast-region.js +162 -0
  137. package/dist/components/cv-toast.d.ts +67 -0
  138. package/dist/components/cv-toast.js +315 -0
  139. package/dist/components/cv-toolbar-item.d.ts +25 -0
  140. package/dist/components/cv-toolbar-item.js +72 -0
  141. package/dist/components/cv-toolbar-separator.d.ts +25 -0
  142. package/dist/components/cv-toolbar-separator.js +45 -0
  143. package/dist/components/cv-toolbar.d.ts +63 -0
  144. package/dist/components/cv-toolbar.js +295 -0
  145. package/dist/components/cv-tooltip.d.ts +83 -0
  146. package/dist/components/cv-tooltip.js +455 -0
  147. package/dist/components/cv-treegrid-cell.d.ts +30 -0
  148. package/dist/components/cv-treegrid-cell.js +57 -0
  149. package/dist/components/cv-treegrid-column.d.ts +37 -0
  150. package/dist/components/cv-treegrid-column.js +53 -0
  151. package/dist/components/cv-treegrid-row.d.ts +55 -0
  152. package/dist/components/cv-treegrid-row.js +90 -0
  153. package/dist/components/cv-treegrid.d.ts +96 -0
  154. package/dist/components/cv-treegrid.js +632 -0
  155. package/dist/components/cv-treeitem.d.ts +58 -0
  156. package/dist/components/cv-treeitem.js +144 -0
  157. package/dist/components/cv-treeview.d.ts +70 -0
  158. package/dist/components/cv-treeview.js +396 -0
  159. package/dist/components/cv-window-splitter.d.ts +79 -0
  160. package/dist/components/cv-window-splitter.js +316 -0
  161. package/dist/components/index.d.ts +94 -0
  162. package/dist/components/index.js +79 -0
  163. package/dist/dialog/create-dialog-controller.d.ts +31 -0
  164. package/dist/dialog/create-dialog-controller.js +320 -0
  165. package/dist/dialog/index.d.ts +2 -0
  166. package/dist/dialog/index.js +1 -0
  167. package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
  168. package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
  169. package/dist/form-associated/withFormAssociated.d.ts +5 -0
  170. package/dist/form-associated/withFormAssociated.js +1 -0
  171. package/dist/index.d.ts +10 -0
  172. package/dist/index.js +9 -0
  173. package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
  174. package/dist/reatom-lit/ReatomLitElement.js +118 -0
  175. package/dist/reatom-lit/html.d.ts +4 -0
  176. package/dist/reatom-lit/html.js +10 -0
  177. package/dist/reatom-lit/index.d.ts +4 -0
  178. package/dist/reatom-lit/index.js +4 -0
  179. package/dist/reatom-lit/watch.d.ts +15 -0
  180. package/dist/reatom-lit/watch.js +40 -0
  181. package/dist/reatom-lit/withReatomElement.d.ts +4 -0
  182. package/dist/reatom-lit/withReatomElement.js +57 -0
  183. package/dist/register.d.ts +1 -0
  184. package/dist/register.js +84 -0
  185. package/dist/styles/component-styles.d.ts +4 -0
  186. package/dist/styles/component-styles.js +78 -0
  187. package/dist/theme/cv-theme-provider.d.ts +32 -0
  188. package/dist/theme/cv-theme-provider.js +110 -0
  189. package/dist/theme/index.d.ts +4 -0
  190. package/dist/theme/index.js +2 -0
  191. package/dist/theme/theme-engine.d.ts +4 -0
  192. package/dist/theme/theme-engine.js +67 -0
  193. package/dist/theme/tokens.css +265 -0
  194. package/dist/theme/types.d.ts +7 -0
  195. package/dist/theme/types.js +1 -0
  196. package/dist/toast/create-toast-controller.d.ts +12 -0
  197. package/dist/toast/create-toast-controller.js +12 -0
  198. package/dist/toast/index.d.ts +2 -0
  199. package/dist/toast/index.js +1 -0
  200. package/package.json +146 -0
  201. package/specs/_template.md +110 -0
  202. package/specs/components/accordion.md +207 -0
  203. package/specs/components/alert.md +83 -0
  204. package/specs/components/badge.md +183 -0
  205. package/specs/components/breadcrumb.md +152 -0
  206. package/specs/components/button.md +227 -0
  207. package/specs/components/callout.md +153 -0
  208. package/specs/components/card.md +192 -0
  209. package/specs/components/carousel.md +232 -0
  210. package/specs/components/checkbox.md +141 -0
  211. package/specs/components/combobox.md +427 -0
  212. package/specs/components/context-menu.md +375 -0
  213. package/specs/components/copy-button.md +236 -0
  214. package/specs/components/date-picker.md +290 -0
  215. package/specs/components/dialog.md +184 -0
  216. package/specs/components/disclosure.md +151 -0
  217. package/specs/components/drawer.md +216 -0
  218. package/specs/components/feed.md +266 -0
  219. package/specs/components/grid.md +423 -0
  220. package/specs/components/input.md +237 -0
  221. package/specs/components/landmark.md +92 -0
  222. package/specs/components/link.md +117 -0
  223. package/specs/components/listbox.md +327 -0
  224. package/specs/components/menu.md +508 -0
  225. package/specs/components/meter.md +148 -0
  226. package/specs/components/number.md +268 -0
  227. package/specs/components/option.md +167 -0
  228. package/specs/components/popover.md +207 -0
  229. package/specs/components/progress-ring.md +134 -0
  230. package/specs/components/progress.md +110 -0
  231. package/specs/components/radio.md +208 -0
  232. package/specs/components/select.md +305 -0
  233. package/specs/components/sidebar.md +204 -0
  234. package/specs/components/spinbutton.md +157 -0
  235. package/specs/components/spinner.md +83 -0
  236. package/specs/components/switch.md +145 -0
  237. package/specs/components/table.md +372 -0
  238. package/specs/components/tabs.md +242 -0
  239. package/specs/components/textarea.md +166 -0
  240. package/specs/components/theme.md +364 -0
  241. package/specs/components/toast.md +198 -0
  242. package/specs/components/toolbar.md +258 -0
  243. package/specs/components/tooltip.md +152 -0
  244. package/specs/components/treegrid.md +363 -0
  245. package/specs/components/treeview.md +263 -0
  246. 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 |