@uniflowed/ui 0.0.0-alpha.9 → 0.2.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 (65) hide show
  1. package/accordion.js +84 -57
  2. package/alert-dialog.js +284 -0
  3. package/alert.js +142 -0
  4. package/avatar.js +280 -0
  5. package/breadcrumb.js +138 -0
  6. package/calendar.js +587 -0
  7. package/carousel.js +410 -0
  8. package/checkbox.js +215 -31
  9. package/collapsible.js +72 -48
  10. package/color-picker.js +172 -0
  11. package/combobox.js +216 -39
  12. package/context-menu.js +215 -0
  13. package/date-field.js +9 -0
  14. package/date-picker.js +357 -0
  15. package/date-range-picker.js +120 -0
  16. package/dialog.js +243 -178
  17. package/drag-drop.js +125 -0
  18. package/drawer.js +504 -0
  19. package/field.js +260 -43
  20. package/grid-list.js +8 -0
  21. package/hover-card.js +52 -52
  22. package/i18n-provider.js +89 -0
  23. package/index.js +1177 -31
  24. package/input-otp.js +218 -0
  25. package/interactions.js +2327 -0
  26. package/internal/anchor.js +71 -6
  27. package/internal/collection.js +562 -0
  28. package/internal/date-grid.js +260 -0
  29. package/internal/date-range.js +26 -0
  30. package/internal/disclosure.js +201 -0
  31. package/internal/menu-tree.js +228 -0
  32. package/internal/merge-props.js +85 -1
  33. package/internal/roving-focus.js +15 -4
  34. package/internal/segmented-field.js +317 -0
  35. package/internal/selection.js +171 -0
  36. package/internal/visually-hidden-style.js +41 -0
  37. package/list-box.js +13 -0
  38. package/menu.js +553 -361
  39. package/menubar.js +295 -0
  40. package/number-field.js +263 -0
  41. package/package.json +8 -28
  42. package/pagination.js +34 -22
  43. package/popover.js +116 -75
  44. package/progress.js +21 -16
  45. package/radio-group.js +81 -75
  46. package/range-calendar.js +79 -0
  47. package/resizable.js +155 -9
  48. package/scroll-area.js +283 -0
  49. package/select.js +83 -37
  50. package/separator.js +97 -0
  51. package/sheet.js +189 -0
  52. package/sidebar.js +320 -0
  53. package/skeleton.js +163 -0
  54. package/slider.js +95 -89
  55. package/switch.js +42 -34
  56. package/table.js +100 -71
  57. package/tabs.js +100 -91
  58. package/tag-group.js +8 -0
  59. package/time-field.js +8 -0
  60. package/toast.js +36 -66
  61. package/toggle-group.js +53 -49
  62. package/toggle.js +41 -27
  63. package/tooltip.js +48 -55
  64. package/tree.js +8 -0
  65. package/visually-hidden.js +259 -0
package/index.js CHANGED
@@ -32,6 +32,105 @@
32
32
  // A library written in TypeScript can document those constraints; it cannot
33
33
  // state them.
34
34
  //
35
+ // # The copy step copies the look, never the behaviour
36
+ //
37
+ // shadcn's product is not a component. It is `npx shadcn add dialog`, which
38
+ // writes the source into your repository so that you own it and change it —
39
+ // and around that sit `init`, `view`, `search`, `build`, `migrate`, `eject`, an
40
+ // MCP server, a `components.json` and a registry format anybody can publish to.
41
+ // The roadmap first answered that with "typed imports, preset styles, and
42
+ // **no copy step**", and ubugeeei-prod/uf#303 is where the answer was argued.
43
+ // ubugeeei-prod/uf#947 kept half of it and reversed the other half, and the
44
+ // half it kept is this package.
45
+ //
46
+ // **Why this package is still never copied.** A copy is a fork with no
47
+ // upstream, and four things follow. A focus trap fixed here reaches everyone
48
+ // who upgrades and reaches nobody who copied. The composition constraints above
49
+ // are checked *across the boundary*: `Tabs.List` declaring `renders* Tabs.Tab`
50
+ // means something while the library is imported and means nothing once the
51
+ // source has been pasted into an application, because then it is the
52
+ // application's own component and Flow has nothing left to hold it to.
53
+ // `sideEffects: false` and one module per primitive already give a bundler
54
+ // everything a copy would. And the accessibility work stays in one place with
55
+ // one suite over it, rather than in every consumer's repository at the version
56
+ // they took it at.
57
+ //
58
+ // **What `uf ui add` copies instead.** The styled layer. `uf ui add dialog`
59
+ // writes `app/components/ui/dialog.js`, which imports its parts from
60
+ // `@uniflowed/ui` and owns the scrim, the spacing and the tone of the
61
+ // trigger — what a person means when they say "our dialog". None of the four
62
+ // arguments above is reopened by it, because none of them is about the look:
63
+ // the focus trap still arrives by upgrade, and the copied `TabsList` still takes
64
+ // `renders* TabsTab` over a `TabsTab` that renders this package's tab, so the
65
+ // constraint holds in the application's own file. `crates/uf_ui`'s header is
66
+ // the decision in full.
67
+ //
68
+ // **What a caller gets without copying anything.** The reason people copy
69
+ // is to change the markup, so that has to be answered or this is a worse
70
+ // library for the same use. The answer is `render`, which every part that
71
+ // renders an element of its own is growing:
72
+ //
73
+ // <Menu.Item render={(props) => <a href="/settings" {...props} />}>
74
+ // Settings
75
+ // </Menu.Item>
76
+ //
77
+ // The part computes every attribute, every id, every composed handler and every
78
+ // ref exactly as it would have, and hands them to the caller to put on their
79
+ // own element; `children` is among them, so a caller who spreads and
80
+ // self-closes still gets what was written between the tags. What it does *not*
81
+ // hand over is anything true of the element rather than of the part —
82
+ // `type="button"` stays on the `<button>` branch.
83
+ //
84
+ // It is deliberately not Radix's `asChild`. Cloning a child hides which props
85
+ // arrived and in what order; a function is handed the object, so a caller can
86
+ // read it, order it themselves, and drop one on purpose. And the part is still
87
+ // the part: `Menu.Body`'s `renders*` still rejects a `<div>` where a
88
+ // `Menu.Item` belongs, because a `Menu.Item` rendered as an `<a>` is a
89
+ // `Menu.Item`. That is the half a copied source cannot keep, and it is what
90
+ // makes leaving this package uncopied a trade rather than a loss.
91
+ //
92
+ // **Where it is, today.** Every fixed-element part has it in `Accordion`,
93
+ // `Alert`, `AlertDialog`, `Avatar`, `Breadcrumb`, `Collapsible`, `Dialog`,
94
+ // `Drawer`, `Menu`, `ContextMenu`, `Menubar`, `Pagination`, `Popover`, `Sheet`,
95
+ // `Skeleton`, `Slider`, `Table`, `Tabs` and `ToggleGroup`; so do the
96
+ // single-part `Checkbox`, `Progress`, `Separator`, `Switch` and `Toggle`.
97
+ // `Field.Root`, `Field.Label`, `Field.Control`, `Field.Description`,
98
+ // `Field.Status` and `Field.Error` take it too, as do `RadioGroup`,
99
+ // `InputOtp.Separator`, `Tooltip.Trigger`, `Tooltip.Body`,
100
+ // `HoverCard.Trigger`, `HoverCard.Body` and `Sidebar.Item`. A documented
101
+ // escape hatch that is not there is worse than an undocumented one that is, so
102
+ // the state of every part is a table in `packages/ui/ui.test.js` rather than a
103
+ // claim in this paragraph: it names each part as implemented, no-element, or
104
+ // fixed remainder, and the suite holds the lists to the exported files. A part
105
+ // added to this barrel is in none of them and the suite says so.
106
+ //
107
+ // **Are the `internal/` modules ever public?** No, and the consequence is
108
+ // worth stating rather than leaving as an omission. `merge-props.js`,
109
+ // `roving-focus.js`, `controlled-state.js` and the rest each explain in their
110
+ // own header why exporting them would publish a weaker promise than the
111
+ // components make — a consumer who could reach `merge-props.js` could build a
112
+ // part that spreads `rest` last, which is the failure it exists to prevent. So
113
+ // there is no third-party primitive that participates the way these do, and no
114
+ // registry of them: the uf-shaped equivalent of publishing a component is a
115
+ // pull request against this package, where its keyboard map gets the same suite
116
+ // as everything else. That is a real cost of the decision and the right side of
117
+ // it for a library whose value is that the hard parts are correct. What a third
118
+ // party *can* build on is the escape hatch itself, which is the whole public
119
+ // surface it needs: a component of theirs given to `render` receives the props
120
+ // this package would have used, and their own composition sits inside a part
121
+ // that is still checked.
122
+ //
123
+ // **What the CLI adds.** `uf ui add`, `uf ui list` and `uf ui diff`, over a
124
+ // registry of styled components in `registry/ui/` that is embedded into the
125
+ // binary — each one built on parts exported here, and none of them a copy of
126
+ // one. There is still no registry of *primitives*, for the reason the previous
127
+ // paragraph gives, and there is no `components.json`: `uf.config.js` is the one
128
+ // configuration surface by design, so a UI option belongs there rather than in
129
+ // a second file. The affordance `shadcn view` has — "tell me what this
130
+ // component is made of" — is still `uf inspect --json` for the parts, out of
131
+ // `crates/uf_lib/src/ui.rs`, which `cargo test -p uf_lib` holds to this barrel
132
+ // in both directions.
133
+ //
35
134
  // # Styling is a default, not a dependency
