@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,372 @@
1
+ # cv-table
2
+
3
+ Data table for displaying structured tabular content with optional sorting, row selection, and grid-style keyboard navigation.
4
+
5
+ **Headless:** [`createTable`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/table.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-table> (host)
11
+ └── <div part="base" role="table|grid">
12
+ ├── <div role="rowgroup" part="head">
13
+ │ └── <div role="row" part="head-row">
14
+ │ └── <slot name="columns"> ← accepts <cv-table-column> children
15
+ └── <div role="rowgroup" part="body" @cv-table-row-slotchange>
16
+ └── <slot name="rows"> ← accepts <cv-table-row> children
17
+ ```
18
+
19
+ ## Attributes
20
+
21
+ | Attribute | Type | Default | Description |
22
+ | -------------------- | ------- | -------- | --------------------------------------------------------------------------------- |
23
+ | `sort-column` | String | `""` | Currently sorted column id (reflected) |
24
+ | `sort-direction` | String | `"none"` | Sort direction: `none` \| `ascending` \| `descending` (reflected) |
25
+ | `aria-label` | String | `""` | Accessible label for the table root |
26
+ | `aria-labelledby` | String | `""` | `aria-labelledby` reference for the table root |
27
+ | `total-column-count` | Number | `0` | Logical column count for virtualization (reflected) |
28
+ | `total-row-count` | Number | `0` | Logical row count for virtualization (reflected) |
29
+ | `selectable` | String | `""` | Row selection mode: `single` \| `multi`. Empty or absent disables selection. |
30
+ | `interactive` | Boolean | `false` | Enables grid navigation mode (switches role to `grid`, activates roving tabindex) |
31
+ | `sticky-header` | Boolean | `false` | Makes the header row stick to the top when scrolling |
32
+ | `striped` | Boolean | `false` | Alternating row background colors |
33
+ | `compact` | Boolean | `false` | Reduced cell padding for denser display |
34
+ | `bordered` | Boolean | `false` | Visible borders between all cells |
35
+ | `page-size` | Number | `10` | Rows per page for PageUp/PageDown in grid navigation mode (minimum 1) |
36
+
37
+ ## Slots
38
+
39
+ | Slot | Description |
40
+ | --------- | ---------------------------------------------------- |
41
+ | `columns` | `<cv-table-column>` children defining column headers |
42
+ | `rows` | `<cv-table-row>` children containing table data rows |
43
+
44
+ ## CSS Parts
45
+
46
+ | Part | Element | Description |
47
+ | ---------- | ------- | ----------------------------------------------------- |
48
+ | `base` | `<div>` | Root table/grid element with role and ARIA attributes |
49
+ | `head` | `<div>` | Header rowgroup wrapper |
50
+ | `head-row` | `<div>` | Header row containing column slots |
51
+ | `body` | `<div>` | Body rowgroup wrapper containing row slots |
52
+
53
+ ## CSS Custom Properties
54
+
55
+ | Property | Default | Description |
56
+ | ---------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------- |
57
+ | `--cv-table-border-radius` | `var(--cv-radius-md, 10px)` | Border radius of the table container |
58
+ | `--cv-table-border-color` | `var(--cv-color-border, #2a3245)` | Border color of the table and cells |
59
+ | `--cv-table-background` | `var(--cv-color-surface, #141923)` | Table background color |
60
+ | `--cv-table-header-background` | `color-mix(in oklab, var(--cv-color-surface, #141923) 82%, transparent)` | Header row background |
61
+ | `--cv-table-stripe-background` | `color-mix(in oklab, var(--cv-color-surface, #141923) 90%, transparent)` | Background for alternating rows when `striped` |
62
+ | `--cv-table-row-selected-background` | `color-mix(in oklab, var(--cv-color-primary, #65d7ff) 12%, transparent)` | Background for selected rows |
63
+ | `--cv-table-cell-padding-block` | `var(--cv-space-2, 8px)` | Vertical cell padding |
64
+ | `--cv-table-cell-padding-inline` | `var(--cv-space-3, 12px)` | Horizontal cell padding |
65
+ | `--cv-table-compact-cell-padding-block` | `var(--cv-space-1, 4px)` | Vertical cell padding in compact mode |
66
+ | `--cv-table-compact-cell-padding-inline` | `var(--cv-space-2, 8px)` | Horizontal cell padding in compact mode |
67
+ | `--cv-table-focus-outline-color` | `var(--cv-color-primary, #65d7ff)` | Focus ring color for grid navigation |
68
+
69
+ Additionally, component styles depend on theme tokens through fallback values:
70
+
71
+ | Theme Property | Default | Description |
72
+ | -------------------- | --------- | ---------------------------------------------------- |
73
+ | `--cv-color-border` | `#2a3245` | Base border color |
74
+ | `--cv-color-surface` | `#141923` | Surface background color |
75
+ | `--cv-color-text` | `#e8ecf6` | Default text color |
76
+ | `--cv-color-primary` | `#65d7ff` | Primary accent color (focus, selection, active sort) |
77
+ | `--cv-radius-md` | `10px` | Border radius fallback |
78
+ | `--cv-space-1` | `4px` | Small spacing scale |
79
+ | `--cv-space-2` | `8px` | Medium spacing scale |
80
+ | `--cv-space-3` | `12px` | Medium-large spacing scale |
81
+
82
+ ## Visual States
83
+
84
+ | Host selector | Description |
85
+ | ------------------------ | -------------------------------------------------------------- |
86
+ | `:host([striped])` | Alternating row backgrounds via `--cv-table-stripe-background` |
87
+ | `:host([compact])` | Reduced cell padding via compact custom properties |
88
+ | `:host([bordered])` | Visible borders between all cells |
89
+ | `:host([sticky-header])` | Header row uses `position: sticky; top: 0` |
90
+ | `:host([interactive])` | Grid navigation mode active; focus ring on active cell |
91
+ | `:host([selectable])` | Row selection enabled; selected rows highlighted |
92
+
93
+ ## Events
94
+
95
+ | Event | Detail | Description |
96
+ | --------------------- | ------------------------------------------------------------------- | ------------------------------------------------------- |
97
+ | `cv-input` | `{sortColumnId: string \| null, sortDirection: TableSortDirection}` | Fires on sort interaction (before commit) |
98
+ | `cv-change` | `{sortColumnId: string \| null, sortDirection: TableSortDirection}` | Fires when sort state changes |
99
+ | `cv-selection-change` | `{selectedRowIds: string[], selectable: 'single' \| 'multi'}` | Fires when row selection changes via user interaction |
100
+ | `cv-focus-change` | `{rowIndex: number \| null, columnIndex: number \| null}` | Fires when focused cell changes in grid navigation mode |
101
+
102
+ Sort events (`cv-input` / `cv-change`) fire only when sort state changes due to user interaction (column header click or keyboard activation). They share the same detail shape and follow the convention where `cv-input` fires on interaction and `cv-change` fires on committed state change.
103
+
104
+ `cv-selection-change` fires when selection changes due to user interaction (row click, Space key, Ctrl+A). It does not fire for programmatic attribute changes.
105
+
106
+ `cv-focus-change` fires when the focused cell changes during grid navigation. It does not fire for programmatic `setFocusedCell` calls.
107
+
108
+ ## Reactive State Mapping
109
+
110
+ `cv-table` is a visual adapter over headless `createTable`.
111
+
112
+ ### UIKit Property to Headless Binding
113
+
114
+ | UIKit Property | Direction | Headless Binding |
115
+ | -------------------- | ------------- | ---------------------------------------------------------------------------------- |
116
+ | `sort-column` | attr → action | `actions.sortBy(value, direction)` or `actions.clearSort()` when attribute changes |
117
+ | `sort-direction` | attr → action | `actions.sortBy(column, value)` or `actions.clearSort()` when attribute changes |
118
+ | `aria-label` | attr → option | passed as `ariaLabel` in `createTable(options)` |
119
+ | `aria-labelledby` | attr → option | passed as `ariaLabelledBy` in `createTable(options)` |
120
+ | `total-column-count` | attr → option | passed as `totalColumnCount` in `createTable(options)` |
121
+ | `total-row-count` | attr → option | passed as `totalRowCount` in `createTable(options)` |
122
+ | `selectable` | attr → option | passed as `selectable` in `createTable(options)` |
123
+ | `interactive` | attr → option | passed as `interactive` in `createTable(options)` |
124
+ | `page-size` | attr → option | passed as `pageSize` in `createTable(options)` |
125
+
126
+ ### Headless State to DOM Reflection
127
+
128
+ | Headless State | Direction | DOM Reflection |
129
+ | ---------------------------- | ------------ | ------------------------------------------------------- |
130
+ | `state.sortColumnId()` | state → attr | `cv-table[sort-column]` host attribute |
131
+ | `state.sortDirection()` | state → attr | `cv-table[sort-direction]` host attribute |
132
+ | `state.selectedRowIds()` | state → attr | `cv-table-row[selected]` boolean attribute on each row |
133
+ | `state.focusedRowIndex()` | state → DOM | Active cell receives `tabindex="0"` and `.focus()` call |
134
+ | `state.focusedColumnIndex()` | state → DOM | Active cell receives `tabindex="0"` and `.focus()` call |
135
+
136
+ ### Headless Actions Called
137
+
138
+ | Action | UIKit Trigger |
139
+ | ----------------------------------------------- | --------------------------------------------------------------------- |
140
+ | `actions.sortBy(columnId, direction)` | Column header click or keyboard activation cycles sort direction |
141
+ | `actions.clearSort()` | Sort direction cycles back to `none` |
142
+ | `actions.selectRow(rowId)` | Row click when `selectable="single"` |
143
+ | `actions.toggleRowSelection(rowId)` | Row click when `selectable="multi"`, or Space key in interactive mode |
144
+ | `actions.selectAllRows()` | Ctrl/Cmd+A in interactive mode when `selectable="multi"` |
145
+ | `actions.clearSelection()` | Programmatic API |
146
+ | `actions.deselectRow(rowId)` | Programmatic API |
147
+ | `actions.handleKeyDown(event)` | `keydown` event on the grid root when `interactive` is `true` |
148
+ | `actions.moveFocus(direction)` | Arrow key navigation (delegated via `handleKeyDown`) |
149
+ | `actions.moveFocusToStart()` | Ctrl/Cmd+Home (delegated via `handleKeyDown`) |
150
+ | `actions.moveFocusToEnd()` | Ctrl/Cmd+End (delegated via `handleKeyDown`) |
151
+ | `actions.moveFocusToRowStart()` | Home key (delegated via `handleKeyDown`) |
152
+ | `actions.moveFocusToRowEnd()` | End key (delegated via `handleKeyDown`) |
153
+ | `actions.pageUp()` | PageUp key (delegated via `handleKeyDown`) |
154
+ | `actions.pageDown()` | PageDown key (delegated via `handleKeyDown`) |
155
+ | `actions.setFocusedCell(rowIndex, columnIndex)` | Cell click in interactive mode |
156
+
157
+ ### Headless Contracts Spread
158
+
159
+ | Contract | UIKit Target |
160
+ | --------------------------------------------- | ------------------------------------------------ |
161
+ | `contracts.getTableProps()` | Spread onto `[part="base"]` root element |
162
+ | `contracts.getRowProps(rowId)` | Spread onto each `cv-table-row` element |
163
+ | `contracts.getCellProps(rowId, colId, span?)` | Spread onto each `cv-table-cell` element |
164
+ | `contracts.getColumnHeaderProps(colId)` | Spread onto each `cv-table-column` element |
165
+ | `contracts.getRowHeaderProps(rowId, colId)` | Spread onto `cv-table-cell[row-header]` elements |
166
+
167
+ ### UIKit-Only Concerns (Not in Headless)
168
+
169
+ - **Display variants** (`striped`, `compact`, `bordered`): CSS-only visual modifiers, not part of headless state.
170
+ - **Sticky header** (`sticky-header`): CSS `position: sticky` on header, not part of headless state.
171
+ - **Visual selection indicators**: Row background highlighting for selected rows.
172
+ - **DOM focus management**: Calling `.focus()` on cells when `focusedRowIndex`/`focusedColumnIndex` change in headless state.
173
+ - **`preventDefault()`**: Called on keyboard events handled by `handleKeyDown` to prevent scrolling.
174
+ - **`cv-input` / `cv-change` / `cv-selection-change` / `cv-focus-change` events**: Custom DOM events dispatched by the UIKit wrapper, not part of the headless model.
175
+ - **Auto-generated fallback values**: `value` attributes on columns and rows receive auto-generated fallbacks (`column-N`, `row-N`) when not explicitly set.
176
+ - **Slot-based model rebuild**: Model is rebuilt from slotted children on `slotchange` events.
177
+
178
+ UIKit does not own sort, selection, or navigation logic; headless state is the source of truth.
179
+
180
+ ## Usage
181
+
182
+ ```html
183
+ <!-- Basic table -->
184
+ <cv-table aria-label="Users">
185
+ <cv-table-column value="name" label="Name" sortable></cv-table-column>
186
+ <cv-table-column value="email" label="Email"></cv-table-column>
187
+ <cv-table-column value="role" label="Role"></cv-table-column>
188
+
189
+ <cv-table-row value="user-1">
190
+ <cv-table-cell column="name" row-header>Alice</cv-table-cell>
191
+ <cv-table-cell column="email">alice@example.com</cv-table-cell>
192
+ <cv-table-cell column="role">Admin</cv-table-cell>
193
+ </cv-table-row>
194
+ <cv-table-row value="user-2">
195
+ <cv-table-cell column="name" row-header>Bob</cv-table-cell>
196
+ <cv-table-cell column="email">bob@example.com</cv-table-cell>
197
+ <cv-table-cell column="role">Editor</cv-table-cell>
198
+ </cv-table-row>
199
+ </cv-table>
200
+
201
+ <!-- Striped, compact, bordered with sticky header -->
202
+ <cv-table aria-label="Log entries" striped compact bordered sticky-header>
203
+ <cv-table-column value="timestamp" label="Time" sortable></cv-table-column>
204
+ <cv-table-column value="level" label="Level"></cv-table-column>
205
+ <cv-table-column value="message" label="Message"></cv-table-column>
206
+
207
+ <cv-table-row value="log-1">
208
+ <cv-table-cell column="timestamp">12:01:33</cv-table-cell>
209
+ <cv-table-cell column="level">INFO</cv-table-cell>
210
+ <cv-table-cell column="message">Server started</cv-table-cell>
211
+ </cv-table-row>
212
+ </cv-table>
213
+
214
+ <!-- Selectable rows (multi) -->
215
+ <cv-table aria-label="Files" selectable="multi">
216
+ <cv-table-column value="name" label="Name"></cv-table-column>
217
+ <cv-table-column value="size" label="Size"></cv-table-column>
218
+
219
+ <cv-table-row value="file-1">
220
+ <cv-table-cell column="name">readme.md</cv-table-cell>
221
+ <cv-table-cell column="size">4 KB</cv-table-cell>
222
+ </cv-table-row>
223
+ <cv-table-row value="file-2">
224
+ <cv-table-cell column="name">index.ts</cv-table-cell>
225
+ <cv-table-cell column="size">12 KB</cv-table-cell>
226
+ </cv-table-row>
227
+ </cv-table>
228
+
229
+ <!-- Interactive grid with selection -->
230
+ <cv-table aria-label="Spreadsheet" interactive selectable="multi" page-size="5">
231
+ <cv-table-column value="a" label="A"></cv-table-column>
232
+ <cv-table-column value="b" label="B"></cv-table-column>
233
+ <cv-table-column value="c" label="C"></cv-table-column>
234
+
235
+ <cv-table-row value="row-1">
236
+ <cv-table-cell column="a">1</cv-table-cell>
237
+ <cv-table-cell column="b">2</cv-table-cell>
238
+ <cv-table-cell column="c">3</cv-table-cell>
239
+ </cv-table-row>
240
+ <cv-table-row value="row-2">
241
+ <cv-table-cell column="a">4</cv-table-cell>
242
+ <cv-table-cell column="b">5</cv-table-cell>
243
+ <cv-table-cell column="c">6</cv-table-cell>
244
+ </cv-table-row>
245
+ </cv-table>
246
+ ```
247
+
248
+ ## Child Elements
249
+
250
+ ### cv-table-column
251
+
252
+ Column header definition within the table header row.
253
+
254
+ #### Anatomy
255
+
256
+ ```
257
+ <cv-table-column> (host)
258
+ └── <span part="base">
259
+ ├── <slot>${label}</slot>
260
+ └── sort indicator (▲/▼) ← only when sort-direction is ascending/descending
261
+ ```
262
+
263
+ #### Attributes
264
+
265
+ | Attribute | Type | Default | Description |
266
+ | ---------------- | ------- | -------- | ----------------------------------------------------------------------------- |
267
+ | `value` | String | `""` | Unique column identifier. Auto-generated as `column-N` if empty. |
268
+ | `label` | String | `""` | Column header text (used as default slot fallback) |
269
+ | `index` | Number | `0` | 1-based aria-colindex override for virtualized tables |
270
+ | `sortable` | Boolean | `false` | Enables sort interaction on this column |
271
+ | `sort-direction` | String | `"none"` | Current sort state: `none` \| `ascending` \| `descending` (managed by parent) |
272
+
273
+ #### Slots
274
+
275
+ | Slot | Description |
276
+ | ----------- | ------------------------------------------------------- |
277
+ | `(default)` | Column header content (falls back to `label` attribute) |
278
+
279
+ #### CSS Parts
280
+
281
+ | Part | Element | Description |
282
+ | ------ | -------- | ------------------------------------------------------- |
283
+ | `base` | `<span>` | Inline-flex wrapper containing label and sort indicator |
284
+
285
+ #### Visual States
286
+
287
+ | Host selector | Description |
288
+ | -------------------------------------- | ------------------------------------------------------ |
289
+ | `:host([sortable])` | `cursor: pointer` indicating interactive sort |
290
+ | `:host([sort-direction="ascending"])` | Primary color text with ascending indicator (▲) |
291
+ | `:host([sort-direction="descending"])` | Primary color text with descending indicator (▼) |
292
+ | `:host(:focus-visible)` | Focus ring for keyboard activation of sortable columns |
293
+
294
+ ---
295
+
296
+ ### cv-table-row
297
+
298
+ Data row containing cells within the table body.
299
+
300
+ #### Anatomy
301
+
302
+ ```
303
+ <cv-table-row> (host)
304
+ └── <slot> ← accepts <cv-table-cell> children
305
+ ```
306
+
307
+ #### Attributes
308
+
309
+ | Attribute | Type | Default | Description |
310
+ | ---------- | ------- | ------- | ------------------------------------------------------------ |
311
+ | `value` | String | `""` | Unique row identifier. Auto-generated as `row-N` if empty. |
312
+ | `index` | Number | `0` | 1-based aria-rowindex override for virtualized tables |
313
+ | `selected` | Boolean | `false` | Whether this row is selected (reflected from headless state) |
314
+
315
+ #### CSS Parts
316
+
317
+ | Part | Element | Description |
318
+ | ------ | ------- | --------------------------------------- |
319
+ | `base` | host | Row element (uses `display: table-row`) |
320
+
321
+ #### Events
322
+
323
+ | Event | Detail | Description |
324
+ | ------------------------- | ------ | ---------------------------------------------------------------------------- |
325
+ | `cv-table-row-slotchange` | -- | Fires when slotted cell children change; bubbles to parent for model rebuild |
326
+
327
+ #### Visual States
328
+
329
+ | Host selector | Description |
330
+ | ------------------- | ---------------------------------------------------------------- |
331
+ | `:host([selected])` | Selected row background via `--cv-table-row-selected-background` |
332
+
333
+ ---
334
+
335
+ ### cv-table-cell
336
+
337
+ Individual data cell within a table row.
338
+
339
+ #### Anatomy
340
+
341
+ ```
342
+ <cv-table-cell> (host)
343
+ └── <slot>
344
+ ```
345
+
346
+ #### Attributes
347
+
348
+ | Attribute | Type | Default | Description |
349
+ | ------------ | ------- | ------- | ----------------------------------------------------------------------------- |
350
+ | `column` | String | `""` | Column id this cell belongs to. Auto-resolved from positional index if empty. |
351
+ | `row-header` | Boolean | `false` | Marks this cell as a row header (`role="rowheader"`) |
352
+ | `colspan` | Number | `0` | Column span (applied as `aria-colspan` when >= 2) |
353
+ | `rowspan` | Number | `0` | Row span (applied as `aria-rowspan` when >= 2) |
354
+
355
+ #### Slots
356
+
357
+ | Slot | Description |
358
+ | ----------- | ------------ |
359
+ | `(default)` | Cell content |
360
+
361
+ #### CSS Parts
362
+
363
+ | Part | Element | Description |
364
+ | ------ | ------- | ----------------------------------------- |
365
+ | `base` | host | Cell element (uses `display: table-cell`) |
366
+
367
+ #### Visual States
368
+
369
+ | Host selector | Description |
370
+ | ----------------------------- | ----------------------------------------------------------------------------------- |
371
+ | `:host([row-header])` | Bold text (`font-weight: 600`) for row header cells |
372
+ | `:host([data-active="true"])` | Active cell in grid navigation mode (receives `tabindex="0"` via headless contract) |
@@ -0,0 +1,242 @@
1
+ # cv-tabs
2
+
3
+ Tabbed interface for switching between related content panels.
4
+
5
+ **Headless:** [`createTabs`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/tabs.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-tabs> (host)
11
+ └── <div part="base">
12
+ ├── <div part="list" role="tablist">
13
+ │ ├── <slot name="nav"> ← accepts <cv-tab> children
14
+ │ └── <div part="indicator"> ← animated active indicator
15
+ └── <div part="panels">
16
+ └── <slot> ← accepts <cv-tab-panel> children
17
+ ```
18
+
19
+ ## Attributes
20
+
21
+ | Attribute | Type | Default | Description |
22
+ | ----------------- | ------ | -------------- | ----------------------------------- |
23
+ | `value` | String | `""` | Currently selected tab value |
24
+ | `orientation` | String | `"horizontal"` | Layout: `horizontal` \| `vertical` |
25
+ | `activation-mode` | String | `"automatic"` | Activation: `automatic` \| `manual` |
26
+ | `aria-label` | String | `""` | Accessible label for the tablist |
27
+
28
+ ## Slots
29
+
30
+ | Slot | Description |
31
+ | ----------- | ------------------------- |
32
+ | `nav` | `<cv-tab>` children |
33
+ | `(default)` | `<cv-tab-panel>` children |
34
+
35
+ ## CSS Parts
36
+
37
+ | Part | Element | Description |
38
+ | ----------- | ------- | ----------------------------------------------------------- |
39
+ | `base` | `<div>` | Root layout container |
40
+ | `list` | `<div>` | Tablist wrapper |
41
+ | `indicator` | `<div>` | Animated active indicator positioned under the selected tab |
42
+ | `panels` | `<div>` | Panel container |
43
+
44
+ ## CSS Custom Properties
45
+
46
+ | Property | Default | Description |
47
+ | --------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------- |
48
+ | `--cv-tabs-indicator-color` | `var(--cv-color-primary, #65d7ff)` | Color of the active indicator |
49
+ | `--cv-tabs-indicator-size` | `3px` | Indicator thickness: height for horizontal orientation, width for vertical orientation |
50
+
51
+ Additionally, component styles depend on theme tokens through fallback values:
52
+
53
+ | Theme Property | Default | Description |
54
+ | -------------------- | --------- | ------------------------------- |
55
+ | `--cv-space-1` | `4px` | Gap between tabs, list padding |
56
+ | `--cv-space-2` | `8px` | Gap between list and panels |
57
+ | `--cv-space-3` | `12px` | Panels padding |
58
+ | `--cv-radius-md` | `10px` | List and panels border radius |
59
+ | `--cv-color-border` | `#2a3245` | List and panels border |
60
+ | `--cv-color-surface` | `#141923` | List and panels background |
61
+ | `--cv-color-primary` | `#65d7ff` | Focus and selected accent color |
62
+
63
+ ## Visual States
64
+
65
+ | Host selector | Description |
66
+ | --------------------------------- | --------------------------------------------------- |
67
+ | `:host([orientation="vertical"])` | Layout switches to vertical tablist + panel columns |
68
+
69
+ ## Events
70
+
71
+ | Event | Detail | Description |
72
+ | ----------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
73
+ | `cv-input` | `{activeTabId: string \| null, selectedTabId: string \| null}` | Fires on any active or selected state change, including active-only changes that do not change selection |
74
+ | `cv-change` | `{activeTabId: string \| null, selectedTabId: string \| null}` | Fires when selected tab changes |
75
+
76
+ `cv-input` fires on every user-driven state transition (active or selected). `cv-change` fires only when `selectedTabId` changes. Both events share the same detail shape. In `manual` activation mode, arrow-key navigation fires `cv-input` (active change) without `cv-change`; pressing `Enter`/`Space` fires both `cv-input` and `cv-change`.
77
+
78
+ ## Reactive State Mapping
79
+
80
+ `cv-tabs` is a visual adapter over headless `createTabs` reactive state.
81
+
82
+ ### UIKit Property to Headless Binding
83
+
84
+ | UIKit Property | Direction | Headless Binding |
85
+ | ----------------- | ------------- | ------------------------------------------------------ |
86
+ | `value` | attr → action | `actions.select(value)` when `value` attribute changes |
87
+ | `orientation` | attr → option | passed as `orientation` in `createTabs(options)` |
88
+ | `activation-mode` | attr → option | passed as `activationMode` in `createTabs(options)` |
89
+ | `aria-label` | attr → option | passed as `ariaLabel` in `createTabs(options)` |
90
+
91
+ ### Headless State to DOM Reflection
92
+
93
+ | Headless State | Direction | DOM Reflection |
94
+ | ----------------------- | ------------ | --------------------------------------------------------------------- |
95
+ | `state.selectedTabId()` | state → attr | `cv-tabs[value]` host attribute |
96
+ | `state.activeTabId()` | state → attr | `cv-tab[active]` boolean attribute on the active tab element |
97
+ | `state.selectedTabId()` | state → attr | `cv-tab[selected]` boolean attribute on the selected tab element |
98
+ | `state.selectedTabId()` | state → attr | `cv-tab-panel[selected]` and `cv-tab-panel[hidden]` on panel elements |
99
+
100
+ ### Headless Actions Called
101
+
102
+ | Action | UIKit Trigger |
103
+ | ------------------------------ | --------------------------------------------- |
104
+ | `actions.select(id)` | Tab is clicked or tapped (pointer activation) |
105
+ | `actions.handleKeyDown(event)` | `keydown` event on a tab element |
106
+
107
+ ### Headless Contracts Spread
108
+
109
+ | Contract | UIKit Target |
110
+ | ----------------------------- | ------------------------------------------------------------ |
111
+ | `contracts.getTabListProps()` | Spread onto `[part="list"]` element |
112
+ | `contracts.getTabProps(id)` | Spread onto each `cv-tab` element (via attribute sync) |
113
+ | `contracts.getPanelProps(id)` | Spread onto each `cv-tab-panel` element (via attribute sync) |
114
+
115
+ ### UIKit-Only Concerns (Not in Headless)
116
+
117
+ - **Active indicator**: Positioned and animated at the UIKit layer using `selectedTabId` to determine which tab to highlight.
118
+ - **Closable tabs**: Close button rendering and close orchestration are UIKit concerns. Headless handles selection fallback implicitly through model rebuild with an updated tab list (without the closed tab).
119
+ - **`cv-input` / `cv-change` events**: Custom DOM events dispatched by the UIKit wrapper, not part of the headless model.
120
+
121
+ UIKit does not own tab selection logic; headless state is the source of truth.
122
+
123
+ ## Usage
124
+
125
+ ```html
126
+ <cv-tabs value="tab-1">
127
+ <cv-tab slot="nav" value="tab-1">First</cv-tab>
128
+ <cv-tab slot="nav" value="tab-2">Second</cv-tab>
129
+ <cv-tab slot="nav" value="tab-3" disabled>Disabled</cv-tab>
130
+
131
+ <cv-tab-panel tab="tab-1">Content for first tab.</cv-tab-panel>
132
+ <cv-tab-panel tab="tab-2">Content for second tab.</cv-tab-panel>
133
+ <cv-tab-panel tab="tab-3">Content for disabled tab.</cv-tab-panel>
134
+ </cv-tabs>
135
+
136
+ <cv-tabs orientation="vertical" activation-mode="manual">
137
+ <cv-tab slot="nav" value="overview">Overview</cv-tab>
138
+ <cv-tab slot="nav" value="history">History</cv-tab>
139
+ <cv-tab-panel tab="overview">Overview panel.</cv-tab-panel>
140
+ <cv-tab-panel tab="history">History panel.</cv-tab-panel>
141
+ </cv-tabs>
142
+
143
+ <cv-tabs value="home">
144
+ <cv-tab slot="nav" value="home" closable>Home</cv-tab>
145
+ <cv-tab slot="nav" value="settings" closable>Settings</cv-tab>
146
+ <cv-tab-panel tab="home">Home content.</cv-tab-panel>
147
+ <cv-tab-panel tab="settings">Settings content.</cv-tab-panel>
148
+ </cv-tabs>
149
+ ```
150
+
151
+ ## Child Elements
152
+
153
+ ### cv-tab
154
+
155
+ Individual tab trigger within the tablist.
156
+
157
+ #### Anatomy
158
+
159
+ ```
160
+ <cv-tab> (host)
161
+ └── <div class="tab" part="base">
162
+ ├── <slot>
163
+ └── <button part="close-button"> ← only when [closable]
164
+ ```
165
+
166
+ #### Attributes
167
+
168
+ | Attribute | Type | Default | Description |
169
+ | ---------- | ------- | ------- | ------------------------------------------------------- |
170
+ | `value` | String | `""` | Unique identifier linking this tab to a panel |
171
+ | `disabled` | Boolean | `false` | Prevents selection and keyboard activation |
172
+ | `active` | Boolean | `false` | Whether this tab has roving focus (managed by parent) |
173
+ | `selected` | Boolean | `false` | Whether this tab's panel is visible (managed by parent) |
174
+ | `closable` | Boolean | `false` | Shows close affordance for removal flows |
175
+
176
+ #### Slots
177
+
178
+ | Slot | Description |
179
+ | ----------- | ----------------- |
180
+ | `(default)` | Tab label content |
181
+
182
+ #### CSS Parts
183
+
184
+ | Part | Element | Description |
185
+ | -------------- | ---------- | ---------------------------------------------------------- |
186
+ | `base` | `<div>` | Tab interactive wrapper |
187
+ | `close-button` | `<button>` | Close affordance (rendered only when `closable` is `true`) |
188
+
189
+ #### Visual States
190
+
191
+ | Host selector | Description |
192
+ | ------------------- | ------------------------------------------------ |
193
+ | `:host([active])` | Focused tab in roving tabindex model |
194
+ | `:host([selected])` | Selected tab with visible panel |
195
+ | `:host([disabled])` | Disabled appearance and non-interactive behavior |
196
+
197
+ #### Events
198
+
199
+ | Event | Detail | Description |
200
+ | ---------- | ----------------- | --------------------------------------------------------------- |
201
+ | `cv-close` | `{value: string}` | Requests removal of this tab when close affordance is activated |
202
+
203
+ The `cv-close` event bubbles and is composed. It is dispatched when the user activates the close button. The `value` in the detail corresponds to the tab's `value` attribute. The parent `cv-tabs` handles close orchestration: it determines a fallback tab, transitions selection if the closed tab was active or selected, and expects the consumer to remove the `cv-tab` and `cv-tab-panel` elements from the DOM.
204
+
205
+ ---
206
+
207
+ ### cv-tab-panel
208
+
209
+ Content panel associated with a tab.
210
+
211
+ #### Anatomy
212
+
213
+ ```
214
+ <cv-tab-panel> (host)
215
+ └── <div part="base" role="tabpanel">
216
+ └── <slot>
217
+ ```
218
+
219
+ #### Attributes
220
+
221
+ | Attribute | Type | Default | Description |
222
+ | ---------- | ------- | ------- | ------------------------------------------------- |
223
+ | `tab` | String | `""` | Value of the associated `<cv-tab>` |
224
+ | `selected` | Boolean | `false` | Whether this panel is visible (managed by parent) |
225
+
226
+ #### Slots
227
+
228
+ | Slot | Description |
229
+ | ----------- | ------------- |
230
+ | `(default)` | Panel content |
231
+
232
+ #### CSS Parts
233
+
234
+ | Part | Element | Description |
235
+ | ------ | ------- | --------------------- |
236
+ | `base` | `<div>` | Panel content wrapper |
237
+
238
+ #### Visual States
239
+
240
+ | Host selector | Description |
241
+ | ----------------- | --------------------------------- |
242
+ | `:host([hidden])` | Hidden when panel is not selected |