@uniflowed/ui 0.0.0-alpha.18 → 0.0.0-alpha.37

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.
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
@@ -76,14 +175,33 @@
76
175
  // are `Declined` with the preset functions that replace them named on each,
77
176
  // and `cargo test -p uf_lib` fails if a name there stops existing.
78
177
  //
79
- // Five of that twenty are not presentational and are missing rather than
80
- // declined — Alert, Avatar, Breadcrumb, Separator and Skeleton — each for one
81
- // specific reason, and the registry entry for each says which. The shortest is
82
- // Alert: `role="alert"` is a live region, an element already in the document
83
- // when the page loads announces on insertion or not at all, and a permanently
84
- // rendered "your trial ends soon" box carrying that role is either an
85
- // interruption on every page load or silence. `field.js` already makes that
86
- // call correctly for `Field.Error`.
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.
87
205
  //
88
206
  // # What these components promise React
89
207
  //
@@ -103,20 +221,9 @@
103
221
  //
104
222
  // One component does use it, and it is the case the API is actually for.
105
223
  // `toast("Saved")` is called from an event handler or a `catch`, so the queue
106
- // of notifications lives outside React — a store at module scope in
107
- // `toast.js`, read through `useSyncExternalStore` with the cached immutable
108
- // snapshots and the consistent server snapshot that requires. A queue is a
109
- // store; the DOM is not.
110
- //
111
- // That store should be an atom in `@uniflowed/state`, and was one. It is
112
- // written out by hand in `toast.js` because `@uniflowed/ui` is published to
113
- // npm and `@uniflowed/state` is not: its name has never been bound, and
114
- // binding it takes a person with an `npm login` session and a 2FA prompt —
115
- // ubugeeei-prod/uf#210. A published package whose dependency is missing
116
- // installs as nothing, `ETARGET` on the first thing a user types, so this
117
- // package cannot declare that dependency until the name exists. The queue
118
- // moves back to `@uniflowed/state` when #210 binds it. The local store is the
119
- // 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.
120
227
  //
121
228
  // # Server and client
122
229
  //
@@ -184,11 +291,23 @@
184
291
  // a dialog that is not modal, a tooltip describes its trigger and may never
185
292
  // take focus, a hover card is neither and holds links — and because a flag
186
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.
187
306
  //
188
- // Every name below is exported from one of those, so a consumer may import
189
- // `@uniflowed/ui` or `@uniflowed/ui/dialog` and get the same thing. The split
190
- // is by primitive because that is the unit a reader looks for, the unit a
191
- // 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.
192
311
  //
193
312
  // `internal/` holds ten modules and nothing else, each a rule the primitives
194
313
  // must apply identically and a consumer must not be able to apply differently:
@@ -217,6 +336,7 @@ import {
217
336
  AccordionRoot,
218
337
  AccordionTrigger,
219
338
  } from "./accordion.js";