36
135
  //
37
136
  // Nothing here imports StyleX, and nothing here has a StyleX-shaped type. A
@@ -39,6 +138,71 @@
39
138
  // the same components with exactly the same behaviour; the design-system layer
40
139
  // that adds uf's default styles is built *on* these, not into them.
41
140
  //
141
+ // # What is not here, and where it went instead
142
+ //
143
+ // About twenty of the catalogue this package is measured against have no
144
+ // behaviour at all. Badge, Card, Button, Input, Textarea, Label and Aspect
145
+ // Ratio are, between them, a class list and a `<div>`. For a library whose
146
+ // product *is* the styles that is coherent — you copy them in and you own
147
+ // them. It is not coherent here: a `Badge` with no styles is a `<span>`, a
148
+ // `Card` with no styles is a `<div>`, and shipping them from a package that
149
+ // ships no styles would make this a library of empty elements. Shipping them
150
+ // from `@uniflowed/stylex` would make *that* a component library, which its
151
+ // own header forbids. So for a long time the answer on record was both and
152
+ // neither, which is ubugeeei-prod/uf#298.
153
+ //
154
+ // The line that holds is not "styled versus headless". It is **whether the
155
+ // thing has a decision in it**:
156
+ //
157
+ // - An ARIA decision, a state machine or a keyboard requirement makes it a
158
+ // component here, even when it renders a single element. `Progress` is one
159
+ // `<div>`, and it belongs, because the conditional that omits
160
+ // `aria-valuenow` when the amount is unknown — rather than sending
161
+ // `aria-valuenow={0}`, which says "nothing has happened" — is the whole
162
+ // component. `Toggle` and `Checkbox` are the same shape for the same reason.
163
+ // - Anything that is only a class list belongs in `@uniflowed/stylex/preset`,
164
+ // which already has the right form: `buttonStyles({ tone, size })`,
165
+ // `cardStyles()`, `textStyles({ size, tone })` and `fieldStyles()` return
166
+ // `{ className }` for a caller to spread onto their own element. That is a
167
+ // better answer than a `<Badge>`, not a lesser one — a component wrapping a
168
+ // `<button>` takes away `type="submit"`, `formAction`, the ref and every
169
+ // attribute nobody thought to forward, and gives back a class name the
170
+ // caller could have written.
171
+ //
172
+ // This is stated rather than left as an omission, because an omission reads as
173
+ // an oversight and the next contributor closes it with a `<Badge>`.
174
+ // `crates/uf_lib/src/ui.rs` carries the same decision as data: those entries
175
+ // are `Declined` with the preset functions that replace them named on each,
176
+ // and `cargo test -p uf_lib` fails if a name there stops existing.
177
+ //
178
+ // Five of that twenty are on the other side of the line and now ship — Alert,
179
+ // Avatar, Breadcrumb, Separator and Skeleton — each for one specific reason,
180
+ // and each of them one or two elements:
181
+ //
182
+ // - **Alert** is the one whose usual shape is arguably wrong to copy.
183
+ // `role="alert"` is a live region, an element already in the document when
184
+ // the page loads announces on insertion or not at all, and a permanently
185
+ // rendered "your trial ends soon" box carrying that role is either an
186
+ // interruption on every page load or silence. So the role is behind `live`,
187
+ // and a static callout does not get one. `field.js` already makes that call
188
+ // correctly for `Field.Error`.
189
+ // - **Avatar** is a three-state machine — loading, loaded, failed — with the
190
+ // fallback held back so a cached image does not flash somebody's initials,
191
+ // and with `alt=""` by default, because an avatar beside a name that puts
192
+ // the name in `alt` makes every screen reader say it twice.
193
+ // - **Breadcrumb** is `Pagination`'s shape one door along: a named `<nav>`, one
194
+ // `aria-current="page"`, and separators hidden so the trail is not read as
195
+ // "Home slash Settings slash Billing".
196
+ // - **Separator** is two lines with one decision in them, and it is the
197
+ // decision `Progress` is: `role="separator"` with an `aria-orientation` for a
198
+ // boundary a reader should be told about, `aria-hidden` for a rule that is
199
+ // only a rule.
200
+ // - **Skeleton** is the one that silently makes a page worse. A screen of
201
+ // skeletons is a screen of empty boxes, so the boxes are `aria-hidden`, the
202
+ // region they stand in is `aria-busy`, and a live region that was empty for
203
+ // one commit says "Loading" — which is #289's rule met at the moment it bites
204
+ // hardest, because a skeleton screen is busy on its very first render.
205
+ //
42
206
  // # What these components promise React
43
207
  //
44
208
  // Nothing here mutates during a render, reads a ref during a render, or depends
@@ -57,20 +221,9 @@
57
221
  //
58
222
  // One component does use it, and it is the case the API is actually for.
59
223
  // `toast("Saved")` is called from an event handler or a `catch`, so the queue
60
- // of notifications lives outside React — a store at module scope in
61
- // `toast.js`, read through `useSyncExternalStore` with the cached immutable
62
- // snapshots and the consistent server snapshot that requires. A queue is a
63
- // store; the DOM is not.
64
- //
65
- // That store should be an atom in `@uniflowed/state`, and was one. It is
66
- // written out by hand in `toast.js` because `@uniflowed/ui` is published to
67
- // npm and `@uniflowed/state` is not: its name has never been bound, and
68
- // binding it takes a person with an `npm login` session and a 2FA prompt —
69
- // ubugeeei-prod/uf#210. A published package whose dependency is missing
70
- // installs as nothing, `ETARGET` on the first thing a user types, so this
71
- // package cannot declare that dependency until the name exists. The queue
72
- // moves back to `@uniflowed/state` when #210 binds it. The local store is the
73
- // shippable design, not the better one.
224
+ // of notifications lives outside React — an atom in `@uniflowed/state`, read
225
+ // through `useSyncExternalStore` with the cached immutable snapshots and the
226
+ // consistent server snapshot that requires. A queue is a store; the DOM is not.
74
227
  //
75
228
  // # Server and client
76
229
  //
@@ -85,10 +238,29 @@
85
238
  // One root module per primitive, each with its own `exports` subpath, each
86
239
  // named after the thing it implements:
87
240
  //
88
- // - `dialog.js` — the focus trap, focus restore, scroll lock and inert page.
89
- // - `menu.js` — the arrow keys, typeahead, submenus and `Escape` stacking.
90
- // - `combobox.js` — `aria-activedescendant` over a filtered list, and the
91
- // count a screen reader is told.
241
+ // - `dialog.js` — the focus trap, focus restore, scroll lock and inert page,
242
+ // and the three props the four components below it differ from it by.
243
+ // - `alert-dialog.js`, `sheet.js`, `drawer.js` and `sidebar.js` — the four
244
+ // built on that one. An alert dialog is modal and cannot be dismissed by
245
+ // pressing beside it; a sheet is a dialog with an edge; a drawer is a sheet
246
+ // with a gesture, and therefore with WCAG 2.5.7's keyboard equivalent of it;
247
+ // a sidebar is most often neither modal nor a dialog, and becomes both on a
248
+ // narrow viewport.
249
+ // - `carousel.js`, `scroll-area.js` and `input-otp.js` — the three that replace
250
+ // something the browser already does, and so the three that have to be better
251
+ // than what they replaced. Each module's header says what it gives that the
252
+ // plain element does not; if it ever stops being true, the component should
253
+ // be deleted rather than fixed.
254
+ // - `menu.js`, `context-menu.js` and `menubar.js` — the arrow keys, typeahead,
255
+ // submenus and `Escape` stacking, and the two components that are that
256
+ // behaviour opened differently. A context menu is opened by the right button,
257
+ // by `Shift+F10` and by a long press, and opens at a *point*; a menubar is a
258
+ // row of them with one tab stop and arrows that walk between the open menus.
259
+ // shadcn's fourth menu, Dropdown Menu, is `menu.js` under another name and is
260
+ // deliberately not a second export.
261
+ // - `combobox.js` — `aria-activedescendant` over a filtered list, the count a
262
+ // screen reader is told, and the option groups that make a command palette a
263
+ // composition rather than a seventh module.
92
264
  // - `select.js` — the other half of the combobox pattern: the select-only one,
