@uniflowed/ui 0.2.0 → 0.3.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.
package/combobox.js CHANGED
@@ -210,7 +210,7 @@ const ComboboxGroupContext: React.Context<ComboboxGroupState | null> = createCon
210
210
  * `internal/form-value.js` says why it is an `<input>` and why
211
211
  * `@uniflowed/form` does not need it.
212
212
  */
213
- export component ComboboxRoot(
213
+ component ComboboxRoot(
214
214
  children: React.Node,
215
215
  value?: string | null,
216
216
  defaultValue?: string | null = null,
@@ -294,7 +294,7 @@ export component ComboboxRoot(
294
294
  * because the list names it, and naming a label that is not rendered is worse
295
295
  * than leaving the list unnamed.
296
296
  */
297
- export component ComboboxLabel(children: React.Node, ...rest: Rest) {
297
+ component ComboboxLabel(children: React.Node, ...rest: Rest) {
298
298
  const combobox = useCombobox("Combobox.Label");
299
299
  const register = combobox.registerLabel;
300
300
  useEffect(() => {
@@ -310,7 +310,7 @@ export component ComboboxLabel(children: React.Node, ...rest: Rest) {
310
310
  }
311
311
 
312
312
  /** The text field, and every key the pattern defines. */
313
- export component ComboboxInput(...rest: Rest) {
313
+ component ComboboxInput(...rest: Rest) {
314
314
  const combobox = useCombobox("Combobox.Input");
315
315
  // `rest` filtering is render-time props work; ref objects are only passed through later.
316
316
  // uf-lint-disable-next-line react-compiler/refs
@@ -442,7 +442,7 @@ export component ComboboxInput(...rest: Rest) {
442
442
  * the count the live region announces, and the invariant that
443
443
  * `aria-activedescendant` never names an option that has left the list.
444
444
  */
445
- export component ComboboxList(
445
+ component ComboboxList(
446
446
  children: renders* (ComboboxOption | ComboboxGroup),
447
447
  align?: Align = "start",
448
448
  alignOffset?: number = 0,
@@ -463,19 +463,13 @@ export component ComboboxList(
463
463
  // `align="start"` because a list of options belongs under the edge the text
464
464
  // starts at, and `--uf-anchor-trigger-width` is what a stylesheet reads to
465
465
  // make it exactly as wide as the field.
466
- // useAnchor accepts ref objects and reads them from layout/effects.
467
- // uf-lint-disable-next-line react-compiler/refs
468
466
  const anchored = useAnchor({
469
467
  align,
470
468
  alignOffset,
471
- // uf-lint-disable-next-line react-compiler/refs
472
469
  anchorRef: inputRef,
473
470
  avoidCollisions,
474
471
  collisionPadding,
475
- // `open` is combobox metadata; no ref value is read during render.
476
- // uf-lint-disable-next-line react-compiler/refs
477
472
  open: combobox.open,
478
- // uf-lint-disable-next-line react-compiler/refs
479
473
  overlayRef: listRef,
480
474
  side,
481
475
  sideOffset,
@@ -486,8 +480,6 @@ export component ComboboxList(
486
480
  // `children` that no dependency list can describe. Every write below is
487
481
  // guarded by a comparison, so the effect settles after one extra pass rather
488
482
  // than looping.
489
- // This effect measures caller-rendered options after commit.
490
- // uf-lint-disable-next-line react-compiler/immutability
491
483
  useEffect(() => {
492
484
  const list = listRef.current;
493
485
  if (list == null) {
@@ -505,7 +497,6 @@ export component ComboboxList(
505
497
 
506
498
  const wanted = pendingActiveRef.current;
507
499
  if (wanted != null) {
508
- // uf-lint-disable-next-line react-compiler/immutability
509
500
  pendingActiveRef.current = null;
510
501
  setActiveId(moveTo(items, -1, wanted, false)?.id ?? null);
511
502
  return;
@@ -562,7 +553,7 @@ export component ComboboxList(
562
553
  * finds the active option in the document rather than in a registry that could
563
554
  * disagree with it.
564
555
  */
565
- export component ComboboxOption(
556
+ component ComboboxOption(
566
557
  value: string,
567
558
  children: React.Node,
568
559
  label?: string,
@@ -629,10 +620,7 @@ export component ComboboxOption(
629
620
  * and nothing else. `Select.Group` says the same since ubugeeei-prod/uf#562 —
630
621
  * it is the same listbox, and it took a second breaking change to get there.
631
622
  */
632
- export component ComboboxGroup(
633
- children: renders* (ComboboxOption | ComboboxGroupLabel),
634
- ...rest: Rest
635
- ) {
623
+ component ComboboxGroup(children: renders* (ComboboxOption | ComboboxGroupLabel), ...rest: Rest) {
636
624
  const base = useId();
637
625
  const [labelled, setLabelled] = useState(false);
638
626
 
@@ -657,7 +645,7 @@ export component ComboboxGroup(
657
645
  * This is not `Combobox.Label`. That one names the field; this one names a
658
646
  * group of options, and a combobox with groups has both.
659
647
  */
660
- export component ComboboxGroupLabel(children: React.Node, ...rest: Rest) {
648
+ component ComboboxGroupLabel(children: React.Node, ...rest: Rest) {
661
649
  const group = useContext(ComboboxGroupContext);
662
650
  const register = group?.registerLabel;
663
651
 
@@ -683,7 +671,7 @@ export component ComboboxGroupLabel(children: React.Node, ...rest: Rest) {
683
671
  * contain options: an "no matches" row inside one is announced as an option a
684
672
  * reader can choose, and choosing it does nothing.
685
673
  */
686
- export component ComboboxEmpty(children: React.Node, ...rest: Rest) {
674
+ component ComboboxEmpty(children: React.Node, ...rest: Rest) {
687
675
  const combobox = useCombobox("Combobox.Empty");
688
676
  if (!combobox.open || combobox.count > 0) {
689
677
  return null;
@@ -702,7 +690,7 @@ export component ComboboxEmpty(children: React.Node, ...rest: Rest) {
702
690
  * `children` overrides the wording — the default is English and a real
703
691
  * application has a translation table.
704
692
  */
705
- export component ComboboxStatus(children?: React.Node, ...rest: Rest) {
693
+ component ComboboxStatus(children?: React.Node, ...rest: Rest) {
706
694
  const combobox = useCombobox("Combobox.Status");
707
695
  const message = children ?? defaultAnnouncement(combobox.open, combobox.count);
708
696
 
@@ -732,3 +720,24 @@ function labelOf(element: HTMLElement): string {
732
720
  function textOf(element: HTMLElement): string {
733
721
  return (element.textContent ?? "").replace(/\s+/g, " ").trim();
734
722
  }
723
+
724
+ /**
725
+ * The parts, under the names the `Combobox` namespace gives them.
726
+ *
727
+ * `index.js` re-exports this module whole — `export * as Combobox from "./combobox.js"` —
728
+ * so a caller writes `<Combobox.Root>`, and the namespace is the prefix. Each
729
+ * part is still *declared* as `ComboboxRoot`, so React DevTools, a component
730
+ * stack and an error name the part a reader can find rather than one of forty
731
+ * `Root`s.
732
+ */
733
+ export {
734
+ ComboboxRoot as Root,
735
+ ComboboxLabel as Label,
736
+ ComboboxInput as Input,
737
+ ComboboxList as List,
738
+ ComboboxOption as Option,
739
+ ComboboxGroup as Group,
740
+ ComboboxGroupLabel as GroupLabel,
741
+ ComboboxEmpty as Empty,
742
+ ComboboxStatus as Status,
743
+ };
package/context-menu.js CHANGED
@@ -91,7 +91,7 @@ function pointAt(x: number, y: number): Rect {
91
91
  * Renders no element of its own, for the reason `Menu.Root` gives: the trigger
92
92
  * and the body are siblings in whatever layout the caller wrote.
93
93
  */
94
- export component ContextMenuRoot(
94
+ component ContextMenuRoot(
95
95
  children: React.Node,
96
96
  defaultOpen?: boolean = false,
97
97
  open?: boolean,
@@ -131,7 +131,7 @@ hook usePoint(part: string): PointState {
131
131
  * region of the page. The module header says why it is in the tab order and
132
132
  * when a caller should take it out again.
133
133
  */
134
- export component ContextMenuTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
134
+ component ContextMenuTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
135
135
  const menu = useMenu("ContextMenu.Trigger");
136
136
  const { openAt } = usePoint("ContextMenu.Trigger");
137
137
  const triggerRef = useRef<HTMLElement | null>(null);
@@ -213,3 +213,32 @@ export component ContextMenuTrigger(children: React.Node, render?: RenderProp, .
213
213
  }
214
214
 
215
215
  export type { MenuSelect } from "./menu.js";
216
+
217
+ /**
218
+ * The parts, under the names the `ContextMenu` namespace gives them.
219
+ *
220
+ * `index.js` re-exports this module whole — `export * as ContextMenu from "./context-menu.js"` —
221
+ * so a caller writes `<ContextMenu.Root>`, and the namespace is the prefix. Each
222
+ * part is still *declared* as `ContextMenuRoot`, so React DevTools, a component
223
+ * stack and an error name the part a reader can find rather than one of forty
224
+ * `Root`s.
225
+ */
226
+ export { ContextMenuRoot as Root, ContextMenuTrigger as Trigger };
227
+
228
+ /**
229
+ * The parts `ContextMenu` shares with `Menu`, re-exported so the `ContextMenu`
230
+ * namespace is complete on its own: `<ContextMenu.Body>` is `Menu`'s part,
231
+ * not a copy of it.
232
+ */
233
+ export {
234
+ Body,
235
+ Item,
236
+ CheckboxItem,
237
+ RadioGroup,
238
+ RadioItem,
239
+ Separator,
240
+ Group,
241
+ Label,
242
+ Sub,
243
+ SubTrigger,
244
+ } from "./menu.js";
package/date-picker.js CHANGED
@@ -51,17 +51,17 @@ import type { PlainDate } from "@uniflowed/core/temporal";
51
51
  import { Temporal } from "@uniflowed/core/temporal";
52
52
 
53
53
  import type { DateValue } from "./calendar.js";
54
- import { CalendarRoot } from "./calendar.js";
54
+ import { Root as CalendarRoot } from "./calendar.js";
55
55
  import { useControlled } from "./internal/controlled-state.js";
56
56
  import type { Align, LogicalSide } from "./internal/anchor.js";
57
- import type { Rest } from "./internal/merge-props.js";
57
+ import type { PartEvent, Rest } from "./internal/merge-props.js";
58
58
  import {
59
59
  composeHandlers,
60
60
  composeRefs,
61
61
  forwarded,
62
62
  withoutComposed,
63
63
  } from "./internal/merge-props.js";
64
- import { PopoverBody, PopoverRoot, PopoverTrigger } from "./popover.js";
64
+ import { Body as PopoverBody, Root as PopoverRoot, Trigger as PopoverTrigger } from "./popover.js";
65
65
 
66
66
  type DatePickerState = {|
67
67
  /** The text in the field, which is the draft while one is being typed. */
@@ -118,7 +118,7 @@ function isoParse(text: string): PlainDate | null {
118
118
  * siblings in whatever layout the caller wrote, and a wrapper would put a
119
119
  * `<div>` between them that they then have to style around.
120
120
  */
121
- export component DatePickerRoot(
121
+ component DatePickerRoot(
122
122
  children: React.Node,
123
123
  defaultOpen?: boolean = false,
124
124
  defaultValue?: DateValue | null = null,
@@ -251,7 +251,17 @@ const CalendarSettings: React.Context<CalendarSettingsValue> = createContext({
251
251
  * does not open the popover, the button beside it does, and telling a reader the
252
252
  * field expands something would be a promise the field does not keep.
253
253
  */
254
- export component DatePickerInput(...rest: Rest) {
254
+ /**
255
+ * The text in the field an event came from.
256
+ *
257
+ * `currentTarget` is the `input` this part renders, so it is always an
258
+ * `HTMLInputElement`. Asked rather than cast, so that the check is the type.
259
+ */
260
+ function typedText(target: mixed): string {
261
+ return target instanceof HTMLInputElement ? target.value : "";
262
+ }
263
+
264
+ component DatePickerInput(...rest: Rest) {
255
265
  const picker = useDatePicker("DatePicker.Input");
256
266
  // `rest` filtering is render-time props work; ref objects are only passed through later.
257
267
  // uf-lint-disable-next-line react-compiler/refs
@@ -264,12 +274,12 @@ export component DatePickerInput(...rest: Rest) {
264
274
  // Input events commit typed text while focus stays on the field ref.
265
275
  // uf-lint-disable-next-line react-compiler/refs
266
276
  onBlur={composeHandlers(rest.onBlur, (event) => {
267
- picker.commit((event.currentTarget: $FlowFixMe).value);
277
+ picker.commit(typedText(event.currentTarget));
268
278
  })}
269
279
  // Input events commit typed text while focus stays on the field ref.
270
280
  // uf-lint-disable-next-line react-compiler/refs
271
281
  onChange={composeHandlers(rest.onChange, (event) => {
272
- picker.setDraft((event.currentTarget: $FlowFixMe).value);
282
+ picker.setDraft(typedText(event.currentTarget));
273
283
  })}
274
284
  // Key events commit typed text while focus stays on the field ref.
275
285
  // uf-lint-disable-next-line react-compiler/refs
@@ -280,7 +290,7 @@ export component DatePickerInput(...rest: Rest) {
280
290
  // Claimed, so a date picker inside a form is not a control where
281
291
  // pressing Enter to confirm what you typed submits the page instead.
282
292
  event.preventDefault();
283
- picker.commit((event.currentTarget: $FlowFixMe).value);
293
+ picker.commit(typedText(event.currentTarget));
284
294
  })}
285
295
  // React calls callback refs during commit; the calendar and picker read the field later.
286
296
  // uf-lint-disable-next-line react-compiler/refs
@@ -295,7 +305,7 @@ export component DatePickerInput(...rest: Rest) {
295
305
  }
296
306
 
297
307
  /** The button that opens the calendar. */
298
- export component DatePickerTrigger(children: React.Node, ...rest: Rest) {
308
+ component DatePickerTrigger(children: React.Node, ...rest: Rest) {
299
309
  // `forwarded`, because this part renders another part rather than an
300
310
  // intrinsic; `internal/merge-props.js` says what that costs and why.
301
311
  return <PopoverTrigger {...forwarded(rest)}>{children}</PopoverTrigger>;
@@ -308,7 +318,7 @@ export component DatePickerTrigger(children: React.Node, ...rest: Rest) {
308
318
  * `Calendar.Month` — because where those sit is a design decision and there is
309
319
  * no arrangement of them this module could impose that would suit every one.
310
320
  */
311
- export component DatePickerCalendar(
321
+ component DatePickerCalendar(
312
322
  children: React.Node,
313
323
  align?: Align = "start",
314
324
  side?: LogicalSide = "bottom",
@@ -327,7 +337,7 @@ export component DatePickerCalendar(
327
337
  // The date, not the first button in the popover. The APG's date picker
328
338
  // dialog puts focus on the grid for the same reason.
329
339
  initialFocus={dayRef}
330
- onKeyDown={composeHandlers(rest.onKeyDown, (event) => {
340
+ onKeyDown={composeHandlers(rest.onKeyDown, (event: PartEvent) => {
331
341
  if (event.key !== "Escape") {
332
342
  return;
333
343
  }
@@ -355,3 +365,19 @@ export component DatePickerCalendar(
355
365
  </PopoverBody>
356
366
  );
357
367
  }
368
+
369
+ /**
370
+ * The parts, under the names the `DatePicker` namespace gives them.
371
+ *
372
+ * `index.js` re-exports this module whole — `export * as DatePicker from "./date-picker.js"` —
373
+ * so a caller writes `<DatePicker.Root>`, and the namespace is the prefix. Each
374
+ * part is still *declared* as `DatePickerRoot`, so React DevTools, a component
375
+ * stack and an error name the part a reader can find rather than one of forty
376
+ * `Root`s.
377
+ */
378
+ export {
379
+ DatePickerRoot as Root,
380
+ DatePickerInput as Input,
381
+ DatePickerTrigger as Trigger,
382
+ DatePickerCalendar as Calendar,
383
+ };
@@ -8,10 +8,10 @@ import type { Rest } from "./internal/merge-props.js";
8
8
  import { forwarded } from "./internal/merge-props.js";
9
9
  import type { DateRange } from "./internal/date-range.js";
10
10
  import { validateRange, unavailableInRange } from "./internal/date-range.js";
11
- import { CalendarMonth } from "./calendar.js";
12
- import { RangeCalendarRoot } from "./range-calendar.js";
11
+ import { Month as CalendarMonth } from "./calendar.js";
12
+ import { Root as RangeCalendarRoot } from "./range-calendar.js";
13
13
  import { DateField } from "./date-field.js";
14
- import { PopoverRoot, PopoverBody, PopoverTrigger } from "./popover.js";
14
+ import { Root as PopoverRoot, Body as PopoverBody, Trigger as PopoverTrigger } from "./popover.js";
15
15
 
16
16
  type Picker = {
17
17
  range: DateRange | null,
@@ -27,7 +27,7 @@ hook usePicker(): Picker {
27
27
  if (picker == null) throw new Error("DateRangePicker parts must be inside DateRangePicker.Root");
28
28
  return picker;
29
29
  }
30
- export component DateRangePickerRoot(
30
+ component DateRangePickerRoot(
31
31
  children: React.Node,
32
32
  value?: DateRange | null,
33
33
  defaultValue?: DateRange | null = null,
@@ -58,7 +58,7 @@ export component DateRangePickerRoot(
58
58
  </PickerContext.Provider>
59
59
  );
60
60
  }
61
- export component DateRangePickerStartField(...rest: Rest) {
61
+ component DateRangePickerStartField(...rest: Rest) {
62
62
  const picker = usePicker();
63
63
  return (
64
64
  <DateField
@@ -76,7 +76,7 @@ export component DateRangePickerStartField(...rest: Rest) {
76
76
  />
77
77
  );
78
78
  }
79
- export component DateRangePickerEndField(...rest: Rest) {
79
+ component DateRangePickerEndField(...rest: Rest) {
80
80
  const picker = usePicker();
81
81
  return (
82
82
  <DateField
@@ -94,10 +94,10 @@ export component DateRangePickerEndField(...rest: Rest) {
94
94
  />
95
95
  );
96
96
  }
97
- export component DateRangePickerTrigger(children: React.Node, ...rest: Rest) {
97
+ component DateRangePickerTrigger(children: React.Node, ...rest: Rest) {
98
98
  return <PopoverTrigger {...forwarded(rest)}>{children}</PopoverTrigger>;
99
99
  }
100
- export component DateRangePickerCalendar(children?: React.Node = <CalendarMonth />, ...rest: Rest) {
100
+ component DateRangePickerCalendar(children?: React.Node = <CalendarMonth />, ...rest: Rest) {
101
101
  const picker = usePicker();
102
102
  const dayRef = useRef<HTMLElement | null>(null);
103
103
  return (
@@ -118,3 +118,20 @@ export component DateRangePickerCalendar(children?: React.Node = <CalendarMonth
118
118
  </PopoverBody>
119
119
  );
120
120
  }
121
+
122
+ /**
123
+ * The parts, under the names the `DateRangePicker` namespace gives them.
124
+ *
125
+ * `index.js` re-exports this module whole — `export * as DateRangePicker from "./date-range-picker.js"` —
126
+ * so a caller writes `<DateRangePicker.Root>`, and the namespace is the prefix. Each
127
+ * part is still *declared* as `DateRangePickerRoot`, so React DevTools, a component
128
+ * stack and an error name the part a reader can find rather than one of forty
129
+ * `Root`s.
130
+ */
131
+ export {
132
+ DateRangePickerRoot as Root,
133
+ DateRangePickerStartField as StartField,
134
+ DateRangePickerEndField as EndField,
135
+ DateRangePickerTrigger as Trigger,
136
+ DateRangePickerCalendar as Calendar,
137
+ };
package/dialog.js CHANGED
@@ -133,7 +133,7 @@ hook useDialog(part: string): DialogState {
133
133
  }
134
134
 
135
135
  /** The dialog, open or closed. Uncontrolled unless `open` is given. */
136
- export component DialogRoot(
136
+ component DialogRoot(
137
137
  children: React.Node,
138
138
  defaultOpen?: boolean = false,
139
139
  open?: boolean,
@@ -170,7 +170,7 @@ export component DialogRoot(
170
170
  * else, which is what keeps "focus comes back here" true of whatever the
171
171
  * caller rendered.
172
172
  */
173
- export component DialogTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
173
+ component DialogTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
174
174
  const dialog = useDialog("Dialog.Trigger");
175
175
  const props = withProps(withoutComposed(rest, ["onClick", "ref"]), {
176
176
  // Only while it is open. An `aria-controls` naming an element that is not
@@ -203,7 +203,7 @@ export component DialogTrigger(children: React.Node, render?: RenderProp, ...res
203
203
  * their own backdrop or omits one entirely must still get it. That lives on
204
204
  * `Dialog.Body`, which is the part that knows where "outside" is.
205
205
  */
206
- export component DialogOverlay(render?: RenderProp, ...rest: Rest) {
206
+ component DialogOverlay(render?: RenderProp, ...rest: Rest) {
207
207
  const dialog = useDialog("Dialog.Overlay");
208
208
  if (!dialog.open) {
209
209
  return null;
@@ -222,7 +222,7 @@ export component DialogOverlay(render?: RenderProp, ...rest: Rest) {
222
222
  * which is the half of "modal" that CSS cannot express; `inert` on everything
223
223
  * outside is the half the browser enforces.
224
224
  */
225
- export component DialogBody(
225
+ component DialogBody(
226
226
  children: React.Node,
227
227
  dismissOnOutsidePress?: boolean = true,
228
228
  initialFocus?: { current: HTMLElement | null },
@@ -381,7 +381,7 @@ export component DialogBody(
381
381
  * rendered — a conditional title that is absent used to leave the dialog
382
382
  * pointing at an id nothing had.
383
383
  */
384
- export component DialogTitle(children: React.Node, render?: RenderProp, ...rest: Rest) {
384
+ component DialogTitle(children: React.Node, render?: RenderProp, ...rest: Rest) {
385
385
  const dialog = useDialog("Dialog.Title");
386
386
  const register = dialog.registerTitle;
387
387
  useEffect(() => {
@@ -407,7 +407,7 @@ export component DialogTitle(children: React.Node, render?: RenderProp, ...rest:
407
407
  * the one moment the reader has to decide whether they care — so this is where
408
408
  * "this cannot be undone" belongs, not in body text further down.
409
409
  */
410
- export component DialogDescription(children: React.Node, render?: RenderProp, ...rest: Rest) {
410
+ component DialogDescription(children: React.Node, render?: RenderProp, ...rest: Rest) {
411
411
  const dialog = useDialog("Dialog.Description");
412
412
  const register = dialog.registerDescription;
413
413
  useEffect(() => {
@@ -431,7 +431,7 @@ export component DialogDescription(children: React.Node, render?: RenderProp, ..
431
431
  * styling layer has a name to attach to, and contributes no semantics because
432
432
  * it has none to contribute.
433
433
  */
434
- export component DialogHeader(children: React.Node, render?: RenderProp, ...rest: Rest) {
434
+ component DialogHeader(children: React.Node, render?: RenderProp, ...rest: Rest) {
435
435
  const props = withProps(rest, { children });
436
436
  if (render != null) {
437
437
  return render(props);
@@ -440,7 +440,7 @@ export component DialogHeader(children: React.Node, render?: RenderProp, ...rest
440
440
  }
441
441
 
442
442
  /** The bottom of the dialog, where the actions go. See `Dialog.Header`. */
443
- export component DialogFooter(children: React.Node, render?: RenderProp, ...rest: Rest) {
443
+ component DialogFooter(children: React.Node, render?: RenderProp, ...rest: Rest) {
444
444
  const props = withProps(rest, { children });
445
445
  if (render != null) {
446
446
  return render(props);
@@ -449,7 +449,7 @@ export component DialogFooter(children: React.Node, render?: RenderProp, ...rest
449
449
  }
450
450
 
451
451
  /** A button that closes the dialog. */
452
- export component DialogClose(children: React.Node, render?: RenderProp, ...rest: Rest) {
452
+ component DialogClose(children: React.Node, render?: RenderProp, ...rest: Rest) {
453
453
  const dialog = useDialog("Dialog.Close");
454
454
  const props = withProps(withoutComposed(rest, ["onClick"]), {
455
455
  children,
@@ -500,7 +500,7 @@ function concealOutside(element: HTMLElement): () => void {
500
500
  }
501
501
  restore.push({
502
502
  element: sibling,
503
- hidden: sibling.getAttribute("aria-hidden"),
503
+ hidden: sibling.getAttribute("aria-hidden") ?? null,
504
504
  inert: sibling.hasAttribute("inert"),
505
505
  });
506
506
  sibling.setAttribute("aria-hidden", "true");
@@ -525,3 +525,24 @@ function concealOutside(element: HTMLElement): () => void {
525
525
  }
526
526
  };
527
527
  }
528
+
529
+ /**
530
+ * The parts, under the names the `Dialog` namespace gives them.
531
+ *
532
+ * `index.js` re-exports this module whole — `export * as Dialog from "./dialog.js"` —
533
+ * so a caller writes `<Dialog.Root>`, and the namespace is the prefix. Each
534
+ * part is still *declared* as `DialogRoot`, so React DevTools, a component
535
+ * stack and an error name the part a reader can find rather than one of forty
536
+ * `Root`s.
537
+ */
538
+ export {
539
+ DialogRoot as Root,
540
+ DialogTrigger as Trigger,
541
+ DialogOverlay as Overlay,
542
+ DialogBody as Body,
543
+ DialogHeader as Header,
544
+ DialogFooter as Footer,
545
+ DialogTitle as Title,
546
+ DialogDescription as Description,
547
+ DialogClose as Close,
548
+ };
package/drawer.js CHANGED
@@ -62,15 +62,15 @@ import {
62
62
  withoutComposed,
63
63
  } from "./internal/merge-props.js";
64
64
  import {
65
- SheetBody,
66
- SheetClose,
67
- SheetDescription,
68
- SheetFooter,
69
- SheetHeader,
70
- SheetOverlay,
71
- SheetRoot,
72
- SheetTitle,
73
- SheetTrigger,
65
+ Body as SheetBody,
66
+ Close as SheetClose,
67
+ Description as SheetDescription,
68
+ Footer as SheetFooter,
69
+ Header as SheetHeader,
70
+ Overlay as SheetOverlay,
71
+ Root as SheetRoot,
72
+ Title as SheetTitle,
73
+ Trigger as SheetTrigger,
74
74
  } from "./sheet.js";
75
75
  import { useControlled } from "./internal/controlled-state.js";
76
76
 
@@ -132,7 +132,7 @@ hook useDrawer(part: string): DrawerState {
132
132
  * `onOpenChange` behave exactly as they do everywhere else in this package,
133
133
  * because `internal/controlled-state.js` is what answers here too.
134
134
  */
135
- export component DrawerRoot(
135
+ component DrawerRoot(
136
136
  children: React.Node,
137
137
  defaultOpen?: boolean = false,
138
138
  defaultSnapPoint?: number = 0,
@@ -173,7 +173,7 @@ export component DrawerRoot(
173
173
  }
174
174
 
175
175
  /** What opens it, and what focus comes back to when it closes. */
176
- export component DrawerTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
176
+ component DrawerTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
177
177
  return (
178
178
  <SheetTrigger {...forwarded(rest)} render={render}>
179
179
  {children}
@@ -182,7 +182,7 @@ export component DrawerTrigger(children: React.Node, render?: RenderProp, ...res
182
182
  }
183
183
 
184
184
  /** The backdrop. It carries the edge, the same as a sheet's. */
185
- export component DrawerOverlay(render?: RenderProp, ...rest: Rest) {
185
+ component DrawerOverlay(render?: RenderProp, ...rest: Rest) {
186
186
  return <SheetOverlay {...forwarded(rest)} render={render} />;
187
187
  }
188
188
 
@@ -194,7 +194,7 @@ export component DrawerOverlay(render?: RenderProp, ...rest: Rest) {
194
194
  * no drag to provide an alternative to — and a drawer with a handle and no
195
195
  * `Drawer.Close` has a gesture that is the only way out.
196
196
  */
197
- export component DrawerBody(children: React.Node, render?: RenderProp, ...rest: Rest) {
197
+ component DrawerBody(children: React.Node, render?: RenderProp, ...rest: Rest) {
198
198
  const drawer = useDrawer("Drawer.Body");
199
199
  const { bodyRef, snapIndex, snapPoints } = drawer;
200
200
  const reducedMotion = usePrefersReducedMotion();
@@ -260,7 +260,7 @@ component RequireCloseForTheDrag() {
260
260
  }
261
261
 
262
262
  /** The top of the drawer, where the handle usually goes. */
263
- export component DrawerHeader(children: React.Node, render?: RenderProp, ...rest: Rest) {
263
+ component DrawerHeader(children: React.Node, render?: RenderProp, ...rest: Rest) {
264
264
  return (
265
265
  <SheetHeader {...forwarded(rest)} render={render}>
266
266
  {children}
@@ -269,7 +269,7 @@ export component DrawerHeader(children: React.Node, render?: RenderProp, ...rest
269
269
  }
270
270
 
271
271
  /** The bottom of the drawer, where the actions go. */
272
- export component DrawerFooter(children: React.Node, render?: RenderProp, ...rest: Rest) {
272
+ component DrawerFooter(children: React.Node, render?: RenderProp, ...rest: Rest) {
273
273
  return (
274
274
  <SheetFooter {...forwarded(rest)} render={render}>
275
275
  {children}
@@ -278,7 +278,7 @@ export component DrawerFooter(children: React.Node, render?: RenderProp, ...rest
278
278
  }
279
279
 
280
280
  /** The drawer's accessible name. */
281
- export component DrawerTitle(children: React.Node, render?: RenderProp, ...rest: Rest) {
281
+ component DrawerTitle(children: React.Node, render?: RenderProp, ...rest: Rest) {
282
282
  return (
283
283
  <SheetTitle {...forwarded(rest)} render={render}>
284
284
  {children}
@@ -287,7 +287,7 @@ export component DrawerTitle(children: React.Node, render?: RenderProp, ...rest:
287
287
  }
288
288
 
289
289
  /** What the drawer is for, announced after its name. */
290
- export component DrawerDescription(children: React.Node, render?: RenderProp, ...rest: Rest) {
290
+ component DrawerDescription(children: React.Node, render?: RenderProp, ...rest: Rest) {
291
291
  return (
292
292
  <SheetDescription {...forwarded(rest)} render={render}>
293
293
  {children}
@@ -301,7 +301,7 @@ export component DrawerDescription(children: React.Node, render?: RenderProp, ..
301
301
  *
302
302
  * It registers itself so `Drawer.Body` can tell whether the gesture has one.
303
303
  */
304
- export component DrawerClose(children: React.Node, render?: RenderProp, ...rest: Rest) {
304
+ component DrawerClose(children: React.Node, render?: RenderProp, ...rest: Rest) {
305
305
  const drawer = useDrawer("Drawer.Close");
306
306
  const closeCountRef = drawer.closeCountRef;
307
307
 
@@ -336,11 +336,7 @@ export component DrawerClose(children: React.Node, render?: RenderProp, ...rest:
336
336
  * `label` because a slider with no accessible name is announced as "slider",
337
337
  * which is the same failure `Resizable.Handle` names.
338
338
  */
339
- export component DrawerHandle(
340
- label?: string = "Resize the drawer",
341
- render?: RenderProp,
342
- ...rest: Rest
343
- ) {
339
+ component DrawerHandle(label?: string = "Resize the drawer", render?: RenderProp, ...rest: Rest) {
344
340
  const drawer = useDrawer("Drawer.Handle");
345
341
  const { bodyRef, close, handleCountRef, setSnapIndex, side, snapIndex, snapPoints } = drawer;
346
342
  const passed = withoutComposed(rest, [
@@ -359,17 +355,12 @@ export component DrawerHandle(
359
355
  // So `Drawer.Body` knows there is a drag to provide an alternative to. A
360
356
  // drawer with no handle has no gesture, and requiring a close button of one
361
357
  // would be this component inventing a rule WCAG did not write.
362
- useEffect(
363
- () => {
364
- handleCountRef.current += 1;
365
- return () => {
366
- handleCountRef.current -= 1;
367
- };
368
- },
369
- // The context ref object is stable; the effect registers this part's mount.
370
- // uf-lint-disable-next-line react-compiler/refs
371
- [handleCountRef],
372
- );
358
+ useEffect(() => {
359
+ handleCountRef.current += 1;
360
+ return () => {
361
+ handleCountRef.current -= 1;
362
+ };
363
+ }, [handleCountRef]);
373
364
 
374
365
  /** Move by one snap point, or close when there is no smaller one. */
375
366
  const step = (towardsOpen: boolean) => {
@@ -502,3 +493,25 @@ const CLOSES_WITH: { readonly [Edge]: string } = {
502
493
  right: "ArrowRight",
503
494
  top: "ArrowUp",
504
495
  };
496
+
497
+ /**
498
+ * The parts, under the names the `Drawer` namespace gives them.
499
+ *
500
+ * `index.js` re-exports this module whole — `export * as Drawer from "./drawer.js"` —
501
+ * so a caller writes `<Drawer.Root>`, and the namespace is the prefix. Each
502
+ * part is still *declared* as `DrawerRoot`, so React DevTools, a component
503
+ * stack and an error name the part a reader can find rather than one of forty
504
+ * `Root`s.
505
+ */
506
+ export {
507
+ DrawerRoot as Root,
508
+ DrawerTrigger as Trigger,
509
+ DrawerOverlay as Overlay,
510
+ DrawerBody as Body,
511
+ DrawerHandle as Handle,
512
+ DrawerHeader as Header,
513
+ DrawerFooter as Footer,
514
+ DrawerTitle as Title,
515
+ DrawerDescription as Description,
516
+ DrawerClose as Close,
517
+ };