@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
package/src/SkipNav/types.ts
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Props for {@link SkipNav.Link} — `children`, an optional
|
|
5
|
-
* plus all
|
|
4
|
+
* Props for {@link SkipNavLink | `SkipNav.Link`} — `children`, an optional
|
|
5
|
+
* `contentId`, plus all native `<a>` attributes.
|
|
6
6
|
*/
|
|
7
7
|
export type SkipNavLinkProps = {
|
|
8
|
+
/** The visible link text (e.g. `"Skip to main content"`). */
|
|
8
9
|
children?: ReactNode;
|
|
9
10
|
/**
|
|
10
|
-
* Id of the matching {@link SkipNav.Content} target.
|
|
11
|
-
* is derived as `#${contentId}`. Defaults to
|
|
12
|
-
*
|
|
11
|
+
* Id of the matching {@link SkipNavContent | `SkipNav.Content`} target.
|
|
12
|
+
* The link's `href` is derived as `#${contentId}`. Defaults to the shared
|
|
13
|
+
* id (`"primitiv-skip-nav"`), so an unconfigured
|
|
14
|
+
* `SkipNav.Link` / `SkipNav.Content` pair works out of the box. Override
|
|
15
|
+
* only when you also set a matching native `id` prop on `SkipNav.Content`.
|
|
16
|
+
* @default "primitiv-skip-nav"
|
|
13
17
|
*/
|
|
14
18
|
contentId?: string;
|
|
15
19
|
} & AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
|
-
* Props for {@link SkipNav.Content} — `children` plus
|
|
19
|
-
*
|
|
22
|
+
* Props for {@link SkipNavContent | `SkipNav.Content`} — `children` plus
|
|
23
|
+
* all native `<div>` attributes.
|
|
20
24
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
25
|
+
* The component always renders with `tabIndex={-1}` (making it a valid
|
|
26
|
+
* programmatic focus target) and sets `id` to `"primitiv-skip-nav"` by
|
|
27
|
+
* default. Pass a native `id` prop to override the default; when you do,
|
|
28
|
+
* set the same value as `contentId` on {@link SkipNavLink | `SkipNav.Link`}
|
|
29
|
+
* so the anchor's `href` resolves correctly.
|
|
23
30
|
*/
|
|
24
31
|
export type SkipNavContentProps = {
|
|
32
|
+
/** The main page content that follows the skip target. */
|
|
25
33
|
children?: ReactNode;
|
|
26
34
|
} & HTMLAttributes<HTMLDivElement>;
|
package/src/Slider/README.md
CHANGED
|
@@ -10,11 +10,11 @@ styles ship.
|
|
|
10
10
|
```tsx
|
|
11
11
|
import { Slider } from "@primitiv-ui/react";
|
|
12
12
|
|
|
13
|
-
<Slider.Root defaultValue={[40]}
|
|
13
|
+
<Slider.Root defaultValue={[40]}>
|
|
14
14
|
<Slider.Track>
|
|
15
15
|
<Slider.Range />
|
|
16
16
|
</Slider.Track>
|
|
17
|
-
<Slider.Thumb />
|
|
17
|
+
<Slider.Thumb aria-label="Volume" />
|
|
18
18
|
</Slider.Root>
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -56,12 +56,12 @@ Pass `defaultValue` (or omit it for a single thumb seeded at `min`). The
|
|
|
56
56
|
component owns the value internally.
|
|
57
57
|
|
|
58
58
|
```tsx
|
|
59
|
-
<Slider.Root defaultValue={[25, 75]}
|
|
59
|
+
<Slider.Root defaultValue={[25, 75]}>
|
|
60
60
|
<Slider.Track>
|
|
61
61
|
<Slider.Range />
|
|
62
62
|
</Slider.Track>
|
|
63
|
-
<Slider.Thumb />
|
|
64
|
-
<Slider.Thumb />
|
|
63
|
+
<Slider.Thumb aria-label="Minimum" />
|
|
64
|
+
<Slider.Thumb aria-label="Maximum" />
|
|
65
65
|
</Slider.Root>
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -92,10 +92,40 @@ only the final value.
|
|
|
92
92
|
defaultValue={[40]}
|
|
93
93
|
onValueCommit={(value) => save(value)}
|
|
94
94
|
>
|
|
95
|
-
|
|
95
|
+
...
|
|
96
96
|
</Slider.Root>
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
## Labelling
|
|
100
|
+
|
|
101
|
+
**The accessible name goes on the `Thumb`, not the Root.** The Thumb is the
|
|
102
|
+
`role="slider"`; the Root renders a plain `<span>` with no role, so an
|
|
103
|
+
`aria-label` there is attached to nothing and announced nowhere. It is a silent
|
|
104
|
+
failure — the slider still looks and behaves correctly — and the examples in this
|
|
105
|
+
file used to model it.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<Slider.Thumb aria-label="Volume" />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For a **visible** label, give it an id and point the thumb at it. A
|
|
112
|
+
`<label htmlFor>` cannot associate with a `<span role="slider">`, and `Slider`
|
|
113
|
+
does not read `FieldContext` (only `Input`, `Textarea` and `Select` do) — so a
|
|
114
|
+
`Field.Label` does not reach the thumb on its own:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<Field.Root>
|
|
118
|
+
<Field.Label id="volume-label">Volume</Field.Label>
|
|
119
|
+
<Slider.Root defaultValue={[40]}>
|
|
120
|
+
<Slider.Track><Slider.Range /></Slider.Track>
|
|
121
|
+
<Slider.Thumb aria-labelledby="volume-label" />
|
|
122
|
+
</Slider.Root>
|
|
123
|
+
</Field.Root>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
On a range, name each thumb separately — two thumbs sharing one label are
|
|
127
|
+
announced identically, so you cannot tell by ear which end you are on.
|
|
128
|
+
|
|
99
129
|
## Keyboard interaction
|
|
100
130
|
|
|
101
131
|
Each thumb is independently focusable.
|
|
@@ -141,7 +171,7 @@ surrounding `<form>`. Multi-thumb sliders suffix the name with `[]`
|
|
|
141
171
|
```tsx
|
|
142
172
|
<form>
|
|
143
173
|
<Slider.Root defaultValue={[10, 90]} name="price">
|
|
144
|
-
|
|
174
|
+
...
|
|
145
175
|
</Slider.Root>
|
|
146
176
|
</form>
|
|
147
177
|
```
|
package/src/Slider/Slider.tsx
CHANGED
|
@@ -55,6 +55,8 @@ import { getRangeStyle } from "./utils";
|
|
|
55
55
|
*
|
|
56
56
|
* @throws if `min` is not less than `max`, or `step` is not greater than 0.
|
|
57
57
|
*
|
|
58
|
+
* @extends HTMLSpanElement
|
|
59
|
+
*
|
|
58
60
|
* @example Single-thumb, uncontrolled
|
|
59
61
|
* ```tsx
|
|
60
62
|
* <Slider.Root defaultValue={[50]}>
|
|
@@ -153,6 +155,15 @@ SliderRoot.displayName = "SliderRoot";
|
|
|
153
155
|
* **`asChild` prop.** Render a consumer element in place of the `<span>`.
|
|
154
156
|
*
|
|
155
157
|
* @throws if rendered outside a `Slider.Root`.
|
|
158
|
+
*
|
|
159
|
+
* @extends HTMLSpanElement
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```tsx
|
|
163
|
+
* <Slider.Track>
|
|
164
|
+
* <Slider.Range />
|
|
165
|
+
* </Slider.Track>
|
|
166
|
+
* ```
|
|
156
167
|
*/
|
|
157
168
|
export function SliderTrack({
|
|
158
169
|
children,
|
|
@@ -184,6 +195,15 @@ SliderTrack.displayName = "SliderTrack";
|
|
|
184
195
|
* **`asChild` prop.** Render a consumer element in place of the `<span>`.
|
|
185
196
|
*
|
|
186
197
|
* @throws if rendered outside a `Slider.Root`.
|
|
198
|
+
*
|
|
199
|
+
* @extends HTMLSpanElement
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```tsx
|
|
203
|
+
* <Slider.Track>
|
|
204
|
+
* <Slider.Range />
|
|
205
|
+
* </Slider.Track>
|
|
206
|
+
* ```
|
|
187
207
|
*/
|
|
188
208
|
export function SliderRange({
|
|
189
209
|
style,
|
|
@@ -226,12 +246,24 @@ SliderRange.displayName = "SliderRange";
|
|
|
226
246
|
* **Styling hooks.** `data-orientation` and `data-disabled`. Position is set
|
|
227
247
|
* via an inline `style` offset; consumer `style` is shallow-merged on top.
|
|
228
248
|
*
|
|
229
|
-
* Provide an accessible name with `aria-label` /
|
|
249
|
+
* **Accessibility.** Provide an accessible name with `aria-label` /
|
|
250
|
+
* `aria-labelledby`. `aria-valuenow` always tracks the raw number; when that
|
|
251
|
+
* number is not self-explanatory to a screen reader (a currency amount, a
|
|
252
|
+
* date, a named step), pass `aria-valuetext` with the human-readable form
|
|
253
|
+
* (e.g. `aria-valuetext="$50"`) — it is forwarded straight through to the
|
|
254
|
+
* element and read in place of the bare number.
|
|
230
255
|
*
|
|
231
256
|
* **`asChild` prop.** Render a consumer element in place of the `<span>`;
|
|
232
257
|
* the slider role, ARIA value attributes, keyboard handler, and ref merge in.
|
|
233
258
|
*
|
|
234
259
|
* @throws if rendered outside a `Slider.Root`.
|
|
260
|
+
*
|
|
261
|
+
* @extends HTMLSpanElement
|
|
262
|
+
*
|
|
263
|
+
* @example Named value with `aria-valuetext`
|
|
264
|
+
* ```tsx
|
|
265
|
+
* <Slider.Thumb aria-label="Price" aria-valuetext={`$${value}`} />
|
|
266
|
+
* ```
|
|
235
267
|
*/
|
|
236
268
|
export function SliderThumb({
|
|
237
269
|
style,
|
|
@@ -298,18 +330,24 @@ export type TSliderCompound = typeof SliderRoot & {
|
|
|
298
330
|
* - {@link SliderRange | `Slider.Range`} — the filled portion of the track.
|
|
299
331
|
* - {@link SliderThumb | `Slider.Thumb`} — a draggable handle; one per value.
|
|
300
332
|
*
|
|
333
|
+
* The accessible name belongs on the **Thumb**, which is the `role="slider"`.
|
|
334
|
+
* The Root renders a plain `<span>` with no role, so an `aria-label` there is
|
|
335
|
+
* attached to nothing and announced nowhere — a silent failure, because the
|
|
336
|
+
* slider still looks and behaves correctly. This example used to put it on the
|
|
337
|
+
* Root.
|
|
338
|
+
*
|
|
301
339
|
* @example
|
|
302
340
|
* ```tsx
|
|
303
341
|
* import { Slider } from "@primitiv-ui/react";
|
|
304
342
|
*
|
|
305
|
-
* <Slider.Root defaultValue={[40]}
|
|
343
|
+
* <Slider.Root defaultValue={[40]}>
|
|
306
344
|
* <Slider.Track><Slider.Range /></Slider.Track>
|
|
307
|
-
* <Slider.Thumb />
|
|
345
|
+
* <Slider.Thumb aria-label="Volume" />
|
|
308
346
|
* </Slider.Root>
|
|
309
347
|
* ```
|
|
310
348
|
*
|
|
311
349
|
* @see {@link SliderRoot} for state modes, form submission, and validation.
|
|
312
|
-
* @see {@link SliderThumb} for keyboard interaction.
|
|
350
|
+
* @see {@link SliderThumb} for keyboard interaction and labelling.
|
|
313
351
|
*/
|
|
314
352
|
const SliderCompound: TSliderCompound = Object.assign(SliderRoot, {
|
|
315
353
|
Root: SliderRoot,
|
|
@@ -3,19 +3,46 @@ import { createStrictContext } from "../utils/index.ts";
|
|
|
3
3
|
|
|
4
4
|
import type { SliderDirection, SliderOrientation } from "./types";
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Shared state published by {@link SliderRoot | `Slider.Root`} to its
|
|
8
|
+
* sub-components: the current values, range/step bounds, orientation and
|
|
9
|
+
* direction, thumb registration, and the value-change / value-commit
|
|
10
|
+
* callbacks. Read it with {@link useSliderContext}; it throws when a
|
|
11
|
+
* sub-component is rendered outside a `<Slider.Root>`.
|
|
12
|
+
*/
|
|
7
13
|
export type SliderContextValue = {
|
|
14
|
+
/** The current value array — one entry per registered thumb, in order. */
|
|
8
15
|
values: number[];
|
|
16
|
+
/** Lowest value (track start edge). */
|
|
9
17
|
min: number;
|
|
18
|
+
/** Highest value (track end edge). */
|
|
10
19
|
max: number;
|
|
20
|
+
/** Granularity; every value snaps to a multiple of this anchored at `min`. */
|
|
11
21
|
step: number;
|
|
22
|
+
/** Resolved layout axis; see {@link SliderOrientation}. */
|
|
12
23
|
orientation: SliderOrientation;
|
|
24
|
+
/** Resolved reading direction (after inheriting from `DirectionProvider`); see {@link SliderDirection}. */
|
|
13
25
|
dir: SliderDirection;
|
|
26
|
+
/** Whether the axis is reversed relative to its natural direction. */
|
|
14
27
|
inverted: boolean;
|
|
28
|
+
/** Whether interaction is disabled across every part. */
|
|
15
29
|
disabled: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Registers (or, with `null`, unregisters) a thumb by its stable id so Root
|
|
32
|
+
* can order the thumbs and focus them on pointer interaction. Called from
|
|
33
|
+
* each {@link SliderThumb | `Slider.Thumb`} on mount/unmount.
|
|
34
|
+
*/
|
|
16
35
|
registerThumb: (id: string, element: HTMLSpanElement | null) => void;
|
|
36
|
+
/** Registered thumb ids in DOM order; a thumb's index into `values` is its position here. */
|
|
17
37
|
orderedThumbIds: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Sets the thumb at `index` to `value`, clamped between its neighbours (plus
|
|
40
|
+
* `minStepsBetweenThumbs`) and the `[min, max]` bounds. Returns the next
|
|
41
|
+
* value array when it changed, or `null` when the clamped value was
|
|
42
|
+
* unchanged (so callers can skip a redundant commit).
|
|
43
|
+
*/
|
|
18
44
|
setThumbValue: (index: number, value: number) => number[] | null;
|
|
45
|
+
/** Fires `onValueCommit` with the settled array at the end of an interaction. */
|
|
19
46
|
commit: (values: number[]) => void;
|
|
20
47
|
};
|
|
21
48
|
|
package/src/Slider/types.ts
CHANGED
|
@@ -1,57 +1,205 @@
|
|
|
1
1
|
import type { ComponentProps } from "react";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Layout axis of the slider track. `"horizontal"` lays the track out along
|
|
5
|
+
* the x-axis and binds ArrowLeft/ArrowRight (plus ArrowUp/ArrowDown);
|
|
6
|
+
* `"vertical"` lays it out along the y-axis, positions thumbs from the
|
|
7
|
+
* bottom edge, and maps ArrowUp to *increase*.
|
|
8
|
+
*/
|
|
4
9
|
export type SliderOrientation = "horizontal" | "vertical";
|
|
5
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Reading direction of a **horizontal** slider, affecting which end of the
|
|
12
|
+
* track is the minimum and the arrow-key direction. `"rtl"` mirrors the axis
|
|
13
|
+
* so the value increases leftwards. Ignored on the vertical axis.
|
|
14
|
+
*/
|
|
6
15
|
export type SliderDirection = "ltr" | "rtl";
|
|
7
16
|
|
|
8
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Props common to both the controlled ({@link SliderRootControlledProps}) and
|
|
19
|
+
* uncontrolled ({@link SliderRootUncontrolledProps}) `Slider.Root` shapes:
|
|
20
|
+
* the range/step bounds, orientation, reading direction, form `name`, and the
|
|
21
|
+
* native `<span>` attributes.
|
|
22
|
+
*
|
|
23
|
+
* `defaultValue` and `dir` are `Omit`-ted from the inherited `<span>` props
|
|
24
|
+
* because both are redeclared with narrower slider-specific types — the
|
|
25
|
+
* native `defaultValue` is a string/number DOM attribute (replaced by the
|
|
26
|
+
* `number[]` value array) and the native `dir` is `string` (narrowed to
|
|
27
|
+
* {@link SliderDirection}). Omitting them keeps the narrowed types (and this
|
|
28
|
+
* JSDoc) visible to the docs-data extractor rather than the wider DOM ones.
|
|
29
|
+
*/
|
|
9
30
|
export type SliderRootSharedProps = Omit<
|
|
10
31
|
ComponentProps<"span">,
|
|
11
32
|
"defaultValue" | "dir"
|
|
12
33
|
> & {
|
|
34
|
+
/**
|
|
35
|
+
* Lowest value the slider can take — the value at the track's start edge.
|
|
36
|
+
* Must be strictly less than {@link SliderRootSharedProps.max | `max`};
|
|
37
|
+
* `Slider.Root` throws during render otherwise.
|
|
38
|
+
* @default 0
|
|
39
|
+
*/
|
|
13
40
|
min?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Highest value the slider can take — the value at the track's end edge.
|
|
43
|
+
* Must be strictly greater than {@link SliderRootSharedProps.min | `min`};
|
|
44
|
+
* `Slider.Root` throws during render otherwise.
|
|
45
|
+
* @default 100
|
|
46
|
+
*/
|
|
14
47
|
max?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Granularity of the value: every thumb value snaps to a multiple of `step`
|
|
50
|
+
* anchored at `min`, and each arrow-key press moves by one `step` (Page
|
|
51
|
+
* Up/Down move by ten). Fractional steps (e.g. `0.1`) are snapped to the
|
|
52
|
+
* step's decimal precision. Must be greater than `0`; `Slider.Root` throws
|
|
53
|
+
* during render otherwise.
|
|
54
|
+
* @default 1
|
|
55
|
+
*/
|
|
15
56
|
step?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Minimum gap, expressed **in steps**, enforced between adjacent thumbs on a
|
|
59
|
+
* multi-thumb slider. The gap in value units is `minStepsBetweenThumbs *
|
|
60
|
+
* step`. A thumb is clamped so it can never come closer than this to its
|
|
61
|
+
* neighbours (and can never cross them). Has no effect on a single-thumb
|
|
62
|
+
* slider.
|
|
63
|
+
* @default 0
|
|
64
|
+
*/
|
|
16
65
|
minStepsBetweenThumbs?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Layout axis of the track; see {@link SliderOrientation}. Sets
|
|
68
|
+
* `data-orientation` and each thumb's `aria-orientation`, and selects which
|
|
69
|
+
* pointer axis and arrow keys drive the value.
|
|
70
|
+
* @default "horizontal"
|
|
71
|
+
*/
|
|
17
72
|
orientation?: SliderOrientation;
|
|
73
|
+
/**
|
|
74
|
+
* Reading direction of a horizontal slider; see {@link SliderDirection}.
|
|
75
|
+
* Affects which end is the minimum, the arrow-key direction, pointer
|
|
76
|
+
* mapping, and the inline positioning styles. When omitted, it is inherited
|
|
77
|
+
* from the nearest {@link DirectionProvider}, falling back to `"ltr"` when
|
|
78
|
+
* there is no provider; an explicit prop always wins.
|
|
79
|
+
*/
|
|
18
80
|
dir?: SliderDirection;
|
|
81
|
+
/**
|
|
82
|
+
* Reverse the direction the value increases along the axis. Combines with
|
|
83
|
+
* {@link SliderRootSharedProps.orientation | `orientation`} and
|
|
84
|
+
* {@link SliderRootSharedProps.dir | `dir`} to flip the pointer mapping,
|
|
85
|
+
* arrow keys, and the edge the thumb/range offset is anchored to.
|
|
86
|
+
* @default false
|
|
87
|
+
*/
|
|
19
88
|
inverted?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Disable all interaction. Every part receives `data-disabled=""`, thumbs
|
|
91
|
+
* leave the tab order and gain `aria-disabled="true"`, and both keyboard and
|
|
92
|
+
* pointer handlers become no-ops.
|
|
93
|
+
* @default false
|
|
94
|
+
*/
|
|
20
95
|
disabled?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* When set, `Slider.Root` renders one hidden `<input>` per thumb carrying
|
|
98
|
+
* that thumb's value so the slider posts with a surrounding `<form>`. A
|
|
99
|
+
* multi-thumb slider suffixes the name with `[]` (e.g. `name="range"` →
|
|
100
|
+
* `range[]`); a single-thumb slider uses the bare name. When omitted, no
|
|
101
|
+
* hidden inputs are rendered.
|
|
102
|
+
*/
|
|
21
103
|
name?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Render the child element in place of the default `<span>` via the
|
|
106
|
+
* {@link Slot} pattern, merging `Slider.Root`'s `dir`, `data-*` hooks,
|
|
107
|
+
* pointer handler, and ref onto it.
|
|
108
|
+
* @default false
|
|
109
|
+
*/
|
|
22
110
|
asChild?: boolean;
|
|
23
111
|
};
|
|
24
112
|
|
|
25
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Uncontrolled variant of {@link SliderRootProps}: the component owns the
|
|
115
|
+
* value array internally. Seed it with `defaultValue` (or omit it for a
|
|
116
|
+
* single thumb seeded at `min`); `value` is forbidden.
|
|
117
|
+
*/
|
|
26
118
|
export type SliderRootUncontrolledProps = SliderRootSharedProps & {
|
|
119
|
+
/**
|
|
120
|
+
* The value array on first render — one number per
|
|
121
|
+
* {@link SliderThumb | `Slider.Thumb`}, in order (`[value]` for a single
|
|
122
|
+
* thumb, `[low, high]` for a range). After mount the component owns the
|
|
123
|
+
* value. Defaults to `[min]` when omitted.
|
|
124
|
+
*/
|
|
27
125
|
defaultValue?: number[];
|
|
126
|
+
/** Forbidden in uncontrolled mode — use `defaultValue` instead. */
|
|
28
127
|
value?: never;
|
|
128
|
+
/**
|
|
129
|
+
* Called with the full next value array on every change (each arrow-key
|
|
130
|
+
* press and every pointer increment during a drag). Optional in
|
|
131
|
+
* uncontrolled mode.
|
|
132
|
+
*/
|
|
29
133
|
onValueChange?: (value: number[]) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Called **once** with the settled value array when an interaction ends — a
|
|
136
|
+
* pointer release, or a completed keyboard press. Use it to persist only the
|
|
137
|
+
* final value rather than every intermediate step.
|
|
138
|
+
*/
|
|
30
139
|
onValueCommit?: (value: number[]) => void;
|
|
31
140
|
};
|
|
32
141
|
|
|
33
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Controlled variant of {@link SliderRootProps}: the parent owns the value
|
|
144
|
+
* array via `value` and is expected to keep it in sync through
|
|
145
|
+
* `onValueChange`. `defaultValue` is forbidden.
|
|
146
|
+
*/
|
|
34
147
|
export type SliderRootControlledProps = SliderRootSharedProps & {
|
|
148
|
+
/** Forbidden in controlled mode — use `value` instead. */
|
|
35
149
|
defaultValue?: never;
|
|
150
|
+
/**
|
|
151
|
+
* The current value array — one number per
|
|
152
|
+
* {@link SliderThumb | `Slider.Thumb`}, in order (`[value]` for a single
|
|
153
|
+
* thumb, `[low, high]` for a range). Must be kept in sync by the parent via
|
|
154
|
+
* `onValueChange`.
|
|
155
|
+
*/
|
|
36
156
|
value: number[];
|
|
157
|
+
/**
|
|
158
|
+
* Called with the full next value array on every change (each arrow-key
|
|
159
|
+
* press and every pointer increment during a drag). Update `value` from it
|
|
160
|
+
* to keep the slider in sync.
|
|
161
|
+
*/
|
|
37
162
|
onValueChange?: (value: number[]) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Called **once** with the settled value array when an interaction ends — a
|
|
165
|
+
* pointer release, or a completed keyboard press. Use it to persist only the
|
|
166
|
+
* final value rather than every intermediate step.
|
|
167
|
+
*/
|
|
38
168
|
onValueCommit?: (value: number[]) => void;
|
|
39
169
|
};
|
|
40
170
|
|
|
41
|
-
/** Props for `Slider.Root` — the discriminated union of controlled ({@link SliderRootControlledProps}) and uncontrolled ({@link SliderRootUncontrolledProps}) modes. */
|
|
171
|
+
/** Props for {@link SliderRoot | `Slider.Root`} — the discriminated union of controlled ({@link SliderRootControlledProps}) and uncontrolled ({@link SliderRootUncontrolledProps}) modes; only one shape is accepted by TypeScript at a time. */
|
|
42
172
|
export type SliderRootProps =
|
|
43
173
|
| SliderRootUncontrolledProps
|
|
44
174
|
| SliderRootControlledProps;
|
|
45
175
|
|
|
46
|
-
/** Props for `Slider.Track` — the full-length rail; native `<span>`
|
|
176
|
+
/** Props for {@link SliderTrack | `Slider.Track`} — the full-length rail the thumbs travel along; all native `<span>` attributes plus `asChild`. */
|
|
47
177
|
export type SliderTrackProps = ComponentProps<"span"> & {
|
|
178
|
+
/**
|
|
179
|
+
* Render the child element in place of the default `<span>` via the
|
|
180
|
+
* {@link Slot} pattern, merging the `data-orientation` / `data-disabled`
|
|
181
|
+
* hooks onto it.
|
|
182
|
+
* @default false
|
|
183
|
+
*/
|
|
48
184
|
asChild?: boolean;
|
|
49
185
|
};
|
|
50
|
-
/** Props for `Slider.Range` — the filled segment between the
|
|
186
|
+
/** Props for {@link SliderRange | `Slider.Range`} — the filled segment between the lowest and highest thumb (or from the track start to the single thumb); all native `<span>` attributes plus `asChild`. */
|
|
51
187
|
export type SliderRangeProps = ComponentProps<"span"> & {
|
|
188
|
+
/**
|
|
189
|
+
* Render the child element in place of the default `<span>` via the
|
|
190
|
+
* {@link Slot} pattern, merging the `data-orientation` / `data-disabled`
|
|
191
|
+
* hooks and the computed inline positioning `style` onto it.
|
|
192
|
+
* @default false
|
|
193
|
+
*/
|
|
52
194
|
asChild?: boolean;
|
|
53
195
|
};
|
|
54
|
-
/** Props for `Slider.Thumb` — a draggable handle; native `<span>`
|
|
196
|
+
/** Props for {@link SliderThumb | `Slider.Thumb`} — a draggable, focusable handle; render one per entry in the value array. All native `<span>` attributes plus `asChild`. */
|
|
55
197
|
export type SliderThumbProps = ComponentProps<"span"> & {
|
|
198
|
+
/**
|
|
199
|
+
* Render the child element in place of the default `<span>` via the
|
|
200
|
+
* {@link Slot} pattern, merging the `role="slider"` semantics, `aria-value*`
|
|
201
|
+
* attributes, keyboard handler, positioning `style`, and ref onto it.
|
|
202
|
+
* @default false
|
|
203
|
+
*/
|
|
56
204
|
asChild?: boolean;
|
|
57
205
|
};
|
package/src/Slot/Slot.tsx
CHANGED
|
@@ -34,7 +34,15 @@ import { AnyProps, PossibleRef } from "../types";
|
|
|
34
34
|
function setRef<T>(ref: PossibleRef<T>, value: T) {
|
|
35
35
|
if (typeof ref === "function") {
|
|
36
36
|
ref(value);
|
|
37
|
-
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Forcing this guard true dereferences a null/undefined ref (a child with no
|
|
40
|
+
// ref of its own), throwing during React's commit-phase ref assignment. The
|
|
41
|
+
// "no ref of its own" test catches it, but Stryker's vitest runner can't
|
|
42
|
+
// attribute that commit-phase throw (the Avatar/Slot-guard limitation) — not
|
|
43
|
+
// equivalent.
|
|
44
|
+
// Stryker disable next-line ConditionalExpression
|
|
45
|
+
if (ref !== null && ref !== undefined) {
|
|
38
46
|
(ref as RefObject<T>).current = value;
|
|
39
47
|
}
|
|
40
48
|
}
|
|
@@ -42,7 +50,15 @@ function setRef<T>(ref: PossibleRef<T>, value: T) {
|
|
|
42
50
|
/**
|
|
43
51
|
* Combines multiple refs into a single callback ref that sets all of them
|
|
44
52
|
* simultaneously. Handles function refs, object refs (`{ current }`), and
|
|
45
|
-
* `undefined` — any of which may be mixed freely
|
|
53
|
+
* `undefined` — any of which may be mixed freely, in any order, and any
|
|
54
|
+
* number of them.
|
|
55
|
+
*
|
|
56
|
+
* Used throughout the headless components whenever a sub-component owns an
|
|
57
|
+
* internal ref (for measuring, focus management, roving-tabindex, etc.)
|
|
58
|
+
* *and* needs to forward the consumer's own `ref` to the same DOM node —
|
|
59
|
+
* two refs, one element, one callback ref that satisfies both. `Slot`
|
|
60
|
+
* itself uses `composeRefs` internally to merge its `forwardedRef` with the
|
|
61
|
+
* child element's own ref (see {@link Slot}).
|
|
46
62
|
*
|
|
47
63
|
* @example Compose an internal ref with a consumer-supplied external ref:
|
|
48
64
|
* ```tsx
|
|
@@ -67,6 +83,10 @@ function getRef(element: React.ReactElement): PossibleRef<unknown> {
|
|
|
67
83
|
const { props, ref } = element as React.ReactElement & {
|
|
68
84
|
ref?: PossibleRef<unknown>;
|
|
69
85
|
};
|
|
86
|
+
// Stryker disable next-line ConditionalExpression: in React 19, element.ref
|
|
87
|
+
// returns the same ref as props.ref for real elements, so preferring props.ref
|
|
88
|
+
// vs. falling back to ref is indistinguishable at runtime — an equivalent
|
|
89
|
+
// mutant. The ternary stays for the React <=18 shape, where the two differ.
|
|
70
90
|
return (props as AnyProps).ref !== undefined
|
|
71
91
|
? ((props as AnyProps).ref as PossibleRef<unknown>)
|
|
72
92
|
: ref;
|
|
@@ -109,29 +129,67 @@ function mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps {
|
|
|
109
129
|
/**
|
|
110
130
|
* Renders its single child element with the Slot's own props merged in.
|
|
111
131
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
132
|
+
* `Slot` is the composition primitive behind every `asChild` prop in this
|
|
133
|
+
* library (`Button`, `Tabs.Trigger`, `Select.Root`, `Breadcrumb.Link`, and
|
|
134
|
+
* more). A component that normally renders its own DOM element (say,
|
|
135
|
+
* `<button>`) can instead delegate rendering to a single consumer-supplied
|
|
136
|
+
* element — typically a routing library's `<Link>` or a custom styled
|
|
137
|
+
* element — while preserving all of its own behaviour: ARIA attributes,
|
|
138
|
+
* data attributes, event handlers, and `ref` all still land on the
|
|
139
|
+
* rendered DOM node, just not the node `Slot` itself would have created.
|
|
140
|
+
* The pattern avoids an extra wrapper element in the DOM, which matters for
|
|
141
|
+
* layout (no unwanted `<button><a>...</a></button>` nesting) and for CSS
|
|
142
|
+
* selectors that expect a single element.
|
|
116
143
|
*
|
|
117
|
-
* **
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
144
|
+
* **Usage shape** — every component using `Slot` follows the same
|
|
145
|
+
* branch:
|
|
146
|
+
*
|
|
147
|
+
* ```tsx
|
|
148
|
+
* if (asChild) {
|
|
149
|
+
* return <Slot {...ownProps}>{children}</Slot>;
|
|
150
|
+
* }
|
|
151
|
+
* return <button {...ownProps}>{children}</button>;
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* `ownProps` is the exact same object passed to both branches — `Slot`'s
|
|
155
|
+
* job is only to change *where* those props land, never to change what
|
|
156
|
+
* they are.
|
|
124
157
|
*
|
|
125
|
-
* **
|
|
126
|
-
*
|
|
127
|
-
* -
|
|
128
|
-
*
|
|
158
|
+
* **Prop-merging rules** (`Slot`'s own props are the "slot" side, the
|
|
159
|
+
* single child's own props are the "child" side):
|
|
160
|
+
* - **Event handlers** (any prop matching `/^on[A-Z]/`) compose — the
|
|
161
|
+
* **child's** handler fires first, then the **slot's**. This is the
|
|
162
|
+
* opposite composition direction, and a different merge strategy, from
|
|
163
|
+
* the standalone {@link composeEventHandlers} helper (their-handler-first
|
|
164
|
+
* with a `preventDefault()` veto) — the two are unrelated mechanisms
|
|
165
|
+
* that happen to solve adjacent problems. `Slot`'s handlers always both
|
|
166
|
+
* run; there is no opt-out.
|
|
167
|
+
* - **`style`** is shallow-merged — the child's style object wins on key
|
|
168
|
+
* collisions, so a consumer's inline `style` can override individual
|
|
169
|
+
* properties Slot would otherwise set.
|
|
170
|
+
* - **`className`** strings are concatenated (`"slotClass childClass"`),
|
|
171
|
+
* so both the component's own class hooks and the consumer's class
|
|
172
|
+
* survive.
|
|
173
|
+
* - **All other props** default to the child's value; Slot's value is only
|
|
174
|
+
* used as a fallback when the child doesn't specify that prop at all
|
|
175
|
+
* (key absent, or explicitly `undefined`).
|
|
176
|
+
* - **Refs** from both sides are composed via {@link composeRefs}, so
|
|
177
|
+
* neither the component's internal ref nor the consumer's own ref on the
|
|
178
|
+
* child element is lost.
|
|
129
179
|
*
|
|
130
|
-
* **
|
|
180
|
+
* **Constraints.** Exactly one React element child is required — `Slot`
|
|
181
|
+
* throws `"Slot requires exactly one React element child."` for zero
|
|
182
|
+
* children or a non-element child (e.g. a plain string). The child must
|
|
183
|
+
* also accept a `ref` (a DOM element or a `forwardRef` component); passing
|
|
184
|
+
* a plain function component as the child silently drops the ref.
|
|
185
|
+
*
|
|
186
|
+
* **React version compatibility.** Slot reads the child's own ref from
|
|
131
187
|
* `element.props.ref` (React 19+) with a fallback to `element.ref`
|
|
132
188
|
* (React ≤18) so both runtime versions compose refs correctly.
|
|
133
189
|
*
|
|
134
|
-
* @
|
|
190
|
+
* @extends HTMLElement
|
|
191
|
+
*
|
|
192
|
+
* @example Basic `asChild` branch inside a component
|
|
135
193
|
* ```tsx
|
|
136
194
|
* // Inside a component that normally renders <button>:
|
|
137
195
|
* if (asChild) {
|
|
@@ -139,11 +197,28 @@ function mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps {
|
|
|
139
197
|
* }
|
|
140
198
|
* return <button {...buttonProps}>{children}</button>;
|
|
141
199
|
* ```
|
|
200
|
+
*
|
|
201
|
+
* @example Consumer rendering a router link in place of the default element
|
|
202
|
+
* ```tsx
|
|
203
|
+
* <Button asChild onClick={trackClick}>
|
|
204
|
+
* <RouterLink to="/settings" className="settings-link">
|
|
205
|
+
* Settings
|
|
206
|
+
* </RouterLink>
|
|
207
|
+
* </Button>
|
|
208
|
+
* // Rendered DOM: a single <a class="...settings-link"> — trackClick and the
|
|
209
|
+
* // link's own onClick both fire; no wrapping <button>.
|
|
210
|
+
* ```
|
|
142
211
|
*/
|
|
143
212
|
export const Slot: ForwardRefExoticComponent<
|
|
144
213
|
PropsWithoutRef<SlotProps> & RefAttributes<HTMLElement>
|
|
145
214
|
> = forwardRef<HTMLElement, SlotProps>(
|
|
146
215
|
({ children, ...slotProps }, forwardedRef) => {
|
|
216
|
+
// Stryker disable next-line ConditionalExpression: forcing this guard false
|
|
217
|
+
// lets invalid children fall through to a *different* throw (the destructure
|
|
218
|
+
// in getRef below), which the error tests do catch — but Stryker's vitest
|
|
219
|
+
// runner does not attribute that React render-time throw to a covering test
|
|
220
|
+
// (the same limitation seen on Avatar's ref guard). The operator/block twins
|
|
221
|
+
// of this line are scored; only this variant can't be. Not equivalent.
|
|
147
222
|
if (Children.count(children) !== 1 || !isValidElement(children)) {
|
|
148
223
|
throw new Error("Slot requires exactly one React element child.");
|
|
149
224
|
}
|