@primitiv-ui/react 0.1.28 → 0.1.29
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/README.md +4 -0
- package/package.json +6 -1
- package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
- package/src/AccessibleIcon/types.ts +15 -2
- package/src/Accordion/Accordion.tsx +25 -4
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
- package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
- package/src/Accordion/types.ts +72 -21
- package/src/Alert/Alert.tsx +36 -7
- package/src/Alert/__tests__/Alert.test.tsx +5 -0
- package/src/Alert/types.ts +18 -2
- package/src/Avatar/Avatar.tsx +99 -15
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
- package/src/Avatar/hooks/useAvatarImage.ts +16 -0
- package/src/Avatar/types.ts +15 -3
- package/src/Breadcrumb/Breadcrumb.tsx +136 -18
- package/src/Breadcrumb/README.md +47 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
- package/src/Breadcrumb/types.ts +73 -7
- package/src/Button/Button.tsx +2 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
- package/src/Button/types.ts +21 -0
- package/src/Carousel/Carousel.tsx +263 -15
- package/src/Carousel/README.md +749 -72
- package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
- package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
- package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
- package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
- package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +88 -0
- package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
- package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
- package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
- package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
- package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
- package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
- package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
- package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
- package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
- package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
- package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
- package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
- package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
- package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
- package/src/Carousel/hooks/index.ts +1 -0
- package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
- package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
- package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
- package/src/Carousel/loopEngine.ts +54 -0
- package/src/Carousel/types.ts +457 -35
- package/src/Checkbox/Checkbox.tsx +9 -1
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
- package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
- package/src/Checkbox/types.ts +19 -0
- package/src/CheckboxCard/CheckboxCard.tsx +13 -4
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
- package/src/CheckboxCard/types.ts +65 -17
- package/src/Collapsible/Collapsible.tsx +49 -3
- package/src/Collapsible/README.md +40 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
- package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
- package/src/Collapsible/types.ts +89 -22
- package/src/ContextMenu/ContextMenu.tsx +270 -4
- package/src/ContextMenu/types.ts +148 -6
- package/src/DirectionProvider/DirectionProvider.tsx +41 -9
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
- package/src/DirectionProvider/types.ts +28 -3
- package/src/Divider/Divider.tsx +24 -10
- package/src/Divider/__tests__/Divider.test.tsx +5 -0
- package/src/Divider/types.ts +10 -3
- package/src/Drawer/Drawer.tsx +315 -0
- package/src/Drawer/README.md +124 -0
- package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
- package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
- package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
- package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
- package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
- package/src/Drawer/index.ts +2 -0
- package/src/Drawer/types.ts +66 -0
- package/src/Dropdown/Dropdown.tsx +117 -0
- package/src/Dropdown/types.ts +241 -11
- package/src/EmptyState/EmptyState.tsx +74 -4
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
- package/src/EmptyState/types.ts +49 -6
- package/src/Field/Field.tsx +23 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
- package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
- package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
- package/src/Field/types.ts +60 -12
- package/src/Fieldset/Fieldset.tsx +54 -18
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
- package/src/Fieldset/types.ts +20 -2
- package/src/Input/Input.tsx +14 -9
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
- package/src/Input/types.ts +19 -2
- package/src/InputGroup/InputGroup.tsx +11 -3
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
- package/src/InputGroup/types.ts +26 -5
- package/src/MillerColumns/MillerColumns.tsx +86 -19
- package/src/MillerColumns/types.ts +83 -27
- package/src/Modal/Modal.tsx +31 -4
- package/src/Modal/README.md +19 -10
- package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
- package/src/Modal/hooks/useModalContent.ts +32 -0
- package/src/Modal/types.ts +110 -29
- package/src/NavigationMenu/NavigationMenu.tsx +890 -0
- package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
- package/src/NavigationMenu/README.md +355 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
- package/src/NavigationMenu/constants.ts +5 -0
- package/src/NavigationMenu/hooks/index.ts +7 -0
- package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
- package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
- package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
- package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
- package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
- package/src/NavigationMenu/index.ts +3 -0
- package/src/NavigationMenu/types.ts +259 -0
- package/src/NavigationMenu/utils.ts +89 -0
- package/src/Popover/Popover.tsx +427 -0
- package/src/Popover/PopoverContext.ts +13 -0
- package/src/Popover/README.md +106 -0
- package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
- package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
- package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
- package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
- package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
- package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
- package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
- package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
- package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
- package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
- package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
- package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
- package/src/Popover/hooks/index.ts +3 -0
- package/src/Popover/hooks/usePopoverContent.ts +98 -0
- package/src/Popover/hooks/usePopoverRoot.ts +84 -0
- package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
- package/src/Popover/index.ts +2 -0
- package/src/Popover/types.ts +197 -0
- package/src/Portal/Portal.tsx +50 -10
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
- package/src/Portal/types.ts +5 -0
- package/src/Progress/Progress.tsx +33 -1
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
- package/src/Progress/types.ts +20 -4
- package/src/Radio/Radio.tsx +9 -1
- package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
- package/src/Radio/hooks/useRadioRoot.ts +3 -0
- package/src/Radio/types.ts +20 -0
- package/src/RadioCard/RadioCard.tsx +11 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
- package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
- package/src/RadioCard/types.ts +95 -22
- package/src/RadioGroup/RadioGroup.tsx +11 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
- package/src/RadioGroup/types.ts +73 -9
- package/src/SegmentedControl/README.md +154 -0
- package/src/SegmentedControl/SegmentedControl.tsx +309 -0
- package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
- package/src/SegmentedControl/hooks/index.ts +2 -0
- package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
- package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +86 -0
- package/src/SegmentedControl/index.ts +2 -0
- package/src/SegmentedControl/types.ts +138 -0
- package/src/Select/README.md +122 -157
- package/src/Select/Select.tsx +595 -68
- package/src/Select/SelectContext.ts +38 -0
- package/src/Select/SelectItemIndicatorContext.ts +29 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
- package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
- package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
- package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
- package/src/Select/__tests__/Select.form.test.tsx +38 -0
- package/src/Select/__tests__/Select.group.test.tsx +6 -6
- package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
- package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
- package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
- package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
- package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +4 -4
- package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
- package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
- package/src/Select/__tests__/Select.selection.test.tsx +104 -0
- package/src/Select/__tests__/Select.separator.test.tsx +71 -0
- package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
- package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
- package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
- package/src/Select/constants.ts +8 -0
- package/src/Select/hooks/index.ts +2 -0
- package/src/Select/hooks/useSelectContent.ts +149 -0
- package/src/Select/hooks/useSelectRoot.ts +149 -0
- package/src/Select/types.ts +199 -31
- package/src/SkipNav/SkipNav.tsx +69 -29
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
- package/src/SkipNav/types.ts +17 -9
- package/src/Slider/Slider.tsx +33 -1
- package/src/Slider/SliderContext.ts +28 -1
- package/src/Slider/types.ts +157 -9
- package/src/Slot/Slot.tsx +94 -19
- package/src/Slot/__tests__/Slot.test.tsx +110 -0
- package/src/Slot/composeEventHandlers.ts +26 -2
- package/src/Slot/types.ts +16 -0
- package/src/Status/Status.tsx +36 -15
- package/src/Status/__tests__/Status.test.tsx +5 -0
- package/src/Status/types.ts +12 -1
- package/src/Switch/Switch.tsx +15 -2
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
- package/src/Switch/hooks/useSwitchRoot.ts +3 -0
- package/src/Switch/types.ts +49 -22
- package/src/Table/Table.tsx +44 -10
- package/src/Table/types.ts +66 -17
- package/src/Tabs/Tabs.tsx +8 -0
- package/src/Tabs/types.ts +29 -4
- package/src/Textarea/Textarea.tsx +9 -1
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
- package/src/Textarea/types.ts +20 -3
- package/src/Toggle/Toggle.tsx +51 -13
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
- package/src/Toggle/types.ts +54 -4
- package/src/ToggleGroup/ToggleGroup.tsx +14 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
- package/src/ToggleGroup/types.ts +55 -2
- package/src/Tooltip/Tooltip.tsx +98 -20
- package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +13 -1
- package/src/Tooltip/types.ts +89 -8
- package/src/Tree/Tree.tsx +187 -33
- package/src/Tree/types.ts +54 -12
- package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
- package/src/VisuallyHidden/types.ts +11 -1
- package/src/index.ts +4 -0
- package/src/test/intersectionObserverPolyfill.ts +7 -0
- package/src/test/resizeObserverPolyfill.ts +80 -0
- package/src/utils/focusable.ts +15 -0
- package/src/utils/index.ts +1 -0
|
@@ -115,4 +115,9 @@ describe("Input basic rendering", () => {
|
|
|
115
115
|
// Assert
|
|
116
116
|
expect(onChange).toHaveBeenCalledTimes(2);
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
it('sets displayName to "Input" for React DevTools and stack traces', () => {
|
|
120
|
+
// Assert — an empty displayName would render it as an anonymous component.
|
|
121
|
+
expect(Input.displayName).toBe("Input");
|
|
122
|
+
});
|
|
118
123
|
});
|
package/src/Input/types.ts
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { ComponentProps, ReactNode, Ref } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Props for {@link Input} — all native `<input>` attributes
|
|
5
|
-
* `asChild` escape hatch and a typed `ref`.
|
|
4
|
+
* Props for {@link Input} — all native `<input>` attributes (with `type`
|
|
5
|
+
* defaulting to `"text"`) plus the `asChild` escape hatch and a typed `ref`.
|
|
6
6
|
*/
|
|
7
7
|
export type InputProps = ComponentProps<"input"> & {
|
|
8
|
+
/**
|
|
9
|
+
* Renders the child element instead of a native `<input>`, merging all
|
|
10
|
+
* props — `aria-*`, `data-*`, event handlers, `ref` — onto it via
|
|
11
|
+
* {@link Slot}. Event handlers compose (child runs first). `type` is
|
|
12
|
+
* not forwarded in this mode; the child owns its own type semantics.
|
|
13
|
+
* See the `asChild` example on {@link Input}.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
8
16
|
asChild?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Forwarded to the underlying `HTMLInputElement`. Under `asChild`,
|
|
19
|
+
* merged onto the rendered child via {@link Slot}.
|
|
20
|
+
*/
|
|
9
21
|
ref?: Ref<HTMLInputElement>;
|
|
22
|
+
/**
|
|
23
|
+
* Under `asChild`, becomes the single child element `Slot` merges props
|
|
24
|
+
* onto. Not rendered by the native `<input>` path (inputs are void
|
|
25
|
+
* elements in HTML).
|
|
26
|
+
*/
|
|
10
27
|
children?: ReactNode;
|
|
11
28
|
};
|
|
@@ -19,6 +19,8 @@ import { InputGroupAdornmentProps, InputGroupRootProps } from "./types";
|
|
|
19
19
|
* element instead of `<div>` — e.g. a `<label>` so a click anywhere on
|
|
20
20
|
* the frame focuses the wrapped input.
|
|
21
21
|
*
|
|
22
|
+
* @extends HTMLDivElement
|
|
23
|
+
*
|
|
22
24
|
* @example Basic frame with no adornments
|
|
23
25
|
* ```tsx
|
|
24
26
|
* <InputGroup>
|
|
@@ -56,6 +58,7 @@ export function InputGroupRoot({
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/** @internal */
|
|
61
|
+
// Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
|
|
59
62
|
InputGroupRoot.displayName = "InputGroupRoot";
|
|
60
63
|
|
|
61
64
|
/**
|
|
@@ -76,6 +79,8 @@ InputGroupRoot.displayName = "InputGroupRoot";
|
|
|
76
79
|
* search, opens a colour picker, or any other action. Event handlers
|
|
77
80
|
* compose (child runs first); refs forward to the consumer element.
|
|
78
81
|
*
|
|
82
|
+
* @extends HTMLSpanElement
|
|
83
|
+
*
|
|
79
84
|
* @example Decorative leading icon
|
|
80
85
|
* ```tsx
|
|
81
86
|
* <InputGroup.LeadingAdornment>
|
|
@@ -131,6 +136,8 @@ InputGroupLeadingAdornment.displayName = "InputGroupLeadingAdornment";
|
|
|
131
136
|
* element such as `<button>`. Event handlers compose (child runs
|
|
132
137
|
* first); refs forward to the consumer element.
|
|
133
138
|
*
|
|
139
|
+
* @extends HTMLSpanElement
|
|
140
|
+
*
|
|
134
141
|
* @example Trailing clear button via asChild
|
|
135
142
|
* ```tsx
|
|
136
143
|
* <InputGroup.TrailingAdornment asChild>
|
|
@@ -191,9 +198,10 @@ export type TInputGroupCompound = typeof InputGroupRoot & {
|
|
|
191
198
|
* trailing slot, `data-input-group-adornment="trailing"`.
|
|
192
199
|
*
|
|
193
200
|
* **State coordination.** None. CSS handles disabled / invalid /
|
|
194
|
-
* focus-within styling via `:has()` and `:focus-within`.
|
|
195
|
-
*
|
|
196
|
-
*
|
|
201
|
+
* focus-within styling via `:has()` and `:focus-within`. {@link Field}
|
|
202
|
+
* sits *outside* `InputGroup` and owns the label / error / description
|
|
203
|
+
* wiring, so the two compose without `InputGroup` needing to know about
|
|
204
|
+
* it — wrap the `Input` in an `InputGroup` inside a `Field.Root`.
|
|
197
205
|
*
|
|
198
206
|
* @example Leading search icon
|
|
199
207
|
* ```tsx
|
|
@@ -103,4 +103,18 @@ describe("InputGroup basic rendering", () => {
|
|
|
103
103
|
expect(screen.getByTestId("lead")).toHaveClass("lead");
|
|
104
104
|
expect(screen.getByTestId("trail")).toHaveClass("trail");
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
it("sets a displayName on the compound and each adornment", () => {
|
|
108
|
+
// Assert — empty displayNames would render each as anonymous in DevTools.
|
|
109
|
+
// `InputGroup`, `InputGroup.Root`, and the underlying function are one
|
|
110
|
+
// object (Object.assign compound), so the compound's "InputGroup" alias is
|
|
111
|
+
// the observable Root displayName; the adornments are distinct objects.
|
|
112
|
+
expect(InputGroup.displayName).toBe("InputGroup");
|
|
113
|
+
expect(InputGroup.LeadingAdornment.displayName).toBe(
|
|
114
|
+
"InputGroupLeadingAdornment",
|
|
115
|
+
);
|
|
116
|
+
expect(InputGroup.TrailingAdornment.displayName).toBe(
|
|
117
|
+
"InputGroupTrailingAdornment",
|
|
118
|
+
);
|
|
119
|
+
});
|
|
106
120
|
});
|
package/src/InputGroup/types.ts
CHANGED
|
@@ -1,22 +1,43 @@
|
|
|
1
1
|
import { ComponentProps, ReactNode, Ref } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Props for {@link InputGroup.Root} —
|
|
5
|
-
*
|
|
4
|
+
* Props for {@link InputGroupRoot | `InputGroup.Root`} — the frame
|
|
5
|
+
* wrapper. Extends the native `<div>` attributes plus the `asChild`
|
|
6
|
+
* escape hatch and a typed `ref`.
|
|
6
7
|
*/
|
|
7
8
|
export type InputGroupRootProps = ComponentProps<"div"> & {
|
|
9
|
+
/** Render the consumer's element instead of `<div>` via the
|
|
10
|
+
* {@link Slot} pattern — e.g. a `<label>` so the whole frame is
|
|
11
|
+
* clickable. The `data-input-group` hook is preserved.
|
|
12
|
+
* @default false */
|
|
8
13
|
asChild?: boolean;
|
|
14
|
+
/** Forwarded to the underlying `HTMLDivElement` (or the `asChild`
|
|
15
|
+
* element). */
|
|
9
16
|
ref?: Ref<HTMLDivElement>;
|
|
17
|
+
/** The wrapped control and its optional leading / trailing
|
|
18
|
+
* adornments. */
|
|
10
19
|
children?: ReactNode;
|
|
11
20
|
};
|
|
12
21
|
|
|
13
22
|
/**
|
|
14
|
-
* Props
|
|
15
|
-
* {@link InputGroup.
|
|
16
|
-
*
|
|
23
|
+
* Props shared by
|
|
24
|
+
* {@link InputGroupLeadingAdornment | `InputGroup.LeadingAdornment`} and
|
|
25
|
+
* {@link InputGroupTrailingAdornment | `InputGroup.TrailingAdornment`} —
|
|
26
|
+
* the positioned adornment slots. Extends the native `<span>` attributes
|
|
27
|
+
* plus the `asChild` escape hatch and a typed `ref`.
|
|
17
28
|
*/
|
|
18
29
|
export type InputGroupAdornmentProps = ComponentProps<"span"> & {
|
|
30
|
+
/** Render the consumer's element instead of `<span>` via the
|
|
31
|
+
* {@link Slot} pattern — e.g. an interactive `<button>`. Event
|
|
32
|
+
* handlers compose (child runs first) and the `data-input-group-adornment`
|
|
33
|
+
* hook is preserved.
|
|
34
|
+
* @default false */
|
|
19
35
|
asChild?: boolean;
|
|
36
|
+
/** Forwarded to the underlying `HTMLSpanElement` (or the `asChild`
|
|
37
|
+
* element). */
|
|
20
38
|
ref?: Ref<HTMLSpanElement>;
|
|
39
|
+
/** The adornment content — a decorative icon (mark it
|
|
40
|
+
* `aria-hidden="true"`), suffix text, or an interactive control with an
|
|
41
|
+
* accessible name. */
|
|
21
42
|
children?: ReactNode;
|
|
22
43
|
};
|
|
@@ -33,11 +33,41 @@ import type {
|
|
|
33
33
|
* The root of a Miller Columns widget — a horizontal strip of vertical
|
|
34
34
|
* lists where selecting a node reveals its children in the next column.
|
|
35
35
|
*
|
|
36
|
-
* Renders the strip container (`role="tree"`)
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
36
|
+
* Renders the strip container (`role="tree"`, `data-orientation="horizontal"`)
|
|
37
|
+
* into which every {@link MillerColumnsColumn | `MillerColumns.Column`}
|
|
38
|
+
* projects itself via a portal. Authoring the tree is **recursive** and
|
|
39
|
+
* data-less: there is no `items` prop — an {@link MillerColumnsItem | `Item`}
|
|
40
|
+
* declares its child column as a *nested* `<MillerColumns.Column>`, and the
|
|
41
|
+
* strip flattens those nested columns into a single left-to-right row by
|
|
42
|
+
* rendering one `display: contents` slot per visible depth and portalling
|
|
43
|
+
* each column into its slot. This fixes left-to-right DOM order by slot
|
|
44
|
+
* order rather than by portal commit order.
|
|
45
|
+
*
|
|
46
|
+
* **Controlled vs uncontrolled.** State is the *selection path* — an array
|
|
47
|
+
* of item ids, root column first (`["docs", "guides"]`). The two modes are
|
|
48
|
+
* statically discriminated at the type level:
|
|
49
|
+
*
|
|
50
|
+
* - **Uncontrolled** — pass {@link MillerColumnsRootUncontrolledProps.defaultValue | `defaultValue`}
|
|
51
|
+
* (or omit it to start empty). The component owns the path.
|
|
52
|
+
* - **Controlled** — pass {@link MillerColumnsRootControlledProps.value | `value`}
|
|
53
|
+
* and {@link MillerColumnsRootControlledProps.onValueChange | `onValueChange`}
|
|
54
|
+
* together; every selection defers back through the callback.
|
|
55
|
+
*
|
|
56
|
+
* **Roving tabindex.** The whole tree has exactly **one** Tab stop
|
|
57
|
+
* (`tabIndex={0}`), not one per column. It follows the last-focused item;
|
|
58
|
+
* before any focus it defaults to the deepest selected item, falling back
|
|
59
|
+
* to the first enabled root item. Within a column, Up/Down/Home/End move
|
|
60
|
+
* focus; ArrowRight opens (and steps into) a branch item's child column,
|
|
61
|
+
* ArrowLeft returns to the parent item.
|
|
62
|
+
*
|
|
63
|
+
* **Context.** Root establishes {@link MillerColumnsContext} (selection
|
|
64
|
+
* path, portal slots, item registry, column widths, and focus commands);
|
|
65
|
+
* each Column provides {@link MillerColumnsColumnContext} (its depth) and
|
|
66
|
+
* each Item {@link MillerColumnsItemContext} (`selected` / `hasChildren`).
|
|
67
|
+
* Read the current selection from consumer content with
|
|
68
|
+
* {@link useMillerColumnsSelection}.
|
|
69
|
+
*
|
|
70
|
+
* @extends HTMLDivElement
|
|
41
71
|
*
|
|
42
72
|
* @example
|
|
43
73
|
* ```tsx
|
|
@@ -86,9 +116,20 @@ MillerColumnsRoot.displayName = "MillerColumnsRoot";
|
|
|
86
116
|
|
|
87
117
|
/**
|
|
88
118
|
* A single vertical list of items within the strip. Renders a
|
|
89
|
-
* `<div role="group">` that is portal-projected
|
|
90
|
-
*
|
|
91
|
-
*
|
|
119
|
+
* `<div role="group">` (carrying `data-depth`) that is **portal-projected**
|
|
120
|
+
* into the {@link MillerColumnsRoot | `Root`} strip's depth slot, so a
|
|
121
|
+
* `Column` nested inside an `Item` still appears side-by-side with its
|
|
122
|
+
* ancestors rather than in document flow. Until its slot has mounted the
|
|
123
|
+
* component renders `null`.
|
|
124
|
+
*
|
|
125
|
+
* It reads its depth from {@link MillerColumnsContext} and re-provides it to
|
|
126
|
+
* descendant {@link MillerColumnsItem | Items} via
|
|
127
|
+
* {@link MillerColumnsColumnContext}. A width set by a
|
|
128
|
+
* {@link MillerColumnsResizeHandle | `ResizeHandle`} in this column is
|
|
129
|
+
* applied here as an inline `width` style (consumer `style` still wins for
|
|
130
|
+
* every other property).
|
|
131
|
+
*
|
|
132
|
+
* @extends HTMLDivElement
|
|
92
133
|
*
|
|
93
134
|
* @example
|
|
94
135
|
* ```tsx
|
|
@@ -128,27 +169,44 @@ export function MillerColumnsColumn({
|
|
|
128
169
|
MillerColumnsColumn.displayName = "MillerColumnsColumn";
|
|
129
170
|
|
|
130
171
|
/**
|
|
131
|
-
* A single selectable node within a `Column
|
|
132
|
-
* `<div role="treeitem">` for its cell content
|
|
172
|
+
* A single selectable node within a {@link MillerColumnsColumn | `Column`}.
|
|
173
|
+
* Renders a `<div role="treeitem">` for its cell content, with
|
|
174
|
+
* `aria-selected`, `aria-level` (1-based depth), and `data-state` /
|
|
175
|
+
* `data-depth` styling hooks.
|
|
133
176
|
*
|
|
134
177
|
* The {@link MillerColumnsItemProps.value | `value`} prop is the stable
|
|
135
178
|
* identifier used to match this item against the active selection path.
|
|
136
179
|
*
|
|
137
|
-
* An `Item` becomes a branch by nesting a
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* is a
|
|
180
|
+
* **Branch vs leaf.** An `Item` becomes a *branch* by nesting a
|
|
181
|
+
* `<MillerColumns.Column>` among its children — `partitionItemChildren`
|
|
182
|
+
* splits the children into the cell content and that single optional
|
|
183
|
+
* nested column (matched however deeply it is wrapped in fragments; a
|
|
184
|
+
* second nested column throws). A branch carries `aria-expanded` and
|
|
185
|
+
* mounts its child column (projecting it into the strip) only while the
|
|
186
|
+
* item is selected; an item with no nested column is a *leaf*.
|
|
187
|
+
*
|
|
188
|
+
* **Keyboard & selection.** Clicking or activating (Enter/Space) selects
|
|
189
|
+
* the item, truncating the path to this depth. ArrowRight on a branch opens
|
|
190
|
+
* and steps into the child column; ArrowLeft returns focus to the parent
|
|
191
|
+
* item. The item participates in the tree-wide single roving Tab stop.
|
|
192
|
+
*
|
|
193
|
+
* **Disabled.** Pass `disabled` to render `aria-disabled="true"` /
|
|
194
|
+
* `data-disabled`; the item is skipped by roving navigation and cannot be
|
|
195
|
+
* selected, focused, or activated.
|
|
141
196
|
*
|
|
142
197
|
* **`asChild` prop.** Pass `asChild` to render the cell as a
|
|
143
198
|
* consumer-supplied element (e.g. an `<a>`) instead of the default
|
|
144
199
|
* `<div>`. All treeitem ARIA attributes, event handlers, and the internal
|
|
145
|
-
* ref are merged onto that child. A nested
|
|
146
|
-
* still declared as a sibling of the cell
|
|
200
|
+
* ref are merged onto that child via the {@link Slot} pattern. A nested
|
|
201
|
+
* `<MillerColumns.Column>` is still declared as a sibling of the cell
|
|
202
|
+
* element.
|
|
147
203
|
*
|
|
148
204
|
* **Ref forwarding.** A `ref` prop (React 19 ref-as-prop style) is
|
|
149
205
|
* forwarded to the rendered element and composed with the library's
|
|
150
206
|
* internal ref.
|
|
151
207
|
*
|
|
208
|
+
* @extends HTMLDivElement
|
|
209
|
+
*
|
|
152
210
|
* @example Leaf
|
|
153
211
|
* ```tsx
|
|
154
212
|
* <MillerColumns.Item value="guides">Guides</MillerColumns.Item>
|
|
@@ -204,6 +262,8 @@ MillerColumnsItem.displayName = "MillerColumnsItem";
|
|
|
204
262
|
* - `data-has-children` — always present (the indicator only renders
|
|
205
263
|
* for branch items).
|
|
206
264
|
*
|
|
265
|
+
* @extends HTMLSpanElement
|
|
266
|
+
*
|
|
207
267
|
* @example
|
|
208
268
|
* ```tsx
|
|
209
269
|
* <MillerColumns.Item value="docs">
|
|
@@ -243,10 +303,13 @@ MillerColumnsItemIndicator.displayName = "MillerColumnsItemIndicator";
|
|
|
243
303
|
* `<div role="separator" aria-orientation="vertical">` that, while
|
|
244
304
|
* pointer-dragged, drives that column's width as state on the `Root`.
|
|
245
305
|
*
|
|
246
|
-
* Must be rendered among a `MillerColumns.Column`'s
|
|
247
|
-
* the column's depth from
|
|
306
|
+
* Must be rendered among a {@link MillerColumnsColumn | `MillerColumns.Column`}'s
|
|
307
|
+
* children — it reads the column's depth from
|
|
308
|
+
* {@link MillerColumnsColumnContext}. Position it with CSS (typically
|
|
248
309
|
* absolutely, against the column's trailing edge).
|
|
249
310
|
*
|
|
311
|
+
* @extends HTMLDivElement
|
|
312
|
+
*
|
|
250
313
|
* @example
|
|
251
314
|
* ```tsx
|
|
252
315
|
* <MillerColumns.Column>
|
|
@@ -277,7 +340,11 @@ MillerColumnsResizeHandle.displayName = "MillerColumnsResizeHandle";
|
|
|
277
340
|
*
|
|
278
341
|
* Pair it with {@link useMillerColumnsSelection} to render content for
|
|
279
342
|
* the current selection. Author it as the **last child** of `Root`, a
|
|
280
|
-
* sibling of the root `Column`.
|
|
343
|
+
* sibling of the root `Column`. It subscribes to
|
|
344
|
+
* {@link MillerColumnsContext} (so it must live inside a `Root`) but does
|
|
345
|
+
* not portal — it stays in document flow after the projected columns.
|
|
346
|
+
*
|
|
347
|
+
* @extends HTMLDivElement
|
|
281
348
|
*
|
|
282
349
|
* @example
|
|
283
350
|
* ```tsx
|
|
@@ -1,76 +1,132 @@
|
|
|
1
1
|
import { ComponentProps, ReactNode, Ref } from "react";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Shared base for both {@link MillerColumnsRootProps} variants — the native
|
|
5
|
+
* `<div>` attributes (minus `ref`, which the strip owns internally).
|
|
6
|
+
*/
|
|
7
|
+
export type MillerColumnsRootBaseProps = Omit<
|
|
8
|
+
ComponentProps<"div">,
|
|
9
|
+
"ref" | "defaultValue"
|
|
10
|
+
> & {
|
|
11
|
+
/**
|
|
12
|
+
* The strip's tree, authored recursively: root
|
|
13
|
+
* {@link MillerColumnsColumnProps | Column(s)} whose branch
|
|
14
|
+
* {@link MillerColumnsItemProps | Items} nest further Columns, plus an
|
|
15
|
+
* optional trailing {@link MillerColumnsPreviewPanelProps | PreviewPanel}.
|
|
16
|
+
* There is no `items` data prop — the JSX *is* the data.
|
|
17
|
+
*/
|
|
6
18
|
children: ReactNode;
|
|
7
19
|
};
|
|
8
20
|
|
|
9
21
|
/**
|
|
10
|
-
*
|
|
11
|
-
* selection path. Pass `defaultValue`
|
|
22
|
+
* Uncontrolled variant of {@link MillerColumnsRootProps}: the component owns
|
|
23
|
+
* the selection path. Pass `defaultValue` (or omit it to start empty).
|
|
12
24
|
*/
|
|
13
25
|
export type MillerColumnsRootUncontrolledProps = MillerColumnsRootBaseProps & {
|
|
14
|
-
/** Initial selection path
|
|
26
|
+
/** Initial selection path on first render, root column first (e.g.
|
|
27
|
+
* `["docs", "guides"]`). The component owns the path thereafter. */
|
|
15
28
|
defaultValue?: string[];
|
|
16
|
-
/** Forbidden in uncontrolled mode. */
|
|
29
|
+
/** Forbidden in uncontrolled mode — use `defaultValue` instead. */
|
|
17
30
|
value?: never;
|
|
18
|
-
/** Forbidden in uncontrolled mode
|
|
31
|
+
/** Forbidden in uncontrolled mode — pass `value` + `onValueChange` to
|
|
32
|
+
* control the component. */
|
|
19
33
|
onValueChange?: never;
|
|
20
34
|
};
|
|
21
35
|
|
|
22
36
|
/**
|
|
23
|
-
*
|
|
37
|
+
* Controlled variant of {@link MillerColumnsRootProps}: the parent owns the
|
|
24
38
|
* selection path. Pass `value` and `onValueChange` together.
|
|
25
39
|
*/
|
|
26
40
|
export type MillerColumnsRootControlledProps = MillerColumnsRootBaseProps & {
|
|
27
|
-
/** Forbidden in controlled mode. */
|
|
41
|
+
/** Forbidden in controlled mode — use `value` instead. */
|
|
28
42
|
defaultValue?: never;
|
|
29
|
-
/** The controlled selection path, root column first.
|
|
43
|
+
/** The controlled selection path, root column first. Must be kept in sync
|
|
44
|
+
* by the parent via `onValueChange`. */
|
|
30
45
|
value: string[];
|
|
31
|
-
/** Called with the new path whenever the selection changes.
|
|
46
|
+
/** Called with the new selection path whenever the selection changes.
|
|
47
|
+
* Required in controlled mode. */
|
|
32
48
|
onValueChange: (path: string[]) => void;
|
|
33
49
|
};
|
|
34
50
|
|
|
35
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Props for {@link MillerColumnsRoot | `MillerColumns.Root`}.
|
|
53
|
+
*
|
|
54
|
+
* Resolves to either {@link MillerColumnsRootUncontrolledProps} or
|
|
55
|
+
* {@link MillerColumnsRootControlledProps} — only one shape is accepted by
|
|
56
|
+
* TypeScript at a time.
|
|
57
|
+
*/
|
|
36
58
|
export type MillerColumnsRootProps =
|
|
37
59
|
| MillerColumnsRootUncontrolledProps
|
|
38
60
|
| MillerColumnsRootControlledProps;
|
|
39
61
|
|
|
40
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Props for {@link MillerColumnsColumn | `MillerColumns.Column`}, a single
|
|
64
|
+
* vertical list. Rendered element is portal-projected into the Root strip.
|
|
65
|
+
*/
|
|
41
66
|
export type MillerColumnsColumnProps = ComponentProps<"div"> & {
|
|
42
|
-
/** The
|
|
67
|
+
/** The column's {@link MillerColumnsItemProps | Items} and, optionally, a
|
|
68
|
+
* {@link MillerColumnsResizeHandleProps | ResizeHandle}. */
|
|
43
69
|
children: ReactNode;
|
|
44
70
|
};
|
|
45
71
|
|
|
46
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Props for {@link MillerColumnsItem | `MillerColumns.Item`}, a single
|
|
74
|
+
* selectable node within a column.
|
|
75
|
+
*
|
|
76
|
+
* Generic over the rendered element type so `asChild` consumers can type
|
|
77
|
+
* the forwarded `ref` (e.g. `MillerColumns.Item<HTMLAnchorElement>`).
|
|
78
|
+
*/
|
|
47
79
|
export type MillerColumnsItemProps<T extends HTMLElement = HTMLDivElement> =
|
|
48
80
|
Omit<ComponentProps<"div">, "ref"> & {
|
|
49
|
-
/** Stable identifier used to match this item against the selection path.
|
|
81
|
+
/** Stable identifier used to match this item against the selection path.
|
|
82
|
+
* The item is selected when the path entry at its depth equals `value`. */
|
|
50
83
|
value: string;
|
|
51
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* When `true`, the item renders `aria-disabled` / `data-disabled` and is
|
|
86
|
+
* skipped by roving navigation — it cannot be selected, focused, or
|
|
87
|
+
* activated.
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
52
90
|
disabled?: boolean;
|
|
53
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Render the cell as a single consumer-supplied child element (e.g. an
|
|
93
|
+
* `<a>`) instead of the default `<div>`, merging the treeitem ARIA,
|
|
94
|
+
* handlers, and ref onto it via the {@link Slot} pattern. Any nested
|
|
95
|
+
* `<MillerColumns.Column>` remains a sibling of the cell element.
|
|
96
|
+
* @default false
|
|
97
|
+
*/
|
|
54
98
|
asChild?: boolean;
|
|
55
|
-
/**
|
|
99
|
+
/** The cell content and, for a branch item, a single nested
|
|
100
|
+
* `<MillerColumns.Column>` (split out by `partitionItemChildren`). */
|
|
56
101
|
children: ReactNode;
|
|
57
|
-
/**
|
|
58
|
-
* `asChild`, specify the child's element type.
|
|
102
|
+
/** Forwarded to the rendered element. Defaults to `HTMLDivElement`; when
|
|
103
|
+
* using `asChild`, specify the child's element type. Composed with the
|
|
104
|
+
* library's internal ref. */
|
|
59
105
|
ref?: Ref<T>;
|
|
60
106
|
};
|
|
61
107
|
|
|
62
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Props for {@link MillerColumnsItemIndicator | `MillerColumns.ItemIndicator`},
|
|
110
|
+
* the branch chevron affordance (renders nothing for leaf items).
|
|
111
|
+
*/
|
|
63
112
|
export type MillerColumnsItemIndicatorProps = ComponentProps<"span"> & {
|
|
64
|
-
/** The glyph to render (
|
|
113
|
+
/** The glyph to render (e.g. `▸`). Optional. */
|
|
65
114
|
children?: ReactNode;
|
|
66
115
|
};
|
|
67
116
|
|
|
68
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Props for {@link MillerColumnsResizeHandle | `MillerColumns.ResizeHandle`},
|
|
119
|
+
* the drag-to-resize separator for its containing column.
|
|
120
|
+
*/
|
|
69
121
|
export type MillerColumnsResizeHandleProps = ComponentProps<"div">;
|
|
70
122
|
|
|
71
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Props for {@link MillerColumnsPreviewPanel | `MillerColumns.PreviewPanel`},
|
|
125
|
+
* the trailing preview pane.
|
|
126
|
+
*/
|
|
72
127
|
export type MillerColumnsPreviewPanelProps = ComponentProps<"div"> & {
|
|
73
|
-
/** The preview content for the current selection
|
|
128
|
+
/** The preview content for the current selection (typically driven by
|
|
129
|
+
* {@link useMillerColumnsSelection}). */
|
|
74
130
|
children?: ReactNode;
|
|
75
131
|
};
|
|
76
132
|
|
package/src/Modal/Modal.tsx
CHANGED
|
@@ -97,6 +97,8 @@ ModalRoot.displayName = "ModalRoot";
|
|
|
97
97
|
* element (e.g. a router `<Link>`) with the trigger's ARIA attributes,
|
|
98
98
|
* composed event handlers, and ref merged in.
|
|
99
99
|
*
|
|
100
|
+
* @extends HTMLButtonElement
|
|
101
|
+
*
|
|
100
102
|
* @example
|
|
101
103
|
* ```tsx
|
|
102
104
|
* <Modal.Trigger>Open</Modal.Trigger>
|
|
@@ -177,6 +179,8 @@ ModalPortal.displayName = "ModalPortal";
|
|
|
177
179
|
* **`forceMount` prop.** Pass `forceMount` to keep the overlay in the
|
|
178
180
|
* DOM while `open` is false so a CSS exit animation can play.
|
|
179
181
|
*
|
|
182
|
+
* @extends HTMLDivElement
|
|
183
|
+
*
|
|
180
184
|
* @example
|
|
181
185
|
* ```tsx
|
|
182
186
|
* <Modal.Overlay />
|
|
@@ -215,8 +219,15 @@ ModalOverlay.displayName = "ModalOverlay";
|
|
|
215
219
|
/**
|
|
216
220
|
* The native `<dialog>` element. React drives its `showModal()` and
|
|
217
221
|
* `close()` calls in response to `open` state changes, letting the
|
|
218
|
-
* browser own
|
|
219
|
-
*
|
|
222
|
+
* browser own the inert background, top-layer rendering, and the `Esc`
|
|
223
|
+
* key.
|
|
224
|
+
*
|
|
225
|
+
* **Focus trapping.** `showModal()` inerts the background but does
|
|
226
|
+
* *not* cycle focus — `Tab` past the last element escapes to the
|
|
227
|
+
* browser chrome, then wraps back in. To conform to the WAI-ARIA APG
|
|
228
|
+
* Modal Dialog pattern, a `keydown` handler wraps `Tab` (last → first)
|
|
229
|
+
* and `Shift+Tab` (first → last); interior Tabs are left to the
|
|
230
|
+
* browser's native, inert-aware order.
|
|
220
231
|
*
|
|
221
232
|
* **ARIA wiring.** `aria-labelledby` and `aria-describedby` are set
|
|
222
233
|
* automatically when `Modal.Title` and `Modal.Description` are rendered
|
|
@@ -234,11 +245,21 @@ ModalOverlay.displayName = "ModalOverlay";
|
|
|
234
245
|
* `PointerEvent` is passed through, not a synthetic React event.
|
|
235
246
|
*
|
|
236
247
|
* **`asChild` is intentionally not supported.** The native dialog
|
|
237
|
-
* primitive is what provides the
|
|
238
|
-
* swapping it for a
|
|
248
|
+
* primitive is what provides the inert background and top-layer
|
|
249
|
+
* stacking (and hosts the Tab-wrap handler above); swapping it for a
|
|
250
|
+
* `<div>` would break all of it.
|
|
239
251
|
*
|
|
240
252
|
* **Styling hook.** `data-state="open" | "closed"` on the dialog.
|
|
241
253
|
*
|
|
254
|
+
* **Nested modals.** A `Modal.Root` may be rendered inside another modal's
|
|
255
|
+
* `Modal.Content`. Each root owns its own {@link ModalContextValue | context}
|
|
256
|
+
* and dialog, so every dialog stacks independently in the browser's top
|
|
257
|
+
* layer and each dismissal gesture (Close, Esc, click-outside) resolves
|
|
258
|
+
* against the nearest dialog only — closing the inner modal leaves the
|
|
259
|
+
* outer one open.
|
|
260
|
+
*
|
|
261
|
+
* @extends HTMLDialogElement
|
|
262
|
+
*
|
|
242
263
|
* @example
|
|
243
264
|
* ```tsx
|
|
244
265
|
* <Modal.Content
|
|
@@ -293,6 +314,8 @@ ModalContent.displayName = "ModalContent";
|
|
|
293
314
|
* an `<h3>` for a nested dialog or a styled heading component); the
|
|
294
315
|
* id is still registered.
|
|
295
316
|
*
|
|
317
|
+
* @extends HTMLHeadingElement
|
|
318
|
+
*
|
|
296
319
|
* @example
|
|
297
320
|
* ```tsx
|
|
298
321
|
* <Modal.Title>Payment</Modal.Title>
|
|
@@ -340,6 +363,8 @@ ModalTitle.displayName = "ModalTitle";
|
|
|
340
363
|
* Pass `asChild` to render any consumer-supplied element; the id is
|
|
341
364
|
* still registered.
|
|
342
365
|
*
|
|
366
|
+
* @extends HTMLParagraphElement
|
|
367
|
+
*
|
|
343
368
|
* @example
|
|
344
369
|
* ```tsx
|
|
345
370
|
* <Modal.Description>
|
|
@@ -386,6 +411,8 @@ ModalDescription.displayName = "ModalDescription";
|
|
|
386
411
|
* text link or an icon-only `<button>` of your own) with the close
|
|
387
412
|
* behaviour merged in.
|
|
388
413
|
*
|
|
414
|
+
* @extends HTMLButtonElement
|
|
415
|
+
*
|
|
389
416
|
* @example
|
|
390
417
|
* ```tsx
|
|
391
418
|
* <Modal.Close>Cancel</Modal.Close>
|
package/src/Modal/README.md
CHANGED
|
@@ -5,10 +5,16 @@ A headless, accessible compound component implementing the
|
|
|
5
5
|
|
|
6
6
|
Modal is built on the native
|
|
7
7
|
[`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
|
|
8
|
-
element and its `showModal()` API, so
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
element and its `showModal()` API, so the inert background, top-layer
|
|
9
|
+
stacking, and Esc-to-close are handled by the browser. The React layer
|
|
10
|
+
adds what native `<dialog>` doesn't give you:
|
|
11
|
+
|
|
12
|
+
- **Focus trapping.** `showModal()` inerts the background but does
|
|
13
|
+
_not_ cycle focus — Tab past the last element escapes to the browser
|
|
14
|
+
chrome and back. A `keydown` handler on the dialog wraps `Tab`
|
|
15
|
+
(last → first) and `Shift+Tab` (first → last) so focus stays inside,
|
|
16
|
+
as the WAI-ARIA APG requires. Interior Tabs stay native, so the
|
|
17
|
+
browser's inert-aware order (radio groups, etc.) is untouched.
|
|
12
18
|
- Click-outside-to-close via a `pointerdown` listener on the dialog
|
|
13
19
|
that checks the pointer against `getBoundingClientRect()` — coords
|
|
14
20
|
outside the rect mean the pointer landed on the native `::backdrop`.
|
|
@@ -68,10 +74,11 @@ README for the canonical composed example; nothing below changes.
|
|
|
68
74
|
|
|
69
75
|
## Keyboard interaction
|
|
70
76
|
|
|
71
|
-
| Key
|
|
72
|
-
|
|
|
73
|
-
| `Esc`
|
|
74
|
-
| `Tab`
|
|
77
|
+
| Key | Behaviour |
|
|
78
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `Esc` | Closes the modal (native `cancel` event). Preventable via `onEscapeKeyDown` |
|
|
80
|
+
| `Tab` | Wraps from the last focusable back to the first — focus stays inside the dialog. `showModal()` inerts the background but doesn't cycle focus, so this wrap is added by a `keydown` handler |
|
|
81
|
+
| `Shift+Tab` | Wraps from the first focusable to the last |
|
|
75
82
|
|
|
76
83
|
## State modes
|
|
77
84
|
|
|
@@ -156,8 +163,10 @@ handlers, so only its ARIA and `data-state` are forwarded.
|
|
|
156
163
|
```
|
|
157
164
|
|
|
158
165
|
`Modal.Content` is intentionally **not** slot-able. Its native
|
|
159
|
-
`<dialog>` element
|
|
160
|
-
|
|
166
|
+
`<dialog>` element — the inert background and top-layer stacking — is
|
|
167
|
+
the whole reason Modal needs no scroll-lock library and only a minimal
|
|
168
|
+
Tab-wrap (rather than a full focus-trap library), so we don't expose a
|
|
169
|
+
way to swap it.
|
|
161
170
|
|
|
162
171
|
## Animation hooks
|
|
163
172
|
|