@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,427 @@
1
+ # cv-combobox
2
+
3
+ Combobox input with popup listbox, supporting editable and select-only modes, single and multi-select, clearable behavior, and grouped options.
4
+
5
+ **Headless:** [`createCombobox`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/combobox.md)
6
+
7
+ ## Cross-Spec Consistency
8
+
9
+ This document is the UIKit surface contract for Combobox.
10
+
11
+ - Headless `createCombobox` is the source of truth for state, transitions, and invariants.
12
+ - UIKit mirrors headless contracts through DOM attributes and events.
13
+ - Any intentional divergence between UIKit and headless MUST be documented in both specs.
14
+
15
+ ## Anatomy
16
+
17
+ ### Editable mode (default)
18
+
19
+ ```
20
+ <cv-combobox> (host)
21
+ └── <div part="base">
22
+ ├── <div part="input-wrapper">
23
+ │ ├── <div part="tags"> ← only when [multiple], contains selected tags
24
+ │ │ ├── <span part="tag"> ← one per selected item (up to max-tags-visible)
25
+ │ │ │ ├── <span part="tag-label">
26
+ │ │ │ └── <button part="tag-remove">
27
+ │ │ └── <span part="tag-overflow"> ← "+N more" when overflow
28
+ │ ├── <input part="input" role="combobox">
29
+ │ ├── <button part="clear-button"> ← only when [clearable] and value is present
30
+ │ └── <span part="expand-icon">
31
+ └── <div part="listbox" role="listbox">
32
+ ├── <div part="group" role="group"> ← one per cv-combobox-group
33
+ │ ├── <div part="group-label" role="presentation">
34
+ │ └── <slot> ← accepts <cv-combobox-option> within group
35
+ └── <slot> ← accepts <cv-combobox-option> (ungrouped)
36
+ ```
37
+
38
+ ### Select-only mode
39
+
40
+ ```
41
+ <cv-combobox type="select-only"> (host)
42
+ └── <div part="base">
43
+ ├── <div part="input-wrapper">
44
+ │ ├── <div part="tags"> ← only when [multiple]
45
+ │ │ └── (same tag structure as editable)
46
+ │ ├── <div part="trigger" role="combobox"> ← replaces <input> in select-only
47
+ │ │ └── <span part="label"> ← selected value text or placeholder
48
+ │ ├── <button part="clear-button"> ← only when [clearable] and value is present
49
+ │ └── <span part="expand-icon">
50
+ └── <div part="listbox" role="listbox">
51
+ └── (same listbox structure as editable)
52
+ ```
53
+
54
+ ## Attributes
55
+
56
+ | Attribute | Type | Default | Description |
57
+ | ------------------ | ------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- |
58
+ | `value` | String | `""` | Selected option id. In multi mode, space-delimited string of selected option values. |
59
+ | `input-value` | String | `""` | Editable input text. Read-only in select-only mode. |
60
+ | `open` | Boolean | `false` | Popup open state |
61
+ | `type` | String | `"editable"` | Combobox mode: `"editable"` \| `"select-only"` |
62
+ | `multiple` | Boolean | `false` | Enables multi-select behavior |
63
+ | `clearable` | Boolean | `false` | Shows clear button when a value is selected |
64
+ | `max-tags-visible` | Number | `3` | Maximum tags shown before "+N more" overflow. `0` = unlimited. Only meaningful when `multiple` is `true`. |
65
+ | `open-on-focus` | Boolean | `true` | Opens popup when input receives focus |
66
+ | `open-on-click` | Boolean | `true` | Opens popup on input/trigger click when closed |
67
+ | `close-on-select` | Boolean | `true` (single) / `false` (multi) | Closes popup after selection commit. Default depends on `multiple`. |
68
+ | `match-mode` | String | `"includes"` | Default filter mode: `includes` \| `startsWith`. Ignored in select-only mode. |
69
+ | `placeholder` | String | `""` | Placeholder text for input or trigger |
70
+ | `disabled` | Boolean | `false` | Prevents interaction |
71
+ | `size` | String | `"medium"` | Size: `small` \| `medium` \| `large` |
72
+ | `aria-label` | String | `""` | Accessible label for input/listbox |
73
+
74
+ ## Sizes
75
+
76
+ | Size | `--cv-combobox-min-height` | `--cv-combobox-padding-inline` |
77
+ | -------- | -------------------------- | ------------------------------ |
78
+ | `small` | `30px` | `var(--cv-space-2, 8px)` |
79
+ | `medium` | `36px` | `var(--cv-space-3, 12px)` |
80
+ | `large` | `42px` | `var(--cv-space-4, 16px)` |
81
+
82
+ ## Slots
83
+
84
+ | Slot | Description |
85
+ | ----------- | -------------------------------------------------------------------- |
86
+ | `(default)` | One or more `<cv-combobox-option>` or `<cv-combobox-group>` children |
87
+ | `prefix` | Icon or element before the input/trigger |
88
+ | `suffix` | Icon or element after the input/trigger (before expand icon) |
89
+
90
+ ## CSS Parts
91
+
92
+ | Part | Element | Description |
93
+ | --------------- | ---------- | ----------------------------------------------------------------- |
94
+ | `base` | `<div>` | Root layout container |
95
+ | `input-wrapper` | `<div>` | Wrapper around input/trigger, tags, clear button, and expand icon |
96
+ | `cv-input` | `<input>` | Editable combobox control (editable mode only) |
97
+ | `trigger` | `<div>` | Button-like trigger control (select-only mode only) |
98
+ | `label` | `<span>` | Selected value text inside trigger (select-only mode) |
99
+ | `listbox` | `<div>` | Popup listbox container |
100
+ | `tags` | `<div>` | Container for selected item tags (multi-select only) |
101
+ | `tag` | `<span>` | Individual selected item tag (multi-select only) |
102
+ | `tag-label` | `<span>` | Text label inside a tag |
103
+ | `tag-remove` | `<button>` | Remove button inside a tag |
104
+ | `tag-overflow` | `<span>` | "+N more" overflow indicator |
105
+ | `clear-button` | `<button>` | Clear selection button (clearable mode only) |
106
+ | `expand-icon` | `<span>` | Dropdown expand/collapse indicator icon |
107
+ | `group` | `<div>` | Option group container inside the listbox |
108
+ | `group-label` | `<div>` | Group header label inside the listbox |
109
+ | `prefix` | `<span>` | Wrapper around the `prefix` slot |
110
+ | `suffix` | `<span>` | Wrapper around the `suffix` slot |
111
+
112
+ ## CSS Custom Properties
113
+
114
+ | Property | Default | Description |
115
+ | ------------------------------ | --------------------------------- | ------------------------------------------ |
116
+ | `--cv-combobox-min-width` | `260px` | Minimum inline size of the host |
117
+ | `--cv-combobox-min-height` | `36px` | Minimum block size of the input/trigger |
118
+ | `--cv-combobox-padding-inline` | `var(--cv-space-3, 12px)` | Horizontal padding of the input/trigger |
119
+ | `--cv-combobox-max-height` | `220px` | Maximum block size of the listbox popup |
120
+ | `--cv-combobox-border-color` | `var(--cv-color-border, #2a3245)` | Border color for input/trigger and listbox |
121
+ | `--cv-combobox-border-radius` | `var(--cv-radius-sm, 6px)` | Border radius of the input/trigger |
122
+ | `--cv-combobox-listbox-radius` | `var(--cv-radius-md, 10px)` | Border radius of the listbox popup |
123
+ | `--cv-combobox-gap` | `var(--cv-space-1, 4px)` | Gap between base layout sections |
124
+ | `--cv-combobox-tag-gap` | `var(--cv-space-1, 4px)` | Gap between tags in multi-select |
125
+ | `--cv-combobox-tag-radius` | `var(--cv-radius-sm, 6px)` | Border radius of tag chips |
126
+ | `--cv-combobox-font-size` | `inherit` | Font size of the input/trigger text |
127
+
128
+ ## Visual States
129
+
130
+ | Host selector | Description |
131
+ | ----------------------------- | ---------------------------------------------------- |
132
+ | `:host([disabled])` | Reduced opacity (`0.55`), `cursor: not-allowed` |
133
+ | `:host([open])` | Popup listbox is visible |
134
+ | `:host([type="select-only"])` | Trigger is a button-like element instead of an input |
135
+ | `:host([multiple])` | Multi-select mode with tag chips |
136
+ | `:host([clearable])` | Clear button may be shown |
137
+ | `:host([size="small"])` | Small size overrides |
138
+ | `:host([size="large"])` | Large size overrides |
139
+
140
+ ## ARIA Contract
141
+
142
+ ### Editable mode
143
+
144
+ - Input role is `combobox`
145
+ - Input exposes `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls`, `aria-autocomplete="list"`
146
+ - When popup is open and active option exists, input exposes `aria-activedescendant`
147
+
148
+ ### Select-only mode
149
+
150
+ - Trigger role is `combobox`
151
+ - Trigger exposes `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls`
152
+ - `aria-autocomplete` is **not** present (no text input)
153
+ - When popup is open and active option exists, trigger exposes `aria-activedescendant`
154
+
155
+ ### Common
156
+
157
+ - Popup role is `listbox`
158
+ - Options use role `option`
159
+ - When `multiple=true`, listbox exposes `aria-multiselectable="true"`
160
+ - Each selected option exposes `aria-selected="true"` (all selected in multi mode, not just one)
161
+ - Option groups use `role="group"` with `aria-labelledby` pointing to the group label element
162
+ - Group label elements use `role="presentation"`
163
+
164
+ All ARIA attributes are derived from headless contracts (`getInputProps`, `getListboxProps`, `getOptionProps`, `getGroupProps`, `getGroupLabelProps`). UIKit does not compute ARIA state independently.
165
+
166
+ ## Events
167
+
168
+ | Event | Detail | Description |
169
+ | ----------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
170
+ | `cv-input` | `{value: string \| null, inputValue: string, activeId: string \| null, open: boolean, selectedIds: string[]}` | Fires when combobox interaction changes observable state |
171
+ | `cv-change` | `{value: string \| null, inputValue: string, activeId: string \| null, open: boolean, selectedIds: string[]}` | Fires when selected option(s) change |
172
+ | `cv-clear` | `{}` | Fires when the clear button is clicked |
173
+
174
+ In multi mode, `cv-input` fires on each toggle and `cv-change` fires on each toggle (since every toggle changes selection). The `selectedIds` array in the detail reflects all currently selected option ids.
175
+
176
+ ## Reactive State Mapping
177
+
178
+ `cv-combobox` is a visual adapter over headless `createCombobox` reactive state.
179
+
180
+ ### Attribute to Headless (UIKit -> Headless)
181
+
182
+ | UIKit Property | Direction | Headless Binding |
183
+ | ----------------- | -------------- | ------------------------------------------------------------------------------------------------ |
184
+ | `value` | attr -> action | `actions.select(id)` / `actions.clearSelection()`. In multi mode, parsed as space-delimited ids. |
185
+ | `input-value` | attr -> action | `actions.setInputValue(value)` |
186
+ | `open` | attr -> action | `actions.open()` / `actions.close()` |
187
+ | `type` | attr -> option | passed as `type` in `createCombobox(options)` |
188
+ | `multiple` | attr -> option | passed as `multiple` in `createCombobox(options)` |
189
+ | `clearable` | attr -> option | passed as `clearable` in `createCombobox(options)` |
190
+ | `close-on-select` | attr -> option | passed as `closeOnSelect` in `createCombobox(options)` |
191
+ | `match-mode` | attr -> option | passed as `matchMode` in `createCombobox(options)` |
192
+ | `aria-label` | attr -> option | passed as `ariaLabel` in `createCombobox(options)` |
193
+
194
+ ### Headless to DOM (Headless -> UIKit)
195
+
196
+ | Headless State | Direction | DOM Reflection |
197
+ | ---------------------- | --------------- | --------------------------------------------------------------- |
198
+ | `state.selectedId()` | state -> attr | `[value]` host attribute (single mode) |
199
+ | `state.selectedIds()` | state -> attr | `[value]` host attribute as space-delimited string (multi mode) |
200
+ | `state.inputValue()` | state -> attr | `[input-value]` host attribute |
201
+ | `state.isOpen()` | state -> attr | `[open]` host attribute |
202
+ | `state.activeId()` | state -> render | `aria-activedescendant` on input/trigger |
203
+ | `state.hasSelection()` | state -> render | clear button visibility |
204
+ | `state.type()` | state -> render | determines input vs trigger rendering |
205
+ | `state.multiple()` | state -> render | determines tag rendering |
206
+
207
+ ### Contract Spreading
208
+
209
+ - `contracts.getInputProps()` is spread onto `[part="input"]` (editable) or `[part="trigger"]` (select-only) -- applies `role`, `aria-haspopup`, `aria-expanded`, `aria-controls`, `aria-autocomplete` (editable only), `aria-activedescendant`, `aria-label`
210
+ - `contracts.getListboxProps()` is spread onto `[part="listbox"]` -- applies `role`, `tabindex`, `aria-label`, `aria-multiselectable` (multi only)
211
+ - `contracts.getOptionProps(id)` is spread onto each `cv-combobox-option` -- applies `role`, `tabindex`, `aria-selected`, `aria-disabled`, `data-active`
212
+ - `contracts.getGroupProps(groupId)` is spread onto each `[part="group"]` -- applies `role`, `aria-labelledby`
213
+ - `contracts.getGroupLabelProps(groupId)` is spread onto each `[part="group-label"]` -- applies `id`, `role`
214
+ - `contracts.getVisibleOptions()` drives option/group visibility (supports grouped structure; empty groups are hidden)
215
+ - `contracts.getFlatVisibleOptions()` available for navigation index calculations
216
+
217
+ ### UIKit-Only Concerns (NOT in headless)
218
+
219
+ - Tag/chip rendering for multi-select selected items
220
+ - "+N more" overflow display for multi-select (controlled by `max-tags-visible`)
221
+ - Clear button rendering and visibility (uses `state.hasSelection()` + `clearable` attribute)
222
+ - Select-only trigger visual (button-like with selected label + expand icon)
223
+ - Option group visual styling (indentation, group header)
224
+ - Popup positioning and animation
225
+ - `cv-clear` event dispatch
226
+ - Size variants (`small` / `medium` / `large`)
227
+
228
+ ## Behavioral Contract
229
+
230
+ ### Editable Mode (default)
231
+
232
+ - Text input updates `input-value`, opens popup, and filters visible options
233
+ - Focus opens popup only when `open-on-focus=true`
234
+ - Input click opens popup only when `open-on-click=true`
235
+ - Arrow/Home/End navigation follows headless combobox behavior
236
+ - Enter commits active option (`value`, `input-value`, popup closes only when `close-on-select=true`)
237
+ - Escape closes popup without clearing committed selection
238
+ - Clicking outside closes popup
239
+ - `match-mode="startsWith"` uses case-insensitive starts-with filtering
240
+ - Slot changes rebuild model while preserving still-valid selected value
241
+
242
+ ### Select-Only Mode
243
+
244
+ - `input-value` is not user-editable; `setInputValue` is a no-op in headless
245
+ - Trigger displays the selected option's label (or placeholder when no selection)
246
+ - Keyboard when closed: `Space`/`Enter` opens popup; `ArrowDown`/`ArrowUp` opens and activates first/last option
247
+ - Keyboard when open: `ArrowDown`/`ArrowUp` navigate; `Enter`/`Space` commit active option; `Escape` closes; `Home`/`End` navigate to first/last
248
+ - Type-to-select via printable characters: typeahead jumps to matching option by label prefix
249
+ - Filtering is disabled; all non-disabled options are always visible
250
+
251
+ ### Multi-Select
252
+
253
+ - `commitActive` toggles the active option in `selectedIds` instead of replacing selection
254
+ - `select(id)` toggles the option instead of replacing
255
+ - Listbox stays open after each selection (default `close-on-select=false`)
256
+ - `input-value` is NOT overwritten on commit (it drives filtering in editable multi mode)
257
+ - In select-only multi mode, `inputValue` is always `""` (trigger shows tags instead)
258
+ - Tags/chips are rendered inside `[part="tags"]` for each selected item
259
+ - When `selectedIds.length > max-tags-visible`, overflow shows "+N more" in `[part="tag-overflow"]`
260
+ - Clicking `[part="tag-remove"]` calls `actions.removeSelected(id)`
261
+ - `value` attribute reflects all selected ids as a space-delimited string
262
+
263
+ ### Clearable
264
+
265
+ - Clear button `[part="clear-button"]` is visible when `clearable=true` and `state.hasSelection()` is true
266
+ - Clicking the clear button calls `actions.clear()` (resets both selection and input value)
267
+ - `cv-clear` event is dispatched when the clear button is clicked
268
+
269
+ ### Option Groups
270
+
271
+ - `<cv-combobox-group label="Name">` wraps `<cv-combobox-option>` children into a visual group
272
+ - Groups are rendered as `[part="group"]` with `role="group"` and `aria-labelledby` pointing to `[part="group-label"]`
273
+ - Groups with all options filtered out are hidden
274
+ - Navigation crosses group boundaries seamlessly (headless handles this via flat visible options)
275
+
276
+ ### Disabled State
277
+
278
+ - When `disabled=true`, the combobox is non-interactive: input/trigger cannot be focused, popup cannot open, clear/tag-remove buttons are inert
279
+
280
+ ## Optional Advanced Behaviors (Future Scope)
281
+
282
+ These behaviors are optional and currently not required on `cv-combobox`:
283
+
284
+ - free-text/custom value commit when no option is active
285
+ - async option loading
286
+ - inline autocomplete completion rendering
287
+
288
+ ## Usage
289
+
290
+ ```html
291
+ <!-- Basic editable combobox -->
292
+ <cv-combobox aria-label="Search">
293
+ <cv-combobox-option value="a">Alpha</cv-combobox-option>
294
+ <cv-combobox-option value="b">Beta</cv-combobox-option>
295
+ <cv-combobox-option value="c" disabled>Gamma</cv-combobox-option>
296
+ </cv-combobox>
297
+
298
+ <!-- Select-only combobox -->
299
+ <cv-combobox type="select-only" aria-label="Country" placeholder="Select a country">
300
+ <cv-combobox-option value="us">United States</cv-combobox-option>
301
+ <cv-combobox-option value="uk">United Kingdom</cv-combobox-option>
302
+ <cv-combobox-option value="de">Germany</cv-combobox-option>
303
+ </cv-combobox>
304
+
305
+ <!-- Multi-select editable -->
306
+ <cv-combobox multiple aria-label="Tags" placeholder="Add tags...">
307
+ <cv-combobox-option value="js">JavaScript</cv-combobox-option>
308
+ <cv-combobox-option value="ts">TypeScript</cv-combobox-option>
309
+ <cv-combobox-option value="py">Python</cv-combobox-option>
310
+ <cv-combobox-option value="rs">Rust</cv-combobox-option>
311
+ </cv-combobox>
312
+
313
+ <!-- Multi-select select-only with max tags -->
314
+ <cv-combobox type="select-only" multiple max-tags-visible="2" aria-label="Assignees">
315
+ <cv-combobox-option value="alice">Alice</cv-combobox-option>
316
+ <cv-combobox-option value="bob">Bob</cv-combobox-option>
317
+ <cv-combobox-option value="carol">Carol</cv-combobox-option>
318
+ <cv-combobox-option value="dave">Dave</cv-combobox-option>
319
+ </cv-combobox>
320
+
321
+ <!-- Clearable combobox -->
322
+ <cv-combobox clearable aria-label="Fruit">
323
+ <cv-combobox-option value="apple">Apple</cv-combobox-option>
324
+ <cv-combobox-option value="banana">Banana</cv-combobox-option>
325
+ <cv-combobox-option value="cherry">Cherry</cv-combobox-option>
326
+ </cv-combobox>
327
+
328
+ <!-- Grouped options -->
329
+ <cv-combobox aria-label="City">
330
+ <cv-combobox-group label="North America">
331
+ <cv-combobox-option value="nyc">New York</cv-combobox-option>
332
+ <cv-combobox-option value="la">Los Angeles</cv-combobox-option>
333
+ <cv-combobox-option value="tor">Toronto</cv-combobox-option>
334
+ </cv-combobox-group>
335
+ <cv-combobox-group label="Europe">
336
+ <cv-combobox-option value="lon">London</cv-combobox-option>
337
+ <cv-combobox-option value="par">Paris</cv-combobox-option>
338
+ <cv-combobox-option value="ber">Berlin</cv-combobox-option>
339
+ </cv-combobox-group>
340
+ </cv-combobox>
341
+
342
+ <!-- With prefix/suffix slots -->
343
+ <cv-combobox aria-label="Search" clearable>
344
+ <icon-search slot="prefix"></icon-search>
345
+ <cv-combobox-option value="a">Alpha</cv-combobox-option>
346
+ <cv-combobox-option value="b">Beta</cv-combobox-option>
347
+ </cv-combobox>
348
+
349
+ <!-- Small size -->
350
+ <cv-combobox size="small" aria-label="Quick select">
351
+ <cv-combobox-option value="a">Alpha</cv-combobox-option>
352
+ <cv-combobox-option value="b">Beta</cv-combobox-option>
353
+ </cv-combobox>
354
+ ```
355
+
356
+ ## Child Elements
357
+
358
+ ### cv-combobox-option
359
+
360
+ Individual option within a combobox. The parent `cv-combobox` manages all ARIA attributes on this element via headless contracts.
361
+
362
+ #### Anatomy
363
+
364
+ ```
365
+ <cv-combobox-option> (host)
366
+ └── <div part="base">
367
+ └── <slot>
368
+ ```
369
+
370
+ #### Attributes
371
+
372
+ | Attribute | Type | Default | Description |
373
+ | ---------- | ------- | ------- | ----------------------------------------------------------------------------- |
374
+ | `value` | String | `""` | Unique identifier for this option. Auto-generated as `option-{n}` if omitted. |
375
+ | `disabled` | Boolean | `false` | Whether the option is disabled |
376
+ | `selected` | Boolean | `false` | Whether the option is selected. Managed by parent. |
377
+ | `active` | Boolean | `false` | Whether the option is the active (highlighted) option. Managed by parent. |
378
+
379
+ #### Slots
380
+
381
+ | Slot | Description |
382
+ | ----------- | -------------------- |
383
+ | `(default)` | Option label content |
384
+
385
+ #### CSS Parts
386
+
387
+ | Part | Element | Description |
388
+ | ------ | ------- | ----------------------------------- |
389
+ | `base` | `<div>` | Root wrapper for the option content |
390
+
391
+ #### Visual States
392
+
393
+ | Host selector | Description |
394
+ | ------------------------------------------------- | ----------------------------------------- |
395
+ | `:host([selected])` | Option is currently selected |
396
+ | `:host([active])` / `:host([data-active="true"])` | Option is the active (highlighted) option |
397
+ | `:host([disabled])` | Option is disabled |
398
+ | `:host([hidden])` | Option is filtered out or popup is closed |
399
+
400
+ ### cv-combobox-group
401
+
402
+ Groups related options under a labeled header. Must be a direct child of `cv-combobox`.
403
+
404
+ #### Anatomy
405
+
406
+ ```
407
+ <cv-combobox-group> (host)
408
+ └── <slot> ← accepts <cv-combobox-option> children
409
+ ```
410
+
411
+ #### Attributes
412
+
413
+ | Attribute | Type | Default | Description |
414
+ | --------- | ------ | ------- | ----------------------------------------------------------------------------------------------------- |
415
+ | `label` | String | `""` | Visible group header text. Also used for `aria-labelledby` linkage via headless `getGroupLabelProps`. |
416
+
417
+ #### Slots
418
+
419
+ | Slot | Description |
420
+ | ----------- | ------------------------------------------- |
421
+ | `(default)` | One or more `<cv-combobox-option>` children |
422
+
423
+ #### Visual States
424
+
425
+ | Host selector | Description |
426
+ | ----------------- | ------------------------------------------ |
427
+ | `:host([hidden])` | All options in this group are filtered out |