@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,208 @@
1
+ # cv-radio-group
2
+
3
+ Set of mutually exclusive options where only one can be selected at a time.
4
+
5
+ **Headless:** [`createRadioGroup`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/radio-group.md)
6
+
7
+ ## Cross-Spec Consistency
8
+
9
+ This document is the UIKit surface contract for Radio Group.
10
+
11
+ - The canonical state model, invariants, and user-driven transitions are defined by the headless spec.
12
+ - Any intentional divergence between UIKit and headless MUST be explicitly documented in both specs to prevent drift.
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ <cv-radio-group> (host)
18
+ └── <div part="base" role="radiogroup">
19
+ └── <slot> ← accepts <cv-radio> children
20
+ ```
21
+
22
+ ## Attributes
23
+
24
+ | Attribute | Type | Default | Description |
25
+ | ------------- | ------- | -------------- | ------------------------------------- |
26
+ | `value` | String | `""` | Value of the currently selected radio |
27
+ | `orientation` | String | `"horizontal"` | Layout: `horizontal` \| `vertical` |
28
+ | `disabled` | Boolean | `false` | Prevents interaction for all radios |
29
+ | `aria-label` | String | `""` | Accessible label for the group |
30
+
31
+ ## Slots
32
+
33
+ | Slot | Description |
34
+ | ----------- | --------------------- |
35
+ | `(default)` | `<cv-radio>` children |
36
+
37
+ ## CSS Parts
38
+
39
+ | Part | Element | Description |
40
+ | ------ | ------- | ---------------------------------------------- |
41
+ | `base` | `<div>` | Root layout container with `role="radiogroup"` |
42
+
43
+ ## CSS Custom Properties
44
+
45
+ | Property | Default | Description |
46
+ | ---------------------- | ------------------------ | --------------------------- |
47
+ | `--cv-radio-group-gap` | `var(--cv-space-2, 8px)` | Spacing between radio items |
48
+
49
+ ## Visual States
50
+
51
+ | Host selector | Description |
52
+ | --------------------------------- | ------------------------------------ |
53
+ | `:host([disabled])` | All child radios are non-interactive |
54
+ | `:host([orientation="vertical"])` | Items stacked vertically |
55
+
56
+ ## Reactive State Mapping
57
+
58
+ `cv-radio-group` is a visual adapter over headless `createRadioGroup`.
59
+
60
+ | UIKit Property | Direction | Headless Binding |
61
+ | -------------- | ------------- | -------------------------------------------------------------------------- |
62
+ | `value` | attr → action | `actions.select(value)` on change; initial passed as `initialValue` option |
63
+ | `disabled` | attr → action | `actions.setDisabled(value)` |
64
+ | `orientation` | attr → option | passed as `orientation` in `createRadioGroup(options)` |
65
+ | `aria-label` | attr → option | passed as `ariaLabel` in `createRadioGroup(options)` |
66
+
67
+ | Headless State | Direction | DOM Reflection |
68
+ | -------------------- | ------------ | ------------------------------------------------------------ |
69
+ | `state.value()` | state → attr | `[value]` host attribute; reflected onto `cv-radio[checked]` |
70
+ | `state.activeId()` | state → attr | reflected onto `cv-radio[active]` |
71
+ | `state.isDisabled()` | state → attr | `[disabled]` host attribute |
72
+
73
+ - `contracts.getRootProps()` is spread onto the inner `[part="base"]` element to apply `role`, `aria-label`, `aria-disabled`, `aria-orientation`, and `onKeyDown` handler.
74
+ - `contracts.getRadioProps(id)` is spread onto each `cv-radio` child to apply `role`, `tabindex`, `aria-checked`, `aria-disabled`, `aria-describedby`, `data-active`, `onClick`, and `onKeyDown`.
75
+ - UIKit dispatches `cv-input` and `cv-change` events by observing `state.value()` changes triggered by user activation (not by controlled attribute updates).
76
+ - UIKit does not own selection or navigation logic; headless state is the source of truth.
77
+
78
+ ## Events
79
+
80
+ | Event | Detail | Description |
81
+ | ----------- | ----------------------------------- | ----------------------------------- |
82
+ | `cv-input` | `{value: string, activeId: string}` | Fires on user selection interaction |
83
+ | `cv-change` | `{value: string, activeId: string}` | Fires when selected value commits |
84
+
85
+ ## Usage
86
+
87
+ ```html
88
+ <cv-radio-group value="opt-1">
89
+ <cv-radio value="opt-1">Option 1</cv-radio>
90
+ <cv-radio value="opt-2">Option 2</cv-radio>
91
+ <cv-radio value="opt-3" disabled>Option 3</cv-radio>
92
+ </cv-radio-group>
93
+
94
+ <cv-radio-group orientation="vertical" aria-label="Payment method">
95
+ <cv-radio value="card">Credit card</cv-radio>
96
+ <cv-radio value="paypal">PayPal</cv-radio>
97
+ <cv-radio value="bank">Bank transfer</cv-radio>
98
+ </cv-radio-group>
99
+
100
+ <cv-radio-group disabled>
101
+ <cv-radio value="a">Disabled A</cv-radio>
102
+ <cv-radio value="b">Disabled B</cv-radio>
103
+ </cv-radio-group>
104
+
105
+ <cv-radio-group value="med">
106
+ <cv-radio value="sm" size="small">Small radio</cv-radio>
107
+ <cv-radio value="med" size="medium">Medium radio</cv-radio>
108
+ <cv-radio value="lg" size="large">Large radio</cv-radio>
109
+ </cv-radio-group>
110
+
111
+ <cv-radio-group value="with-desc">
112
+ <cv-radio value="with-desc">
113
+ Primary option
114
+ <span slot="description">Additional details about this option</span>
115
+ </cv-radio>
116
+ <cv-radio value="other">Other option</cv-radio>
117
+ </cv-radio-group>
118
+ ```
119
+
120
+ ## Child Elements
121
+
122
+ ### cv-radio
123
+
124
+ Individual radio option within a radio group. Purely presentational — all state and ARIA are managed by the parent `cv-radio-group`.
125
+
126
+ #### Anatomy
127
+
128
+ ```
129
+ <cv-radio> (host)
130
+ └── <div part="base">
131
+ ├── <span part="indicator">
132
+ │ └── <span part="dot">
133
+ ├── <span part="label">
134
+ │ └── <slot>
135
+ └── <span part="description">
136
+ └── <slot name="description">
137
+ ```
138
+
139
+ #### Attributes
140
+
141
+ | Attribute | Type | Default | Description |
142
+ | ---------- | ------- | ---------- | ------------------------------------------------------ |
143
+ | `value` | String | `""` | Unique identifier for this radio option |
144
+ | `disabled` | Boolean | `false` | Prevents interaction for this radio |
145
+ | `checked` | Boolean | `false` | Whether this radio is selected (managed by group) |
146
+ | `active` | Boolean | `false` | Whether this radio has roving focus (managed by group) |
147
+ | `size` | String | `"medium"` | Size: `small` \| `medium` \| `large` |
148
+
149
+ #### Sizes
150
+
151
+ | Size | `--cv-radio-indicator-size` | `--cv-radio-dot-size` |
152
+ | -------- | --------------------------- | --------------------- |
153
+ | `small` | `16px` | `6px` |
154
+ | `medium` | `20px` | `8px` |
155
+ | `large` | `24px` | `10px` |
156
+
157
+ #### Slots
158
+
159
+ | Slot | Description |
160
+ | ------------- | ---------------------------------------- |
161
+ | `(default)` | Label text for the radio option |
162
+ | `description` | Secondary text displayed below the label |
163
+
164
+ #### CSS Parts
165
+
166
+ | Part | Element | Description |
167
+ | ------------- | -------- | ------------------------------------------ |
168
+ | `base` | `<div>` | Root layout wrapper |
169
+ | `indicator` | `<span>` | Circular border container for the dot |
170
+ | `dot` | `<span>` | Inner filled circle (visible when checked) |
171
+ | `label` | `<span>` | Wrapper around the default slot |
172
+ | `description` | `<span>` | Wrapper around the `description` slot |
173
+
174
+ #### CSS Custom Properties
175
+
176
+ | Property | Default | Description |
177
+ | --------------------------- | ------------------------ | ----------------------------------- |
178
+ | `--cv-radio-indicator-size` | `20px` | Outer size of the radio circle |
179
+ | `--cv-radio-dot-size` | `8px` | Inner dot size when checked |
180
+ | `--cv-radio-gap` | `var(--cv-space-2, 8px)` | Spacing between indicator and label |
181
+
182
+ #### Visual States
183
+
184
+ | Host selector | Description |
185
+ | ----------------------- | ----------------------------------------------- |
186
+ | `:host([checked])` | Primary-tinted indicator border, dot visible |
187
+ | `:host([disabled])` | Reduced opacity (`0.55`), `cursor: not-allowed` |
188
+ | `:host([active])` | Focused radio in roving tabindex model |
189
+ | `:host(:focus-visible)` | Focus ring on the host element |
190
+ | `:host([size="small"])` | Small size overrides |
191
+ | `:host([size="large"])` | Large size overrides |
192
+
193
+ #### Events
194
+
195
+ None. All events are dispatched by the parent `cv-radio-group`.
196
+
197
+ ## Parity Matrix (Headless vs UIKit)
198
+
199
+ | Surface | Headless | UIKit |
200
+ | ------------------- | ----------------------------------- | ------------------------------------------------- |
201
+ | Selection model | single selection via `value` atom | `value` attribute on group |
202
+ | Focus model | roving tabindex via `activeId` atom | `active` attribute on radio |
203
+ | Disabled semantics | group-level + per-item | `disabled` on group + individual radio |
204
+ | Navigation | arrow keys with wrapping, Home/End | delegated to headless `handleKeyDown` |
205
+ | Description linkage | `describedBy` on `RadioGroupItem` | `description` slot with `aria-describedby` |
206
+ | Size | not applicable | `small` \| `medium` \| `large` on radio |
207
+ | Orientation | `orientation` option | `orientation` attribute on group |
208
+ | Events | N/A (actions/state API) | `cv-input` / `cv-change` with `{value, activeId}` |
@@ -0,0 +1,305 @@
1
+ # cv-select
2
+
3
+ Single or multi-selection dropdown that composes a combobox trigger with a listbox popup, following the W3C APG Select-Only Combobox pattern.
4
+
5
+ **Headless:** [`createSelect`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/select.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-select> (host)
11
+ └── <div part="base">
12
+ ├── <div part="trigger" role="combobox">
13
+ │ ├── <slot name="trigger"> ← fallback: selected label / placeholder
14
+ │ ├── <button part="clear-button" aria-hidden="true"> ← only when clearable + has value
15
+ │ └── <span part="chevron" aria-hidden="true">
16
+ └── <div part="listbox" role="listbox">
17
+ └── <slot> ← cv-select-option / cv-select-group children
18
+ ```
19
+
20
+ ## Attributes
21
+
22
+ | Attribute | Type | Default | Description |
23
+ | ----------------- | ------- | ---------- | ----------------------------------------------- |
24
+ | `value` | String | `""` | Currently selected option value (single-select) |
25
+ | `open` | Boolean | `false` | Whether the listbox popup is visible |
26
+ | `selection-mode` | String | `"single"` | Selection mode: `single` \| `multiple` |
27
+ | `aria-label` | String | `""` | Accessible label for the trigger |
28
+ | `close-on-select` | Boolean | `true` | Close popup after an option is selected |
29
+ | `placeholder` | String | `""` | Hint text when no option is selected |
30
+ | `disabled` | Boolean | `false` | Prevents all interaction |
31
+ | `required` | Boolean | `false` | Marks the field as required for form validation |
32
+ | `clearable` | Boolean | `false` | Shows a clear button when a value is selected |
33
+ | `size` | String | `"medium"` | Size: `small` \| `medium` \| `large` |
34
+
35
+ Non-reflected properties:
36
+
37
+ | Property | Type | Default | Description |
38
+ | ---------------- | ---------- | ------- | --------------------------------------------------------- |
39
+ | `selectedValues` | `string[]` | `[]` | Array of selected option values (useful in multiple mode) |
40
+
41
+ ## Sizes
42
+
43
+ | Size | `--cv-select-min-height` | `--cv-select-padding-inline` | `--cv-select-padding-block` |
44
+ | -------- | ------------------------ | ---------------------------- | --------------------------- |
45
+ | `small` | `30px` | `var(--cv-space-2, 8px)` | `var(--cv-space-1, 4px)` |
46
+ | `medium` | `36px` | `var(--cv-space-3, 12px)` | `var(--cv-space-2, 8px)` |
47
+ | `large` | `42px` | `var(--cv-space-4, 16px)` | `var(--cv-space-2, 8px)` |
48
+
49
+ ## Slots
50
+
51
+ | Slot | Description |
52
+ | ----------- | ------------------------------------------------------------- |
53
+ | `(default)` | `cv-select-option` and `cv-select-group` children |
54
+ | `trigger` | Custom trigger content (replaces default selected label text) |
55
+
56
+ ## CSS Parts
57
+
58
+ | Part | Element | Description |
59
+ | -------------- | ---------- | -------------------------------------------------------------------- |
60
+ | `base` | `<div>` | Root layout wrapper |
61
+ | `trigger` | `<div>` | Combobox trigger that opens/closes the listbox |
62
+ | `chevron` | `<span>` | Dropdown arrow indicator |
63
+ | `clear-button` | `<button>` | Clear value button (only rendered when `clearable` and value is set) |
64
+ | `listbox` | `<div>` | Popup container holding options |
65
+
66
+ ## CSS Custom Properties
67
+
68
+ | Property | Default | Description |
69
+ | ---------------------------- | ------------------------- | --------------------------------- |
70
+ | `--cv-select-inline-size` | `260px` | Inline size of the host element |
71
+ | `--cv-select-min-height` | `36px` | Minimum block size of the trigger |
72
+ | `--cv-select-padding-inline` | `var(--cv-space-3, 12px)` | Horizontal padding of the trigger |
73
+ | `--cv-select-padding-block` | `var(--cv-space-2, 8px)` | Vertical padding of the trigger |
74
+
75
+ ## Visual States
76
+
77
+ | Host selector | Description |
78
+ | ------------------------------------ | --------------------------------------------------------------- |
79
+ | `:host([open])` | Listbox popup is visible |
80
+ | `:host([selection-mode="multiple"])` | Multiple selection mode active |
81
+ | `:host([disabled])` | Reduced opacity, `cursor: not-allowed`, all interaction blocked |
82
+ | `:host([required])` | Field is required |
83
+ | `:host([clearable])` | Clear button visible when value is set |
84
+ | `:host([size="small"])` | Small size overrides |
85
+ | `:host([size="large"])` | Large size overrides |
86
+
87
+ ## Events
88
+
89
+ | Event | Detail | Description |
90
+ | ----------- | ------------------------------------------------------------------------------------ | --------------------------------------------------- |
91
+ | `cv-input` | `{value: string \| null, values: string[], activeId: string \| null, open: boolean}` | Fires on any state change (selection, active, open) |
92
+ | `cv-change` | `{value: string \| null, values: string[], activeId: string \| null, open: boolean}` | Fires only when selected value(s) change |
93
+
94
+ ## Keyboard Interaction
95
+
96
+ ### Trigger focused (listbox closed)
97
+
98
+ | Key | Action |
99
+ | -------------------- | ----------------------------------- |
100
+ | `ArrowDown` / `Home` | Open listbox and focus first option |
101
+ | `ArrowUp` / `End` | Open listbox and focus last option |
102
+ | `Enter` / `Space` | Toggle listbox open/close |
103
+
104
+ ### Listbox open (DOM focus remains on trigger)
105
+
106
+ | Key | Action |
107
+ | ----------------- | ------------------------------------------------- |
108
+ | `ArrowDown` | Move visual focus to next option |
109
+ | `ArrowUp` | Move visual focus to previous option |
110
+ | `Home` | Move visual focus to first option |
111
+ | `End` | Move visual focus to last option |
112
+ | `Enter` / `Space` | Select active option (close if `close-on-select`) |
113
+ | `Escape` / `Tab` | Close listbox without changing selection |
114
+
115
+ ### When disabled
116
+
117
+ All keyboard handlers are no-ops.
118
+
119
+ ## ARIA Contract
120
+
121
+ | Element | Attribute | Value |
122
+ | ------- | ----------------------- | ----------------------------------------- |
123
+ | trigger | `role` | `combobox` |
124
+ | trigger | `tabindex` | `0` |
125
+ | trigger | `aria-haspopup` | `listbox` |
126
+ | trigger | `aria-expanded` | `true` / `false` |
127
+ | trigger | `aria-controls` | listbox element id |
128
+ | trigger | `aria-activedescendant` | id of visually focused option (when open) |
129
+ | trigger | `aria-disabled` | `true` (when disabled) |
130
+ | trigger | `aria-required` | `true` (when required) |
131
+ | trigger | `aria-label` | accessible label text |
132
+ | listbox | `role` | `listbox` |
133
+ | listbox | `aria-activedescendant` | id of focused option |
134
+ | listbox | `aria-multiselectable` | `true` (when `selection-mode="multiple"`) |
135
+ | option | `role` | `option` |
136
+ | option | `aria-selected` | `true` / `false` |
137
+ | option | `aria-disabled` | `true` (when disabled) |
138
+
139
+ ## Reactive State Mapping
140
+
141
+ | UIKit Property | Direction | Headless Binding |
142
+ | ---------------- | --------- | ------------------------------------- |
143
+ | `value` | → | `actions.select(id)` on change |
144
+ | `disabled` | → | `actions.setDisabled(value)` |
145
+ | `required` | → | `actions.setRequired(value)` |
146
+ | `open` | ← | `state.isOpen()` |
147
+ | `selectedValues` | ← | `state.selectedIds()` |
148
+ | trigger ARIA | ← | `contracts.getTriggerProps()` spread |
149
+ | listbox ARIA | ← | `contracts.getListboxProps()` spread |
150
+ | option ARIA | ← | `contracts.getOptionProps(id)` spread |
151
+ | trigger label | ← | `contracts.getValueText()` |
152
+
153
+ ## Usage
154
+
155
+ ```html
156
+ <!-- Basic single-select -->
157
+ <cv-select placeholder="Choose a fruit">
158
+ <cv-select-option value="apple">Apple</cv-select-option>
159
+ <cv-select-option value="banana">Banana</cv-select-option>
160
+ <cv-select-option value="cherry">Cherry</cv-select-option>
161
+ </cv-select>
162
+
163
+ <!-- Pre-selected value -->
164
+ <cv-select value="banana">
165
+ <cv-select-option value="apple">Apple</cv-select-option>
166
+ <cv-select-option value="banana">Banana</cv-select-option>
167
+ <cv-select-option value="cherry">Cherry</cv-select-option>
168
+ </cv-select>
169
+
170
+ <!-- With size variant -->
171
+ <cv-select size="small" placeholder="Small select">
172
+ <cv-select-option value="a">Option A</cv-select-option>
173
+ <cv-select-option value="b">Option B</cv-select-option>
174
+ </cv-select>
175
+
176
+ <!-- Disabled -->
177
+ <cv-select disabled placeholder="Cannot interact">
178
+ <cv-select-option value="a">Option A</cv-select-option>
179
+ </cv-select>
180
+
181
+ <!-- Clearable -->
182
+ <cv-select clearable value="apple">
183
+ <cv-select-option value="apple">Apple</cv-select-option>
184
+ <cv-select-option value="banana">Banana</cv-select-option>
185
+ </cv-select>
186
+
187
+ <!-- Required -->
188
+ <cv-select required placeholder="Required field">
189
+ <cv-select-option value="yes">Yes</cv-select-option>
190
+ <cv-select-option value="no">No</cv-select-option>
191
+ </cv-select>
192
+
193
+ <!-- Grouped options -->
194
+ <cv-select placeholder="Choose a color">
195
+ <cv-select-group label="Warm">
196
+ <cv-select-option value="red">Red</cv-select-option>
197
+ <cv-select-option value="orange">Orange</cv-select-option>
198
+ </cv-select-group>
199
+ <cv-select-group label="Cool">
200
+ <cv-select-option value="blue">Blue</cv-select-option>
201
+ <cv-select-option value="green">Green</cv-select-option>
202
+ </cv-select-group>
203
+ </cv-select>
204
+
205
+ <!-- Multiple selection -->
206
+ <cv-select selection-mode="multiple" placeholder="Select tags">
207
+ <cv-select-option value="a11y">Accessibility</cv-select-option>
208
+ <cv-select-option value="perf">Performance</cv-select-option>
209
+ <cv-select-option value="ux">UX</cv-select-option>
210
+ </cv-select>
211
+
212
+ <!-- Disabled option -->
213
+ <cv-select>
214
+ <cv-select-option value="active">Active</cv-select-option>
215
+ <cv-select-option value="archived" disabled>Archived</cv-select-option>
216
+ </cv-select>
217
+
218
+ <!-- Keep open after selection -->
219
+ <cv-select selection-mode="multiple" close-on-select="false">
220
+ <cv-select-option value="a">Option A</cv-select-option>
221
+ <cv-select-option value="b">Option B</cv-select-option>
222
+ </cv-select>
223
+ ```
224
+
225
+ ## Child Elements
226
+
227
+ ### cv-select-option
228
+
229
+ Selectable item within a `cv-select` or `cv-select-group`.
230
+
231
+ #### Anatomy
232
+
233
+ ```
234
+ <cv-select-option> (host)
235
+ └── <div part="base" class="option">
236
+ └── <slot>
237
+ ```
238
+
239
+ #### Attributes
240
+
241
+ | Attribute | Type | Default | Description |
242
+ | ---------- | ------- | ------- | ------------------------------------------------- |
243
+ | `value` | String | `""` | Option value submitted to the parent select |
244
+ | `disabled` | Boolean | `false` | Prevents selection |
245
+ | `selected` | Boolean | `false` | Reflects selected state (managed by parent) |
246
+ | `active` | Boolean | `false` | Reflects active/focused state (managed by parent) |
247
+
248
+ #### Slots
249
+
250
+ | Slot | Description |
251
+ | ----------- | ----------------- |
252
+ | `(default)` | Option label text |
253
+
254
+ #### CSS Parts
255
+
256
+ | Part | Element | Description |
257
+ | ------ | ------- | ------------------- |
258
+ | `base` | `<div>` | Option root wrapper |
259
+
260
+ #### Visual States
261
+
262
+ | Host selector | Description |
263
+ | ------------------- | ----------------------------------------------- |
264
+ | `:host([active])` | Option has keyboard focus (primary tint at 24%) |
265
+ | `:host([selected])` | Option is selected (primary tint at 32%) |
266
+ | `:host([disabled])` | Option is non-selectable (opacity 0.5) |
267
+ | `:host([hidden])` | Option is hidden when listbox is closed |
268
+
269
+ ---
270
+
271
+ ### cv-select-group
272
+
273
+ Groups related options under a visible label.
274
+
275
+ #### Anatomy
276
+
277
+ ```
278
+ <cv-select-group> (host)
279
+ ├── <div part="label" class="label"> ← group label text
280
+ └── <slot> ← cv-select-option children
281
+ ```
282
+
283
+ #### Attributes
284
+
285
+ | Attribute | Type | Default | Description |
286
+ | --------- | ------ | ------- | ---------------- |
287
+ | `label` | String | `""` | Group label text |
288
+
289
+ #### Slots
290
+
291
+ | Slot | Description |
292
+ | ----------- | --------------------------- |
293
+ | `(default)` | `cv-select-option` children |
294
+
295
+ #### CSS Parts
296
+
297
+ | Part | Element | Description |
298
+ | ------- | ------- | ------------------------ |
299
+ | `label` | `<div>` | Group label text element |
300
+
301
+ #### Visual States
302
+
303
+ | Host selector | Description |
304
+ | ----------------- | -------------------------------------- |
305
+ | `:host([hidden])` | Group is hidden when listbox is closed |