@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,536 @@
1
+ # Date Input
2
+
3
+ Type a date one part at a time, in the order your user's language writes it.
4
+
5
+ Use a Date Input for dates people know by heart, like a birthday or a booking date. Each part of the date is its own small input, ordered the way the locale writes it.
6
+
7
+ ```vue title="basic.vue"
8
+ <script setup lang="ts">
9
+ import { DateInput } from "@ui-sdk/vue/date-input"
10
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
11
+ </script>
12
+
13
+ <template>
14
+ <Field>
15
+ <FieldLabel>Start date</FieldLabel>
16
+ <DateInput />
17
+ </Field>
18
+ </template>
19
+ ```
20
+
21
+ Click the month and type 11042026. Each part moves to the next as it fills. Press ArrowUp or ArrowDown on a part to step it.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @ui-sdk/vue @ui-sdk/core
27
+ ```
28
+
29
+ ## Composition
30
+
31
+ Use the following composition to build a `DateInput`:
32
+
33
+ ```text
34
+ DateInput
35
+ ├── DateInputSegment
36
+ └── DateInputSegment
37
+ ```
38
+
39
+ `DateInput` renders a group with one `DateInputSegment` for each part of the date: month, day and year in English, day, month and year in German. Each segment you type in is a small input. The slashes or dots between them are segments too, as plain text. `DateInput` renders its segments for you; render them yourself only to change how they look, as in [Custom Rendering](#custom-rendering).
40
+
41
+ ## Usage
42
+
43
+ Put the input in a [Field](./field.md) and give it a label:
44
+
45
+ ```vue
46
+ <script setup lang="ts">
47
+ import { DateInput } from "@ui-sdk/vue/date-input"
48
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
49
+ </script>
50
+
51
+ <template>
52
+ <Field>
53
+ <FieldLabel>Start date</FieldLabel>
54
+ <DateInput default-value="2026-11-02" />
55
+ </Field>
56
+ </template>
57
+ ```
58
+
59
+ The value is an ISO date string like `"2026-11-02"`, the same as a native date input's. It stays `null` until every segment holds something, so your app only sees whole dates.
60
+
61
+ Type digits and each segment fills, then moves on. A month that can't take another digit, like 4, moves on at once. Typing a separator, like `/` or `.`, moves on too. The day is kept within its month: moving from January 31 to February shows the 28th. Paste a whole date into any segment, as `2026-11-02` or written the way the locale writes it.
62
+
63
+ ## Field
64
+
65
+ The Field's label names the group, and clicking it focuses the first segment. Its description is read with the input.
66
+
67
+ ```vue title="field.vue"
68
+ <script setup lang="ts">
69
+ import { DateInput } from "@ui-sdk/vue/date-input"
70
+ import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
71
+ </script>
72
+
73
+ <template>
74
+ <Field>
75
+ <FieldLabel>Birthday</FieldLabel>
76
+ <DateInput name="birthday" />
77
+ <FieldDescription>We send a card on the day.</FieldDescription>
78
+ </Field>
79
+ </template>
80
+ ```
81
+
82
+ Click Birthday to focus the month, then type a date. Screen readers read the description with the input.
83
+
84
+ ## Date and Time
85
+
86
+ Set `granularity` to `"hour"`, `"minute"` or `"second"` to add the time. The value becomes a local date and time like `"2026-11-02T14:30"`. For a time alone, use [Time Input](./time-input.md).
87
+
88
+ ```vue title="date-time.vue"
89
+ <script setup lang="ts">
90
+ import { DateInput } from "@ui-sdk/vue/date-input"
91
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
92
+ </script>
93
+
94
+ <template>
95
+ <Field>
96
+ <FieldLabel>Departure</FieldLabel>
97
+ <DateInput granularity="minute" default-value="2026-11-02T14:30" />
98
+ </Field>
99
+ </template>
100
+ ```
101
+
102
+ Tab through the date and time. Type a P or an A in the last segment to switch between AM and PM.
103
+
104
+ ## Min and Max
105
+
106
+ `min` and `max` take ISO dates. A date outside them is invalid: the input sets `data-invalid`, and its form won't submit.
107
+
108
+ ```vue title="min-max.vue"
109
+ <script setup lang="ts">
110
+ import { DateInput } from "@ui-sdk/vue/date-input"
111
+ import {
112
+ Field,
113
+ FieldDescription,
114
+ FieldError,
115
+ FieldLabel,
116
+ } from "@ui-sdk/vue/field"
117
+ </script>
118
+
119
+ <template>
120
+ <Field>
121
+ <FieldLabel>Check-in</FieldLabel>
122
+ <DateInput min="2026-01-01" max="2026-12-31" default-value="2026-06-15" />
123
+ <FieldDescription>Any day in 2026.</FieldDescription>
124
+ <FieldError />
125
+ </Field>
126
+ </template>
127
+ ```
128
+
129
+ Change the year of Check-in to 2027 to see it turn invalid, then back to 2026.
130
+
131
+ ## Unavailable Dates
132
+
133
+ `unavailable` takes the same matchers as [Calendar](./calendar.md#unavailable-dates): ISO dates, `{ from, to }` ranges, weekdays like `{ weekday: [0, 6] }`, or a function. An unavailable date is invalid with the message "This date isn't available."
134
+
135
+ ```vue title="unavailable.vue"
136
+ <script setup lang="ts">
137
+ import { DateInput } from "@ui-sdk/vue/date-input"
138
+ import {
139
+ Field,
140
+ FieldDescription,
141
+ FieldError,
142
+ FieldLabel,
143
+ } from "@ui-sdk/vue/field"
144
+ </script>
145
+
146
+ <template>
147
+ <Field>
148
+ <FieldLabel>Delivery date</FieldLabel>
149
+ <DateInput default-value="2026-11-02" :unavailable="{ weekday: [0, 6] }" />
150
+ <FieldDescription>We deliver Monday to Friday.</FieldDescription>
151
+ <FieldError />
152
+ </Field>
153
+ </template>
154
+ ```
155
+
156
+ Step the day of Delivery date back from Monday the 2nd to Sunday the 1st. The input turns invalid until you pick a weekday again.
157
+
158
+ ## Locale
159
+
160
+ The input reads its order, separators and clock from `locale`, or from the page's language when you leave it out. The digits are always 0 to 9, and the calendar is always the Gregorian one, so the value means the same date everywhere. On the server, the input renders in English until it reads the page's language; set `locale` when that first render matters.
161
+
162
+ ```vue title="locale.vue"
163
+ <script setup lang="ts">
164
+ import { DateInput } from "@ui-sdk/vue/date-input"
165
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
166
+ </script>
167
+
168
+ <template>
169
+ <div data-slot="stack">
170
+ <Field>
171
+ <FieldLabel>Date (en-GB)</FieldLabel>
172
+ <DateInput locale="en-GB" default-value="2026-11-02" />
173
+ </Field>
174
+ <Field>
175
+ <FieldLabel>Datum (de-DE)</FieldLabel>
176
+ <DateInput locale="de-DE" default-value="2026-11-02" />
177
+ </Field>
178
+ <Field>
179
+ <FieldLabel>日付 (ja-JP)</FieldLabel>
180
+ <DateInput locale="ja-JP" default-value="2026-11-02" />
181
+ </Field>
182
+ </div>
183
+ </template>
184
+ ```
185
+
186
+ The same date in British English, German and Japanese. Each orders its parts its own way.
187
+
188
+ ## Disabled
189
+
190
+ Set `disabled` on the input, or on its Field or fieldset, to keep it from focus and from its form.
191
+
192
+ ```vue title="disabled.vue"
193
+ <script setup lang="ts">
194
+ import { DateInput } from "@ui-sdk/vue/date-input"
195
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
196
+ </script>
197
+
198
+ <template>
199
+ <Field>
200
+ <FieldLabel>Start date</FieldLabel>
201
+ <DateInput default-value="2026-11-02" disabled />
202
+ </Field>
203
+ </template>
204
+ ```
205
+
206
+ Click Start date or press Tab to reach it. Focus skips it, and the date stays November 2, 2026.
207
+
208
+ ## Read-Only
209
+
210
+ Set `read-only` for a date people can focus, read and submit as it is.
211
+
212
+ ```vue title="read-only.vue"
213
+ <script setup lang="ts">
214
+ import { DateInput } from "@ui-sdk/vue/date-input"
215
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
216
+ </script>
217
+
218
+ <template>
219
+ <Field>
220
+ <FieldLabel>Signed on</FieldLabel>
221
+ <DateInput default-value="2026-11-02" read-only />
222
+ </Field>
223
+ </template>
224
+ ```
225
+
226
+ Focus Signed on and press ArrowUp or type a digit. The date stays the same.
227
+
228
+ ## Form
229
+
230
+ A hidden input submits the ISO date under `name`. An empty or partly typed date submits an empty string. Set `required` to need a date before the form submits. Enter in a segment submits the form with what was typed.
231
+
232
+ ```vue title="form.vue"
233
+ <script setup lang="ts">
234
+ import { DateInput } from "@ui-sdk/vue/date-input"
235
+ import { Field, FieldError, FieldLabel } from "@ui-sdk/vue/field"
236
+ import { TimeInput } from "@ui-sdk/vue/time-input"
237
+ import { useLog } from "./log"
238
+
239
+ const log = useLog()
240
+
241
+ function onSubmit(event: SubmitEvent) {
242
+ const data = [...new FormData(event.currentTarget as HTMLFormElement)].map(
243
+ ([name, value]) => `${name}=${value}`
244
+ )
245
+ log(`Submitted ${data.join(", ")}`)
246
+ }
247
+ </script>
248
+
249
+ <template>
250
+ <form data-slot="stack" @submit.prevent="onSubmit">
251
+ <Field>
252
+ <FieldLabel>Date</FieldLabel>
253
+ <DateInput name="date" default-value="2026-11-02" required />
254
+ <FieldError />
255
+ </Field>
256
+ <Field>
257
+ <FieldLabel>Time</FieldLabel>
258
+ <TimeInput name="time" default-value="09:30" required />
259
+ <FieldError />
260
+ </Field>
261
+ <div data-slot="row">
262
+ <button type="reset">Reset</button>
263
+ <button type="submit" data-variant="solid">Book</button>
264
+ </div>
265
+ </form>
266
+ </template>
267
+ ```
268
+
269
+ Clear the day of Date with Backspace and choose Book to see the form stop. Fill it in and choose Book: the console logs the values. Reset brings back the defaults.
270
+
271
+ A partly typed date stops the form too, with "Enter a valid date." Set `form` to the id of a form elsewhere in the page to submit and validate with it. A form reset restores `default-value` and reports the `reset` reason.
272
+
273
+ ## Controlled
274
+
275
+ Bind `v-model:value`, and listen to `@value-change` for the reason. Both get each whole date, and `null` once any part of it is erased. A half-typed date already reads `null`, so setting the value to `null` leaves it as typed. Call `clear()` on a [controller](#controller) to empty every segment.
276
+
277
+ ```vue title="controlled.vue"
278
+ <script setup lang="ts">
279
+ import { DateInput } from "@ui-sdk/vue/date-input"
280
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
281
+ import { ref } from "vue"
282
+ import { useLog } from "./log"
283
+
284
+ const log = useLog()
285
+ const value = ref<string | null>("2026-11-02")
286
+ </script>
287
+
288
+ <template>
289
+ <div data-slot="stack">
290
+ <div data-slot="controls">
291
+ <button type="button" @click="value = null">Clear</button>
292
+ </div>
293
+ <Field>
294
+ <FieldLabel>Due date</FieldLabel>
295
+ <DateInput
296
+ v-model:value="value"
297
+ @value-change="(next) => log(`Due date ${next ?? 'empty'}`)"
298
+ />
299
+ </Field>
300
+ </div>
301
+ </template>
302
+ ```
303
+
304
+ Change Due date: the console logs each whole date. Clear sets the value to null.
305
+
306
+ See [State and Events](../state-and-events.md) for the shared conventions and [Reasons](#reasons) for why each change happened.
307
+
308
+ ## Controller
309
+
310
+ Create a controller with `createDateInputController()` and pass it as `controller` to set, clear or focus the input from elsewhere. `focus("year")` focuses a segment by its unit.
311
+
312
+ ```vue title="controller.vue"
313
+ <script setup lang="ts">
314
+ import { createDateInputController, DateInput } from "@ui-sdk/vue/date-input"
315
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
316
+
317
+ const publish = createDateInputController()
318
+ </script>
319
+
320
+ <template>
321
+ <div data-slot="stack">
322
+ <div data-slot="controls">
323
+ <button type="button" @click="publish.setValue('2026-12-25')">
324
+ Christmas
325
+ </button>
326
+ <button type="button" @click="publish.focus('year')">Edit Year</button>
327
+ <button type="button" @click="publish.clear()">Clear</button>
328
+ </div>
329
+ <Field>
330
+ <FieldLabel>Publish on</FieldLabel>
331
+ <DateInput :controller="publish" />
332
+ </Field>
333
+ </div>
334
+ </template>
335
+ ```
336
+
337
+ Choose Christmas to set the date, Edit Year to focus the year, or Clear to empty it.
338
+
339
+ [Controllers](../controllers.md) covers creating and sharing a controller.
340
+
341
+ ## Custom Rendering
342
+
343
+ Use the default slot to render the segments yourself. It gets `state`, whose `segments` list each segment in order, with the text between them. Render a `DateInputSegment` for each, keyed by its `key`, and give the text between segments content of your own.
344
+
345
+ ```vue title="segments.vue"
346
+ <script setup lang="ts">
347
+ import { DateInput, DateInputSegment } from "@ui-sdk/vue/date-input"
348
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
349
+ </script>
350
+
351
+ <template>
352
+ <Field>
353
+ <FieldLabel>Start date</FieldLabel>
354
+ <DateInput v-slot="{ state }" default-value="2026-11-02">
355
+ <template v-for="segment in state.segments" :key="segment.key">
356
+ <DateInputSegment v-if="segment.editable" :segment="segment" />
357
+ <DateInputSegment v-else :segment="segment">–</DateInputSegment>
358
+ </template>
359
+ </DateInput>
360
+ </Field>
361
+ </template>
362
+ ```
363
+
364
+ The segments here are separated by dashes in place of the locale's slashes. The order stays the locale's.
365
+
366
+ Each part's state is listed under [State](#state).
367
+
368
+ ## Styling
369
+
370
+ Style the root as the field's box, and its segments inside it. Every segment sets `data-type` to its unit, so you can make the year wider or mute the text between segments, which has `data-type="literal"`. An empty segment sets `data-empty` and shows its placeholder. The root sets `data-invalid` while the date is out of range or unavailable.
371
+
372
+ Each part has a `data-slot`, such as `date-input-segment`, and its state attributes are listed under [Data Attributes](#data-attributes).
373
+
374
+ ## Data Attributes
375
+
376
+ ### DateInput
377
+ `[data-slot="date-input"]`
378
+
379
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
380
+ - `data-readonly`: Present when read-only.
381
+ - `data-required`: Present when required.
382
+ - `data-invalid`: Present while the date is before `min`, after `max` or unavailable.
383
+ - `data-empty`: Present while every segment is empty.
384
+
385
+ ### DateInputSegment
386
+ `[data-slot="date-input-segment"]`
387
+
388
+ - `data-type` (`year`, `month`, `day`, `hour`, `minute`, `second`, `dayPeriod`, `literal`): The segment's unit, or `literal` for the text between segments.
389
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
390
+ - `data-readonly`: Present when read-only.
391
+ - `data-required`: Present when required.
392
+ - `data-invalid`: Present while the date is before `min`, after `max` or unavailable.
393
+ - `data-empty`: Present while the segment is empty. It shows its placeholder.
394
+
395
+ ## API Reference
396
+
397
+ Props are the same as below, in kebab case in templates, and callbacks are events.
398
+
399
+ ### DateInput
400
+
401
+ - `value?` (`string | null`): The value: an ISO date like `"2026-11-02"`, with a time granularity a date and time like `"2026-11-02T14:30"`, or `null`. Pass it to control the input.
402
+ - `defaultValue?` (`string | null`): The value it starts with, when uncontrolled, and goes back to when its form resets.
403
+ - `@value-change` (`((value: string | null, details: ChangeDetails<DateInputValueChangeReason>) => void)`): Runs when the value wants to change: once every segment holds something, and again with `null` when one is emptied. `details.cancel()` keeps the value.
404
+ - `granularity?` (`DateInputGranularity`): The smallest unit it shows. `"day"` shows a date, and `"hour"`, `"minute"` or `"second"` add the time down to it.
405
+ - `hourCycle?` (`HourCycle`): A 12 or 24 hour clock for the time. Defaults to the locale's.
406
+ - `min?` (`string`): The earliest value, as an ISO date or date and time. A value before it is invalid.
407
+ - `max?` (`string`): The latest value. A date alone takes any time that day. A value after it is invalid.
408
+ - `unavailable?` (`DateMatchers`): Dates that can't be chosen, as in Calendar: ISO dates, `{ from, to }` ranges, weekdays or a function. A value on one is invalid.
409
+ - `locale?` (`string`): The language whose order, separators and clock it shows. Defaults to the page's.
410
+ - `disabled?` (`boolean`): Whether it can't be used.
411
+ - `readOnly?` (`boolean`): Whether the value can be seen but not changed.
412
+ - `required?` (`boolean`): Whether a value must be entered before its form submits.
413
+ - `name?` (`string`): The name the value submits under.
414
+ - `form?` (`string`): The id of the form it belongs to, when it isn't inside it.
415
+ - `controller?` (`object`): Drives it from anywhere. Create one with `createDateInputController()`.
416
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
417
+
418
+ ### DateInputSegment
419
+
420
+ - `segment` (`DateInputSegmentData`): The segment it shows, from the root's `segments`.
421
+
422
+ ### State
423
+
424
+ What render slots and the default slot receive.
425
+
426
+ - `value` (`string | null`): The value, or `null` while empty or partly filled.
427
+ - `segments` (`DateInputSegmentData[]`): The segments in the order the locale shows them, with the text between them. Render one part for each.
428
+ - `disabled` (`boolean`): Whether it can't be used, by itself or by a `<fieldset>`.
429
+ - `readOnly` (`boolean`): Whether the value can be seen but not changed.
430
+ - `invalid` (`boolean`): Whether the value is before `min`, after `max` or an unavailable date.
431
+
432
+ Each segment's state, `DateInputSegmentState`, is the segment with the input's state beside it:
433
+
434
+ - `key` (`string`): Tells segments apart: the type, or `"literal-<n>"` for the text between them.
435
+ - `type` (`DateInputSegmentType`)
436
+ - `text` (`string`): What it shows: the number, padded, or AM and PM in the locale. Empty while it holds nothing.
437
+ - `value` (`number | null`): The number it holds, or `null` while empty. AM is 0 and PM is 1.
438
+ - `min` (`number`): The smallest number it takes.
439
+ - `max` (`number`): The largest number it takes, like the days in the month.
440
+ - `empty` (`boolean`): Whether it holds nothing yet.
441
+ - `editable` (`boolean`): Whether it's a segment you type in, rather than the text between segments.
442
+ - `label` (`string`): Its name in the locale, like "month", which names it to assistive technology.
443
+ - `disabled` (`boolean`): Whether it can't be used.
444
+ - `readOnly` (`boolean`): Whether its value can't be changed.
445
+ - `invalid` (`boolean`): Whether the value is before `min`, after `max` or an unavailable date.
446
+
447
+ ### Reasons
448
+
449
+ `details.reason` says why the value changed.
450
+
451
+ - `input-change` (`Event`): Typing, pasting or dropping changed a text field's value; in a combobox it can also open or close it, or Enter keeps the typed text as the value.
452
+ - `step-key` (`KeyboardEvent`): Arrow, Page Up, Page Down, Home or End stepped a slider or a number input.
453
+ - `paste` (`ClipboardEvent`): Files were pasted, like a screenshot, or text and tokens into a token input.
454
+ - `reset` (`none`): A form reset.
455
+ - `restore` (`none`): The browser restored state, like after going back, or a saved value was read back from storage.
456
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
457
+
458
+ ### Controller
459
+
460
+ Create one with `createDateInputController()` and pass it as `controller`.
461
+
462
+ Every write method takes an optional `{ reason, event }`, runs the change callback like typing, and returns `false` when the change is canceled.
463
+
464
+ - `getValue()` (`() => string | null`): The value, or `null` while empty or partly filled.
465
+ - `setValue(value, options?)` (`(value: string | null, options?: DateInputChangeOptions | undefined) => boolean`): Sets the value. Returns false when it isn't a value the input can show, or when canceled.
466
+ - `clear(options?)` (`(options?: DateInputChangeOptions | undefined) => boolean`): Empties every segment.
467
+ - `focus(segment?)` (`(segment?: string | undefined) => void`): Focuses a segment, like `"month"`, or the first one.
468
+
469
+ ### Events
470
+
471
+ Events get the new value first, then the details. `v-model:value` only updates when the change wasn't canceled.
472
+
473
+ | Binding | On | Receives |
474
+ | --------------- | ----------- | -------------------------------------------------------------------------------------- |
475
+ | `v-model:value` | `DateInput` | The ISO date, or null while empty or partly typed |
476
+ | `@value-change` | `DateInput` | `(value, details)`: the ISO date, or null once it's erased, and the [reason](#reasons) |
477
+
478
+ ## Accessibility
479
+
480
+ The root is a group, named by its Field's label or by your `aria-label`. Each segment is a [spinbutton](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) named by its unit, like "month", with its value, range and text. An empty segment reads as "Empty". The text between segments is hidden from screen readers, which read each segment's name instead.
481
+
482
+ Segments are real inputs, so phones show a number pad and typing works before the page's script runs.
483
+
484
+ ### Keyboard
485
+
486
+ | Key | Action |
487
+ | ---------------------- | --------------------------------------------------------------------------- |
488
+ | 0 to 9 | Fill the segment, then move to the next one. |
489
+ | ArrowUp / ArrowDown | Step the segment by one, wrapping around. An empty segment starts at today. |
490
+ | PageUp / PageDown | Step by more: a year by 5, a month by 3, a day by 7, minutes by 15. |
491
+ | Home / End | Go to the segment's first or last value. |
492
+ | ArrowLeft / ArrowRight | Move to the segment before or after. |
493
+ | Backspace | Remove the last digit, then empty the segment, then move back. |
494
+ | Delete | Empty the segment. |
495
+ | A / P | Choose AM or PM, in a time with a 12-hour clock. |
496
+ | Enter | Submit the form with what was typed. |
497
+ | Tab / Shift+Tab | Move through the segments, then out of the input. |
498
+
499
+ In a right-to-left page, the segments keep the locale's order, and ArrowLeft and ArrowRight follow the order on screen.
500
+
501
+ ### Tested Behavior
502
+
503
+ Every line below is an automated test.
504
+
505
+ - Each unit is a spinbutton in the locale's order.
506
+ - Another locale orders the segments its own way.
507
+ - Typing fills each segment, moves on and reports the date once.
508
+ - A press on the box or between segments focuses the nearest segment.
509
+ - A digit that can't start a longer number moves on at once.
510
+ - A separator moves to the next segment.
511
+ - Typing with separators over a filled date or time keeps every segment.
512
+ - ArrowUp and ArrowDown step a segment and wrap around.
513
+ - PageUp and PageDown take bigger steps.
514
+ - Home and End go to a segment's first and last values.
515
+ - ArrowLeft and ArrowRight move between segments.
516
+ - Tab moves through the segments, then out.
517
+ - Backspace empties a segment, then moves back.
518
+ - A day past the end of a new month is moved back to its last day.
519
+ - Pasting a date fills every segment.
520
+ - A time takes hours, minutes and AM or PM.
521
+ - A 24-hour clock has no AM or PM.
522
+ - A date with a time shows its hour and minute too.
523
+ - A controller sets and clears the date.
524
+ - A canceled change keeps the date.
525
+ - The form submits the ISO date and time under their names.
526
+ - Enter submits the form with what was typed.
527
+ - Resetting the form brings back the default date.
528
+ - A partly filled date can't be submitted.
529
+ - A required empty date can't be submitted.
530
+ - A date after max is invalid.
531
+ - An unavailable date is invalid.
532
+ - The field's label focuses the first segment.
533
+ - A read-only date takes focus but doesn't change.
534
+ - A disabled date can't be focused.
535
+
536
+ Runs in React, Vue and web components.