@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,207 @@
1
+ # cv-popover
2
+
3
+ Non-modal overlay anchored to a trigger element for contextual content such as menus, tooltips, or forms.
4
+
5
+ **Headless:** [`createPopover`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/popover.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-popover> (host)
11
+ └── <div part="base">
12
+ ├── <button part="trigger" type="button">
13
+ │ └── <slot name="trigger">
14
+ ├── <div part="content" role="dialog">
15
+ │ ├── <slot>
16
+ │ └── <span part="arrow">
17
+ │ └── <slot name="arrow">
18
+ └── (document-level listeners for outside dismiss)
19
+ ```
20
+
21
+ ## Attributes
22
+
23
+ | Attribute | Type | Default | Description |
24
+ | -------------------------- | ------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
25
+ | `open` | Boolean | `false` | Whether the popover content is visible |
26
+ | `placement` | String | `"bottom-start"` | Content placement relative to the anchor: `top` \| `top-start` \| `top-end` \| `bottom` \| `bottom-start` \| `bottom-end` \| `left` \| `left-start` \| `left-end` \| `right` \| `right-start` \| `right-end` |
27
+ | `anchor` | String | `"trigger"` | Positioning reference: `trigger` \| `host` |
28
+ | `offset` | Number | `4` | Distance (in px) between anchor and content |
29
+ | `arrow` | Boolean | `false` | Show an arrow pointing toward the anchor |
30
+ | `close-on-escape` | Boolean | `true` | Whether Escape key closes the popover |
31
+ | `close-on-outside-pointer` | Boolean | `true` | Whether clicking outside closes the popover |
32
+ | `close-on-outside-focus` | Boolean | `true` | Whether focusing outside closes the popover |
33
+ | `aria-label` | String | `""` | Accessible label for content panel |
34
+ | `aria-labelledby` | String | `""` | Id of element labelling the content panel |
35
+
36
+ ## Slots
37
+
38
+ | Slot | Description |
39
+ | ----------- | --------------------------------------------- |
40
+ | `(default)` | Popover content |
41
+ | `trigger` | Trigger element (defaults to a styled button) |
42
+ | `arrow` | Custom arrow content (replaces default arrow) |
43
+
44
+ ## CSS Parts
45
+
46
+ | Part | Element | Description |
47
+ | --------- | ---------- | --------------------------------------------- |
48
+ | `base` | `<div>` | Root layout wrapper with `position: relative` |
49
+ | `trigger` | `<button>` | Trigger element that opens/closes the popover |
50
+ | `content` | `<div>` | Popover content panel with `role="dialog"` |
51
+ | `arrow` | `<span>` | Arrow element pointing toward the anchor |
52
+
53
+ ## CSS Custom Properties
54
+
55
+ | Property | Default | Description |
56
+ | ------------------------------ | -------------------------------- | ------------------------------------- |
57
+ | `--cv-popover-offset` | `var(--cv-space-1, 4px)` | Distance between anchor and content |
58
+ | `--cv-popover-min-inline-size` | `max(220px, 100%)` | Minimum width of content panel |
59
+ | `--cv-popover-max-inline-size` | `min(560px, calc(100vw - 32px))` | Maximum width of content panel |
60
+ | `--cv-popover-padding` | `var(--cv-space-3, 12px)` | Content panel padding |
61
+ | `--cv-popover-border-radius` | `var(--cv-radius-md, 10px)` | Content panel border radius |
62
+ | `--cv-popover-z-index` | `20` | Content panel stacking order |
63
+ | `--cv-popover-arrow-size` | `8px` | Width and height of the arrow element |
64
+
65
+ Additionally, component styles depend on theme tokens through fallback values:
66
+
67
+ | Theme Property | Default | Description |
68
+ | ----------------------------- | ------------------------------- | ------------------------------------ |
69
+ | `--cv-color-border` | `#2a3245` | Border color for trigger and content |
70
+ | `--cv-color-surface` | `#141923` | Trigger background color |
71
+ | `--cv-color-surface-elevated` | `#1d2432` | Content panel background color |
72
+ | `--cv-color-text` | `#e8ecf6` | Default text color |
73
+ | `--cv-color-primary` | `#65d7ff` | Focus ring color |
74
+ | `--cv-shadow-1` | `0 2px 8px rgba(0, 0, 0, 0.24)` | Content panel box shadow |
75
+ | `--cv-radius-sm` | `6px` | Trigger border radius fallback |
76
+ | `--cv-radius-md` | `10px` | Content border radius fallback |
77
+ | `--cv-space-1` | `4px` | Small spacing scale fallback |
78
+ | `--cv-space-2` | `8px` | Medium spacing scale fallback |
79
+ | `--cv-space-3` | `12px` | Medium-large spacing scale fallback |
80
+
81
+ ## Visual States
82
+
83
+ | Host selector | Description |
84
+ | ----------------------------------- | -------------------------------------------------------- |
85
+ | `:host([open])` | Content panel is visible |
86
+ | `:host(:not([open]))` | Content panel is hidden |
87
+ | `:host([placement="top"])` | Content positioned above anchor, centered |
88
+ | `:host([placement="top-start"])` | Content positioned above anchor, start-aligned |
89
+ | `:host([placement="top-end"])` | Content positioned above anchor, end-aligned |
90
+ | `:host([placement="bottom"])` | Content positioned below anchor, centered |
91
+ | `:host([placement="bottom-start"])` | Content positioned below anchor, start-aligned (default) |
92
+ | `:host([placement="bottom-end"])` | Content positioned below anchor, end-aligned |
93
+ | `:host([placement="left"])` | Content positioned to the left, centered |
94
+ | `:host([placement="left-start"])` | Content positioned to the left, start-aligned |
95
+ | `:host([placement="left-end"])` | Content positioned to the left, end-aligned |
96
+ | `:host([placement="right"])` | Content positioned to the right, centered |
97
+ | `:host([placement="right-start"])` | Content positioned to the right, start-aligned |
98
+ | `:host([placement="right-end"])` | Content positioned to the right, end-aligned |
99
+ | `:host([anchor="host"])` | Content positioned relative to host instead of trigger |
100
+ | `:host([arrow])` | Arrow element is visible |
101
+
102
+ ## Events
103
+
104
+ | Event | Detail | Cancelable | Description |
105
+ | -------------- | -------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
106
+ | `beforetoggle` | `{open: boolean, openedBy: string \| null, dismissIntent: string \| null}` | Yes (on open) | Fires before the open state changes. Canceling (via `preventDefault()`) when opening prevents the popover from opening. Not cancelable on close. |
107
+ | `toggle` | `{open: boolean, openedBy: string \| null, dismissIntent: string \| null}` | No | Fires after the open state has changed |
108
+
109
+ Event detail fields:
110
+
111
+ - `open` — the new visibility state (`true` when opening, `false` when closing)
112
+ - `openedBy` — how the popover was opened: `"keyboard"` \| `"pointer"` \| `"programmatic"` \| `null`
113
+ - `dismissIntent` — why the popover was closed: `"escape"` \| `"outside-pointer"` \| `"outside-focus"` \| `"programmatic"` \| `null`
114
+
115
+ ## Reactive State Mapping
116
+
117
+ `cv-popover` is a visual adapter over headless `createPopover`.
118
+
119
+ | UIKit Property | Direction | Headless Binding |
120
+ | -------------------------- | -------------- | ----------------------------------------------------------------------- |
121
+ | `open` | attr -> action | `actions.open(source)` / `actions.close(intent)` based on boolean value |
122
+ | `close-on-escape` | attr -> option | `closeOnEscape` passed to `createPopover(options)` |
123
+ | `close-on-outside-pointer` | attr -> option | `closeOnOutsidePointer` passed to `createPopover(options)` |
124
+ | `close-on-outside-focus` | attr -> option | `closeOnOutsideFocus` passed to `createPopover(options)` |
125
+ | `aria-label` | attr -> option | `ariaLabel` passed to `createPopover(options)` |
126
+ | `aria-labelledby` | attr -> option | `ariaLabelledBy` passed to `createPopover(options)` |
127
+
128
+ | Headless State | Direction | DOM Reflection |
129
+ | --------------------------- | -------------- | ------------------------------------------------------- |
130
+ | `state.isOpen()` | state -> attr | `[open]` host attribute |
131
+ | `state.openedBy()` | state -> event | Included in `beforetoggle` / `toggle` event detail |
132
+ | `state.lastDismissIntent()` | state -> event | Included in `beforetoggle` / `toggle` event detail |
133
+ | `state.restoreTargetId()` | state -> DOM | Focus restored to trigger element after close |
134
+ | `state.useNativePopover()` | state -> DOM | Controls native `showPopover()` / `hidePopover()` calls |
135
+
136
+ - `contracts.getTriggerProps()` is spread onto the inner `[part="trigger"]` element to apply `role`, `aria-haspopup`, `aria-expanded`, `aria-controls`, `tabindex`, `popovertarget` (when native), and keyboard/click handlers.
137
+ - `contracts.getContentProps()` is spread onto the inner `[part="content"]` element to apply `role`, `aria-modal`, `aria-label`, `aria-labelledby`, `tabindex`, `hidden` (when manual mode), `popover="manual"` (when native), and keyboard/outside-dismiss handlers.
138
+ - UIKit dispatches `beforetoggle` (cancelable on open) and `toggle` events by observing `isOpen` changes from user activation.
139
+ - UIKit does not own open/close, keyboard, or dismiss logic; headless state is the source of truth.
140
+ - When `beforetoggle` is canceled on open, UIKit calls `actions.close()` to revert headless state.
141
+
142
+ ### Native Popover API Progressive Enhancement
143
+
144
+ UIKit auto-detects native Popover API support via feature check (`typeof HTMLElement.prototype.showPopover === 'function'`). When supported:
145
+
146
+ 1. `useNativePopover: true` is passed to `createPopover(options)`.
147
+ 2. Content element receives `popover="manual"` attribute (from headless contract) instead of `hidden`.
148
+ 3. UIKit calls `contentElement.showPopover()` when `state.isOpen()` transitions to `true`.
149
+ 4. UIKit calls `contentElement.hidePopover()` when `state.isOpen()` transitions to `false`.
150
+ 5. UIKit listens for native `toggle` events on the content element and calls `actions.handleNativeToggle(newState)` to synchronize headless state.
151
+
152
+ When the native Popover API is not available, the component falls back to `hidden` attribute-based visibility management. Behavior is identical in both modes; the headless layer manages all open/close logic regardless.
153
+
154
+ ### Placement (UIKit-only)
155
+
156
+ Placement is CSS-only (no Floating UI). The `placement` attribute maps to `data-placement` on the content element, which drives absolute positioning rules via CSS selectors. The `anchor` attribute controls whether the content panel is positioned relative to the trigger button or the host element. The `offset` attribute maps to `--cv-popover-offset`.
157
+
158
+ ### Arrow (UIKit-only)
159
+
160
+ When the `arrow` boolean attribute is present, the `[part="arrow"]` element is rendered inside the content panel. It is positioned via CSS to point toward the anchor, with its direction derived from the current `placement`. The `arrow` slot allows custom arrow content. Arrow size is controlled by `--cv-popover-arrow-size`.
161
+
162
+ ## Usage
163
+
164
+ ```html
165
+ <!-- Basic popover -->
166
+ <cv-popover>
167
+ <span slot="trigger">Options</span>
168
+ <p>Popover content here</p>
169
+ </cv-popover>
170
+
171
+ <!-- Custom placement -->
172
+ <cv-popover placement="top" offset="8">
173
+ <span slot="trigger">Help</span>
174
+ <p>Helpful information</p>
175
+ </cv-popover>
176
+
177
+ <!-- With arrow -->
178
+ <cv-popover arrow placement="bottom">
179
+ <span slot="trigger">Info</span>
180
+ <p>Content with arrow pointer</p>
181
+ </cv-popover>
182
+
183
+ <!-- Close policies disabled -->
184
+ <cv-popover close-on-escape="false" close-on-outside-pointer="false">
185
+ <span slot="trigger">Sticky</span>
186
+ <p>Only closes via trigger toggle</p>
187
+ </cv-popover>
188
+
189
+ <!-- Anchored to host -->
190
+ <cv-popover anchor="host" placement="bottom-end">
191
+ <span slot="trigger">Menu</span>
192
+ <nav>Navigation items</nav>
193
+ </cv-popover>
194
+
195
+ <!-- Programmatic open -->
196
+ <cv-popover open>
197
+ <span slot="trigger">Already open</span>
198
+ <p>Visible on mount</p>
199
+ </cv-popover>
200
+
201
+ <!-- With custom arrow -->
202
+ <cv-popover arrow placement="top">
203
+ <span slot="trigger">Custom arrow</span>
204
+ <svg slot="arrow" viewBox="0 0 16 8"><polygon points="8,0 16,8 0,8" /></svg>
205
+ <p>Content</p>
206
+ </cv-popover>
207
+ ```
@@ -0,0 +1,134 @@
1
+ # cv-progress-ring
2
+
3
+ A read-only circular indicator that communicates determinate or indeterminate loading/completion progress via an SVG ring.
4
+
5
+ **Headless:** [`createProgress`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/progress.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-progress-ring> (host)
11
+ └── <div part="base" role="progressbar">
12
+ └── <svg part="svg" viewBox="0 0 100 100">
13
+ │ ├── <circle part="track">
14
+ │ └── <circle part="indicator">
15
+ └── <span part="label">
16
+ └── <slot>
17
+ ```
18
+
19
+ ## Attributes
20
+
21
+ | Attribute | Type | Default | Description |
22
+ | --------------- | ------- | ------- | ----------------------------------------------------------------------------------- |
23
+ | `value` | Number | `0` | Current progress value; clamped to `[min, max]` |
24
+ | `min` | Number | `0` | Minimum boundary |
25
+ | `max` | Number | `100` | Maximum boundary |
26
+ | `indeterminate` | Boolean | `false` | Switches to indeterminate (spinning animation) mode |
27
+ | `value-text` | String | — | Static override for `aria-valuetext`; takes precedence over the percentage fallback |
28
+ | `aria-label` | String | — | Accessible label passed through to headless |
29
+
30
+ ## Slots
31
+
32
+ | Slot | Description |
33
+ | ----------- | ------------------------------------------------------------- |
34
+ | `(default)` | Label content rendered inside the ring (e.g. percentage text) |
35
+
36
+ ## CSS Parts
37
+
38
+ | Part | Element | Description |
39
+ | ----------- | ---------- | ---------------------------------------------------------------- |
40
+ | `base` | `<div>` | Outer container with `role="progressbar"` |
41
+ | `svg` | `<svg>` | SVG element containing track and indicator circles |
42
+ | `track` | `<circle>` | Background circle representing the full track |
43
+ | `indicator` | `<circle>` | Foreground arc representing current progress |
44
+ | `label` | `<span>` | Content overlay centered inside the ring; wraps the default slot |
45
+
46
+ ## CSS Custom Properties
47
+
48
+ | Property | Default | Description |
49
+ | ------------------------------------ | ---------------------------------- | ----------------------------------------------------------- |
50
+ | `--cv-progress-ring-size` | `80px` | Diameter of the ring (sets both inline-size and block-size) |
51
+ | `--cv-progress-ring-track-width` | `4px` | Stroke width of the background track circle |
52
+ | `--cv-progress-ring-indicator-width` | `4px` | Stroke width of the progress indicator arc |
53
+ | `--cv-progress-ring-track-color` | `var(--cv-color-surface, #141923)` | Background color of the track circle stroke |
54
+ | `--cv-progress-ring-indicator-color` | `var(--cv-color-primary, #65d7ff)` | Color of the filled indicator arc stroke |
55
+ | `--cv-progress-ring-label-color` | `var(--cv-color-text, #e8ecf6)` | Text color for the label slot content |
56
+
57
+ ## Visual States
58
+
59
+ | Host selector | Description |
60
+ | ------------------------ | ----------------------------------------------------------------------------------- |
61
+ | `:host([indeterminate])` | Spinning/rotating animation on the indicator arc; indicator has a fixed arc length |
62
+ | `:host([data-complete])` | Success appearance when `value >= max` (indicator stroke uses `--cv-color-success`) |
63
+
64
+ ## Reactive State Mapping
65
+
66
+ `cv-progress-ring` is a visual adapter over headless `createProgress`.
67
+
68
+ | UIKit Property | Direction | Headless Binding |
69
+ | --------------- | -------------- | -------------------------------------------------- |
70
+ | `value` | attr -> action | `actions.setValue(value)` |
71
+ | `min` | attr -> option | Passed as `min` in `createProgress(options)` |
72
+ | `max` | attr -> option | Passed as `max` in `createProgress(options)` |
73
+ | `indeterminate` | attr -> action | `actions.setIndeterminate(value)` |
74
+ | `value-text` | attr -> option | Passed as `valueText` in `createProgress(options)` |
75
+ | `aria-label` | attr -> option | Passed as `ariaLabel` in `createProgress(options)` |
76
+
77
+ | Headless State | Direction | DOM Reflection |
78
+ | ------------------------- | -------------- | -------------------------------------------------------------------------------- |
79
+ | `state.percentage()` | state -> style | Sets `stroke-dashoffset` on the indicator `<circle>` to represent the filled arc |
80
+ | `state.isIndeterminate()` | state -> attr | `[indeterminate]` host attribute |
81
+ | `state.isComplete()` | state -> attr | `[data-complete]` host attribute |
82
+
83
+ - `contracts.getProgressProps()` is spread onto the inner `[part="base"]` element to apply `role`, `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, `aria-valuetext`, `aria-label`, and `id`.
84
+ - ARIA value attributes are present only in determinate mode; headless omits them in indeterminate mode.
85
+ - `aria-valuetext` resolution order (determinate only): `valueText` static override > rounded percentage fallback.
86
+ - UIKit does not own clamping, completion, or percentage logic; headless state is the source of truth.
87
+
88
+ ## Events
89
+
90
+ None. `cv-progress-ring` is a read-only indicator with no user-modifiable state.
91
+
92
+ ## Usage
93
+
94
+ ```html
95
+ <!-- Basic determinate -->
96
+ <cv-progress-ring value="40" aria-label="Upload progress"></cv-progress-ring>
97
+
98
+ <!-- With label slot -->
99
+ <cv-progress-ring value="72" aria-label="Download">72%</cv-progress-ring>
100
+
101
+ <!-- Custom range -->
102
+ <cv-progress-ring value="3" min="0" max="10" aria-label="Steps completed"> 3/10 </cv-progress-ring>
103
+
104
+ <!-- Custom aria-valuetext -->
105
+ <cv-progress-ring value="3" max="10" value-text="Step 3 of 10" aria-label="Wizard progress">
106
+ </cv-progress-ring>
107
+
108
+ <!-- Indeterminate -->
109
+ <cv-progress-ring indeterminate aria-label="Loading"></cv-progress-ring>
110
+
111
+ <!-- Custom sizing and stroke widths -->
112
+ <cv-progress-ring
113
+ value="60"
114
+ aria-label="Battery"
115
+ style="
116
+ --cv-progress-ring-size: 120px;
117
+ --cv-progress-ring-track-width: 8px;
118
+ --cv-progress-ring-indicator-width: 8px;
119
+ --cv-progress-ring-indicator-color: limegreen;
120
+ "
121
+ >60%</cv-progress-ring
122
+ >
123
+
124
+ <!-- Small ring with thin stroke -->
125
+ <cv-progress-ring
126
+ value="80"
127
+ aria-label="Completion"
128
+ style="
129
+ --cv-progress-ring-size: 40px;
130
+ --cv-progress-ring-track-width: 2px;
131
+ --cv-progress-ring-indicator-width: 3px;
132
+ "
133
+ ></cv-progress-ring>
134
+ ```
@@ -0,0 +1,110 @@
1
+ # cv-progress
2
+
3
+ A read-only indicator that communicates determinate or indeterminate loading/completion progress.
4
+
5
+ **Headless:** [`createProgress`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/progress.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-progress> (host)
11
+ └── <div part="base" role="progressbar">
12
+ └── <div part="indicator">
13
+ └── <span part="label">
14
+ └── <slot>
15
+ ```
16
+
17
+ ## Attributes
18
+
19
+ | Attribute | Type | Default | Description |
20
+ | --------------- | ------- | ------- | ----------------------------------------------------------------------------------- |
21
+ | `value` | Number | `0` | Current progress value; clamped to `[min, max]` |
22
+ | `min` | Number | `0` | Minimum boundary |
23
+ | `max` | Number | `100` | Maximum boundary |
24
+ | `indeterminate` | Boolean | `false` | Switches to indeterminate (animated) mode |
25
+ | `value-text` | String | — | Static override for `aria-valuetext`; takes precedence over the percentage fallback |
26
+ | `aria-label` | String | — | Accessible label passed through to headless |
27
+
28
+ ## Slots
29
+
30
+ | Slot | Description |
31
+ | ----------- | ------------------------------------------------------------------ |
32
+ | `(default)` | Label content rendered inside the indicator (e.g. percentage text) |
33
+
34
+ ## CSS Parts
35
+
36
+ | Part | Element | Description |
37
+ | ----------- | -------- | -------------------------------------------------------- |
38
+ | `base` | `<div>` | Outer track container with `role="progressbar"` |
39
+ | `indicator` | `<div>` | Filled portion representing current progress |
40
+ | `label` | `<span>` | Content overlay inside indicator; wraps the default slot |
41
+
42
+ ## CSS Custom Properties
43
+
44
+ | Property | Default | Description |
45
+ | ------------------------------- | ---------------------------------- | ------------------------------------- |
46
+ | `--cv-progress-height` | `10px` | Block size (height) of the track |
47
+ | `--cv-progress-track-color` | `var(--cv-color-surface, #141923)` | Background color of the track |
48
+ | `--cv-progress-indicator-color` | `var(--cv-color-primary, #65d7ff)` | Base color of the filled indicator |
49
+ | `--cv-progress-label-color` | `var(--cv-color-text, #e8ecf6)` | Text color for the label slot content |
50
+
51
+ ## Visual States
52
+
53
+ | Host selector | Description |
54
+ | ------------------------ | ------------------------------------------------------------------ |
55
+ | `:host([indeterminate])` | Animated sliding bar; indicator width fixed, translateX animation |
56
+ | `:host([data-complete])` | Success appearance when `value >= max` (uses `--cv-color-success`) |
57
+
58
+ ## Reactive State Mapping
59
+
60
+ `cv-progress` is a visual adapter over headless `createProgress`.
61
+
62
+ | UIKit Property | Direction | Headless Binding |
63
+ | --------------- | ------------- | -------------------------------------------------- |
64
+ | `value` | attr → action | `actions.setValue(value)` |
65
+ | `min` | attr → option | Passed as `min` in `createProgress(options)` |
66
+ | `max` | attr → option | Passed as `max` in `createProgress(options)` |
67
+ | `indeterminate` | attr → action | `actions.setIndeterminate(value)` |
68
+ | `value-text` | attr → option | Passed as `valueText` in `createProgress(options)` |
69
+ | `aria-label` | attr → option | Passed as `ariaLabel` in `createProgress(options)` |
70
+
71
+ | Headless State | Direction | DOM Reflection |
72
+ | ------------------------- | ------------- | ------------------------------------------------------- |
73
+ | `state.percentage()` | state → style | Sets `--cv-progress-width` on indicator for inline-size |
74
+ | `state.isIndeterminate()` | state → attr | `[indeterminate]` host attribute |
75
+ | `state.isComplete()` | state → attr | `[data-complete]` host attribute |
76
+
77
+ - `contracts.getProgressProps()` is spread onto the inner `[part="base"]` element to apply `role`, `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, `aria-valuetext`, `aria-label`, and `id`.
78
+ - ARIA value attributes are present only in determinate mode; headless omits them in indeterminate mode.
79
+ - `aria-valuetext` resolution order (determinate only): `valueText` static override > rounded percentage fallback.
80
+ - UIKit does not own clamping or completion logic; headless state is the source of truth.
81
+
82
+ ## Events
83
+
84
+ None. `cv-progress` is a read-only indicator with no user-modifiable state.
85
+
86
+ ## Usage
87
+
88
+ ```html
89
+ <!-- Basic determinate -->
90
+ <cv-progress value="40" aria-label="Upload progress"></cv-progress>
91
+
92
+ <!-- With label slot -->
93
+ <cv-progress value="72" aria-label="Download">72%</cv-progress>
94
+
95
+ <!-- Custom range -->
96
+ <cv-progress value="3" min="0" max="10" aria-label="Steps completed"></cv-progress>
97
+
98
+ <!-- Custom aria-valuetext -->
99
+ <cv-progress value="3" max="10" value-text="Step 3 of 10" aria-label="Wizard progress"></cv-progress>
100
+
101
+ <!-- Indeterminate -->
102
+ <cv-progress indeterminate aria-label="Loading"></cv-progress>
103
+
104
+ <!-- Styled via CSS custom properties -->
105
+ <cv-progress
106
+ value="60"
107
+ aria-label="Battery"
108
+ style="--cv-progress-height: 16px; --cv-progress-indicator-color: limegreen;"
109
+ ></cv-progress>
110
+ ```