@chromvoid/uikit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +96 -0
- package/dist/components/cv-accordion-item.d.ts +69 -0
- package/dist/components/cv-accordion-item.js +176 -0
- package/dist/components/cv-accordion.d.ts +79 -0
- package/dist/components/cv-accordion.js +310 -0
- package/dist/components/cv-alert-dialog.d.ts +86 -0
- package/dist/components/cv-alert-dialog.js +393 -0
- package/dist/components/cv-alert.d.ts +48 -0
- package/dist/components/cv-alert.js +156 -0
- package/dist/components/cv-badge.d.ts +56 -0
- package/dist/components/cv-badge.js +280 -0
- package/dist/components/cv-breadcrumb-item.d.ts +35 -0
- package/dist/components/cv-breadcrumb-item.js +64 -0
- package/dist/components/cv-breadcrumb.d.ts +39 -0
- package/dist/components/cv-breadcrumb.js +160 -0
- package/dist/components/cv-button.d.ts +83 -0
- package/dist/components/cv-button.js +541 -0
- package/dist/components/cv-callout.d.ts +32 -0
- package/dist/components/cv-callout.js +221 -0
- package/dist/components/cv-card.d.ts +48 -0
- package/dist/components/cv-card.js +269 -0
- package/dist/components/cv-carousel-slide.d.ts +25 -0
- package/dist/components/cv-carousel-slide.js +51 -0
- package/dist/components/cv-carousel.d.ts +96 -0
- package/dist/components/cv-carousel.js +457 -0
- package/dist/components/cv-checkbox.d.ts +84 -0
- package/dist/components/cv-checkbox.js +274 -0
- package/dist/components/cv-combobox-group.d.ts +15 -0
- package/dist/components/cv-combobox-group.js +34 -0
- package/dist/components/cv-combobox-option.d.ts +30 -0
- package/dist/components/cv-combobox-option.js +66 -0
- package/dist/components/cv-combobox.d.ts +135 -0
- package/dist/components/cv-combobox.js +834 -0
- package/dist/components/cv-command-item.d.ts +30 -0
- package/dist/components/cv-command-item.js +68 -0
- package/dist/components/cv-command-palette.d.ts +105 -0
- package/dist/components/cv-command-palette.js +578 -0
- package/dist/components/cv-context-menu.d.ts +98 -0
- package/dist/components/cv-context-menu.js +515 -0
- package/dist/components/cv-copy-button.d.ts +61 -0
- package/dist/components/cv-copy-button.js +318 -0
- package/dist/components/cv-date-picker.d.ts +161 -0
- package/dist/components/cv-date-picker.js +803 -0
- package/dist/components/cv-dialog.d.ts +89 -0
- package/dist/components/cv-dialog.js +459 -0
- package/dist/components/cv-disclosure.d.ts +57 -0
- package/dist/components/cv-disclosure.js +241 -0
- package/dist/components/cv-drawer.d.ts +102 -0
- package/dist/components/cv-drawer.js +595 -0
- package/dist/components/cv-feed-article.d.ts +26 -0
- package/dist/components/cv-feed-article.js +52 -0
- package/dist/components/cv-feed.d.ts +62 -0
- package/dist/components/cv-feed.js +310 -0
- package/dist/components/cv-grid-cell.d.ts +30 -0
- package/dist/components/cv-grid-cell.js +57 -0
- package/dist/components/cv-grid-column.d.ts +30 -0
- package/dist/components/cv-grid-column.js +43 -0
- package/dist/components/cv-grid-row.d.ts +30 -0
- package/dist/components/cv-grid-row.js +42 -0
- package/dist/components/cv-grid.d.ts +119 -0
- package/dist/components/cv-grid.js +567 -0
- package/dist/components/cv-icon.d.ts +57 -0
- package/dist/components/cv-icon.js +352 -0
- package/dist/components/cv-input.d.ts +127 -0
- package/dist/components/cv-input.js +482 -0
- package/dist/components/cv-landmark.d.ts +32 -0
- package/dist/components/cv-landmark.js +62 -0
- package/dist/components/cv-link.d.ts +22 -0
- package/dist/components/cv-link.js +99 -0
- package/dist/components/cv-listbox-group.d.ts +15 -0
- package/dist/components/cv-listbox-group.js +42 -0
- package/dist/components/cv-listbox.d.ts +81 -0
- package/dist/components/cv-listbox.js +388 -0
- package/dist/components/cv-menu-button.d.ts +118 -0
- package/dist/components/cv-menu-button.js +822 -0
- package/dist/components/cv-menu-group.d.ts +20 -0
- package/dist/components/cv-menu-group.js +48 -0
- package/dist/components/cv-menu-item.d.ts +52 -0
- package/dist/components/cv-menu-item.js +105 -0
- package/dist/components/cv-menu.d.ts +62 -0
- package/dist/components/cv-menu.js +414 -0
- package/dist/components/cv-meter.d.ts +66 -0
- package/dist/components/cv-meter.js +154 -0
- package/dist/components/cv-number.d.ts +139 -0
- package/dist/components/cv-number.js +553 -0
- package/dist/components/cv-option.d.ts +30 -0
- package/dist/components/cv-option.js +84 -0
- package/dist/components/cv-popover.d.ts +87 -0
- package/dist/components/cv-popover.js +373 -0
- package/dist/components/cv-progress-ring.d.ts +45 -0
- package/dist/components/cv-progress-ring.js +169 -0
- package/dist/components/cv-progress.d.ts +45 -0
- package/dist/components/cv-progress.js +148 -0
- package/dist/components/cv-radio-group.d.ts +79 -0
- package/dist/components/cv-radio-group.js +398 -0
- package/dist/components/cv-radio.d.ts +36 -0
- package/dist/components/cv-radio.js +123 -0
- package/dist/components/cv-select-group.d.ts +15 -0
- package/dist/components/cv-select-group.js +44 -0
- package/dist/components/cv-select-option.d.ts +30 -0
- package/dist/components/cv-select-option.js +66 -0
- package/dist/components/cv-select.d.ts +128 -0
- package/dist/components/cv-select.js +666 -0
- package/dist/components/cv-sidebar-item.d.ts +26 -0
- package/dist/components/cv-sidebar-item.js +142 -0
- package/dist/components/cv-sidebar.d.ts +171 -0
- package/dist/components/cv-sidebar.js +767 -0
- package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
- package/dist/components/cv-slider-multi-thumb.js +374 -0
- package/dist/components/cv-slider.d.ts +84 -0
- package/dist/components/cv-slider.js +328 -0
- package/dist/components/cv-spinbutton.d.ts +121 -0
- package/dist/components/cv-spinbutton.js +486 -0
- package/dist/components/cv-spinner.d.ts +18 -0
- package/dist/components/cv-spinner.js +95 -0
- package/dist/components/cv-switch.d.ts +81 -0
- package/dist/components/cv-switch.js +285 -0
- package/dist/components/cv-tab-panel.d.ts +20 -0
- package/dist/components/cv-tab-panel.js +37 -0
- package/dist/components/cv-tab.d.ts +40 -0
- package/dist/components/cv-tab.js +132 -0
- package/dist/components/cv-table-cell.d.ts +31 -0
- package/dist/components/cv-table-cell.js +49 -0
- package/dist/components/cv-table-column.d.ts +37 -0
- package/dist/components/cv-table-column.js +63 -0
- package/dist/components/cv-table-row.d.ts +30 -0
- package/dist/components/cv-table-row.js +45 -0
- package/dist/components/cv-table.d.ts +147 -0
- package/dist/components/cv-table.js +607 -0
- package/dist/components/cv-tabs.d.ts +70 -0
- package/dist/components/cv-tabs.js +524 -0
- package/dist/components/cv-textarea.d.ts +108 -0
- package/dist/components/cv-textarea.js +328 -0
- package/dist/components/cv-toast-region.d.ts +39 -0
- package/dist/components/cv-toast-region.js +162 -0
- package/dist/components/cv-toast.d.ts +67 -0
- package/dist/components/cv-toast.js +315 -0
- package/dist/components/cv-toolbar-item.d.ts +25 -0
- package/dist/components/cv-toolbar-item.js +72 -0
- package/dist/components/cv-toolbar-separator.d.ts +25 -0
- package/dist/components/cv-toolbar-separator.js +45 -0
- package/dist/components/cv-toolbar.d.ts +63 -0
- package/dist/components/cv-toolbar.js +295 -0
- package/dist/components/cv-tooltip.d.ts +83 -0
- package/dist/components/cv-tooltip.js +455 -0
- package/dist/components/cv-treegrid-cell.d.ts +30 -0
- package/dist/components/cv-treegrid-cell.js +57 -0
- package/dist/components/cv-treegrid-column.d.ts +37 -0
- package/dist/components/cv-treegrid-column.js +53 -0
- package/dist/components/cv-treegrid-row.d.ts +55 -0
- package/dist/components/cv-treegrid-row.js +90 -0
- package/dist/components/cv-treegrid.d.ts +96 -0
- package/dist/components/cv-treegrid.js +632 -0
- package/dist/components/cv-treeitem.d.ts +58 -0
- package/dist/components/cv-treeitem.js +144 -0
- package/dist/components/cv-treeview.d.ts +70 -0
- package/dist/components/cv-treeview.js +396 -0
- package/dist/components/cv-window-splitter.d.ts +79 -0
- package/dist/components/cv-window-splitter.js +316 -0
- package/dist/components/index.d.ts +94 -0
- package/dist/components/index.js +79 -0
- package/dist/dialog/create-dialog-controller.d.ts +31 -0
- package/dist/dialog/create-dialog-controller.js +320 -0
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +1 -0
- package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
- package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
- package/dist/form-associated/withFormAssociated.d.ts +5 -0
- package/dist/form-associated/withFormAssociated.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
- package/dist/reatom-lit/ReatomLitElement.js +118 -0
- package/dist/reatom-lit/html.d.ts +4 -0
- package/dist/reatom-lit/html.js +10 -0
- package/dist/reatom-lit/index.d.ts +4 -0
- package/dist/reatom-lit/index.js +4 -0
- package/dist/reatom-lit/watch.d.ts +15 -0
- package/dist/reatom-lit/watch.js +40 -0
- package/dist/reatom-lit/withReatomElement.d.ts +4 -0
- package/dist/reatom-lit/withReatomElement.js +57 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +84 -0
- package/dist/styles/component-styles.d.ts +4 -0
- package/dist/styles/component-styles.js +78 -0
- package/dist/theme/cv-theme-provider.d.ts +32 -0
- package/dist/theme/cv-theme-provider.js +110 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/theme-engine.d.ts +4 -0
- package/dist/theme/theme-engine.js +67 -0
- package/dist/theme/tokens.css +265 -0
- package/dist/theme/types.d.ts +7 -0
- package/dist/theme/types.js +1 -0
- package/dist/toast/create-toast-controller.d.ts +12 -0
- package/dist/toast/create-toast-controller.js +12 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +1 -0
- package/package.json +146 -0
- package/specs/_template.md +110 -0
- package/specs/components/accordion.md +207 -0
- package/specs/components/alert.md +83 -0
- package/specs/components/badge.md +183 -0
- package/specs/components/breadcrumb.md +152 -0
- package/specs/components/button.md +227 -0
- package/specs/components/callout.md +153 -0
- package/specs/components/card.md +192 -0
- package/specs/components/carousel.md +232 -0
- package/specs/components/checkbox.md +141 -0
- package/specs/components/combobox.md +427 -0
- package/specs/components/context-menu.md +375 -0
- package/specs/components/copy-button.md +236 -0
- package/specs/components/date-picker.md +290 -0
- package/specs/components/dialog.md +184 -0
- package/specs/components/disclosure.md +151 -0
- package/specs/components/drawer.md +216 -0
- package/specs/components/feed.md +266 -0
- package/specs/components/grid.md +423 -0
- package/specs/components/input.md +237 -0
- package/specs/components/landmark.md +92 -0
- package/specs/components/link.md +117 -0
- package/specs/components/listbox.md +327 -0
- package/specs/components/menu.md +508 -0
- package/specs/components/meter.md +148 -0
- package/specs/components/number.md +268 -0
- package/specs/components/option.md +167 -0
- package/specs/components/popover.md +207 -0
- package/specs/components/progress-ring.md +134 -0
- package/specs/components/progress.md +110 -0
- package/specs/components/radio.md +208 -0
- package/specs/components/select.md +305 -0
- package/specs/components/sidebar.md +204 -0
- package/specs/components/spinbutton.md +157 -0
- package/specs/components/spinner.md +83 -0
- package/specs/components/switch.md +145 -0
- package/specs/components/table.md +372 -0
- package/specs/components/tabs.md +242 -0
- package/specs/components/textarea.md +166 -0
- package/specs/components/theme.md +364 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +258 -0
- package/specs/components/tooltip.md +152 -0
- package/specs/components/treegrid.md +363 -0
- package/specs/components/treeview.md +263 -0
- package/specs/components/window-splitter.md +225 -0
|
@@ -0,0 +1,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 |
|