93
265
  // with typeahead, option groups and a value a form can submit.
94
266
  // - `tabs.js` — the roving `tabindex`, and automatic versus manual activation.
@@ -119,13 +291,25 @@
119
291
  // a dialog that is not modal, a tooltip describes its trigger and may never
120
292
  // take focus, a hover card is neither and holds links — and because a flag
121
293
  // selecting between them would be one flag every behaviour had to read.
294
+ // - `alert.js`, `avatar.js`, `breadcrumb.js`, `separator.js` and `skeleton.js`
295
+ // — the five above that look like a class list and are not. One or two
296
+ // elements each, and one conditional each: whether a callout announces
297
+ // itself, which of three states an image is in, whether the last crumb is a
298
+ // link, whether a rule is in the accessibility tree, and whether anybody is
299
+ // told the page is loading.
300
+ // - `interactions.js` — the press, the hover, the focus ring, the long press,
301
+ // the drag and the key, and the one module here that exports hooks rather
302
+ // than parts. A press that is released outside does not press, a hover is
303
+ // never a finger's, a ring is drawn for the keyboard and not for the pointer,
304
+ // and a screen reader's click is a press; its header says what each rule
305
+ // prevents and where it differs from React Aria's.
122
306
  //
123
- // Every name below is exported from one of those, so a consumer may import
124
- // `@uniflowed/ui` or `@uniflowed/ui/dialog` and get the same thing. The split
125
- // is by primitive because that is the unit a reader looks for, the unit a
126
- // bundler drops, and the unit the WAI-ARIA practices are written in.
307
+ // Every name below is exported from one of those, and this file is the only way
308
+ // to import any of them: `package.json` exports it alone. The split is by
309
+ // primitive because that is the unit a reader looks for, the unit a bundler
310
+ // drops, and the unit the WAI-ARIA practices are written in.
127
311
  //
128
- // `internal/` holds nine modules and nothing else, each a rule the primitives
312
+ // `internal/` holds ten modules and nothing else, each a rule the primitives
129
313
  // must apply identically and a consumer must not be able to apply differently:
130
314
  // `merge-props.js` (the caller's props go on first, the component's semantics
131
315
  // last), `controlled-state.js` (what "controlled" means here),
@@ -138,8 +322,10 @@
138
322
  // fit where it was asked to go), `focus.js` (which elements a reader can reach,
139
323
  // which a focus trap and a popover want opposite things from), and
140
324
  // `hover-intent.js` (what WCAG requires of content shown on hover or focus,
141
- // which is three clauses and one mechanism). Each says in its own header why it
142
- // is unreachable rather than exported. There is no `internal/props.js`-shaped
325
+ // which is three clauses and one mechanism), and `menu-tree.js` (the chain of
326
+ // open menus the three menu components share, and what `Escape` and choosing an
327
+ // item are defined in terms of). Each says in its own header why it is
328
+ // unreachable rather than exported. There is no `internal/props.js`-shaped
143
329
  // bag of helpers: a module that cannot say what it is about does not belong in
144
330
  // this package.
145
331
 
@@ -150,10 +336,50 @@ import {
150
336
  AccordionRoot,
151
337
  AccordionTrigger,
152
338
  } from "./accordion.js";
339
+ import { AlertDescription, AlertRoot, AlertTitle } from "./alert.js";
340
+ import {
341
+ AlertDialogAction,
342
+ AlertDialogBody,
343
+ AlertDialogCancel,
344
+ AlertDialogDescription,
345
+ AlertDialogFooter,
346
+ AlertDialogHeader,
347
+ AlertDialogOverlay,
348
+ AlertDialogRoot,
349
+ AlertDialogTitle,
350
+ AlertDialogTrigger,
351
+ } from "./alert-dialog.js";
352
+ import { AvatarFallback, AvatarImage, AvatarRoot } from "./avatar.js";
353
+ import {
354
+ BreadcrumbItem,
355
+ BreadcrumbLink,
356
+ BreadcrumbList,
357
+ BreadcrumbPage,
358
+ BreadcrumbRoot,
359
+ BreadcrumbSeparator,
360
+ } from "./breadcrumb.js";
361
+ import {
362
+ CarouselContent,
363
+ CarouselItem,
364
+ CarouselNext,
365
+ CarouselPause,
366
+ CarouselPrevious,
367
+ CarouselRoot,
368
+ } from "./carousel.js";
369
+ import {
370
+ CalendarDay,
371
+ CalendarMonth,
372
+ CalendarNext,
373
+ CalendarPrevious,
374
+ CalendarRoot,
375
+ } from "./calendar.js";
153
376
  import { Checkbox } from "./checkbox.js";
377
+ import { ContextMenuRoot, ContextMenuTrigger } from "./context-menu.js";
154
378
  import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "./collapsible.js";
