@ui-sdk/vue 0.0.0-stage → 0.1.0-beta.1

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 (262) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +10 -2
  3. package/dist/accordion.d.ts +561 -0
  4. package/dist/accordion.js +24 -0
  5. package/dist/agent.d.ts +2 -0
  6. package/dist/agent.js +7 -0
  7. package/dist/alert-dialog.d.ts +231 -0
  8. package/dist/alert-dialog.js +33 -0
  9. package/dist/async-options.d.ts +18 -0
  10. package/dist/async-options.js +40 -0
  11. package/dist/autocomplete.d.ts +384 -0
  12. package/dist/autocomplete.js +344 -0
  13. package/dist/avatar.d.ts +40 -0
  14. package/dist/avatar.js +28 -0
  15. package/dist/button.d.ts +52 -0
  16. package/dist/button.js +43 -0
  17. package/dist/calendar-context.d.ts +10 -0
  18. package/dist/calendar-context.js +7 -0
  19. package/dist/calendar.d.ts +311 -0
  20. package/dist/calendar.js +349 -0
  21. package/dist/carousel.d.ts +236 -0
  22. package/dist/carousel.js +204 -0
  23. package/dist/checkbox.d.ts +211 -0
  24. package/dist/checkbox.js +151 -0
  25. package/dist/children.d.ts +41 -0
  26. package/dist/children.js +135 -0
  27. package/dist/collapsible.d.ts +54 -0
  28. package/dist/collapsible.js +18 -0
  29. package/dist/color-picker.d.ts +187 -0
  30. package/dist/color-picker.js +158 -0
  31. package/dist/color.d.ts +2 -0
  32. package/dist/color.js +21 -0
  33. package/dist/combobox.d.ts +517 -0
  34. package/dist/combobox.js +531 -0
  35. package/dist/command.d.ts +322 -0
  36. package/dist/command.js +345 -0
  37. package/dist/controller-target.d.ts +49 -0
  38. package/dist/controller-target.js +112 -0
  39. package/dist/create-parts.d.ts +126 -0
  40. package/dist/create-parts.js +206 -0
  41. package/dist/date-input.d.ts +133 -0
  42. package/dist/date-input.js +31 -0
  43. package/dist/date-picker.d.ts +328 -0
  44. package/dist/date-picker.js +354 -0
  45. package/dist/date.d.ts +2 -0
  46. package/dist/date.js +59 -0
  47. package/dist/dialog-parts.d.ts +259 -0
  48. package/dist/dialog-parts.js +300 -0
  49. package/dist/dialog.d.ts +251 -0
  50. package/dist/dialog.js +25 -0
  51. package/dist/drawer.d.ts +304 -0
  52. package/dist/drawer.js +373 -0
  53. package/dist/editor-context.d.ts +4 -0
  54. package/dist/editor-context.js +5 -0
  55. package/dist/field-context.d.ts +26 -0
  56. package/dist/field-context.js +44 -0
  57. package/dist/field.d.ts +154 -0
  58. package/dist/field.js +240 -0
  59. package/dist/file-input.d.ts +212 -0
  60. package/dist/file-input.js +255 -0
  61. package/dist/form-inputs.d.ts +16 -0
  62. package/dist/form-inputs.js +25 -0
  63. package/dist/gesture.d.ts +1 -0
  64. package/dist/gesture.js +15 -0
  65. package/dist/history.d.ts +2 -0
  66. package/dist/history.js +11 -0
  67. package/dist/hover-card.d.ts +137 -0
  68. package/dist/hover-card.js +153 -0
  69. package/dist/image-cropper.d.ts +74 -0
  70. package/dist/image-cropper.js +36 -0
  71. package/dist/image.d.ts +2 -0
  72. package/dist/image.js +5 -0
  73. package/dist/index.d.ts +59 -0
  74. package/dist/index.js +60 -0
  75. package/dist/input.d.ts +120 -0
  76. package/dist/input.js +73 -0
  77. package/dist/item-icon.d.ts +34 -0
  78. package/dist/item-icon.js +63 -0
  79. package/dist/label.d.ts +16 -0
  80. package/dist/label.js +8 -0
  81. package/dist/lifecycle.d.ts +11 -0
  82. package/dist/lifecycle.js +91 -0
  83. package/dist/list-items.d.ts +38 -0
  84. package/dist/list-items.js +59 -0
  85. package/dist/list-text.d.ts +7 -0
  86. package/dist/list-text.js +15 -0
  87. package/dist/listbox.d.ts +273 -0
  88. package/dist/listbox.js +126 -0
  89. package/dist/menu.d.ts +678 -0
  90. package/dist/menu.js +607 -0
  91. package/dist/menubar-context.d.ts +5 -0
  92. package/dist/menubar-context.js +5 -0
  93. package/dist/menubar.d.ts +61 -0
  94. package/dist/menubar.js +48 -0
  95. package/dist/meter.d.ts +41 -0
  96. package/dist/meter.js +37 -0
  97. package/dist/navigation-menu.d.ts +228 -0
  98. package/dist/navigation-menu.js +195 -0
  99. package/dist/number-input.d.ts +176 -0
  100. package/dist/number-input.js +231 -0
  101. package/dist/number.d.ts +2 -0
  102. package/dist/number.js +39 -0
  103. package/dist/nuxt/entries.json +977 -0
  104. package/dist/nuxt/module.d.ts +18 -0
  105. package/dist/nuxt/module.js +31 -0
  106. package/dist/nuxt/registrations.d.ts +22 -0
  107. package/dist/nuxt/registrations.js +22 -0
  108. package/dist/observe.d.ts +2 -0
  109. package/dist/observe.js +7 -0
  110. package/dist/otp-input.d.ts +142 -0
  111. package/dist/otp-input.js +130 -0
  112. package/dist/overlays.d.ts +40 -0
  113. package/dist/overlays.js +118 -0
  114. package/dist/pagination.d.ts +201 -0
  115. package/dist/pagination.js +146 -0
  116. package/dist/password-input.d.ts +144 -0
  117. package/dist/password-input.js +134 -0
  118. package/dist/popover.d.ts +211 -0
  119. package/dist/popover.js +258 -0
  120. package/dist/popup.d.ts +60 -0
  121. package/dist/popup.js +89 -0
  122. package/dist/preferences.d.ts +44 -0
  123. package/dist/preferences.js +102 -0
  124. package/dist/primitive.d.ts +2 -0
  125. package/dist/primitive.js +11 -0
  126. package/dist/progress.d.ts +41 -0
  127. package/dist/progress.js +37 -0
  128. package/dist/radio.d.ts +164 -0
  129. package/dist/radio.js +145 -0
  130. package/dist/rating.d.ts +149 -0
  131. package/dist/rating.js +146 -0
  132. package/dist/renderless.d.ts +4 -0
  133. package/dist/renderless.js +28 -0
  134. package/dist/resizable.d.ts +54 -0
  135. package/dist/resizable.js +22 -0
  136. package/dist/scroll-area.d.ts +72 -0
  137. package/dist/scroll-area.js +77 -0
  138. package/dist/segmented-control.d.ts +151 -0
  139. package/dist/segmented-control.js +110 -0
  140. package/dist/segmented-input.d.ts +76 -0
  141. package/dist/segmented-input.js +115 -0
  142. package/dist/select.d.ts +458 -0
  143. package/dist/select.js +468 -0
  144. package/dist/separator.d.ts +14 -0
  145. package/dist/separator.js +8 -0
  146. package/dist/shortcuts.d.ts +32 -0
  147. package/dist/shortcuts.js +56 -0
  148. package/dist/slider.d.ts +65 -0
  149. package/dist/slider.js +194 -0
  150. package/dist/switch.d.ts +103 -0
  151. package/dist/switch.js +71 -0
  152. package/dist/tabs.d.ts +338 -0
  153. package/dist/tabs.js +27 -0
  154. package/dist/text-control.d.ts +89 -0
  155. package/dist/text-control.js +51 -0
  156. package/dist/text-match.d.ts +2 -0
  157. package/dist/text-match.js +7 -0
  158. package/dist/textarea.d.ts +106 -0
  159. package/dist/textarea.js +68 -0
  160. package/dist/time-input.d.ts +131 -0
  161. package/dist/time-input.js +32 -0
  162. package/dist/timeline.d.ts +81 -0
  163. package/dist/timeline.js +30 -0
  164. package/dist/toast-parts.d.ts +203 -0
  165. package/dist/toast-parts.js +270 -0
  166. package/dist/toast.d.ts +2 -0
  167. package/dist/toast.js +21 -0
  168. package/dist/toggle-props.d.ts +22 -0
  169. package/dist/toggle-props.js +14 -0
  170. package/dist/toggle.d.ts +199 -0
  171. package/dist/toggle.js +129 -0
  172. package/dist/token-input.d.ts +136 -0
  173. package/dist/token-input.js +214 -0
  174. package/dist/toolbar-context.d.ts +7 -0
  175. package/dist/toolbar-context.js +20 -0
  176. package/dist/toolbar.d.ts +120 -0
  177. package/dist/toolbar.js +77 -0
  178. package/dist/tooltip.d.ts +160 -0
  179. package/dist/tooltip.js +224 -0
  180. package/dist/tree.d.ts +300 -0
  181. package/dist/tree.js +209 -0
  182. package/dist/trigger-menu.d.ts +452 -0
  183. package/dist/trigger-menu.js +254 -0
  184. package/dist/use-primitive.d.ts +132 -0
  185. package/dist/use-primitive.js +462 -0
  186. package/dist/waterfall.d.ts +201 -0
  187. package/dist/waterfall.js +171 -0
  188. package/docs/README.md +89 -0
  189. package/docs/agents.md +1523 -0
  190. package/docs/components/accordion.md +1369 -0
  191. package/docs/components/alert-dialog.md +799 -0
  192. package/docs/components/autocomplete.md +1857 -0
  193. package/docs/components/avatar.md +272 -0
  194. package/docs/components/button.md +435 -0
  195. package/docs/components/calendar.md +1589 -0
  196. package/docs/components/carousel.md +1171 -0
  197. package/docs/components/checkbox.md +1008 -0
  198. package/docs/components/collapsible.md +536 -0
  199. package/docs/components/color-picker.md +706 -0
  200. package/docs/components/combobox.md +3504 -0
  201. package/docs/components/command.md +1481 -0
  202. package/docs/components/date-input.md +536 -0
  203. package/docs/components/date-picker.md +1244 -0
  204. package/docs/components/dialog.md +1397 -0
  205. package/docs/components/drawer.md +1166 -0
  206. package/docs/components/field.md +538 -0
  207. package/docs/components/file-input.md +611 -0
  208. package/docs/components/hover-card.md +505 -0
  209. package/docs/components/image-cropper.md +556 -0
  210. package/docs/components/input.md +306 -0
  211. package/docs/components/label.md +203 -0
  212. package/docs/components/listbox.md +1004 -0
  213. package/docs/components/menu.md +1229 -0
  214. package/docs/components/menubar.md +443 -0
  215. package/docs/components/meter.md +315 -0
  216. package/docs/components/navigation-menu.md +1523 -0
  217. package/docs/components/number-input.md +1029 -0
  218. package/docs/components/otp-input.md +512 -0
  219. package/docs/components/pagination.md +613 -0
  220. package/docs/components/password-input.md +649 -0
  221. package/docs/components/popover.md +1448 -0
  222. package/docs/components/preferences.md +208 -0
  223. package/docs/components/progress.md +423 -0
  224. package/docs/components/radio.md +785 -0
  225. package/docs/components/rating.md +416 -0
  226. package/docs/components/resizable.md +910 -0
  227. package/docs/components/scroll-area.md +915 -0
  228. package/docs/components/segmented-control.md +435 -0
  229. package/docs/components/select.md +2015 -0
  230. package/docs/components/separator.md +229 -0
  231. package/docs/components/slider.md +1084 -0
  232. package/docs/components/switch.md +565 -0
  233. package/docs/components/tabs.md +944 -0
  234. package/docs/components/textarea.md +299 -0
  235. package/docs/components/time-input.md +224 -0
  236. package/docs/components/timeline.md +654 -0
  237. package/docs/components/toast.md +1224 -0
  238. package/docs/components/toggle.md +500 -0
  239. package/docs/components/token-input.md +953 -0
  240. package/docs/components/toolbar.md +739 -0
  241. package/docs/components/tooltip.md +818 -0
  242. package/docs/components/tree.md +1306 -0
  243. package/docs/components/trigger-menu.md +1223 -0
  244. package/docs/components/waterfall.md +717 -0
  245. package/docs/controllers.md +71 -0
  246. package/docs/core.md +8027 -0
  247. package/docs/custom-parts.md +89 -0
  248. package/docs/first-component.md +83 -0
  249. package/docs/forms.md +122 -0
  250. package/docs/installation.md +72 -0
  251. package/docs/introduction.md +32 -0
  252. package/docs/lists-and-menus.md +122 -0
  253. package/docs/llms.txt +1502 -0
  254. package/docs/parts-and-composition.md +85 -0
  255. package/docs/popups.md +108 -0
  256. package/docs/rendering.md +56 -0
  257. package/docs/server-rendering.md +180 -0
  258. package/docs/state-and-events.md +100 -0
  259. package/docs/styling-and-animation.md +148 -0
  260. package/docs/utilities/async-options.md +348 -0
  261. package/docs/utilities/shortcuts.md +159 -0
  262. package/package.json +352 -3
