@primitiv-ui/react 0.1.28 → 0.1.30
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 +19 -2
- 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 +32 -11
- package/src/Accordion/README.md +2 -2
- 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 +139 -21
- 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/__tests__/Button.contract.test.tsx +1 -1
- package/src/Button/types.ts +21 -0
- package/src/Carousel/Carousel.tsx +268 -20
- package/src/Carousel/README.md +764 -87
- 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 +90 -2
- 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 +458 -36
- package/src/Checkbox/Checkbox.tsx +11 -3
- 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 +14 -5
- package/src/CheckboxCard/README.md +1 -1
- 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 +41 -1
- 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/Combobox/Combobox.tsx +508 -0
- package/src/Combobox/ComboboxContext.ts +23 -0
- package/src/Combobox/README.md +170 -0
- package/src/Combobox/__tests__/Combobox.asChild.test.tsx +62 -0
- package/src/Combobox/__tests__/Combobox.basic-rendering.test.tsx +16 -0
- package/src/Combobox/__tests__/Combobox.content.test.tsx +36 -0
- package/src/Combobox/__tests__/Combobox.controlled-state.test.tsx +88 -0
- package/src/Combobox/__tests__/Combobox.empty.test.tsx +42 -0
- package/src/Combobox/__tests__/Combobox.ids.test.tsx +66 -0
- package/src/Combobox/__tests__/Combobox.item.test.tsx +76 -0
- package/src/Combobox/__tests__/Combobox.keyboard-interaction.test.tsx +196 -0
- package/src/Combobox/__tests__/Combobox.light-dismiss.test.tsx +101 -0
- package/src/Combobox/__tests__/Combobox.query.test.tsx +105 -0
- package/src/Combobox/hooks/index.ts +1 -0
- package/src/Combobox/hooks/useComboboxRoot.ts +2 -0
- package/src/Combobox/index.ts +3 -0
- package/src/Combobox/types.ts +133 -0
- package/src/ContextMenu/ContextMenu.tsx +270 -4
- package/src/ContextMenu/README.md +1 -1
- package/src/ContextMenu/types.ts +148 -6
- package/src/DirectionProvider/DirectionProvider.tsx +41 -9
- package/src/DirectionProvider/README.md +4 -4
- 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.contract.test.tsx +1 -1
- 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 +119 -2
- package/src/Dropdown/README.md +7 -5
- package/src/Dropdown/types.ts +241 -11
- package/src/EmptyState/EmptyState.tsx +74 -4
- package/src/EmptyState/README.md +2 -2
- 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/README.md +1 -1
- 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/hooks/useFieldProps.ts +1 -1
- package/src/Field/types.ts +60 -12
- package/src/Fieldset/Fieldset.tsx +55 -19
- package/src/Fieldset/README.md +2 -2
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
- package/src/Fieldset/types.ts +20 -2
- package/src/Input/Input.tsx +15 -10
- package/src/Input/README.md +2 -2
- 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/Listbox/Listbox.tsx +475 -0
- package/src/Listbox/ListboxContext.ts +48 -0
- package/src/Listbox/README.md +248 -0
- package/src/Listbox/__tests__/Listbox.active-descendant.test.tsx +128 -0
- package/src/Listbox/__tests__/Listbox.asChild.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.basic-rendering.test.tsx +16 -0
- package/src/Listbox/__tests__/Listbox.controlled-state.test.tsx +114 -0
- package/src/Listbox/__tests__/Listbox.disabled.test.tsx +90 -0
- package/src/Listbox/__tests__/Listbox.fixtures.ts +27 -0
- package/src/Listbox/__tests__/Listbox.group-label.test.tsx +97 -0
- package/src/Listbox/__tests__/Listbox.group.test.tsx +45 -0
- package/src/Listbox/__tests__/Listbox.keyboard-edge-cases.test.tsx +144 -0
- package/src/Listbox/__tests__/Listbox.keyboard-interaction.test.tsx +112 -0
- package/src/Listbox/__tests__/Listbox.modifier-keys.test.tsx +86 -0
- package/src/Listbox/__tests__/Listbox.multi-select-keyboard.test.tsx +368 -0
- package/src/Listbox/__tests__/Listbox.multiple-mode.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.mutation-hardening.test.tsx +480 -0
- package/src/Listbox/__tests__/Listbox.orientation.test.tsx +59 -0
- package/src/Listbox/__tests__/Listbox.reading-direction.test.tsx +65 -0
- package/src/Listbox/__tests__/Listbox.reorder.test.tsx +78 -0
- package/src/Listbox/__tests__/Listbox.selection-follows-focus.test.tsx +64 -0
- package/src/Listbox/__tests__/Listbox.strict-context.test.tsx +21 -0
- package/src/Listbox/__tests__/Listbox.typeahead.test.tsx +123 -0
- package/src/Listbox/__tests__/Listbox.uncontrolled-state.test.tsx +48 -0
- package/src/Listbox/constants.ts +2 -0
- package/src/Listbox/hooks/index.ts +1 -0
- package/src/Listbox/hooks/useListboxRoot.ts +407 -0
- package/src/Listbox/index.ts +3 -0
- package/src/Listbox/types.ts +238 -0
- package/src/MillerColumns/MillerColumns.tsx +186 -45
- package/src/MillerColumns/README.md +90 -20
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +45 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +4 -2
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +18 -3
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +48 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +35 -2
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-selection-state.test.tsx +96 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +29 -4
- package/src/MillerColumns/__tests__/MillerColumns.preview-visibility.test.tsx +79 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +189 -0
- package/src/MillerColumns/__tests__/MillerColumns.styled-parts.test.tsx +143 -0
- package/src/MillerColumns/__tests__/MillerColumns.typeahead.test.tsx +104 -0
- package/src/MillerColumns/constants.ts +2 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +5 -2
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +29 -7
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +80 -10
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +4 -0
- package/src/MillerColumns/types.ts +136 -30
- package/src/MillerColumns/utils.ts +69 -3
- package/src/Modal/Modal.tsx +35 -8
- package/src/Modal/README.md +24 -15
- 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/README.md +2 -2
- 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/README.md +1 -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 +10 -2
- 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/README.md +2 -2
- package/src/RadioGroup/RadioGroup.tsx +12 -1
- 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 +87 -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 +596 -69
- 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 +7 -7
- 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/README.md +3 -3
- package/src/SkipNav/SkipNav.tsx +72 -32
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
- package/src/SkipNav/types.ts +17 -9
- package/src/Slider/README.md +37 -7
- package/src/Slider/Slider.tsx +42 -4
- 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 +112 -2
- package/src/Slot/composeEventHandlers.ts +26 -2
- package/src/Slot/types.ts +16 -0
- package/src/SplitButton/README.md +225 -0
- package/src/SplitButton/SplitButton.tsx +457 -0
- package/src/SplitButton/SplitButtonContext.ts +26 -0
- package/src/SplitButton/__tests__/SplitButton.accessibility.test.tsx +55 -0
- package/src/SplitButton/__tests__/SplitButton.asChild.test.tsx +51 -0
- package/src/SplitButton/__tests__/SplitButton.basic-rendering.test.tsx +57 -0
- package/src/SplitButton/__tests__/SplitButton.data-attributes.test.tsx +28 -0
- package/src/SplitButton/__tests__/SplitButton.disabled.test.tsx +79 -0
- package/src/SplitButton/__tests__/SplitButton.error-handling.test.tsx +29 -0
- package/src/SplitButton/__tests__/SplitButton.keyboard-interaction.test.tsx +56 -0
- package/src/SplitButton/__tests__/SplitButton.state-modes.test.tsx +68 -0
- package/src/SplitButton/index.ts +2 -0
- package/src/SplitButton/types.ts +186 -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/README.md +1 -1
- package/src/Switch/Switch.tsx +17 -4
- 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/README.md +110 -8
- package/src/Table/Table.tsx +246 -16
- package/src/Table/TableExpandableContext.ts +7 -0
- package/src/Table/__tests__/Table.DetailRow.test.tsx +83 -0
- package/src/Table/__tests__/Table.ExpandTrigger.test.tsx +102 -0
- package/src/Table/__tests__/Table.Expandable.test.tsx +121 -0
- package/src/Table/types.ts +191 -17
- package/src/Tabs/README.md +4 -4
- package/src/Tabs/Tabs.tsx +13 -4
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +15 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +24 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +36 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +17 -0
- package/src/Tabs/hooks/useTabsContent.ts +5 -0
- package/src/Tabs/hooks/useTabsRoot.ts +29 -5
- package/src/Tabs/hooks/useTabsTrigger.ts +7 -2
- 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/README.md +4 -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/README.md +7 -7
- package/src/Tooltip/Tooltip.tsx +100 -22
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +50 -0
- package/src/Tooltip/__tests__/Tooltip.error-handling.test.tsx +37 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +134 -0
- package/src/Tooltip/__tests__/Tooltip.skip-delay.test.tsx +216 -0
- package/src/Tooltip/__tests__/Tooltip.timer-coordination.test.tsx +223 -0
- package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +3 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +46 -8
- package/src/Tooltip/hooks/useTooltipRoot.ts +30 -6
- package/src/Tooltip/types.ts +89 -8
- package/src/Tree/README.md +69 -5
- package/src/Tree/Tree.tsx +188 -34
- package/src/Tree/__tests__/Tree.styled-wrapper.test.tsx +159 -0
- package/src/Tree/hooks/useTreePath.ts +33 -1
- package/src/Tree/index.ts +6 -1
- package/src/Tree/types.ts +54 -12
- package/src/Tree/utils.ts +46 -4
- 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/hooks/index.ts +1 -0
- package/src/hooks/useTypeahead.ts +95 -0
- package/src/index.ts +10 -0
- package/src/test/intersectionObserverPolyfill.ts +7 -0
- package/src/test/resizeObserverPolyfill.ts +80 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.reading.test.tsx +31 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.resilience.test.tsx +97 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.ssr.test.tsx +20 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.writing.test.tsx +72 -0
- package/src/useLocalStorage/index.ts +1 -0
- package/src/useLocalStorage/useLocalStorage.ts +140 -0
- package/src/useMediaQuery/README.md +66 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.matching.test.tsx +42 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.ssr.test.tsx +19 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.subscription.test.tsx +89 -0
- package/src/useMediaQuery/index.ts +1 -0
- package/src/useMediaQuery/useMediaQuery.ts +49 -0
- package/src/usePagination/README.md +170 -0
- package/src/usePagination/__tests__/usePagination.navigation.test.tsx +109 -0
- package/src/usePagination/__tests__/usePagination.page-count.test.tsx +56 -0
- package/src/usePagination/__tests__/usePagination.page-state.test.tsx +87 -0
- package/src/usePagination/__tests__/usePagination.range.test.tsx +82 -0
- package/src/usePagination/__tests__/usePagination.slice.test.tsx +59 -0
- package/src/usePagination/__tests__/usePagination.windowing.test.tsx +68 -0
- package/src/usePagination/index.ts +1 -0
- package/src/usePagination/usePagination.ts +292 -0
- package/src/utils/focusable.ts +15 -0
- package/src/utils/getKeyToActionMap.ts +1 -1
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, Ref } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mode-independent props shared by every `Listbox.Root` variant — orientation,
|
|
5
|
+
* direction, selection-follows-focus, `asChild`, children, and the ref.
|
|
6
|
+
* Combined with a mode-specific arm to form {@link ListboxRootProps}.
|
|
7
|
+
*/
|
|
8
|
+
export type ListboxRootBaseProps = Omit<
|
|
9
|
+
HTMLAttributes<HTMLDivElement>,
|
|
10
|
+
"defaultValue" | "dir"
|
|
11
|
+
> & {
|
|
12
|
+
/** Layout axis for cursor navigation. `"vertical"` binds ArrowUp/ArrowDown;
|
|
13
|
+
* `"horizontal"` binds ArrowLeft/ArrowRight and additionally emits
|
|
14
|
+
* `aria-orientation="horizontal"` (vertical is the ARIA default, so it is
|
|
15
|
+
* left implicit). Surfaces as `data-orientation` for styling; it does not
|
|
16
|
+
* itself apply any layout.
|
|
17
|
+
* @default "vertical" */
|
|
18
|
+
orientation?: "horizontal" | "vertical";
|
|
19
|
+
/** Reading direction. In `"rtl"` the horizontal arrow pair is mirrored so
|
|
20
|
+
* the cursor follows visual order — meaningful only when
|
|
21
|
+
* {@link ListboxRootBaseProps.orientation} is `"horizontal"`. Inherited from
|
|
22
|
+
* the nearest {@link DirectionProvider} when omitted, falling back to
|
|
23
|
+
* `"ltr"`. */
|
|
24
|
+
dir?: "ltr" | "rtl";
|
|
25
|
+
/** When `true`, moving the cursor with the arrow / Home / End keys or via
|
|
26
|
+
* typeahead also selects the option it lands on — the behaviour of APG's
|
|
27
|
+
* single-select listbox example. Left `false`, arrowing only moves the
|
|
28
|
+
* highlight and the user commits with Enter or Space, which is what a
|
|
29
|
+
* command palette or search-suggestion list needs.
|
|
30
|
+
* @default false */
|
|
31
|
+
selectionFollowsFocus?: boolean;
|
|
32
|
+
/** Render a single consumer-supplied element in place of the native `<div>`,
|
|
33
|
+
* with the listbox's `role`, `tabIndex`, ARIA state, `data-orientation`, ref
|
|
34
|
+
* and keyboard handlers merged onto it via the {@link Slot} pattern.
|
|
35
|
+
* @default false */
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
/** The {@link ListboxOptionProps | `Listbox.Option`} and
|
|
38
|
+
* {@link ListboxGroupProps | `Listbox.Group`} elements that make up the
|
|
39
|
+
* list. */
|
|
40
|
+
children?: ReactNode;
|
|
41
|
+
/** Forwarded to the underlying `HTMLDivElement`. */
|
|
42
|
+
ref?: Ref<HTMLDivElement>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Single-selection mode, uncontrolled — at most one option selected, with the
|
|
47
|
+
* initial choice supplied via `defaultValue`.
|
|
48
|
+
*/
|
|
49
|
+
export type ListboxSingleUncontrolledProps = {
|
|
50
|
+
/** Selects single-selection semantics: at most one option selected at a
|
|
51
|
+
* time, and re-selecting the current option is a no-op rather than a
|
|
52
|
+
* deselect. */
|
|
53
|
+
type: "single";
|
|
54
|
+
/** Value of the option selected on first render. Omit to start with nothing
|
|
55
|
+
* selected. */
|
|
56
|
+
defaultValue?: string;
|
|
57
|
+
/** Forbidden in uncontrolled mode — use
|
|
58
|
+
* {@link ListboxSingleUncontrolledProps.defaultValue}. */
|
|
59
|
+
value?: never;
|
|
60
|
+
/** Called with the newly selected option's value whenever the user commits a
|
|
61
|
+
* choice. */
|
|
62
|
+
onValueChange?: (value: string) => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Single-selection mode, controlled — the selected value is owned by the
|
|
67
|
+
* caller via `value` / `onValueChange`.
|
|
68
|
+
*/
|
|
69
|
+
export type ListboxSingleControlledProps = {
|
|
70
|
+
/** Selects single-selection semantics: at most one option selected at a
|
|
71
|
+
* time, and re-selecting the current option is a no-op rather than a
|
|
72
|
+
* deselect. */
|
|
73
|
+
type: "single";
|
|
74
|
+
/** The currently selected option's value. Must be kept in sync by the caller
|
|
75
|
+
* via {@link ListboxSingleControlledProps.onValueChange}. */
|
|
76
|
+
value: string;
|
|
77
|
+
/** Called with the value the user is asking to select. */
|
|
78
|
+
onValueChange?: (value: string) => void;
|
|
79
|
+
/** Forbidden in controlled mode — use
|
|
80
|
+
* {@link ListboxSingleControlledProps.value}. */
|
|
81
|
+
defaultValue?: never;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Multiple-selection mode, uncontrolled — options toggle independently, with
|
|
86
|
+
* the initial set supplied via `defaultValue`.
|
|
87
|
+
*/
|
|
88
|
+
export type ListboxMultipleUncontrolledProps = {
|
|
89
|
+
/** Selects multiple-selection semantics: any number of options can be
|
|
90
|
+
* selected and each toggles independently. Sets `aria-multiselectable` on
|
|
91
|
+
* the root. */
|
|
92
|
+
type: "multiple";
|
|
93
|
+
/** Values of the options selected on first render. Omit to start with
|
|
94
|
+
* nothing selected. */
|
|
95
|
+
defaultValue?: string[];
|
|
96
|
+
/** Forbidden in uncontrolled mode — use
|
|
97
|
+
* {@link ListboxMultipleUncontrolledProps.defaultValue}. */
|
|
98
|
+
value?: never;
|
|
99
|
+
/** Called with the complete next set of selected values whenever the user
|
|
100
|
+
* toggles any option. */
|
|
101
|
+
onValueChange?: (value: string[]) => void;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Multiple-selection mode, controlled — the set of selected values is owned by
|
|
106
|
+
* the caller via `value` / `onValueChange`.
|
|
107
|
+
*/
|
|
108
|
+
export type ListboxMultipleControlledProps = {
|
|
109
|
+
/** Selects multiple-selection semantics: any number of options can be
|
|
110
|
+
* selected and each toggles independently. Sets `aria-multiselectable` on
|
|
111
|
+
* the root. */
|
|
112
|
+
type: "multiple";
|
|
113
|
+
/** The full set of currently selected values. Must be kept in sync by the
|
|
114
|
+
* caller via {@link ListboxMultipleControlledProps.onValueChange}. */
|
|
115
|
+
value: string[];
|
|
116
|
+
/** Called with the complete next set of selected values whenever the user
|
|
117
|
+
* toggles any option. */
|
|
118
|
+
onValueChange?: (value: string[]) => void;
|
|
119
|
+
/** Forbidden in controlled mode — use
|
|
120
|
+
* {@link ListboxMultipleControlledProps.value}. */
|
|
121
|
+
defaultValue?: never;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Props for {@link ListboxRoot | `Listbox.Root`}. Combines the shared
|
|
126
|
+
* {@link ListboxRootBaseProps} with one mode-specific arm — single or
|
|
127
|
+
* multiple, controlled or uncontrolled — so the `value` / `defaultValue` /
|
|
128
|
+
* `onValueChange` shape is enforced per `type`.
|
|
129
|
+
*/
|
|
130
|
+
export type ListboxRootProps = ListboxRootBaseProps &
|
|
131
|
+
(
|
|
132
|
+
| ListboxSingleUncontrolledProps
|
|
133
|
+
| ListboxSingleControlledProps
|
|
134
|
+
| ListboxMultipleUncontrolledProps
|
|
135
|
+
| ListboxMultipleControlledProps
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Props for {@link ListboxOption | `Listbox.Option`} — one selectable choice.
|
|
140
|
+
* `value` identifies it within the listbox; `children` supplies the label that
|
|
141
|
+
* typeahead matches against.
|
|
142
|
+
*/
|
|
143
|
+
export type ListboxOptionProps = HTMLAttributes<HTMLDivElement> & {
|
|
144
|
+
/** Identifies this option within the listbox. It is this string — not the
|
|
145
|
+
* visible label — that is compared against the root's selected `value` /
|
|
146
|
+
* `defaultValue`; use `children` for the label. */
|
|
147
|
+
value: string;
|
|
148
|
+
/** Removes the option from cursor navigation, focus seeding and typeahead,
|
|
149
|
+
* and makes it unselectable by click or key. The option stays in the DOM and
|
|
150
|
+
* in the accessibility tree, marked `aria-disabled`. Also sets
|
|
151
|
+
* `data-disabled=""` for CSS targeting.
|
|
152
|
+
* @default false */
|
|
153
|
+
disabled?: boolean;
|
|
154
|
+
/** Render a single consumer-supplied element in place of the native `<div>`,
|
|
155
|
+
* with the option's `role`, `id`, `aria-selected`, `data-highlighted` and
|
|
156
|
+
* click handler merged onto it via the {@link Slot} pattern. The child must
|
|
157
|
+
* accept a `ref`.
|
|
158
|
+
* @default false */
|
|
159
|
+
asChild?: boolean;
|
|
160
|
+
/** Forwarded to the underlying `HTMLDivElement`. */
|
|
161
|
+
ref?: Ref<HTMLDivElement>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Props for {@link ListboxGroup | `Listbox.Group`} — a named cluster of
|
|
166
|
+
* options. Grouping is presentational; it does not partition navigation.
|
|
167
|
+
*/
|
|
168
|
+
export type ListboxGroupProps = HTMLAttributes<HTMLDivElement> & {
|
|
169
|
+
/** Accessible name for the group, applied as `aria-label`. APG requires
|
|
170
|
+
* every option group to carry a name — supply it either with this prop (an
|
|
171
|
+
* invisible name) or by rendering a
|
|
172
|
+
* {@link ListboxGroupLabelProps | `Listbox.GroupLabel`} heading inside the
|
|
173
|
+
* group, which names it via `aria-labelledby` instead. A rendered
|
|
174
|
+
* `GroupLabel` takes precedence. */
|
|
175
|
+
label?: string;
|
|
176
|
+
/** Render a single consumer-supplied element in place of the native `<div>`,
|
|
177
|
+
* with the group's `role` and `aria-label` merged onto it via the
|
|
178
|
+
* {@link Slot} pattern.
|
|
179
|
+
* @default false */
|
|
180
|
+
asChild?: boolean;
|
|
181
|
+
/** Forwarded to the underlying `HTMLDivElement`. */
|
|
182
|
+
ref?: Ref<HTMLDivElement>;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Props for {@link ListboxGroupLabel | `Listbox.GroupLabel`} — the visible
|
|
187
|
+
* heading that names its enclosing {@link ListboxGroup | `Listbox.Group`}.
|
|
188
|
+
*/
|
|
189
|
+
export type ListboxGroupLabelProps = HTMLAttributes<HTMLDivElement> & {
|
|
190
|
+
/** Render a single consumer-supplied element in place of the native
|
|
191
|
+
* `<div>`, with the heading's `id` and `role="presentation"` merged onto it
|
|
192
|
+
* via the {@link Slot} pattern.
|
|
193
|
+
* @default false */
|
|
194
|
+
asChild?: boolean;
|
|
195
|
+
/** Forwarded to the underlying `HTMLDivElement`. */
|
|
196
|
+
ref?: Ref<HTMLDivElement>;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Context shared from {@link ListboxGroup | `Listbox.Group`} to its label —
|
|
201
|
+
* the id the heading must adopt so the group can point `aria-labelledby` at
|
|
202
|
+
* it, and the registrar telling the group a heading is present.
|
|
203
|
+
*/
|
|
204
|
+
export type ListboxGroupContextValue = {
|
|
205
|
+
/** The DOM id the {@link ListboxGroupLabel} must render with. */
|
|
206
|
+
labelId: string;
|
|
207
|
+
/** Called by the label on mount/unmount so the group knows whether to emit
|
|
208
|
+
* `aria-labelledby`. */
|
|
209
|
+
registerLabel: (present: boolean) => void;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Context shared from {@link ListboxRoot | `Listbox.Root`} to its options —
|
|
214
|
+
* the selected set, the select action, the virtual-focus cursor, option
|
|
215
|
+
* registration, and the id derivation that wires `aria-activedescendant` to
|
|
216
|
+
* each option's `id`.
|
|
217
|
+
*/
|
|
218
|
+
export type ListboxContextValue = {
|
|
219
|
+
/** Every currently selected option value. Single mode holds at most one. */
|
|
220
|
+
selectedValues: string[];
|
|
221
|
+
/** Selects (single) or toggles (multiple) the given option. */
|
|
222
|
+
select: (optionValue: string) => void;
|
|
223
|
+
/** The option the cursor is on, or `undefined` before the root is focused. */
|
|
224
|
+
activeValue: string | undefined;
|
|
225
|
+
/** Moves the cursor onto the given option and scrolls it into view. Used by
|
|
226
|
+
* `Listbox.Option` so a click leaves the cursor where the user clicked. */
|
|
227
|
+
moveCursor: (optionValue: string) => void;
|
|
228
|
+
/** Registers a mounted option's element and disabled state; pass `null` as
|
|
229
|
+
* the element to unregister. */
|
|
230
|
+
registerOption: (
|
|
231
|
+
optionValue: string,
|
|
232
|
+
element: HTMLElement | null,
|
|
233
|
+
disabled?: boolean,
|
|
234
|
+
) => void;
|
|
235
|
+
/** Derives the stable DOM `id` for an option, shared by the option itself
|
|
236
|
+
* and the root's `aria-activedescendant`. */
|
|
237
|
+
getOptionId: (optionValue: string) => string;
|
|
238
|
+
};
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
useMillerColumnsRoot,
|
|
19
19
|
} from "./hooks/index.ts";
|
|
20
20
|
|
|
21
|
-
import { partitionItemChildren } from "./utils";
|
|
21
|
+
import { partitionAriaProps, partitionItemChildren } from "./utils";
|
|
22
22
|
|
|
23
23
|
import type {
|
|
24
24
|
MillerColumnsRootProps,
|
|
@@ -33,11 +33,60 @@ 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
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
36
|
+
* Renders **two** elements: an outer strip container
|
|
37
|
+
* (`data-miller-columns-strip`, `data-orientation="horizontal"`) that the
|
|
38
|
+
* consumer lays out and scrolls, and an inner `role="tree"` widget
|
|
39
|
+
* (`data-miller-columns-tree`) holding only the columns. The split exists
|
|
40
|
+
* because a tree may own nothing but `treeitem` and `group` — keeping the
|
|
41
|
+
* {@link MillerColumnsPreviewPanel | `PreviewPanel`} outside it means the
|
|
42
|
+
* panel is not an illegal child of the tree.
|
|
43
|
+
*
|
|
44
|
+
* Consequently **`aria-*` props land on the tree widget** (an `aria-label`
|
|
45
|
+
* must name the tree, not its scroll container) while every other prop —
|
|
46
|
+
* `className`, `id`, `style`, `data-*` — lands on the strip container.
|
|
47
|
+
*
|
|
48
|
+
* Every {@link MillerColumnsColumn | `MillerColumns.Column`}
|
|
49
|
+
* projects itself into the tree via a portal. Authoring the tree is **recursive** and
|
|
50
|
+
* data-less: there is no `items` prop — an {@link MillerColumnsItem | `Item`}
|
|
51
|
+
* declares its child column as a *nested* `<MillerColumns.Column>`, and the
|
|
52
|
+
* strip flattens those nested columns into a single left-to-right row by
|
|
53
|
+
* rendering one `display: contents` slot per visible depth and portalling
|
|
54
|
+
* each column into its slot. This fixes left-to-right DOM order by slot
|
|
55
|
+
* order rather than by portal commit order.
|
|
56
|
+
*
|
|
57
|
+
* **Controlled vs uncontrolled.** State is the *selection path* — an array
|
|
58
|
+
* of item ids, root column first (`["docs", "guides"]`). The two modes are
|
|
59
|
+
* statically discriminated at the type level:
|
|
60
|
+
*
|
|
61
|
+
* - **Uncontrolled** — pass {@link MillerColumnsRootUncontrolledProps.defaultValue | `defaultValue`}
|
|
62
|
+
* (or omit it to start empty). The component owns the path.
|
|
63
|
+
* - **Controlled** — pass {@link MillerColumnsRootControlledProps.value | `value`}
|
|
64
|
+
* and {@link MillerColumnsRootControlledProps.onValueChange | `onValueChange`}
|
|
65
|
+
* together; every selection defers back through the callback.
|
|
66
|
+
*
|
|
67
|
+
* **Roving tabindex.** The whole tree has exactly **one** Tab stop
|
|
68
|
+
* (`tabIndex={0}`), not one per column. It follows the last-focused item;
|
|
69
|
+
* before any focus it defaults to the deepest selected item, falling back
|
|
70
|
+
* to the first enabled root item. Within a column, Up/Down/Home/End move
|
|
71
|
+
* focus; ArrowRight opens (and steps into) a branch item's child column,
|
|
72
|
+
* ArrowLeft returns to the parent item. Under
|
|
73
|
+
* {@link MillerColumnsRootBaseProps.dir | `dir="rtl"`} that horizontal pair
|
|
74
|
+
* is mirrored, so the step-in key always points at the child column.
|
|
75
|
+
*
|
|
76
|
+
* **Typeahead.** Printable characters move focus to the first item whose
|
|
77
|
+
* label starts with the accumulated query, wrapping, and cycling through
|
|
78
|
+
* matches when the same character repeats. The query is owned by `Root` —
|
|
79
|
+
* so it survives the focus move a match causes — but resolves against the
|
|
80
|
+
* **focused item's own column**, each column being an independent list.
|
|
81
|
+
*
|
|
82
|
+
* **Context.** Root establishes {@link MillerColumnsContext} (selection
|
|
83
|
+
* path, portal slots, item registry, column widths, and focus commands);
|
|
84
|
+
* each Column provides {@link MillerColumnsColumnContext} (its depth) and
|
|
85
|
+
* each Item {@link MillerColumnsItemContext} (`selected` / `hasChildren`).
|
|
86
|
+
* Read the current selection from consumer content with
|
|
87
|
+
* {@link useMillerColumnsSelection}.
|
|
88
|
+
*
|
|
89
|
+
* @extends HTMLDivElement
|
|
41
90
|
*
|
|
42
91
|
* @example
|
|
43
92
|
* ```tsx
|
|
@@ -53,28 +102,32 @@ export function MillerColumnsRoot({
|
|
|
53
102
|
defaultValue,
|
|
54
103
|
value,
|
|
55
104
|
onValueChange,
|
|
105
|
+
dir,
|
|
56
106
|
...rest
|
|
57
107
|
}: MillerColumnsRootProps): ReactElement {
|
|
58
|
-
const { contextValue, columnCount, registerSlotRef, stripRef } =
|
|
59
|
-
useMillerColumnsRoot(value, defaultValue, onValueChange);
|
|
108
|
+
const { contextValue, columnCount, registerSlotRef, stripRef, resolvedDir } =
|
|
109
|
+
useMillerColumnsRoot(value, defaultValue, onValueChange, dir);
|
|
110
|
+
const { aria, rest: containerProps } = partitionAriaProps(rest);
|
|
60
111
|
|
|
61
112
|
return (
|
|
62
113
|
<MillerColumnsContext.Provider value={contextValue}>
|
|
63
114
|
<div
|
|
64
115
|
ref={stripRef}
|
|
65
|
-
|
|
116
|
+
dir={resolvedDir}
|
|
66
117
|
data-miller-columns-strip=""
|
|
67
118
|
data-orientation="horizontal"
|
|
68
|
-
{...
|
|
119
|
+
{...containerProps}
|
|
69
120
|
>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
<div role="tree" data-miller-columns-tree="" {...aria}>
|
|
122
|
+
{Array.from({ length: columnCount }, (_, depth) => (
|
|
123
|
+
<div
|
|
124
|
+
key={depth}
|
|
125
|
+
data-miller-columns-slot=""
|
|
126
|
+
style={{ display: "contents" }}
|
|
127
|
+
ref={registerSlotRef(depth)}
|
|
128
|
+
/>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
78
131
|
{children}
|
|
79
132
|
</div>
|
|
80
133
|
</MillerColumnsContext.Provider>
|
|
@@ -86,9 +139,24 @@ MillerColumnsRoot.displayName = "MillerColumnsRoot";
|
|
|
86
139
|
|
|
87
140
|
/**
|
|
88
141
|
* A single vertical list of items within the strip. Renders a
|
|
89
|
-
* `<div role="group">` that is portal-projected
|
|
90
|
-
*
|
|
91
|
-
*
|
|
142
|
+
* `<div role="group">` (carrying `data-depth`) that is **portal-projected**
|
|
143
|
+
* into the {@link MillerColumnsRoot | `Root`} strip's depth slot, so a
|
|
144
|
+
* `Column` nested inside an `Item` still appears side-by-side with its
|
|
145
|
+
* ancestors rather than in document flow. Until its slot has mounted the
|
|
146
|
+
* component renders `null`.
|
|
147
|
+
*
|
|
148
|
+
* A column holding no registered items carries **`data-empty`** — selecting
|
|
149
|
+
* a childless branch legitimately opens an empty column, and the styling
|
|
150
|
+
* layer needs to be able to say so rather than show a void.
|
|
151
|
+
*
|
|
152
|
+
* It reads its depth from {@link MillerColumnsContext} and re-provides it to
|
|
153
|
+
* descendant {@link MillerColumnsItem | Items} via
|
|
154
|
+
* {@link MillerColumnsColumnContext}. A width set by a
|
|
155
|
+
* {@link MillerColumnsResizeHandle | `ResizeHandle`} in this column is
|
|
156
|
+
* applied here as an inline `width` style (consumer `style` still wins for
|
|
157
|
+
* every other property).
|
|
158
|
+
*
|
|
159
|
+
* @extends HTMLDivElement
|
|
92
160
|
*
|
|
93
161
|
* @example
|
|
94
162
|
* ```tsx
|
|
@@ -102,7 +170,8 @@ export function MillerColumnsColumn({
|
|
|
102
170
|
style,
|
|
103
171
|
...rest
|
|
104
172
|
}: MillerColumnsColumnProps): ReactPortal | null {
|
|
105
|
-
const { slot, depth, width, columnContextValue } =
|
|
173
|
+
const { slot, depth, width, empty, columnContextValue } =
|
|
174
|
+
useMillerColumnsColumn();
|
|
106
175
|
|
|
107
176
|
if (!slot) {
|
|
108
177
|
return null;
|
|
@@ -114,6 +183,7 @@ export function MillerColumnsColumn({
|
|
|
114
183
|
role="group"
|
|
115
184
|
data-miller-columns-column=""
|
|
116
185
|
data-depth={depth}
|
|
186
|
+
{...(empty ? { "data-empty": "" } : {})}
|
|
117
187
|
style={{ ...style, ...(width !== undefined ? { width } : {}) }}
|
|
118
188
|
{...rest}
|
|
119
189
|
>
|
|
@@ -128,27 +198,51 @@ export function MillerColumnsColumn({
|
|
|
128
198
|
MillerColumnsColumn.displayName = "MillerColumnsColumn";
|
|
129
199
|
|
|
130
200
|
/**
|
|
131
|
-
* A single selectable node within a `Column
|
|
132
|
-
* `<div role="treeitem">` for its cell content
|
|
201
|
+
* A single selectable node within a {@link MillerColumnsColumn | `Column`}.
|
|
202
|
+
* Renders a `<div role="treeitem">` for its cell content, with
|
|
203
|
+
* `aria-selected`, `aria-level` (1-based depth), and `data-state` /
|
|
204
|
+
* `data-depth` styling hooks.
|
|
205
|
+
*
|
|
206
|
+
* **Terminal vs ancestor.** Every item on the active path is
|
|
207
|
+
* `data-state="selected"`, including the ancestors the path merely passes
|
|
208
|
+
* through. The deepest one — the item the user actually landed on — also
|
|
209
|
+
* carries **`data-terminal`**. Style the two differently or the strip loses
|
|
210
|
+
* its depth cue: with a uniform selected state there is no way to tell
|
|
211
|
+
* which of four highlighted rows was clicked.
|
|
133
212
|
*
|
|
134
213
|
* The {@link MillerColumnsItemProps.value | `value`} prop is the stable
|
|
135
214
|
* identifier used to match this item against the active selection path.
|
|
136
215
|
*
|
|
137
|
-
* An `Item` becomes a branch by nesting a
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* is a
|
|
216
|
+
* **Branch vs leaf.** An `Item` becomes a *branch* by nesting a
|
|
217
|
+
* `<MillerColumns.Column>` among its children — `partitionItemChildren`
|
|
218
|
+
* splits the children into the cell content and that single optional
|
|
219
|
+
* nested column (matched however deeply it is wrapped in fragments; a
|
|
220
|
+
* second nested column throws). A branch carries `aria-expanded` and
|
|
221
|
+
* mounts its child column (projecting it into the strip) only while the
|
|
222
|
+
* item is selected; an item with no nested column is a *leaf*.
|
|
223
|
+
*
|
|
224
|
+
* **Keyboard & selection.** Clicking or activating (Enter/Space) selects
|
|
225
|
+
* the item, truncating the path to this depth. ArrowRight on a branch opens
|
|
226
|
+
* and steps into the child column; ArrowLeft returns focus to the parent
|
|
227
|
+
* item. The item participates in the tree-wide single roving Tab stop.
|
|
228
|
+
*
|
|
229
|
+
* **Disabled.** Pass `disabled` to render `aria-disabled="true"` /
|
|
230
|
+
* `data-disabled`; the item is skipped by roving navigation and cannot be
|
|
231
|
+
* selected, focused, or activated.
|
|
141
232
|
*
|
|
142
233
|
* **`asChild` prop.** Pass `asChild` to render the cell as a
|
|
143
234
|
* consumer-supplied element (e.g. an `<a>`) instead of the default
|
|
144
235
|
* `<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
|
|
236
|
+
* ref are merged onto that child via the {@link Slot} pattern. A nested
|
|
237
|
+
* `<MillerColumns.Column>` is still declared as a sibling of the cell
|
|
238
|
+
* element.
|
|
147
239
|
*
|
|
148
240
|
* **Ref forwarding.** A `ref` prop (React 19 ref-as-prop style) is
|
|
149
241
|
* forwarded to the rendered element and composed with the library's
|
|
150
242
|
* internal ref.
|
|
151
243
|
*
|
|
244
|
+
* @extends HTMLDivElement
|
|
245
|
+
*
|
|
152
246
|
* @example Leaf
|
|
153
247
|
* ```tsx
|
|
154
248
|
* <MillerColumns.Item value="guides">Guides</MillerColumns.Item>
|
|
@@ -204,12 +298,14 @@ MillerColumnsItem.displayName = "MillerColumnsItem";
|
|
|
204
298
|
* - `data-has-children` — always present (the indicator only renders
|
|
205
299
|
* for branch items).
|
|
206
300
|
*
|
|
301
|
+
* @extends HTMLSpanElement
|
|
302
|
+
*
|
|
207
303
|
* @example
|
|
208
304
|
* ```tsx
|
|
209
305
|
* <MillerColumns.Item value="docs">
|
|
210
306
|
* Docs
|
|
211
307
|
* <MillerColumns.ItemIndicator>▸</MillerColumns.ItemIndicator>
|
|
212
|
-
* <MillerColumns.Column
|
|
308
|
+
* <MillerColumns.Column>...</MillerColumns.Column>
|
|
213
309
|
* </MillerColumns.Item>
|
|
214
310
|
* ```
|
|
215
311
|
*/
|
|
@@ -239,18 +335,43 @@ export function MillerColumnsItemIndicator({
|
|
|
239
335
|
MillerColumnsItemIndicator.displayName = "MillerColumnsItemIndicator";
|
|
240
336
|
|
|
241
337
|
/**
|
|
242
|
-
* A
|
|
243
|
-
*
|
|
244
|
-
*
|
|
338
|
+
* A resize affordance for the column it is rendered in — the WAI-ARIA
|
|
339
|
+
* window-splitter pattern. Renders a focusable
|
|
340
|
+
* `<div role="separator" aria-orientation="vertical" tabIndex={0}>` that
|
|
341
|
+
* drives its column's width as state on the `Root`.
|
|
342
|
+
*
|
|
343
|
+
* **Pointer and keyboard.** Drag it, or focus it and use the arrow keys:
|
|
344
|
+
* the inline-end arrow widens by
|
|
345
|
+
* {@link MillerColumnsResizeHandleProps.step | `step`} and the inline-start
|
|
346
|
+
* arrow narrows (mirrored under `dir="rtl"`), with Home and End jumping to
|
|
347
|
+
* {@link MillerColumnsResizeHandleProps.minWidth | `minWidth`} /
|
|
348
|
+
* {@link MillerColumnsResizeHandleProps.maxWidth | `maxWidth`}. Both paths
|
|
349
|
+
* clamp to the same bounds. End is a no-op while `maxWidth` is unbounded.
|
|
245
350
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
351
|
+
* **Value semantics.** `aria-valuenow` tracks the column's real rendered
|
|
352
|
+
* width — measured with a `ResizeObserver`, so it is truthful before the
|
|
353
|
+
* first resize and follows CSS- or container-driven changes too.
|
|
354
|
+
* `aria-valuemin` always renders; `aria-valuemax` renders only when
|
|
355
|
+
* `maxWidth` is finite.
|
|
356
|
+
*
|
|
357
|
+
* **Give it an accessible name.** A splitter announces as an unnamed
|
|
358
|
+
* separator otherwise — pass `aria-label` (e.g. `"Resize column"`).
|
|
359
|
+
*
|
|
360
|
+
* Must be rendered among a {@link MillerColumnsColumn | `MillerColumns.Column`}'s
|
|
361
|
+
* children — it reads the column's depth from
|
|
362
|
+
* {@link MillerColumnsColumnContext}. Position it with CSS (typically
|
|
248
363
|
* absolutely, against the column's trailing edge).
|
|
249
364
|
*
|
|
365
|
+
* @extends HTMLDivElement
|
|
366
|
+
*
|
|
250
367
|
* @example
|
|
251
368
|
* ```tsx
|
|
252
369
|
* <MillerColumns.Column>
|
|
253
|
-
* <MillerColumns.ResizeHandle
|
|
370
|
+
* <MillerColumns.ResizeHandle
|
|
371
|
+
* aria-label="Resize column"
|
|
372
|
+
* minWidth={120}
|
|
373
|
+
* maxWidth={400}
|
|
374
|
+
* />
|
|
254
375
|
* <MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
255
376
|
* </MillerColumns.Column>
|
|
256
377
|
* ```
|
|
@@ -271,13 +392,22 @@ MillerColumnsResizeHandle.displayName = "MillerColumnsResizeHandle";
|
|
|
271
392
|
* macOS-Finder-style preview pane.
|
|
272
393
|
*
|
|
273
394
|
* Renders a plain `<div data-miller-columns-preview>` as the last child
|
|
274
|
-
* of the strip
|
|
275
|
-
*
|
|
276
|
-
*
|
|
395
|
+
* of the strip — *outside* the inner `role="tree"` widget, since a tree may
|
|
396
|
+
* own only `treeitem` and `group`. The component is deliberately
|
|
397
|
+
* content-agnostic: it does not know how to preview an item, so the
|
|
398
|
+
* consumer supplies whatever the panel should show.
|
|
399
|
+
*
|
|
400
|
+
* While nothing is selected the panel carries **`data-empty`**, so the
|
|
401
|
+
* styling layer can render a resting state without the consumer having to
|
|
402
|
+
* re-derive the selection.
|
|
277
403
|
*
|
|
278
404
|
* Pair it with {@link useMillerColumnsSelection} to render content for
|
|
279
405
|
* the current selection. Author it as the **last child** of `Root`, a
|
|
280
|
-
* sibling of the root `Column`.
|
|
406
|
+
* sibling of the root `Column`. It subscribes to
|
|
407
|
+
* {@link MillerColumnsContext} (so it must live inside a `Root`) but does
|
|
408
|
+
* not portal — it stays in document flow after the projected columns.
|
|
409
|
+
*
|
|
410
|
+
* @extends HTMLDivElement
|
|
281
411
|
*
|
|
282
412
|
* @example
|
|
283
413
|
* ```tsx
|
|
@@ -296,12 +426,23 @@ MillerColumnsResizeHandle.displayName = "MillerColumnsResizeHandle";
|
|
|
296
426
|
*/
|
|
297
427
|
export function MillerColumnsPreviewPanel({
|
|
298
428
|
children,
|
|
429
|
+
forceMount = false,
|
|
299
430
|
...rest
|
|
300
|
-
}: MillerColumnsPreviewPanelProps): ReactElement {
|
|
301
|
-
useMillerColumnsContext();
|
|
431
|
+
}: MillerColumnsPreviewPanelProps): ReactElement | null {
|
|
432
|
+
const { activePath, terminalIsLeaf } = useMillerColumnsContext();
|
|
433
|
+
|
|
434
|
+
// Finder's rule: the preview occupies the slot *after* the selected item, so
|
|
435
|
+
// it exists only where that item has no children to open there instead.
|
|
436
|
+
if (!forceMount && !terminalIsLeaf) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
302
439
|
|
|
303
440
|
return (
|
|
304
|
-
<div
|
|
441
|
+
<div
|
|
442
|
+
data-miller-columns-preview=""
|
|
443
|
+
{...(activePath.length === 0 ? { "data-empty": "" } : {})}
|
|
444
|
+
{...rest}
|
|
445
|
+
>
|
|
305
446
|
{children}
|
|
306
447
|
</div>
|
|
307
448
|
);
|
|
@@ -329,7 +470,7 @@ type MillerColumnsCompound = typeof MillerColumnsRoot & {
|
|
|
329
470
|
/**
|
|
330
471
|
* MillerColumns — a horizontal strip of vertical lists where selecting a
|
|
331
472
|
* node reveals its children in the next column. Use as a namespace
|
|
332
|
-
* (`MillerColumns.Root`, `MillerColumns.Column`, `MillerColumns.Item`,
|
|
473
|
+
* (`MillerColumns.Root`, `MillerColumns.Column`, `MillerColumns.Item`, ...);
|
|
333
474
|
* the default export is also callable as the Root.
|
|
334
475
|
*/
|
|
335
476
|
const MillerColumnsCompound: MillerColumnsCompound = Object.assign(
|