155
379
  import {
156
380
  ComboboxEmpty,
381
+ ComboboxGroup,
382
+ ComboboxGroupLabel,
157
383
  ComboboxInput,
158
384
  ComboboxLabel,
159
385
  ComboboxList,
@@ -161,6 +387,12 @@ import {
161
387
  ComboboxRoot,
162
388
  ComboboxStatus,
163
389
  } from "./combobox.js";
390
+ import {
391
+ DatePickerCalendar,
392
+ DatePickerInput,
393
+ DatePickerRoot,
394
+ DatePickerTrigger,
395
+ } from "./date-picker.js";
164
396
  import {
165
397
  DialogBody,
166
398
  DialogClose,
@@ -172,19 +404,43 @@ import {
172
404
  DialogTitle,
173
405
  DialogTrigger,
174
406
  } from "./dialog.js";
175
- import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from "./field.js";
407
+ import {
408
+ DrawerBody,
409
+ DrawerClose,
410
+ DrawerDescription,
411
+ DrawerFooter,
412
+ DrawerHandle,
413
+ DrawerHeader,
414
+ DrawerOverlay,
415
+ DrawerRoot,
416
+ DrawerTitle,
417
+ DrawerTrigger,
418
+ } from "./drawer.js";
419
+ import {
420
+ FieldControl,
421
+ FieldDescription,
422
+ FieldError,
423
+ FieldLabel,
424
+ FieldRoot,
425
+ FieldStatus,
426
+ } from "./field.js";
176
427
  import { HoverCardBody, HoverCardRoot, HoverCardTrigger } from "./hover-card.js";
428
+ import { InputOtpGroup, InputOtpRoot, InputOtpSeparator, InputOtpSlot } from "./input-otp.js";
177
429
  import {
178
430
  MenuBody,
431
+ MenuCheckboxItem,
179
432
  MenuGroup,
180
433
  MenuItem,
181
434
  MenuLabel,
435
+ MenuRadioGroup,
436
+ MenuRadioItem,
182
437
  MenuRoot,
183
438
  MenuSeparator,
184
439
  MenuSub,
185
440
  MenuSubTrigger,
186
441
  MenuTrigger,
187
442
  } from "./menu.js";
443
+ import { MenubarMenu, MenubarRoot, MenubarTrigger } from "./menubar.js";
188
444
  import {
189
445
  NavigationMenuBody,
190
446
  NavigationMenuItem,
@@ -204,6 +460,7 @@ import { PopoverBody, PopoverRoot, PopoverTrigger } from "./popover.js";
204
460
  import { Progress } from "./progress.js";
205
461
  import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "./radio-group.js";
206
462
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./resizable.js";
463
+ import { ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaViewport } from "./scroll-area.js";
207
464
  import {
208
465
  SelectGroup,
209
466
  SelectGroupLabel,
@@ -215,6 +472,27 @@ import {
215
472
  SelectTrigger,
216
473
  SelectValue,
217
474
  } from "./select.js";
475
+ import { Separator } from "./separator.js";
476
+ import {
477
+ SheetBody,
478
+ SheetClose,
479
+ SheetDescription,
480
+ SheetFooter,
481
+ SheetHeader,
482
+ SheetOverlay,
483
+ SheetRoot,
484
+ SheetTitle,
485
+ SheetTrigger,
486
+ } from "./sheet.js";
487
+ import {
488
+ SidebarBody,
489
+ SidebarFooter,
490
+ SidebarHeader,
491
+ SidebarItem,
492
+ SidebarRoot,
493
+ SidebarTrigger,
494
+ } from "./sidebar.js";
495
+ import { SkeletonBox, SkeletonRoot } from "./skeleton.js";
218
496
  import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "./slider.js";
219
497
  import { Switch } from "./switch.js";
220
498
  import {
@@ -247,16 +525,240 @@ import { ToggleGroupItem, ToggleGroupRoot } from "./toggle-group.js";
247
525
  import { TooltipBody, TooltipProvider, TooltipRoot, TooltipTrigger } from "./tooltip.js";
248
526
 
249
527
  export type { AccordionType } from "./accordion.js";
528
+ export type { AvatarStatus } from "./avatar.js";
529
+ // A date, however a caller had one to hand: a `PlainDate` from
530
+ // `@uniflowed/temporal`, or the ISO 8601 string a form field or a URL carries.
531
+ export type { DateValue } from "./calendar.js";
250
532
  export type { ActivationMode } from "./tabs.js";
533
+ // What a modal announces itself as, for a caller who holds one in a variable.
534
+ // Two members, not a string: see `dialog.js`.
535
+ export type { DialogRole } from "./dialog.js";
536
+ // Which edge of the viewport a sheet or a drawer is attached to. `Sidebar` has
537
+ // its own two-member union, because a sidebar is never on the top or bottom.
538
+ export type { Edge } from "./sheet.js";
539
+ export type { FieldSource } from "./field.js";
540
+ export type { InputOtpKind } from "./input-otp.js";
541
+ export type { MenuSelect } from "./menu.js";
542
+ // Which way a carousel, a scroll area or a separator runs.
543
+ export type { Orientation } from "./separator.js";
544
+ export type { SidebarSide } from "./sidebar.js";
251
545
  // Where an anchored overlay opens, for a caller who holds one in a variable or
252
546
  // a prop of their own. Unions rather than strings, so `side="botom"` is a type
253
547
  // error at the call rather than an overlay that quietly opens somewhere else.
254
- export type { Align, Side } from "./popover.js";
548
+ // `LogicalSide` is the same four plus `inline-start` and `inline-end`, which
549
+ // are the ones that mean "the way the reader reads" - what a submenu opens
550
+ // onto, and the left of the page in Arabic.
551
+ export type { Align, LogicalSide, Side } from "./popover.js";
255
552
  export type { Sort } from "./table.js";
256
553
  export type { Notification, ToastChanges, ToastOptions, Urgency } from "./toast.js";
257
554
  export type { ToggleGroupType } from "./toggle-group.js";
258
555
 
259
- export { Checkbox, Progress, Switch, Toggle };
556
+ /**
557
+ * Every part, under the name its module gives it.
558
+ *
559
+ * This file is the only entry point the package has — `package.json` exports
560
+ * `.` and nothing else — so a name a module exports and this list leaves out is
561
+ * a name nobody can import. The namespaces below are the same parts spelled for
562
+ * composing a page (`Dialog.Root`); these are for the wrapper, the preset and
563
+ * the test that want one part (`import { DialogRoot } from "@uniflowed/ui"`).
564
+ * `sideEffects: false` lets a bundler keep the module a name comes from and drop
565
+ * the rest, and `uf_rsc` names that module, not the package, as the client
566
+ * boundary of a Server Component that imports the name.
567
+ */
568
+ export {
569
+ AccordionContent,
570
+ AccordionHeader,
571
+ AccordionItem,
572
+ AccordionRoot,
573
+ AccordionTrigger,
574
+ AlertDescription,
575
+ AlertDialogAction,
576
+ AlertDialogBody,
577
+ AlertDialogCancel,
578
+ AlertDialogDescription,
579
+ AlertDialogFooter,
580
+ AlertDialogHeader,
581
+ AlertDialogOverlay,
582
+ AlertDialogRoot,
583
+ AlertDialogTitle,
584
+ AlertDialogTrigger,
585
+ AlertRoot,
586
+ AlertTitle,
587
+ AvatarFallback,
588
+ AvatarImage,
589
+ AvatarRoot,
590
+ BreadcrumbItem,
591
+ BreadcrumbLink,
592
+ BreadcrumbList,
593
+ BreadcrumbPage,
594
+ BreadcrumbRoot,
595
+ BreadcrumbSeparator,
596
+ CalendarDay,
597
+ CalendarMonth,
598
+ CalendarNext,
599
+ CalendarPrevious,
600
+ CalendarRoot,
601
+ CarouselContent,
602
+ CarouselItem,
603
+ CarouselNext,
604
+ CarouselPause,
605
+ CarouselPrevious,
606
+ CarouselRoot,
607
+ CollapsibleContent,
608
+ CollapsibleRoot,
609
+ CollapsibleTrigger,
610
+ ComboboxEmpty,
611
+ ComboboxGroup,
612
+ ComboboxGroupLabel,
613
+ ComboboxInput,
614
+ ComboboxLabel,
615
+ ComboboxList,
616
+ ComboboxOption,
617
+ ComboboxRoot,
618
+ ComboboxStatus,
619
+ ContextMenuRoot,
620
+ ContextMenuTrigger,
621
+ DatePickerCalendar,
622
+ DatePickerInput,
623
+ DatePickerRoot,
624
+ DatePickerTrigger,
625
+ DialogBody,
626
+ DialogClose,
627
+ DialogDescription,
628
+ DialogFooter,
629
+ DialogHeader,
630
+ DialogOverlay,
631
+ DialogRoot,
632
+ DialogTitle,
633
+ DialogTrigger,
634
+ DrawerBody,
635
+ DrawerClose,
636
+ DrawerDescription,
637
+ DrawerFooter,
638
+ DrawerHandle,
639
+ DrawerHeader,
640
+ DrawerOverlay,
641
+ DrawerRoot,
642
+ DrawerTitle,
643
+ DrawerTrigger,
644
+ FieldControl,
645
+ FieldDescription,
646
+ FieldError,
647
+ FieldLabel,
648
+ FieldRoot,
649
+ FieldStatus,
650
+ HoverCardBody,
651
+ HoverCardRoot,
652
+ HoverCardTrigger,
653
+ InputOtpGroup,
654
+ InputOtpRoot,
655
+ InputOtpSeparator,
656
+ InputOtpSlot,
657
+ MenuBody,
658
+ MenuCheckboxItem,
659
+ MenuGroup,
660
+ MenuItem,
661
+ MenuLabel,
662
+ MenuRadioGroup,
663
+ MenuRadioItem,
664
+ MenuRoot,
665
+ MenuSeparator,
666
+ MenuSub,
667
+ MenuSubTrigger,
668
+ MenuTrigger,
669
+ MenubarMenu,
670
+ MenubarRoot,
671
+ MenubarTrigger,
672
+ NavigationMenuBody,
673
+ NavigationMenuItem,
674
+ NavigationMenuLink,
675
+ NavigationMenuList,
676
+ NavigationMenuRoot,
677
+ NavigationMenuTrigger,
678
+ PaginationContent,
679
+ PaginationItem,
680
+ PaginationNext,
681
+ PaginationPrevious,
682
+ PaginationRoot,
683
+ PopoverBody,
684
+ PopoverRoot,
685
+ PopoverTrigger,
686
+ RadioGroupIndicator,
687
+ RadioGroupItem,
688
+ RadioGroupRoot,
689
+ ResizableHandle,
690
+ ResizablePanel,
691
+ ResizablePanelGroup,
692
+ ScrollAreaRoot,
693
+ ScrollAreaScrollbar,
694
+ ScrollAreaViewport,
695
+ SelectGroup,
696
+ SelectGroupLabel,
697
+ SelectLabel,
698
+ SelectList,
699
+ SelectOption,
700
+ SelectRoot,
701
+ SelectSeparator,
702
+ SelectTrigger,
703
+ SelectValue,
704
+ SheetBody,
705
+ SheetClose,
706
+ SheetDescription,
707
+ SheetFooter,
708
+ SheetHeader,
709
+ SheetOverlay,
710
+ SheetRoot,
711
+ SheetTitle,
712
+ SheetTrigger,
713
+ SidebarBody,
714
+ SidebarFooter,
715
+ SidebarHeader,
716
+ SidebarItem,
717
+ SidebarRoot,
718
+ SidebarTrigger,
719
+ SkeletonBox,
720
+ SkeletonRoot,
721
+ SliderRange,
722
+ SliderRoot,
723
+ SliderThumb,
724
+ SliderTrack,
725
+ TableBody,
726
+ TableCaption,
727
+ TableCell,
728
+ TableHead,
729
+ TableHeader,
730
+ TableRoot,
731
+ TableRow,
732
+ TableRowHeader,
733
+ TableRowSelect,
734
+ TableSelectAll,
735
+ TabsList,
736
+ TabsPanel,
737
+ TabsRoot,
738
+ TabsTab,
739
+ ToastAction,
740
+ ToastClose,
741
+ ToastDescription,
742
+ ToastRegion,
743
+ ToastRoot,
744
+ ToastTitle,
745
+ ToggleGroupItem,
746
+ ToggleGroupRoot,
747
+ TooltipBody,
748
+ TooltipProvider,
749
+ TooltipRoot,
750
+ TooltipTrigger,
751
+ };
752
+
753
+ /**
754
+ * The five that are one component rather than a namespace of parts.
755
+ *
756
+ * Each takes `render`, so the control or line a design system already has — a
757
+ * `<div>` with a knob drawn in it, somebody's `<Pressable>`, a presentational
758
+ * meter shell — keeps the role, state, keys and attributes while being their
759
+ * element. See the module headers and the table in `packages/ui/ui.test.js`.
760
+ */
761
+ export { Checkbox, Progress, Separator, Switch, Toggle };
260
762
 
261
763
  /**
262
764
  * Queueing a notification, from anywhere.
@@ -271,6 +773,75 @@ export { Checkbox, Progress, Switch, Toggle };
271
773
  */
272
774
  export { dismissAllToasts, dismissToast, toast, updateToast };
273
775
 
776
+ /**
777
+ * The interactions every part here is made of, for a control of the caller's own.
778
+ *
779
+ * `usePress` is a press from a pointer, a key or assistive technology, with one
780
+ * set of events; `useHover` is a mouse's and a pen's and never a finger's;
781
+ * `useFocusRing` draws a ring for keyboard focus and not for pointer focus;
782
+ * `useLongPress`, `useMove` and `useKeyboard` are the rest; `mergeProps` puts
783
+ * several of them on one element. `interactions.js` says what each one gets
784
+ * right, and where it differs from React Aria's, at length.
785
+ *
786
+ * const { isPressed, pressProps } = usePress({ onPress: save });
787
+ * const { focusProps, isFocusVisible } = useFocusRing();
788
+ * <div {...mergeProps(pressProps, focusProps)} role="button" tabIndex={0} />
789
+ *
790
+ * Hooks rather than parts, and the one module here that exports them: its rules
791
+ * are about input devices rather than about markup this package builds, so a
792
+ * control written with them is the same control a part is rather than a weaker
793
+ * copy of one.
794
+ */
795
+ export {
796
+ getInteractionModality,
797
+ mergeProps,
798
+ useFocusRing,
799
+ useFocusVisible,
800
+ useHover,
801
+ useInteractionModality,
802
+ useInteractOutside,
803
+ useKeyboard,
804
+ useLongPress,
805
+ useMove,
806
+ usePress,
807
+ } from "./interactions.js";
808
+ export type {
809
+ FocusRingOptions,
810
+ FocusRingProps,
811
+ FocusRingResult,
812
+ FocusVisibleResult,
813
+ HoverEvent,
814
+ HoverOptions,
815
+ HoverProps,
816
+ HoverResult,
817
+ InteractionEvent,
818
+ InteractionProps,
819
+ InteractOutsideOptions,
820
+ InteractOutsideRef,
821
+ KeyboardInteraction,
822
+ KeyboardOptions,
823
+ KeyboardProps,
824
+ KeyboardResult,
825
+ LongPressEvent,
826
+ LongPressOptions,
827
+ LongPressProps,
828
+ LongPressResult,
829
+ Modality,
830
+ MoveEndEvent,
831
+ MoveMoveEvent,
832
+ MoveOptions,
833
+ MovePointerType,
834
+ MoveProps,
835
+ MoveResult,
836
+ MoveStartEvent,
837
+ PhysicalPointer,
838
+ PointerType,
839
+ PressEvent,
840
+ PressOptions,
841
+ PressProps,
842
+ PressResult,
843
+ } from "./interactions.js";
844
+
274
845
  /**
275
846
  * An accessible form field.
276
847
  *
@@ -278,14 +849,31 @@ export { dismissAllToasts, dismissToast, toast, updateToast };
278
849
  * <Field.Label>Email</Field.Label>
279
850
  * <Field.Control render={(props) => <input type="email" {...props} />} />
280
851
  * <Field.Description>We will not share it.</Field.Description>
852
+ * <Field.Status>{saving ? "Saving…" : ""}</Field.Status>
281
853
  * <Field.Error>{error}</Field.Error>
282
854
  * </Field.Root>
855
+ *
856
+ * Inside a form, `field` replaces the hand-written `invalid`: the form says
857
+ * whether the field is wrong and what the message is, and the field composes
858
+ * every `aria-*` from that in one place. It also marks the control busy during
859
+ * submit, validation, or async default loading. `@uniflowed/form`'s
860
+ * `useFieldSource` is what produces one, and `field.js`'s header says why the
861
+ * hook lives there rather than here.
862
+ *
863
+ * const email = useFieldSource(form, "email", { required: "We need one" });
864
+ * <Field.Root field={email}>…<Field.Error /></Field.Root>
865
+ *
866
+ * `group` is for a set with no single control to point a `<label for>` at — a
867
+ * radio group, a checkbox group, three selects making a date. The root becomes
868
+ * `role="group"` named by the label, and the description and the error describe
869
+ * the set.
283
870
  */
284
871
  export const Field = {
285
872
  Root: FieldRoot,
286
873
  Label: FieldLabel,
287
874
  Control: FieldControl,
288
875
  Description: FieldDescription,
876
+ Status: FieldStatus,
289
877
  Error: FieldError,
290
878
  };
291
879
 
@@ -303,6 +891,11 @@ export const Field = {
303
891
  * <Tabs.Panel value="one">…</Tabs.Panel>
304
892
  * <Tabs.Panel value="two">…</Tabs.Panel>
305
893
  * </Tabs.Root>
894
+ *
895
+ * Every part takes `render`, so a tab that is also a route — `<Tabs.Tab
896
+ * render={(props) => <a href="#billing" {...props} />}>` — is still a tab, with
897
+ * the roving tab stop and the `aria-controls` a tab has. `Tabs.List`'s
898
+ * `renders* Tabs.Tab` is unaffected, because it is the *part* it constrains.
306
899
  */
307
900
  export const Tabs = {
308
901
  Root: TabsRoot,
@@ -435,6 +1028,12 @@ export const ToggleGroup = {
435
1028
  * </Dialog.Footer>
436
1029
  * </Dialog.Body>
437
1030
  * </Dialog.Root>
1031
+ *
1032
+ * Every part takes `render`. `Dialog.Title` is an `<h2>` by default and the
1033
+ * level is a fact about the page around it rather than about the dialog, so
1034
+ * `render={(props) => <h3 {...props} />}` is how a caller says which — without
1035
+ * losing the id `aria-labelledby` points at. `AlertDialog`, `Sheet` and
1036
+ * `Drawer` are made of these parts and pass `render` straight through.
438
1037
  */
439
1038
  export const Dialog = {
440
1039
  Root: DialogRoot,
@@ -448,6 +1047,199 @@ export const Dialog = {
448
1047
  Close: DialogClose,
449
1048
  };
450
1049
 
1050
+ /**
1051
+ * The confirmation: modal, announced as an alert, and not dismissible by a
1052
+ * press beside it.
1053
+ *
1054
+ * Focus lands on `Cancel` rather than on the first thing in the dialog, and the
1055
+ * description is required — `role="alertdialog"` exists to announce one, so an
1056
+ * alert dialog without it interrupts the reader to say nothing.
1057
+ *
1058
+ * <AlertDialog.Root>
1059
+ * <AlertDialog.Trigger>Delete</AlertDialog.Trigger>
1060
+ * <AlertDialog.Overlay />
1061
+ * <AlertDialog.Body>
1062
+ * <AlertDialog.Header>
1063
+ * <AlertDialog.Title>Delete this project?</AlertDialog.Title>
1064
+ * <AlertDialog.Description>This cannot be undone.</AlertDialog.Description>
1065
+ * </AlertDialog.Header>
1066
+ * <AlertDialog.Footer>
1067
+ * <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
1068
+ * <AlertDialog.Action onClick={remove}>Delete</AlertDialog.Action>
1069
+ * </AlertDialog.Footer>
1070
+ * </AlertDialog.Body>
1071
+ * </AlertDialog.Root>
1072
+ */
1073
+ export const AlertDialog = {
1074
+ Root: AlertDialogRoot,
1075
+ Trigger: AlertDialogTrigger,
1076
+ Overlay: AlertDialogOverlay,
1077
+ Body: AlertDialogBody,
1078
+ Header: AlertDialogHeader,
1079
+ Footer: AlertDialogFooter,
1080
+ Title: AlertDialogTitle,
1081
+ Description: AlertDialogDescription,
1082
+ Action: AlertDialogAction,
1083
+ Cancel: AlertDialogCancel,
1084
+ };
1085
+
1086
+ /**
1087
+ * A modal dialog attached to an edge of the viewport.
1088
+ *
1089
+ * `side` is a union rather than a class name, and every part reports it as
1090
+ * `data-side` — the same attribute `Popover.Body` writes, so one stylesheet
1091
+ * rule covers every overlay in this package.
1092
+ *
1093
+ * <Sheet.Root side="left">
1094
+ * <Sheet.Trigger>Filters</Sheet.Trigger>
1095
+ * <Sheet.Overlay />
1096
+ * <Sheet.Body>
1097
+ * <Sheet.Title>Filters</Sheet.Title>
1098
+ * <Sheet.Close>Done</Sheet.Close>
1099
+ * </Sheet.Body>
1100
+ * </Sheet.Root>
1101
+ */
1102
+ export const Sheet = {
1103
+ Root: SheetRoot,
1104
+ Trigger: SheetTrigger,
1105
+ Overlay: SheetOverlay,
1106
+ Body: SheetBody,
1107
+ Header: SheetHeader,
1108
+ Footer: SheetFooter,
1109
+ Title: SheetTitle,
1110
+ Description: SheetDescription,
1111
+ Close: SheetClose,
1112
+ };
1113
+
1114
+ /**
1115
+ * The sheet you can drag away, with the keyboard that can do everything the
1116
+ * drag can.
1117
+ *
1118
+ * `Drawer.Handle` is a `role="slider"` over the snap points: the arrow keys
1119
+ * move between them, `Home` and `End` go to the ends, and the closing key at
1120
+ * the smallest snap point closes it. WCAG 2.5.7 also wants a single-pointer
1121
+ * alternative, so a drawer with a handle and no `Drawer.Close` raises.
1122
+ *
1123
+ * <Drawer.Root side="bottom" snapPoints={[0.4, 1]}>
1124
+ * <Drawer.Trigger>Details</Drawer.Trigger>
1125
+ * <Drawer.Overlay />
1126
+ * <Drawer.Body>
1127
+ * <Drawer.Handle label="Resize the details" />
1128
+ * <Drawer.Title>Details</Drawer.Title>
1129
+ * <Drawer.Close>Close</Drawer.Close>
1130
+ * </Drawer.Body>
1131
+ * </Drawer.Root>
1132
+ */
1133
+ export const Drawer = {
1134
+ Root: DrawerRoot,
1135
+ Trigger: DrawerTrigger,
1136
+ Overlay: DrawerOverlay,
1137
+ Body: DrawerBody,
1138
+ Handle: DrawerHandle,
1139
+ Header: DrawerHeader,
1140
+ Footer: DrawerFooter,
1141
+ Title: DrawerTitle,
1142
+ Description: DrawerDescription,
1143
+ Close: DrawerClose,
1144
+ };
1145
+
1146
+ /**
1147
+ * Navigation beside the page, which becomes a modal sheet on a narrow one.
1148
+ *
1149
+ * `Sidebar.Item` takes a `label` and keeps it as the button's accessible name
1150
+ * the moment the sidebar collapses to icons — which is the whole reason a
1151
+ * collapsing sidebar is a component rather than a class.
1152
+ *
1153
+ * <Sidebar.Root defaultOpen={fromCookie}>
1154
+ * <Sidebar.Trigger>Menu</Sidebar.Trigger>
1155
+ * <Sidebar.Body label="Main">
1156
+ * <Sidebar.Item label="Settings">
1157
+ * <Gear /> Settings
1158
+ * </Sidebar.Item>
1159
+ * </Sidebar.Body>
1160
+ * </Sidebar.Root>
1161
+ */
1162
+ export const Sidebar = {
1163
+ Root: SidebarRoot,
1164
+ Trigger: SidebarTrigger,
1165
+ Header: SidebarHeader,
1166
+ Body: SidebarBody,
1167
+ Footer: SidebarFooter,
1168
+ Item: SidebarItem,
1169
+ };
1170
+
1171
+ /**
1172
+ * Slides, one at a time, that a reader can stop and cannot fall into.
1173
+ *
1174
+ * `Carousel.Pause` is WCAG 2.2.2's mechanism and must be the first focusable
1175
+ * thing inside the carousel; the slides that are not showing are `inert`, so
1176
+ * `Tab` cannot reach a link nobody can see.
1177
+ *
1178
+ * <Carousel.Root autoplay={5000} count={3} label="Featured">
1179
+ * <Carousel.Pause />
1180
+ * <Carousel.Content>
1181
+ * <Carousel.Item index={0}>…</Carousel.Item>
1182
+ * <Carousel.Item index={1}>…</Carousel.Item>
1183
+ * <Carousel.Item index={2}>…</Carousel.Item>
1184
+ * </Carousel.Content>
1185
+ * <Carousel.Previous />
1186
+ * <Carousel.Next />
1187
+ * </Carousel.Root>
1188
+ */
1189
+ export const Carousel = {
1190
+ Root: CarouselRoot,
1191
+ Content: CarouselContent,
1192
+ Item: CarouselItem,
1193
+ Pause: CarouselPause,
1194
+ Previous: CarouselPrevious,
1195
+ Next: CarouselNext,
1196
+ };
1197
+
1198
+ /**
1199
+ * An overflow container a keyboard can actually scroll.
1200
+ *
1201
+ * `role="region"`, a name and `tabindex="0"`, because a scroll container is not
1202
+ * focusable in every browser and one that is not is one a keyboard reader can
1203
+ * see the top of and nothing else.
1204
+ *
1205
+ * <ScrollArea.Root label="Release notes">
1206
+ * <ScrollArea.Viewport>…</ScrollArea.Viewport>
1207
+ * <ScrollArea.Scrollbar orientation="vertical" />
1208
+ * </ScrollArea.Root>
1209
+ */
1210
+ export const ScrollArea = {
1211
+ Root: ScrollAreaRoot,
1212
+ Viewport: ScrollAreaViewport,
1213
+ Scrollbar: ScrollAreaScrollbar,
1214
+ };
1215
+
1216
+ /**
1217
+ * A one-time code: six boxes drawn over one real `<input>`.
1218
+ *
1219
+ * One input, so `autocomplete="one-time-code"` works, a paste fills every box,
1220
+ * and a form submits one value under one name.
1221
+ *
1222
+ * <InputOtp.Root label="One-time code" length={6} name="code">
1223
+ * <InputOtp.Group>
1224
+ * <InputOtp.Slot index={0} />
1225
+ * <InputOtp.Slot index={1} />
1226
+ * <InputOtp.Slot index={2} />
1227
+ * </InputOtp.Group>
1228
+ * <InputOtp.Separator>-</InputOtp.Separator>
1229
+ * <InputOtp.Group>
1230
+ * <InputOtp.Slot index={3} />
1231
+ * <InputOtp.Slot index={4} />
1232
+ * <InputOtp.Slot index={5} />
1233
+ * </InputOtp.Group>
1234
+ * </InputOtp.Root>
1235
+ */
1236
+ export const InputOtp = {
1237
+ Root: InputOtpRoot,
1238
+ Group: InputOtpGroup,
1239
+ Slot: InputOtpSlot,
1240
+ Separator: InputOtpSeparator,
1241
+ };
1242
+
451
1243
  /**
452
1244
  * A menu, with the keyboard map every native menu has had for thirty years.
453
1245
  *
@@ -467,12 +1259,94 @@ export const Dialog = {
467
1259
  * </Menu.Sub>
468
1260
  * </Menu.Body>
469
1261
  * </Menu.Root>
1262
+ *
1263
+ * Every part takes `render`, which is what makes a menu of links possible — and
1264
+ * a menu of links is the most ordinary menu there is:
1265
+ *
1266
+ * <Menu.Item render={(props) => <a href="/settings" {...props} />}>
1267
+ * Settings
1268
+ * </Menu.Item>
1269
+ *
1270
+ * The `<a>` keeps the middle click, the context menu and the status bar; the
1271
+ * item keeps the role, the id, the roving tab stop and the press that closes
1272
+ * the tree. See the module header for why that is the answer to "no copy step".
470
1273
  */
471
1274
  export const Menu = {
472
1275
  Root: MenuRoot,
473
1276
  Trigger: MenuTrigger,
474
1277
  Body: MenuBody,
475
1278
  Item: MenuItem,
1279
+ CheckboxItem: MenuCheckboxItem,
1280
+ RadioGroup: MenuRadioGroup,
1281
+ RadioItem: MenuRadioItem,
1282
+ Separator: MenuSeparator,
1283
+ Group: MenuGroup,
1284
+ Label: MenuLabel,
1285
+ Sub: MenuSub,
1286
+ SubTrigger: MenuSubTrigger,
1287
+ };
1288
+
1289
+ /**
1290
+ * The same menu, opened by the right button — and by the keyboard.
1291
+ *
1292
+ * `Shift+F10`, the `ContextMenu` key and a long press all open it, because a
1293
+ * command reachable only by right-click is reachable only by a pointer, which
1294
+ * is a WCAG 2.1.1 failure. `context-menu.js` says why the trigger is in the tab
1295
+ * order and when to take it out again.
1296
+ *
1297
+ * The body needs an `aria-label`: its trigger is a table row or a canvas rather
1298
+ * than a short name, so unlike `Menu.Body` it cannot name itself after one.
1299
+ *
1300
+ * <ContextMenu.Root>
1301
+ * <ContextMenu.Trigger>{row}</ContextMenu.Trigger>
1302
+ * <ContextMenu.Body aria-label="Row actions">
1303
+ * <ContextMenu.Item onSelect={rename}>Rename…</ContextMenu.Item>
1304
+ * <ContextMenu.CheckboxItem defaultChecked>Show hidden</ContextMenu.CheckboxItem>
1305
+ * </ContextMenu.Body>
1306
+ * </ContextMenu.Root>
1307
+ */
1308
+ export const ContextMenu = {
1309
+ Root: ContextMenuRoot,
1310
+ Trigger: ContextMenuTrigger,
1311
+ Body: MenuBody,
1312
+ Item: MenuItem,
1313
+ CheckboxItem: MenuCheckboxItem,
1314
+ RadioGroup: MenuRadioGroup,
1315
+ RadioItem: MenuRadioItem,
1316
+ Separator: MenuSeparator,
1317
+ Group: MenuGroup,
1318
+ Label: MenuLabel,
1319
+ Sub: MenuSub,
1320
+ SubTrigger: MenuSubTrigger,
1321
+ };
1322
+
1323
+ /**
1324
+ * A row of menus that behaves as one control: File, Edit, View.
1325
+ *
1326
+ * One tab stop for the whole bar, arrows between the menus, and — the part that
1327
+ * is always missing — arrows *while a menu is open* that close it and open the
1328
+ * next one, so a reader walks File → Edit → View without pressing Escape.
1329
+ *
1330
+ * <Menubar.Root aria-label="Main">
1331
+ * <Menubar.Menu value="file">
1332
+ * <Menubar.Trigger>File</Menubar.Trigger>
1333
+ * <Menubar.Body>
1334
+ * <Menubar.Item onSelect={open}>Open…</Menubar.Item>
1335
+ * </Menubar.Body>
1336
+ * </Menubar.Menu>
1337
+ * </Menubar.Root>
1338
+ */
1339
+ export const Menubar = {
1340
+ Root: MenubarRoot,
1341
+ Menu: MenubarMenu,
1342
+ Trigger: MenubarTrigger,
1343
+ // `Menu.Body` itself: a bar's menu is a root menu, and `menubar.js`'s header
1344
+ // says why a wrapper with the same defaults would be a second place to drift.
1345
+ Body: MenuBody,
1346
+ Item: MenuItem,
1347
+ CheckboxItem: MenuCheckboxItem,
1348
+ RadioGroup: MenuRadioGroup,
1349
+ RadioItem: MenuRadioItem,
476
1350
  Separator: MenuSeparator,
477
1351
  Group: MenuGroup,
478
1352
  Label: MenuLabel,
@@ -489,13 +1363,19 @@ export const Menu = {
489
1363
  * <Combobox.Label>Country</Combobox.Label>
490
1364
  * <Combobox.Input />
491
1365
  * <Combobox.List>
492
- * {matches.map((each) => (
493
- * <Combobox.Option key={each} value={each}>{each}</Combobox.Option>
494
- * ))}
1366
+ * <Combobox.Group>
1367
+ * <Combobox.GroupLabel>Europe</Combobox.GroupLabel>
1368
+ * {european.map((each) => (
1369
+ * <Combobox.Option key={each} value={each}>{each}</Combobox.Option>
1370
+ * ))}
1371
+ * </Combobox.Group>
495
1372
  * </Combobox.List>
496
1373
  * <Combobox.Empty>No matches.</Combobox.Empty>
497
1374
  * <Combobox.Status />
498
1375
  * </Combobox.Root>
1376
+ *
1377
+ * `Combobox.Label` names the field and `Combobox.GroupLabel` names a group of
1378
+ * options, which is why there are two of them.
499
1379
  */
500
1380
  export const Combobox = {
501
1381
  Root: ComboboxRoot,
@@ -503,6 +1383,8 @@ export const Combobox = {
503
1383
  Input: ComboboxInput,
504
1384
  List: ComboboxList,
505
1385
  Option: ComboboxOption,
1386
+ Group: ComboboxGroup,
1387
+ GroupLabel: ComboboxGroupLabel,
506
1388
  Empty: ComboboxEmpty,
507
1389
  Status: ComboboxStatus,
508
1390
  };
@@ -571,6 +1453,64 @@ export const Popover = {
571
1453
  Body: PopoverBody,
572
1454
  };
573
1455
 
1456
+ /**
1457
+ * A month of dates, as one stop in the page's tab order.
1458
+ *
1459
+ * The grid is `role="grid"`, the arrow keys move by a day and by a week, and
1460
+ * `PageUp` and `PageDown` change the month - with `Shift`, the year. Running off
1461
+ * the end of a month shows the next one and lands on its first day, and the
1462
+ * month is announced in a live region when it changes.
1463
+ *
1464
+ * <Calendar.Root defaultValue="2026-10-14" onValueChange={setWhen}>
1465
+ * <Calendar.Previous>Previous month</Calendar.Previous>
1466
+ * <Calendar.Next>Next month</Calendar.Next>
1467
+ * <Calendar.Month />
1468
+ * </Calendar.Root>
1469
+ *
1470
+ * `Calendar.Month` takes a function child when a day needs more than its number
1471
+ * in it - a dot for an appointment, a price for a night - and it is handed the
1472
+ * date and returns a `Calendar.Day`.
1473
+ *
1474
+ * Dates are `@uniflowed/temporal`'s `PlainDate`, or the ISO strings it reads.
1475
+ * `isDateDisabled` marks a day unavailable *without* making it unreachable: it
1476
+ * is `aria-disabled` and the arrow keys still land on it, which is the opposite
1477
+ * of what a disabled menu item does and the only way a reader can find out which
1478
+ * days are unavailable.
1479
+ */
1480
+ export const Calendar = {
1481
+ Root: CalendarRoot,
1482
+ Previous: CalendarPrevious,
1483
+ Next: CalendarNext,
1484
+ Month: CalendarMonth,
1485
+ Day: CalendarDay,
1486
+ };
1487
+
1488
+ /**
1489
+ * A field somebody types a date into, and a calendar for the times they would
1490
+ * rather point at one.
1491
+ *
1492
+ * <DatePicker.Root onValueChange={setWhen} value={when}>
1493
+ * <DatePicker.Input aria-label="Arrive on" />
1494
+ * <DatePicker.Trigger>Choose a date</DatePicker.Trigger>
1495
+ * <DatePicker.Calendar>
1496
+ * <Calendar.Previous>Previous month</Calendar.Previous>
1497
+ * <Calendar.Next>Next month</Calendar.Next>
1498
+ * <Calendar.Month />
1499
+ * </DatePicker.Calendar>
1500
+ * </DatePicker.Root>
1501
+ *
1502
+ * The field is the control and the grid is the second way in: `Escape` and a
1503
+ * chosen date both put focus back on the field. `format` and `parse` are ISO
1504
+ * 8601 both ways unless a caller passes their own - `date-picker.js` says why a
1505
+ * locale format is not this package's to guess.
1506
+ */
1507
+ export const DatePicker = {
1508
+ Root: DatePickerRoot,
1509
+ Input: DatePickerInput,
1510
+ Trigger: DatePickerTrigger,
1511
+ Calendar: DatePickerCalendar,
1512
+ };
1513
+
574
1514
  /**
575
1515
  * A phrase about a control, on hover and on focus, that WCAG would accept.
576
1516
  *
@@ -744,3 +1684,209 @@ export const Pagination = {
744
1684
  Previous: PaginationPrevious,
745
1685
  Next: PaginationNext,
746
1686
  };
1687
+
1688
+ /**
1689
+ * The trail above the page, read as places rather than as punctuation.
1690
+ *
1691
+ * <Breadcrumb.Root>
1692
+ * <Breadcrumb.List>
1693
+ * <Breadcrumb.Item>
1694
+ * <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
1695
+ * </Breadcrumb.Item>
1696
+ * <Breadcrumb.Separator>/</Breadcrumb.Separator>
1697
+ * <Breadcrumb.Item>
1698
+ * <Breadcrumb.Page>Billing</Breadcrumb.Page>
1699
+ * </Breadcrumb.Item>
1700
+ * </Breadcrumb.List>
1701
+ * </Breadcrumb.Root>
1702
+ *
1703
+ * `Pagination`'s shape one door along: a `<nav>` with a name, one
1704
+ * `aria-current="page"`, and the separators out of the accessibility tree so
1705
+ * the trail is not announced as "Home slash Settings slash Billing". The last
1706
+ * crumb is a `Breadcrumb.Page` and not a link, because it is where the reader
1707
+ * already is.
1708
+ */
1709
+ export const Breadcrumb = {
1710
+ Root: BreadcrumbRoot,
1711
+ List: BreadcrumbList,
1712
+ Item: BreadcrumbItem,
1713
+ Link: BreadcrumbLink,
1714
+ Page: BreadcrumbPage,
1715
+ Separator: BreadcrumbSeparator,
1716
+ };
1717
+
1718
+ /**
1719
+ * A callout, and the `live` that decides whether anybody is interrupted by it.
1720
+ *
1721
+ * <Alert.Root>
1722
+ * <Alert.Title>Your trial ends on Friday</Alert.Title>
1723
+ * <Alert.Description>Add a card to keep your projects.</Alert.Description>
1724
+ * </Alert.Root>
1725
+ *
1726
+ * {error != null && (
1727
+ * <Alert.Root live>
1728
+ * <Alert.Title>Could not save</Alert.Title>
1729
+ * <Alert.Description>{error}</Alert.Description>
1730
+ * </Alert.Root>
1731
+ * )}
1732
+ *
1733
+ * The first has no role at all: it was there when the page loaded, so a live
1734
+ * region would announce it on every load or never, and neither is what anybody
1735
+ * wanted. The second appeared because something happened, which is what
1736
+ * `role="alert"` is for. `alert.js`'s header says why there is no polite
1737
+ * version of this and why `Toast` is that instead.
1738
+ */
1739
+ export const Alert = {
1740
+ Root: AlertRoot,
1741
+ Title: AlertTitle,
1742
+ Description: AlertDescription,
1743
+ };
1744
+
1745
+ /**
1746
+ * A picture of a person, and the two states it is not in yet.
1747
+ *
1748
+ * <Avatar.Root>
1749
+ * <Avatar.Image src={person.photo} />
1750
+ * <Avatar.Fallback>{initials(person.name)}</Avatar.Fallback>
1751
+ * </Avatar.Root>
1752
+ *
1753
+ * The fallback is absent while the image is loading and present once it has
1754
+ * failed, held back long enough that a cached image never flashes initials.
1755
+ * `alt` defaults to `""`, because an avatar beside the name it belongs to is
1756
+ * decorative and a component that helpfully puts the name there makes every
1757
+ * screen reader say it twice; pass `alt` where the picture is the only thing
1758
+ * identifying the person.
1759
+ */
1760
+ export const Avatar = {
1761
+ Root: AvatarRoot,
1762
+ Image: AvatarImage,
1763
+ Fallback: AvatarFallback,
1764
+ };
1765
+
1766
+ /**
1767
+ * The grey boxes, and the sentence that stops them being an empty page.
1768
+ *
1769
+ * <Skeleton.Root busy={pending}>
1770
+ * {pending ? <Skeleton.Box /> : <Invoices rows={invoices} />}
1771
+ * </Skeleton.Root>
1772
+ *
1773
+ * The boxes are `aria-hidden`, the region is `aria-busy`, and a live region
1774
+ * that was mounted empty for a commit says "Loading" — a skeleton screen is
1775
+ * busy on its first render, so a region rendered with its message already in it
1776
+ * announces nothing at all. Keep the root mounted across the load and toggle
1777
+ * `busy`; unmounting it takes the region away before it can say the wait is
1778
+ * over.
1779
+ */
1780
+ export const Skeleton = {
1781
+ Root: SkeletonRoot,
1782
+ Box: SkeletonBox,
1783
+ };
1784
+
1785
+ export { I18nProvider, useLocale, useCollator, useFilter } from "./i18n-provider.js";
1786
+ import {
1787
+ NumberFieldRoot,
1788
+ NumberFieldInput,
1789
+ NumberFieldIncrement,
1790
+ NumberFieldDecrement,
1791
+ } from "./number-field.js";
1792
+ export const NumberField = {
1793
+ Root: NumberFieldRoot,
1794
+ Input: NumberFieldInput,
1795
+ Increment: NumberFieldIncrement,
1796
+ Decrement: NumberFieldDecrement,
1797
+ };
1798
+
1799
+ export { ListBox } from "./list-box.js";
1800
+ export { GridList } from "./grid-list.js";
1801
+ export { Tree } from "./tree.js";
1802
+ export { TagGroup } from "./tag-group.js";
1803
+ export type { CollectionItem, CollectionItemState, CollectionProps } from "./list-box.js";
1804
+
1805
+ export type { Drop, DragAndDrop } from "./drag-drop.js";
1806
+ export { useDragAndDrop } from "./drag-drop.js";
1807
+ import {
1808
+ ColorPickerRoot,
1809
+ ColorPickerInput,
1810
+ ColorPickerField,
1811
+ ColorPickerChannel,
1812
+ ColorPickerSwatch,
1813
+ } from "./color-picker.js";
1814
+ export const ColorPicker = {
1815
+ Root: ColorPickerRoot,
1816
+ Input: ColorPickerInput,
1817
+ Field: ColorPickerField,
1818
+ Channel: ColorPickerChannel,
1819
+ Swatch: ColorPickerSwatch,
1820
+ };
1821
+
1822
+ export { DateField } from "./date-field.js";
1823
+ export { TimeField } from "./time-field.js";
1824
+ export type { DateFieldProps } from "./date-field.js";
1825
+ import { RangeCalendarRoot } from "./range-calendar.js";
1826
+ import {
1827
+ DateRangePickerRoot,
1828
+ DateRangePickerStartField,
1829
+ DateRangePickerEndField,
1830
+ DateRangePickerTrigger,
1831
+ DateRangePickerCalendar,
1832
+ } from "./date-range-picker.js";
1833
+ export type { DateRange } from "./range-calendar.js";
1834
+ export const RangeCalendar = {
1835
+ Root: RangeCalendarRoot,
1836
+ Month: CalendarMonth,
1837
+ Day: CalendarDay,
1838
+ Previous: CalendarPrevious,
1839
+ Next: CalendarNext,
1840
+ };
1841
+ export const DateRangePicker = {
1842
+ Root: DateRangePickerRoot,
1843
+ StartField: DateRangePickerStartField,
1844
+ EndField: DateRangePickerEndField,
1845
+ Trigger: DateRangePickerTrigger,
1846
+ Calendar: DateRangePickerCalendar,
1847
+ };
1848
+
1849
+ export {
1850
+ NumberFieldRoot,
1851
+ NumberFieldInput,
1852
+ NumberFieldIncrement,
1853
+ NumberFieldDecrement,
1854
+ parseNumber,
1855
+ } from "./number-field.js";
1856
+ export type { NumberFormatOptions } from "./number-field.js";
1857
+ export {
1858
+ ColorPickerRoot,
1859
+ ColorPickerInput,
1860
+ ColorPickerField,
1861
+ ColorPickerChannel,
1862
+ ColorPickerSwatch,
1863
+ parseColor,
1864
+ } from "./color-picker.js";
1865
+ export { RangeCalendarRoot } from "./range-calendar.js";
1866
+ export {
1867
+ DateRangePickerRoot,
1868
+ DateRangePickerStartField,
1869
+ DateRangePickerEndField,
1870
+ DateRangePickerTrigger,
1871
+ DateRangePickerCalendar,
1872
+ } from "./date-range-picker.js";
1873
+ export type { Locale } from "./i18n-provider.js";
1874
+ export { startsWithLocale } from "./i18n-provider.js";
1875
+
1876
+ /**
1877
+ * For a screen reader and nobody else.
1878
+ *
1879
+ * `VisuallyHidden` is text that stays in the accessibility tree and off the
1880
+ * screen — an icon button's name, a skip link (`focusable`) that appears when a
1881
+ * keyboard reaches it. `announce` says something through one pair of live
1882
+ * regions shared by the whole document, from an event handler, an effect or a
1883
+ * `catch`, without the caller rendering a region of their own:
1884
+ *
1885
+ * announce(`${results.length} results`);
1886
+ * announce("Could not save", { politeness: "assertive" });
1887
+ *
1888
+ * `visually-hidden.js` says why a region rendered with its message is silent,
1889
+ * and why these regions stay readable while a dialog hides the rest of the page.
1890
+ */
1891
+ export { VisuallyHidden, announce, clearAnnouncements } from "./visually-hidden.js";
1892
+ export type { AnnounceOptions, Politeness } from "./visually-hidden.js";