@@ -0,0 +1,85 @@
1
+ # Parts and Composition
2
+
3
+ Put a component's parts together and choose what each one renders.
4
+
5
+ A component can have several parts. A Popover has a root to hold its state, a trigger to open it, and content to show.
6
+
7
+ ## Put Parts Together
8
+
9
+ Import the parts you need and nest them as shown in the component's Composition:
10
+
11
+ ```vue
12
+ <script setup lang="ts">
13
+ import {
14
+ Popover,
15
+ PopoverClose,
16
+ PopoverContent,
17
+ PopoverTitle,
18
+ PopoverTrigger,
19
+ } from "@ui-sdk/vue/popover"
20
+ </script>
21
+
22
+ <template>
23
+ <Popover>
24
+ <PopoverTrigger>Details</PopoverTrigger>
25
+ <PopoverContent>
26
+ <PopoverTitle>Project Details</PopoverTitle>
27
+ <p>Last updated today.</p>
28
+ <PopoverClose>Done</PopoverClose>
29
+ </PopoverContent>
30
+ </Popover>
31
+ </template>
32
+ ```
33
+
34
+ `Popover` shares state without adding an element. Other roots, such as `Tabs`, do render an element. Parts find their root through the framework's context, so you can put your own components between them.
35
+
36
+ ## Choose the Element
37
+
38
+ Render a menu item as a link:
39
+
40
+ ```vue
41
+ <script setup lang="ts">
42
+ import { Menu, MenuContent, MenuItem, MenuTrigger } from "@ui-sdk/vue/menu"
43
+ </script>
44
+
45
+ <template>
46
+ <Menu>
47
+ <MenuTrigger>Account</MenuTrigger>
48
+ <MenuContent>
49
+ <MenuItem href="/settings">
50
+ <template #render="{ props }">
51
+ <a v-bind="props">Settings</a>
52
+ </template>
53
+ </MenuItem>
54
+ </MenuContent>
55
+ </Menu>
56
+ </template>
57
+ ```
58
+
59
+ The `#render` slot receives the part's `props` and `state`. Bind the props with `v-bind="props"` to keep the part's behavior connected. It also receives `children`, the part's own content, like a slider thumb's range input: render it inside your element with `<component :is="children" />`. A component works too: the part binds to its root element, so give the component one element at its root.
60
+
61
+ Your event handler runs first. Calling `event.preventDefault()` skips the built-in handler. Keep the supplied roles, IDs, and ARIA state when rendering your own element. A name or typing hint you pass replaces the part's own: `aria-label`, `aria-labelledby`, `aria-description`, `title`, `autocomplete`, `autocapitalize`, `autocorrect`, `inputmode` and `spellcheck`. Roles, `tabindex`, references like `aria-controls` and `aria-describedby`, state attributes and `data-slot` stay the part's, since its behavior reads them.
62
+
63
+ ## Read the State
64
+
65
+ The part can pass its state to its content:
66
+
67
+ ```vue
68
+ <script setup lang="ts">
69
+ import { Toggle } from "@ui-sdk/vue/toggle"
70
+ </script>
71
+
72
+ <template>
73
+ <Toggle v-slot="{ state }" aria-label="Mute">{{
74
+ state.pressed ? "Mute on" : "Mute off"
75
+ }}</Toggle>
76
+ </template>
77
+ ```
78
+
79
+ Keep the accessible name stable when the content changes. Here, `aria-label="Mute"` names the toggle and `aria-pressed` reports its state.
80
+
81
+ ## Name the Control
82
+
83
+ Use the component's label or title part, such as `FieldLabel` or `DialogTitle`. Give controls without visible text an `aria-label`.
84
+
85
+ [Custom Parts](./custom-parts.md) covers writing a part with logic of its own.
package/docs/popups.md ADDED
@@ -0,0 +1,108 @@
1
+ # Popups
2
+
3
+ Place popup content, control dismissal, and choose when it stays mounted.
4
+
5
+ Popovers, menus, selects, and dialogs use the browser's top layer. They render in place, above the page, so you don't need a portal or Positioner part. Their events bubble through the elements around them like any other child's, so a click handler or a link wrapped around a popup also gets the clicks inside it. Keep popups outside clickable cards and links.
6
+
7
+ ## Place the Content
8
+
9
+ Set placement on the content part:
10
+
11
+ ```vue
12
+ <script setup lang="ts">
13
+ import {
14
+ Popover,
15
+ PopoverClose,
16
+ PopoverContent,
17
+ PopoverTitle,
18
+ PopoverTrigger,
19
+ } from "@ui-sdk/vue/popover"
20
+ </script>
21
+
22
+ <template>
23
+ <Popover>
24
+ <PopoverTrigger>Details</PopoverTrigger>
25
+ <PopoverContent side="bottom" align="start" :side-offset="8">
26
+ <PopoverTitle>Project Details</PopoverTitle>
27
+ <p>Last updated today.</p>
28
+ <PopoverClose>Done</PopoverClose>
29
+ </PopoverContent>
30
+ </Popover>
31
+ </template>
32
+ ```
33
+
34
+ The popup flips or shifts when there isn't enough room.
35
+
36
+ | Prop | What It Does |
37
+ | ------------------- | ---------------------------------------------------------------------------------------------------- |
38
+ | `side` | Chooses `top`, `bottom`, `left`, `right`, `start`, or `end`. Start and end follow reading direction. |
39
+ | `align` | Aligns the popup to the start, center, or end of that side. |
40
+ | `side-offset` | Sets the gap from the anchor, in pixels. |
41
+ | `align-offset` | Shifts it along its side. |
42
+ | `collision-padding` | Leaves space from the window's edges. |
43
+
44
+ An `anchor` positions the popup against another element. The component's page lists the anchors it accepts.
45
+
46
+ ## Control Dismissal
47
+
48
+ Set `dismissible` to false to stop Escape, outside presses, focus leaving, Back, and sheet swipes from closing it. The trigger, Close part, and controller can still close it:
49
+
50
+ ```vue
51
+ <script setup lang="ts">
52
+ import {
53
+ Popover,
54
+ PopoverClose,
55
+ PopoverContent,
56
+ PopoverTitle,
57
+ PopoverTrigger,
58
+ } from "@ui-sdk/vue/popover"
59
+ </script>
60
+
61
+ <template>
62
+ <Popover :dismissible="false">
63
+ <PopoverTrigger>Details</PopoverTrigger>
64
+ <PopoverContent>
65
+ <PopoverTitle>Project Details</PopoverTitle>
66
+ <p>Last updated today.</p>
67
+ <PopoverClose>Done</PopoverClose>
68
+ </PopoverContent>
69
+ </Popover>
70
+ </template>
71
+ ```
72
+
73
+ Use `@dismiss-attempt` to hear each refused dismissal. [State and Events](./state-and-events.md) shows how to cancel just one reason.
74
+
75
+ ## Modals and Sheets
76
+
77
+ `modal` set to true makes the rest of the page inert and locks scrolling. Each component documents its default.
78
+
79
+ Menu, Select, Combobox, Autocomplete, Popover, and Dialog support `presentation="auto"` to become a sheet on small touch screens. Use `presentation="sheet"` to always show a sheet.
80
+
81
+ ## Focus
82
+
83
+ Dialogs, alert dialogs, drawers, and popovers move focus into their content when they open. Where it lands depends on how they opened:
84
+
85
+ | Opened With | Focus Goes To |
86
+ | -------------------------------------------- | ------------------------------------------------------------ |
87
+ | Keyboard, screen reader, mouse, pen, or code | The element `initial-focus` picks. |
88
+ | Touch | The content itself, so no on-screen keyboard appears. |
89
+ | Hover | Nowhere. Focus stays where it is. |
90
+ | Rendered open | Nowhere, except in a dialog, which takes focus as it mounts. |
91
+
92
+ `initial-focus` defaults to `"auto"`: the first focusable element, or the content when it has none. A dialog tries an element with `autofocus` first, and an alert dialog tries its first Close part. Pass `"content"` to focus the content itself, `"none"` to leave focus alone, or a CSS selector, an element, or a function that returns one.
93
+
94
+ `modal` decides whether Tab can leave. With `true` or `"trap-focus"`, Tab and Shift+Tab wrap inside the content. With `false`, Tab moves from the trigger into a popover and on to the page after it, and focus leaving closes the popover. A non-modal dialog stays open.
95
+
96
+ When a popup closes while focus is inside it, focus returns to the trigger. If the trigger is gone or can't take focus, focus goes to the element focused before the popup opened, then where the popup it was opened from would return focus, then to the nearest focusable element after where the trigger was. A popup a controller opened returns to the element focused before it first. Set `final-focus` to `"none"` to leave focus alone, or to a selector, an element, or a function to send it somewhere else. Closing because focus moved away never moves it back, and neither does pressing something focusable outside a non-modal popup.
97
+
98
+ Menus, selects, and comboboxes handle focus within their own lists, and tooltips never move it. Each component's page covers its own.
99
+
100
+ ## Mounting and Containers
101
+
102
+ Closed content unmounts after its exit animation. `keep-mounted` leaves it in the page, hidden. `@open-change-complete` runs when opening or closing finishes, including animations.
103
+
104
+ Use `container` to render into another element, a shadow root, or an element in an iframe. Focus, Escape, and scroll locking then work in the document and shadow root the content renders in. Keep a dialog containing a form outside any surrounding form, or move its content with `container`, to avoid nested forms.
105
+
106
+ In a page `<KeepAlive>` caches, an open popup leaves with the page, from its `container` too, and comes back open when the page does, with focus in it if it's modal.
107
+
108
+ [Styling and Animation](./styling-and-animation.md) covers transitions. For toasts and confirms, mount `OverlayHost` once near your app's root; [Toast](./components/toast.md) and [Alert Dialog](./components/alert-dialog.md) show the setup.
@@ -0,0 +1,56 @@
1
+ # Rendering Primitives
2
+
3
+ Turn a Core primitive into Vue components with createParts.
4
+
5
+ This page renders the rating from [Compose Behaviors](./core.md#compose-behaviors). Start with that guide's [complete `rating.ts`](./core.md#complete-primitive): it already handles selection, arrow keys, and focus. Here we'll connect its `root` and `star` slots to Vue.
6
+
7
+ ## Create the Parts
8
+
9
+ `createParts` maps slots to elements. Save this beside `rating.ts`:
10
+
11
+ ```ts title="rating-parts.ts"
12
+ import { createParts } from "@ui-sdk/vue/primitive"
13
+ import { rating } from "./rating"
14
+
15
+ const { Root: RatingRoot, Star: RatingStar } = createParts(rating, {
16
+ root: "div",
17
+ star: "button",
18
+ })
19
+
20
+ export { RatingRoot, RatingStar }
21
+ ```
22
+
23
+ `Root` creates the instance and shares it with the parts inside. Each mapped slot becomes a component. Slot names become Pascal case: `star` becomes `Star`, and `tab-icon` would become `TabIcon`.
24
+
25
+ If you leave out the `root` tag, `Root` still shares the instance but renders only its children.
26
+
27
+ ## Render the Rating
28
+
29
+ The root accepts the primitive's options and the element's props. Each star receives its slot value and a name a screen reader can announce:
30
+
31
+ ```vue
32
+ <script setup lang="ts">
33
+ import { RatingRoot, RatingStar } from "./rating-parts"
34
+ </script>
35
+
36
+ <template>
37
+ <RatingRoot :default-value="4" aria-label="Rating" class="flex gap-1">
38
+ <RatingStar
39
+ v-for="star in 5"
40
+ :key="star"
41
+ :value="String(star)"
42
+ :aria-label="`${star} ${star === 1 ? 'star' : 'stars'}`"
43
+ class="opacity-40 data-filled:opacity-100"
44
+ >★</RatingStar
45
+ >
46
+ </RatingRoot>
47
+ </template>
48
+ ```
49
+
50
+ The primitive supplies `data-filled`; these classes make the filled stars easier to see. [Styling and Animation](./styling-and-animation.md) covers other ways to style parts.
51
+
52
+ Generated parts also support the same default and `#render` slots as the components in the catalog. Use [Parts and Composition](./parts-and-composition.md) when you want to change their markup.
53
+
54
+ ## Add Logic to a Part
55
+
56
+ Keep `createParts` when mapping slots to elements is enough. [Custom Parts](./custom-parts.md) uses the same rating to show how `usePrimitive` and `usePart` let you write your own root or child part.
@@ -0,0 +1,180 @@
1
+ # Server Rendering
2
+
3
+ Render initial HTML on the server and add interactions in the browser.
4
+
5
+ ui-sdk parts render HTML with Vue's server renderer. Their initial attributes include ARIA state and the group's tab stop. In Nuxt, pages and components render this way with no plugin or module.
6
+
7
+ ## Render the Initial State
8
+
9
+ ```vue
10
+ <script setup lang="ts">
11
+ import { Toggle } from "@ui-sdk/vue/toggle"
12
+ </script>
13
+
14
+ <template>
15
+ <Toggle default-pressed>Bold</Toggle>
16
+ </template>
17
+ ```
18
+
19
+ The server sends a button with `aria-pressed="true"`. It can toggle after hydration. Native HTML behavior, such as submitting a form, can work before hydration.
20
+
21
+ ## Read Browser Values After Mount
22
+
23
+ The server does not know the browser’s language. Start with the same placeholder on the server and client, then read the language after mount:
24
+
25
+ ```vue
26
+ <script setup lang="ts">
27
+ import { onMounted, ref } from "vue"
28
+
29
+ const language = ref("")
30
+ onMounted(() => {
31
+ language.value = navigator.language
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <p>{{ language || "Loading language…" }}</p>
37
+ </template>
38
+ ```
39
+
40
+ Both initial renders show “Loading language…”. Once mounted, the paragraph shows the browser’s language. Read other browser-only values in the same place, or in an event handler.
41
+
42
+ ## Saved Preferences and Storage
43
+
44
+ Preferences like a color scheme or an emoji skin tone are saved in the browser, so the server can't render them. [Preferences](./core.md#preferences) covers the preferences themselves; this is how they fit server rendering.
45
+
46
+ ### Show Them Before Hydration
47
+
48
+ Render `Preferences` around your app. On the server it renders one inline script as its first child, which puts every saved value on its `data-*` attribute as the page loads, before Vue hydrates, so CSS that selects those attributes is right from the first paint:
49
+
50
+ ```vue title="app.vue"
51
+ <script setup lang="ts">
52
+ import { Preferences } from "@ui-sdk/vue/preferences"
53
+ import Page from "./page.vue"
54
+ import { colorScheme, skinTone } from "./preferences"
55
+
56
+ const preferences = [colorScheme, skinTone]
57
+ </script>
58
+
59
+ <template>
60
+ <Preferences :preferences="preferences">
61
+ <Page />
62
+ </Preferences>
63
+ </template>
64
+ ```
65
+
66
+ Its element marks its attributes with `data-allow-mismatch="attribute"`, since the script changes them before hydration. A preference made with `global: true` goes on `<html>` instead. Pass `nonce` if your Content Security Policy only allows inline scripts with one.
67
+
68
+ Content that reads a preference through `usePreference` renders the default on the server and the saved value once mounted. Style by the attribute instead wherever the first paint has to be right, as [Style by Preferences](./core.md#style-by-preferences) shows.
69
+
70
+ ### Read a Cookie on the Server
71
+
72
+ A preference kept in a cookie can render on the server too, content included. Read the request's `Cookie` header on the server, however your server or framework gives you the request, and pass it to your app as a prop, like `createSSRApp(App, { cookie })`. `setup` runs once per request, so create the preference there and share it with `provide`:
73
+
74
+ ```vue title="app.vue"
75
+ <script setup lang="ts">
76
+ import { createCookieStorage, createPreference } from "@ui-sdk/core"
77
+ import { Preferences } from "@ui-sdk/vue/preferences"
78
+ import { provide } from "vue"
79
+ import Page from "./page.vue"
80
+
81
+ const props = defineProps<{ cookie: string | null }>()
82
+ const skinTone = createPreference({
83
+ values: ["default", "light", "medium", "dark"],
84
+ defaultValue: "default",
85
+ attribute: "data-skin-tone",
86
+ storage: createCookieStorage("skin-tone", { cookie: props.cookie }),
87
+ })
88
+ provide("skin-tone", skinTone)
89
+ </script>
90
+
91
+ <template>
92
+ <Preferences :preferences="[skinTone]">
93
+ <Page />
94
+ </Preferences>
95
+ </template>
96
+ ```
97
+
98
+ The server reads the value from `cookie`, and the browser from `document.cookie`, so both render it and hydration matches.
99
+
100
+ Nuxt creates the app for you, so read the header with `useRequestHeader` in `app.vue` instead of taking a prop:
101
+
102
+ ```vue title="app.vue"
103
+ <script setup lang="ts">
104
+ import { createCookieStorage, createPreference } from "@ui-sdk/core"
105
+ import { Preferences } from "@ui-sdk/vue/preferences"
106
+
107
+ const skinTone = createPreference({
108
+ values: ["default", "light", "medium", "dark"],
109
+ defaultValue: "default",
110
+ attribute: "data-skin-tone",
111
+ storage: createCookieStorage("skin-tone", {
112
+ cookie: useRequestHeader("cookie"),
113
+ }),
114
+ })
115
+ provide("skin-tone", skinTone)
116
+ </script>
117
+
118
+ <template>
119
+ <Preferences :preferences="[skinTone]">
120
+ <NuxtPage />
121
+ </Preferences>
122
+ </template>
123
+ ```
124
+
125
+ `useRequestHeader` returns the whole `Cookie` header on the server and undefined in the browser, where the storage reads `document.cookie` instead. `useCookie` doesn't fit here, since it reads one cookie by name and the storage takes the header.
126
+
127
+ ### Render the Storage Script Yourself
128
+
129
+ `Preferences` is the usual way. For one component that keeps its own value, like a toolbar's density, a storage's `script` returns the same kind of inline script for a single element. Render it right after the element, since it writes to the element right before it. Vue templates leave `<script>` tags out, so render it from a render function:
130
+
131
+ ```ts title="toolbar.ts"
132
+ import { createLocalStorage } from "@ui-sdk/core"
133
+ import { defineComponent, h, onMounted, shallowRef } from "vue"
134
+
135
+ const density = createLocalStorage("density")
136
+ const script = density.script({
137
+ attribute: "data-density",
138
+ values: ["comfortable", "compact"],
139
+ })
140
+
141
+ const Toolbar = defineComponent((_props, { slots }) => {
142
+ const value = shallowRef("comfortable")
143
+ // The script is in the server's HTML until then.
144
+ const mounted = shallowRef(false)
145
+ onMounted(() => {
146
+ value.value = density.get() === "compact" ? "compact" : "comfortable"
147
+ mounted.value = true
148
+ })
149
+ return () => [
150
+ h(
151
+ "div",
152
+ { "data-density": value.value, "data-allow-mismatch": "attribute" },
153
+ slots.default?.()
154
+ ),
155
+ mounted.value || !script ? null : h("script", { innerHTML: script }),
156
+ ]
157
+ })
158
+
159
+ export { Toolbar }
160
+ ```
161
+
162
+ - **Only from the server.** The browser never runs a script Vue inserts, so drop it once mounted.
163
+ - **The element differs until hydration.** The script changes its attribute before Vue hydrates, so mark it with `data-allow-mismatch="attribute"`. Once mounted, render the saved value from state, as above.
164
+ - **Shared by ID.** Every script with the same storage kind and ID reads the storage once, however many elements render it, and the scripts are identical, so compression shrinks the repeats.
165
+ - **Only safe values.** Any script on your site can write the saved value, so `script` only sets attributes that start with `data-`, and only to one of `values`.
166
+ - **Not for cookies.** A cookie storage's `script` returns undefined, since the server reads the cookie and renders the value itself.
167
+
168
+ To run before the page's content at all, render `preferencesScript([...])` from `@ui-sdk/core` in `<head>` yourself, wherever your server writes it. Only global preferences make sense there, since the others go on the script's parent element.
169
+
170
+ ## Keep IDs and Values Consistent
171
+
172
+ Use the same initial component values on the server and client. For example, do not render an open dialog on the server and a closed one for the first client render.
173
+
174
+ Parts generate IDs for relationships such as a Dialog trigger’s `aria-controls` and a Field label’s `for`. You usually do not need to supply them.
175
+
176
+ Pass an explicit `id` when something outside the component needs to refer to a part. On the server, render order matters: if a Field label renders before a control with your ID, the label initially uses the generated ID. Hydration updates the reference to yours. The same applies to Dialog triggers, titles, and descriptions.
177
+
178
+ Use classes or data attributes for styling, rather than generated IDs.
179
+
180
+ Popups render in place, so your server layout doesn't need a portal root. [Popups](./popups.md) covers containers; [Forms](./forms.md) covers submission and validation.
@@ -0,0 +1,100 @@
1
+ # State and Events
2
+
3
+ Set an initial value, control it, and respond to changes.
4
+
5
+ Components manage their own state unless you pass a controlled value. A change callback lets you respond in either case.
6
+
7
+ ## Set the Starting Value
8
+
9
+ Use a default prop to choose the starting state. Here, Bold starts pressed and changes when you click it:
10
+
11
+ ```vue
12
+ <script setup lang="ts">
13
+ import { Toggle } from "@ui-sdk/vue/toggle"
14
+ </script>
15
+
16
+ <template>
17
+ <Toggle default-pressed>Bold</Toggle>
18
+ </template>
19
+ ```
20
+
21
+ The prop depends on the component: `default-pressed`, `default-checked`, `default-open`, or `default-value`.
22
+
23
+ ## Control the Value
24
+
25
+ Use `v-model` when the parent needs the current value:
26
+
27
+ ```vue
28
+ <script setup lang="ts">
29
+ import { Toggle } from "@ui-sdk/vue/toggle"
30
+ import { ref } from "vue"
31
+
32
+ const pressed = ref(false)
33
+ </script>
34
+
35
+ <template>
36
+ <Toggle v-model="pressed">Bold</Toggle>
37
+ <output>{{ pressed ? "On" : "Off" }}</output>
38
+ </template>
39
+ ```
40
+
41
+ `v-model` keeps the toggle’s `pressed` value in your ref. `v-model:pressed` works here too. Clicking Bold changes both the button and the output.
42
+
43
+ The main value depends on the component: `checked` for Checkbox, `open` for Popover, and `value` for Select. Name another binding when you need it, such as `v-model:open` on Select or `v-model:query` on Combobox.
44
+
45
+ If you pass `:pressed` without `v-model`, update that value yourself in `@pressed-change`. A value the parent never updates stays fixed.
46
+
47
+ To keep a value in the URL, update your ref through `v-model` or the event, then call `router.push`. A value that changes only when the navigation finishes leaves the component showing the old one until then, and logs a warning in development.
48
+
49
+ ## Respond to a Change
50
+
51
+ Events such as `@pressed-change` receive the requested value and `details`. They run before `v-model` updates.
52
+
53
+ | Detail | What It Tells You |
54
+ | ---------- | ------------------------------------------------------------------------------------------------ |
55
+ | `reason` | What requested the change, such as `press` or `outside-press`. Each component lists its reasons. |
56
+ | `event` | The DOM event, when there is one. |
57
+ | `source` | `keyboard`, `mouse`, `touch`, `pen`, `virtual`, `programmatic`, or `agent`. |
58
+ | `cancel()` | Stops the requested change. |
59
+
60
+ For example, keep a popover open when someone clicks outside it:
61
+
62
+ ```vue
63
+ <script setup lang="ts">
64
+ import {
65
+ Popover,
66
+ PopoverClose,
67
+ PopoverContent,
68
+ PopoverTitle,
69
+ PopoverTrigger,
70
+ } from "@ui-sdk/vue/popover"
71
+
72
+ function keepOpen(
73
+ open: boolean,
74
+ details: { reason: string; cancel: () => void }
75
+ ) {
76
+ if (!open && details.reason === "outside-press") {
77
+ details.cancel()
78
+ }
79
+ }
80
+ </script>
81
+
82
+ <template>
83
+ <Popover @open-change="keepOpen">
84
+ <PopoverTrigger>Details</PopoverTrigger>
85
+ <PopoverContent>
86
+ <PopoverTitle>Project Details</PopoverTitle>
87
+ <p>Last updated today.</p>
88
+ <PopoverClose>Done</PopoverClose>
89
+ </PopoverContent>
90
+ </Popover>
91
+ </template>
92
+ ```
93
+
94
+ The Done button still closes it. Cancellation also works when you control the value. Add `debug` to a root to log changes while you work.
95
+
96
+ ## Empty and Multiple Values
97
+
98
+ Select, Tabs, and Radio Group report `null` when nothing is selected. A multiple Select or Combobox uses an array. Accordion and Toggle Group always use arrays.
99
+
100
+ To change a component from outside its parts, use a [Controller](./controllers.md).
@@ -0,0 +1,148 @@
1
+ # Styling and Animation
2
+
3
+ Style parts and their state, then add entry and exit transitions.
4
+
5
+ Parts render without styles. The component demos use plain CSS, and the same state attributes and CSS variables work with Tailwind CSS or any other stylesheet.
6
+
7
+ ## Style a Part
8
+
9
+ Every part has a `data-slot` attribute. State adds attributes such as `data-pressed` and `data-disabled`:
10
+
11
+ ```vue
12
+ <script setup lang="ts">
13
+ import { Toggle } from "@ui-sdk/vue/toggle"
14
+ </script>
15
+
16
+ <template>
17
+ <Toggle
18
+ class="rounded-md border border-neutral-300 px-2.5 py-1.5 text-sm data-pressed:bg-neutral-100 data-disabled:opacity-50"
19
+ >Bold</Toggle
20
+ >
21
+ </template>
22
+ ```
23
+
24
+ Vue's `class` and `style` bindings accept strings, arrays, and objects. They aren't functions of the part's state. Use data attributes, or read `state` in a `#render` slot.
25
+
26
+ Most state attributes are present or absent. Others have values, such as `data-side="top"`. Each component's Styling section lists its attributes and CSS variables. Use those rather than generated IDs.
27
+
28
+ ## CSS Modules
29
+
30
+ Import a CSS Module and pass its class to the part. State selectors work the same way:
31
+
32
+ ```vue
33
+ <script setup lang="ts">
34
+ import { Toggle } from "@ui-sdk/vue/toggle"
35
+ import styles from "./toggle.module.css"
36
+ </script>
37
+
38
+ <template>
39
+ <Toggle :class="styles.toggle">Bold</Toggle>
40
+ </template>
41
+ ```
42
+
43
+ ```css title="toggle.module.css"
44
+ .toggle {
45
+ cursor: default;
46
+ border: 1px solid var(--input, #e5e5e5);
47
+ border-radius: 1.125rem;
48
+ background: transparent;
49
+ padding: 0.375rem 0.625rem;
50
+ color: inherit;
51
+ font: inherit;
52
+ font-size: 0.875rem;
53
+ }
54
+
55
+ .toggle[data-pressed] {
56
+ background: var(--muted, #f5f5f5);
57
+ }
58
+
59
+ .toggle[data-disabled] {
60
+ opacity: 0.5;
61
+ }
62
+
63
+ .toggle:focus-visible {
64
+ outline: 2px solid var(--ring, #a3a3a3);
65
+ outline-offset: 2px;
66
+ }
67
+ ```
68
+
69
+ ## Animate Opening and Closing
70
+
71
+ Popup content gets `data-starting-style` before it enters and `data-ending-style` while it exits. It stays mounted until the exit transition or animation finishes:
72
+
73
+ ```vue
74
+ <script setup lang="ts">
75
+ import {
76
+ Popover,
77
+ PopoverClose,
78
+ PopoverContent,
79
+ PopoverTitle,
80
+ PopoverTrigger,
81
+ } from "@ui-sdk/vue/popover"
82
+ </script>
83
+
84
+ <template>
85
+ <Popover>
86
+ <PopoverTrigger>Details</PopoverTrigger>
87
+ <PopoverContent
88
+ class="origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-96 data-starting-style:opacity-0 data-ending-style:scale-96 data-ending-style:opacity-0 motion-reduce:transition-none"
89
+ >
90
+ <PopoverTitle>Project Details</PopoverTitle>
91
+ <p>Last updated today.</p>
92
+ <PopoverClose>Done</PopoverClose>
93
+ </PopoverContent>
94
+ </Popover>
95
+ </template>
96
+ ```
97
+
98
+ Put exit styles on `data-ending-style`. `data-closed` describes the state but doesn't keep the element around for an exit.
99
+
100
+ Popups provide `--anchor-width` and `--available-height` for sizing. [Popups](./popups.md) covers mounting and placement.
101
+
102
+ ## The ui-sdk Layer
103
+
104
+ A few behaviors need a little CSS, like keeping a closed popup hidden or dimming the page behind a sheet, so ui-sdk adds it to the page in a cascade layer named `ui-sdk`. Declare that layer at the top of your global stylesheet, the one `main.ts` or `nuxt.config.ts` loads, so your own styles win over it wherever the page puts them. With Tailwind, put it above `@import "tailwindcss"`:
105
+
106
+ ```css
107
+ @layer ui-sdk;
108
+ ```
109
+
110
+ A component's `<style>` block loads with the component, too late to come first, so keep the line in the global stylesheet.
111
+
112
+ ui-sdk adds this CSS once a component mounts, so HTML rendered on the server doesn't have it until the page hydrates. Closed content kept in the page, like a tooltip's, carries `hidden`, and a class that sets `display` shows it until then. Tailwind's base styles already hide `hidden` elements. With other CSS, add the same rule to your global stylesheet:
113
+
114
+ ```css
115
+ @layer ui-sdk {
116
+ :where([data-slot][hidden]:not([hidden="until-found"])) {
117
+ display: none !important;
118
+ }
119
+ }
120
+ ```
121
+
122
+ It leaves `hidden="until-found"` alone, so find in page can still open a closed panel.
123
+
124
+ ## Content Security Policy
125
+
126
+ A Content Security Policy without `'unsafe-inline'` in `style-src` blocks the CSS ui-sdk adds. Give ui-sdk your policy's nonce with a meta tag in the page's `<head>`:
127
+
128
+ ```html
129
+ <meta property="csp-nonce" nonce="your-nonce" />
130
+ ```
131
+
132
+ With Vite, set `html.cspNonce` in your Vite config to a placeholder, and Vite writes this tag for you. Your server replaces the placeholder with a new nonce on each request.
133
+
134
+ In Nuxt, the nuxt-security module writes the tag only in development. For production, add a Nitro plugin, like `server/plugins/csp-nonce.ts`, that writes it into every page with the module's nonce:
135
+
136
+ ```ts
137
+ export default defineNitroPlugin((nitroApp) => {
138
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
139
+ html.head.push(
140
+ `<meta property="csp-nonce" nonce="${event.context.security.nonce}">`
141
+ )
142
+ })
143
+ })
144
+ ```
145
+
146
+ Pages rendered on the server also carry a few `style` attributes, like the one that hides a select's native input, so the policy needs `style-src-attr 'unsafe-inline'` too.
147
+
148
+ If your policy has no nonce for styles, add the tag without the `nonce` attribute. ui-sdk then adds its CSS as constructed style sheets, which no policy blocks. In Nuxt, `useHead` leaves out a meta tag without `content`, so give it one. ui-sdk doesn't read it: `useHead({ meta: [{ property: "csp-nonce", content: "none" }] })`. If your code replaces `document.adoptedStyleSheets`, ui-sdk adds its sheets back the next time a component mounts.