339
+ import { AlertDescription, AlertRoot, AlertTitle } from "./alert.js";
220
340
  import {
221
341
  AlertDialogAction,
222
342
  AlertDialogBody,
@@ -229,6 +349,15 @@ import {
229
349
  AlertDialogTitle,
230
350
  AlertDialogTrigger,
231
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";
232
361
  import {
233
362
  CarouselContent,
234
363
  CarouselItem,
@@ -287,7 +416,14 @@ import {
287
416
  DrawerTitle,
288
417
  DrawerTrigger,
289
418
  } from "./drawer.js";
290
- import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from "./field.js";
419
+ import {
420
+ FieldControl,
421
+ FieldDescription,
422
+ FieldError,
423
+ FieldLabel,
424
+ FieldRoot,
425
+ FieldStatus,
426
+ } from "./field.js";
291
427
  import { HoverCardBody, HoverCardRoot, HoverCardTrigger } from "./hover-card.js";
292
428
  import { InputOtpGroup, InputOtpRoot, InputOtpSeparator, InputOtpSlot } from "./input-otp.js";
293
429
  import {
@@ -336,6 +472,7 @@ import {
336
472
  SelectTrigger,
337
473
  SelectValue,
338
474
  } from "./select.js";
475
+ import { Separator } from "./separator.js";
339
476
  import {
340
477
  SheetBody,
341
478
  SheetClose,
@@ -355,6 +492,7 @@ import {
355
492
  SidebarRoot,
356
493
  SidebarTrigger,
357
494
  } from "./sidebar.js";
495
+ import { SkeletonBox, SkeletonRoot } from "./skeleton.js";
358
496
  import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "./slider.js";
359
497
  import { Switch } from "./switch.js";
360
498
  import {
@@ -387,6 +525,7 @@ import { ToggleGroupItem, ToggleGroupRoot } from "./toggle-group.js";
387
525
  import { TooltipBody, TooltipProvider, TooltipRoot, TooltipTrigger } from "./tooltip.js";
388
526
 
389
527
  export type { AccordionType } from "./accordion.js";
528
+ export type { AvatarStatus } from "./avatar.js";
390
529
  // A date, however a caller had one to hand: a `PlainDate` from
391
530
  // `@uniflowed/temporal`, or the ISO 8601 string a form field or a URL carries.
392
531
  export type { DateValue } from "./calendar.js";
@@ -397,7 +536,11 @@ export type { DialogRole } from "./dialog.js";
397
536
  // Which edge of the viewport a sheet or a drawer is attached to. `Sidebar` has
398
537
  // its own two-member union, because a sidebar is never on the top or bottom.
399
538
  export type { Edge } from "./sheet.js";
539
+ export type { FieldSource } from "./field.js";
400
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";
401
544
  export type { SidebarSide } from "./sidebar.js";
402
545
  // Where an anchored overlay opens, for a caller who holds one in a variable or
403
546
  // a prop of their own. Unions rather than strings, so `side="botom"` is a type
@@ -410,7 +553,212 @@ export type { Sort } from "./table.js";
410
553
  export type { Notification, ToastChanges, ToastOptions, Urgency } from "./toast.js";
411
554
  export type { ToggleGroupType } from "./toggle-group.js";
412
555
 
413
- 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 };
414
762
 
415
763
  /**
416
764
  * Queueing a notification, from anywhere.
@@ -425,6 +773,72 @@ export { Checkbox, Progress, Switch, Toggle };
425
773
  */
426
774
  export { dismissAllToasts, dismissToast, toast, updateToast };
427
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
+ useKeyboard,
803
+ useLongPress,
804
+ useMove,
805
+ usePress,
806
+ } from "./interactions.js";
807
+ export type {
808
+ FocusRingOptions,
809
+ FocusRingProps,
810
+ FocusRingResult,
811
+ FocusVisibleResult,
812
+ HoverEvent,
813
+ HoverOptions,
814
+ HoverProps,
815
+ HoverResult,
816
+ InteractionEvent,
817
+ InteractionProps,
818
+ KeyboardInteraction,
819
+ KeyboardOptions,
820
+ KeyboardProps,
821
+ KeyboardResult,
822
+ LongPressEvent,
823
+ LongPressOptions,
824
+ LongPressProps,
825
+ LongPressResult,
826
+ Modality,
827
+ MoveEndEvent,
828
+ MoveMoveEvent,
829
+ MoveOptions,
830
+ MovePointerType,
831
+ MoveProps,
832
+ MoveResult,
833
+ MoveStartEvent,
834
+ PhysicalPointer,
835
+ PointerType,
836
+ PressEvent,
837
+ PressOptions,
838
+ PressProps,
839
+ PressResult,
840
+ } from "./interactions.js";
841
+
428
842
  /**
429
843
  * An accessible form field.
430
844
  *
@@ -432,14 +846,16 @@ export { dismissAllToasts, dismissToast, toast, updateToast };
432
846
  * <Field.Label>Email</Field.Label>
433
847
  * <Field.Control render={(props) => <input type="email" {...props} />} />
434
848
  * <Field.Description>We will not share it.</Field.Description>
849
+ * <Field.Status>{saving ? "Saving…" : ""}</Field.Status>
435
850
  * <Field.Error>{error}</Field.Error>
436
851
  * </Field.Root>
437
852
  *
438
853
  * Inside a form, `field` replaces the hand-written `invalid`: the form says
439
854
  * whether the field is wrong and what the message is, and the field composes
440
- * every `aria-*` from that in one place. `@uniflowed/form`'s `useFieldSource`
441
- * is what produces one, and `field.js`'s header says why the hook lives there
442
- * rather than here.
855
+ * every `aria-*` from that in one place. It also marks the control busy during
856
+ * submit, validation, or async default loading. `@uniflowed/form`'s
857
+ * `useFieldSource` is what produces one, and `field.js`'s header says why the
858
+ * hook lives there rather than here.
443
859
  *
444
860
  * const email = useFieldSource(form, "email", { required: "We need one" });
445
861
  * <Field.Root field={email}>…<Field.Error /></Field.Root>
@@ -454,6 +870,7 @@ export const Field = {
454
870
  Label: FieldLabel,
455
871
  Control: FieldControl,
456
872
  Description: FieldDescription,
873
+ Status: FieldStatus,
457
874
  Error: FieldError,
458
875
  };
459
876
 
@@ -471,6 +888,11 @@ export const Field = {
471
888
  * <Tabs.Panel value="one">…</Tabs.Panel>
472
889
  * <Tabs.Panel value="two">…</Tabs.Panel>
473
890
  * </Tabs.Root>
891
+ *
892
+ * Every part takes `render`, so a tab that is also a route — `<Tabs.Tab
893
+ * render={(props) => <a href="#billing" {...props} />}>` — is still a tab, with
894
+ * the roving tab stop and the `aria-controls` a tab has. `Tabs.List`'s
895
+ * `renders* Tabs.Tab` is unaffected, because it is the *part* it constrains.
474
896
  */
475
897
  export const Tabs = {
476
898
  Root: TabsRoot,
@@ -603,6 +1025,12 @@ export const ToggleGroup = {
603
1025
  * </Dialog.Footer>
604
1026
  * </Dialog.Body>
605
1027
  * </Dialog.Root>
1028
+ *
1029
+ * Every part takes `render`. `Dialog.Title` is an `<h2>` by default and the
1030
+ * level is a fact about the page around it rather than about the dialog, so
1031
+ * `render={(props) => <h3 {...props} />}` is how a caller says which — without
1032
+ * losing the id `aria-labelledby` points at. `AlertDialog`, `Sheet` and
1033
+ * `Drawer` are made of these parts and pass `render` straight through.
606
1034
  */
607
1035
  export const Dialog = {
608
1036
  Root: DialogRoot,
@@ -828,6 +1256,17 @@ export const InputOtp = {
828
1256
  * </Menu.Sub>
829
1257
  * </Menu.Body>
830
1258
  * </Menu.Root>
1259
+ *
1260
+ * Every part takes `render`, which is what makes a menu of links possible — and
1261
+ * a menu of links is the most ordinary menu there is:
1262
+ *
1263
+ * <Menu.Item render={(props) => <a href="/settings" {...props} />}>
1264
+ * Settings
1265
+ * </Menu.Item>
1266
+ *
1267
+ * The `<a>` keeps the middle click, the context menu and the status bar; the
1268
+ * item keeps the role, the id, the roving tab stop and the press that closes
1269
+ * the tree. See the module header for why that is the answer to "no copy step".
831
1270
  */
832
1271
  export const Menu = {
833
1272
  Root: MenuRoot,
@@ -1242,3 +1681,100 @@ export const Pagination = {
1242
1681
  Previous: PaginationPrevious,
1243
1682
  Next: PaginationNext,
1244
1683
  };
1684
+
1685
+ /**
1686
+ * The trail above the page, read as places rather than as punctuation.
1687
+ *
1688
+ * <Breadcrumb.Root>
1689
+ * <Breadcrumb.List>
1690
+ * <Breadcrumb.Item>
1691
+ * <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
1692
+ * </Breadcrumb.Item>
1693
+ * <Breadcrumb.Separator>/</Breadcrumb.Separator>
1694
+ * <Breadcrumb.Item>
1695
+ * <Breadcrumb.Page>Billing</Breadcrumb.Page>
1696
+ * </Breadcrumb.Item>
1697
+ * </Breadcrumb.List>
1698
+ * </Breadcrumb.Root>
1699
+ *
1700
+ * `Pagination`'s shape one door along: a `<nav>` with a name, one
1701
+ * `aria-current="page"`, and the separators out of the accessibility tree so
1702
+ * the trail is not announced as "Home slash Settings slash Billing". The last
1703
+ * crumb is a `Breadcrumb.Page` and not a link, because it is where the reader
1704
+ * already is.
1705
+ */
1706
+ export const Breadcrumb = {
1707
+ Root: BreadcrumbRoot,
1708
+ List: BreadcrumbList,
1709
+ Item: BreadcrumbItem,
1710
+ Link: BreadcrumbLink,
1711
+ Page: BreadcrumbPage,
1712
+ Separator: BreadcrumbSeparator,
1713
+ };
1714
+
1715
+ /**
1716
+ * A callout, and the `live` that decides whether anybody is interrupted by it.
1717
+ *
1718
+ * <Alert.Root>
1719
+ * <Alert.Title>Your trial ends on Friday</Alert.Title>
1720
+ * <Alert.Description>Add a card to keep your projects.</Alert.Description>
1721
+ * </Alert.Root>
1722
+ *
1723
+ * {error != null && (
1724
+ * <Alert.Root live>
1725
+ * <Alert.Title>Could not save</Alert.Title>
1726
+ * <Alert.Description>{error}</Alert.Description>
1727
+ * </Alert.Root>
1728
+ * )}
1729
+ *
1730
+ * The first has no role at all: it was there when the page loaded, so a live
1731
+ * region would announce it on every load or never, and neither is what anybody
1732
+ * wanted. The second appeared because something happened, which is what
1733
+ * `role="alert"` is for. `alert.js`'s header says why there is no polite
1734
+ * version of this and why `Toast` is that instead.
1735
+ */
1736
+ export const Alert = {
1737
+ Root: AlertRoot,
1738
+ Title: AlertTitle,
1739
+ Description: AlertDescription,
1740
+ };
1741
+
1742
+ /**
1743
+ * A picture of a person, and the two states it is not in yet.
1744
+ *
1745
+ * <Avatar.Root>
1746
+ * <Avatar.Image src={person.photo} />
1747
+ * <Avatar.Fallback>{initials(person.name)}</Avatar.Fallback>
1748
+ * </Avatar.Root>
1749
+ *
1750
+ * The fallback is absent while the image is loading and present once it has
1751
+ * failed, held back long enough that a cached image never flashes initials.
1752
+ * `alt` defaults to `""`, because an avatar beside the name it belongs to is
1753
+ * decorative and a component that helpfully puts the name there makes every
1754
+ * screen reader say it twice; pass `alt` where the picture is the only thing
1755
+ * identifying the person.
1756
+ */
1757
+ export const Avatar = {
1758
+ Root: AvatarRoot,
1759
+ Image: AvatarImage,
1760
+ Fallback: AvatarFallback,
1761
+ };
1762
+
1763
+ /**
1764
+ * The grey boxes, and the sentence that stops them being an empty page.
1765
+ *
1766
+ * <Skeleton.Root busy={pending}>
1767
+ * {pending ? <Skeleton.Box /> : <Invoices rows={invoices} />}
1768
+ * </Skeleton.Root>
1769
+ *
1770
+ * The boxes are `aria-hidden`, the region is `aria-busy`, and a live region
1771
+ * that was mounted empty for a commit says "Loading" — a skeleton screen is
1772
+ * busy on its first render, so a region rendered with its message already in it
1773
+ * announces nothing at all. Keep the root mounted across the load and toggle
1774
+ * `busy`; unmounting it takes the region away before it can say the wait is
1775
+ * over.
1776
+ */
1777
+ export const Skeleton = {
1778
+ Root: SkeletonRoot,
1779
+ Box: SkeletonBox,
1780
+ };