@primitiv-ui/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/package.json +59 -0
- package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
- package/src/AccessibleIcon/README.md +42 -0
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
- package/src/AccessibleIcon/index.ts +2 -0
- package/src/AccessibleIcon/types.ts +8 -0
- package/src/Accordion/Accordion.tsx +412 -0
- package/src/Accordion/AccordionContext.ts +12 -0
- package/src/Accordion/README.md +202 -0
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
- package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
- package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
- package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
- package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
- package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
- package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
- package/src/Accordion/hooks/index.ts +6 -0
- package/src/Accordion/hooks/useAccordionContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
- package/src/Accordion/hooks/useAccordionItem.ts +22 -0
- package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
- package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
- package/src/Accordion/index.ts +1 -0
- package/src/Accordion/types.ts +81 -0
- package/src/Alert/Alert.tsx +43 -0
- package/src/Alert/README.md +54 -0
- package/src/Alert/__tests__/Alert.test.tsx +28 -0
- package/src/Alert/index.ts +2 -0
- package/src/Alert/types.ts +5 -0
- package/src/Avatar/Avatar.tsx +149 -0
- package/src/Avatar/AvatarContext.ts +20 -0
- package/src/Avatar/README.md +116 -0
- package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
- package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
- package/src/Avatar/hooks/index.ts +2 -0
- package/src/Avatar/hooks/useAvatarContext.ts +1 -0
- package/src/Avatar/hooks/useAvatarImage.ts +40 -0
- package/src/Avatar/index.ts +3 -0
- package/src/Avatar/types.ts +44 -0
- package/src/Breadcrumb/Breadcrumb.tsx +234 -0
- package/src/Breadcrumb/README.md +111 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
- package/src/Breadcrumb/index.ts +2 -0
- package/src/Breadcrumb/types.ts +22 -0
- package/src/Button/Button.tsx +95 -0
- package/src/Button/README.md +112 -0
- package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
- package/src/Button/__tests__/Button.contract.test.tsx +72 -0
- package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
- package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
- package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
- package/src/Button/index.ts +2 -0
- package/src/Button/types.ts +8 -0
- package/src/Carousel/Carousel.tsx +708 -0
- package/src/Carousel/CarouselContext.ts +11 -0
- package/src/Carousel/README.md +848 -0
- package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
- package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
- package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
- package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
- package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
- package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
- package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
- package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
- package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
- package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
- package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
- package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
- package/src/Carousel/hooks/index.ts +4 -0
- package/src/Carousel/hooks/useCarouselContext.ts +13 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
- package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
- package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
- package/src/Carousel/index.ts +3 -0
- package/src/Carousel/types.ts +400 -0
- package/src/Checkbox/Checkbox.tsx +228 -0
- package/src/Checkbox/CheckboxContext.ts +12 -0
- package/src/Checkbox/README.md +156 -0
- package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
- package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
- package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
- package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
- package/src/Checkbox/hooks/index.ts +2 -0
- package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
- package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
- package/src/Checkbox/index.ts +1 -0
- package/src/Checkbox/types.ts +33 -0
- package/src/CheckboxCard/CheckboxCard.tsx +208 -0
- package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
- package/src/CheckboxCard/README.md +114 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
- package/src/CheckboxCard/hooks/index.ts +2 -0
- package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
- package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
- package/src/CheckboxCard/index.ts +3 -0
- package/src/CheckboxCard/types.ts +33 -0
- package/src/Collapsible/Collapsible.tsx +316 -0
- package/src/Collapsible/CollapsibleContext.ts +7 -0
- package/src/Collapsible/README.md +174 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
- package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
- package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
- package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
- package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
- package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
- package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
- package/src/Collapsible/hooks/index.ts +2 -0
- package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
- package/src/Collapsible/index.ts +1 -0
- package/src/Collapsible/types.ts +48 -0
- package/src/ContextMenu/ContextMenu.tsx +1004 -0
- package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
- package/src/ContextMenu/ContextMenuContext.ts +21 -0
- package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
- package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
- package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
- package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
- package/src/ContextMenu/README.md +275 -0
- package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
- package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
- package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
- package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
- package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
- package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
- package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
- package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
- package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
- package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
- package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
- package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
- package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
- package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
- package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
- package/src/ContextMenu/constants.ts +4 -0
- package/src/ContextMenu/index.ts +1 -0
- package/src/ContextMenu/types.ts +199 -0
- package/src/DirectionProvider/DirectionContext.ts +21 -0
- package/src/DirectionProvider/DirectionProvider.tsx +31 -0
- package/src/DirectionProvider/README.md +62 -0
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
- package/src/DirectionProvider/index.ts +3 -0
- package/src/DirectionProvider/types.ts +10 -0
- package/src/Divider/Divider.tsx +57 -0
- package/src/Divider/README.md +57 -0
- package/src/Divider/__tests__/Divider.test.tsx +41 -0
- package/src/Divider/index.ts +1 -0
- package/src/Divider/types.ts +5 -0
- package/src/Dropdown/Dropdown.tsx +842 -0
- package/src/Dropdown/DropdownContentContext.ts +15 -0
- package/src/Dropdown/DropdownContext.ts +17 -0
- package/src/Dropdown/DropdownGroupContext.ts +8 -0
- package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
- package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
- package/src/Dropdown/DropdownSubContext.ts +15 -0
- package/src/Dropdown/README.md +284 -0
- package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
- package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
- package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
- package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
- package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
- package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
- package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
- package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
- package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
- package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
- package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
- package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
- package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
- package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
- package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
- package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
- package/src/Dropdown/constants.ts +4 -0
- package/src/Dropdown/hooks/index.ts +9 -0
- package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
- package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
- package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
- package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
- package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
- package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
- package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
- package/src/Dropdown/index.ts +1 -0
- package/src/Dropdown/types.ts +200 -0
- package/src/EmptyState/EmptyState.tsx +245 -0
- package/src/EmptyState/README.md +129 -0
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
- package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
- package/src/EmptyState/index.ts +2 -0
- package/src/EmptyState/types.ts +21 -0
- package/src/Field/Field.tsx +239 -0
- package/src/Field/FieldContext.ts +22 -0
- package/src/Field/README.md +167 -0
- package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
- package/src/Field/hooks/index.ts +2 -0
- package/src/Field/hooks/useFieldContext.ts +1 -0
- package/src/Field/hooks/useFieldProps.ts +57 -0
- package/src/Field/index.ts +2 -0
- package/src/Field/types.ts +33 -0
- package/src/Fieldset/Fieldset.tsx +104 -0
- package/src/Fieldset/README.md +74 -0
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
- package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
- package/src/Fieldset/index.ts +2 -0
- package/src/Fieldset/types.ts +5 -0
- package/src/Input/Input.tsx +120 -0
- package/src/Input/README.md +180 -0
- package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
- package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
- package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
- package/src/Input/index.ts +2 -0
- package/src/Input/types.ts +7 -0
- package/src/InputGroup/InputGroup.tsx +228 -0
- package/src/InputGroup/README.md +178 -0
- package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
- package/src/InputGroup/index.ts +2 -0
- package/src/InputGroup/types.ts +13 -0
- package/src/MillerColumns/MillerColumns.tsx +329 -0
- package/src/MillerColumns/MillerColumnsContext.ts +25 -0
- package/src/MillerColumns/README.md +278 -0
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
- package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
- package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
- package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
- package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
- package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
- package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
- package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
- package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
- package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
- package/src/MillerColumns/hooks/index.ts +7 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
- package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +3 -0
- package/src/MillerColumns/types.ts +93 -0
- package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
- package/src/MillerColumns/utils.ts +75 -0
- package/src/Modal/Modal.tsx +474 -0
- package/src/Modal/ModalContext.ts +13 -0
- package/src/Modal/README.md +207 -0
- package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
- package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
- package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
- package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
- package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
- package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
- package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
- package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
- package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
- package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
- package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
- package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
- package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
- package/src/Modal/hooks/index.ts +4 -0
- package/src/Modal/hooks/useModalContent.ts +62 -0
- package/src/Modal/hooks/useModalContext.ts +1 -0
- package/src/Modal/hooks/useModalRoot.ts +81 -0
- package/src/Modal/hooks/useModalTrigger.ts +25 -0
- package/src/Modal/index.ts +3 -0
- package/src/Modal/types.ts +76 -0
- package/src/Portal/Portal.tsx +28 -0
- package/src/Portal/README.md +70 -0
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
- package/src/Portal/index.ts +2 -0
- package/src/Portal/types.ts +6 -0
- package/src/Progress/Progress.tsx +178 -0
- package/src/Progress/ProgressContext.ts +15 -0
- package/src/Progress/README.md +112 -0
- package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
- package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
- package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
- package/src/Progress/hooks/index.ts +2 -0
- package/src/Progress/hooks/useProgressContext.ts +1 -0
- package/src/Progress/hooks/useProgressRoot.ts +45 -0
- package/src/Progress/index.ts +3 -0
- package/src/Progress/types.ts +43 -0
- package/src/RadioCard/README.md +133 -0
- package/src/RadioCard/RadioCard.tsx +334 -0
- package/src/RadioCard/RadioCardContext.ts +23 -0
- package/src/RadioCard/RadioCardItemContext.ts +10 -0
- package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
- package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
- package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
- package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
- package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
- package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
- package/src/RadioCard/hooks/index.ts +3 -0
- package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
- package/src/RadioCard/index.ts +4 -0
- package/src/RadioCard/types.ts +51 -0
- package/src/RadioGroup/README.md +185 -0
- package/src/RadioGroup/RadioGroup.tsx +353 -0
- package/src/RadioGroup/RadioGroupContext.ts +23 -0
- package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
- package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
- package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
- package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
- package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
- package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
- package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
- package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
- package/src/RadioGroup/hooks/index.ts +3 -0
- package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
- package/src/RadioGroup/index.ts +1 -0
- package/src/RadioGroup/types.ts +51 -0
- package/src/Select/README.md +203 -0
- package/src/Select/Select.tsx +204 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
- package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
- package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
- package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
- package/src/Select/__tests__/Select.group.test.tsx +42 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
- package/src/Select/index.ts +2 -0
- package/src/Select/types.ts +89 -0
- package/src/SkipNav/README.md +87 -0
- package/src/SkipNav/SkipNav.tsx +116 -0
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
- package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
- package/src/SkipNav/index.ts +1 -0
- package/src/SkipNav/types.ts +26 -0
- package/src/Slider/README.md +215 -0
- package/src/Slider/Slider.tsx +308 -0
- package/src/Slider/SliderContext.ts +24 -0
- package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
- package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
- package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
- package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
- package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
- package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
- package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
- package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
- package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
- package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
- package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
- package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
- package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
- package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
- package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
- package/src/Slider/hooks/index.ts +3 -0
- package/src/Slider/hooks/useSliderContext.ts +1 -0
- package/src/Slider/hooks/useSliderRoot.ts +197 -0
- package/src/Slider/hooks/useSliderThumb.ts +77 -0
- package/src/Slider/index.ts +3 -0
- package/src/Slider/types.ts +48 -0
- package/src/Slider/utils.ts +155 -0
- package/src/Slot/Slot.tsx +158 -0
- package/src/Slot/__tests__/Slot.test.tsx +163 -0
- package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
- package/src/Slot/composeEventHandlers.ts +38 -0
- package/src/Slot/index.ts +3 -0
- package/src/Slot/types.ts +5 -0
- package/src/Status/README.md +50 -0
- package/src/Status/Status.tsx +44 -0
- package/src/Status/__tests__/Status.test.tsx +28 -0
- package/src/Status/index.ts +2 -0
- package/src/Status/types.ts +5 -0
- package/src/Switch/README.md +121 -0
- package/src/Switch/Switch.tsx +167 -0
- package/src/Switch/SwitchContext.ts +10 -0
- package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
- package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
- package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
- package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
- package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
- package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
- package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
- package/src/Switch/hooks/index.ts +2 -0
- package/src/Switch/hooks/useSwitchContext.ts +1 -0
- package/src/Switch/hooks/useSwitchRoot.ts +28 -0
- package/src/Switch/index.ts +3 -0
- package/src/Switch/types.ts +37 -0
- package/src/Table/README.md +205 -0
- package/src/Table/Table.tsx +380 -0
- package/src/Table/__tests__/Table.Body.test.tsx +61 -0
- package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
- package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
- package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
- package/src/Table/__tests__/Table.Head.test.tsx +61 -0
- package/src/Table/__tests__/Table.Header.test.tsx +73 -0
- package/src/Table/__tests__/Table.Root.test.tsx +49 -0
- package/src/Table/__tests__/Table.Row.test.tsx +67 -0
- package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
- package/src/Table/index.ts +1 -0
- package/src/Table/types.ts +63 -0
- package/src/Tabs/README.md +110 -0
- package/src/Tabs/Tabs.tsx +434 -0
- package/src/Tabs/TabsContext.ts +13 -0
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
- package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
- package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
- package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
- package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
- package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
- package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
- package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
- package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
- package/src/Tabs/hooks/index.ts +4 -0
- package/src/Tabs/hooks/useTabsContent.ts +27 -0
- package/src/Tabs/hooks/useTabsContext.ts +1 -0
- package/src/Tabs/hooks/useTabsRoot.ts +148 -0
- package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
- package/src/Tabs/index.ts +3 -0
- package/src/Tabs/types.ts +99 -0
- package/src/Tabs/utils.ts +8 -0
- package/src/Textarea/README.md +98 -0
- package/src/Textarea/Textarea.tsx +93 -0
- package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
- package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
- package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
- package/src/Textarea/index.ts +2 -0
- package/src/Textarea/types.ts +7 -0
- package/src/Toggle/README.md +97 -0
- package/src/Toggle/Toggle.tsx +81 -0
- package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
- package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
- package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
- package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
- package/src/Toggle/index.ts +2 -0
- package/src/Toggle/types.ts +23 -0
- package/src/ToggleGroup/README.md +137 -0
- package/src/ToggleGroup/ToggleGroup.tsx +298 -0
- package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
- package/src/ToggleGroup/hooks/index.ts +2 -0
- package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
- package/src/ToggleGroup/index.ts +2 -0
- package/src/ToggleGroup/types.ts +72 -0
- package/src/Tooltip/README.md +214 -0
- package/src/Tooltip/Tooltip.tsx +260 -0
- package/src/Tooltip/TooltipContext.ts +20 -0
- package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
- package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
- package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
- package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
- package/src/Tooltip/hooks/index.ts +4 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
- package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
- package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/types.ts +64 -0
- package/src/Tree/README.md +339 -0
- package/src/Tree/Tree.tsx +571 -0
- package/src/Tree/TreeContext.ts +24 -0
- package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
- package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
- package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
- package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
- package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
- package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
- package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
- package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
- package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
- package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
- package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
- package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
- package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
- package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
- package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
- package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
- package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
- package/src/Tree/hooks/index.ts +3 -0
- package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
- package/src/Tree/hooks/useTreePath.ts +68 -0
- package/src/Tree/hooks/useTreeRoot.ts +279 -0
- package/src/Tree/index.ts +3 -0
- package/src/Tree/types.ts +224 -0
- package/src/Tree/utils.ts +59 -0
- package/src/VisuallyHidden/README.md +58 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
- package/src/VisuallyHidden/index.ts +2 -0
- package/src/VisuallyHidden/types.ts +5 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useCollection.ts +74 -0
- package/src/hooks/useControllableState.ts +81 -0
- package/src/hooks/useRovingTabindex.ts +178 -0
- package/src/index.ts +38 -0
- package/src/test/intersectionObserverPolyfill.ts +83 -0
- package/src/test/popoverPolyfill.ts +86 -0
- package/src/test/scrollPolyfill.ts +23 -0
- package/src/types.ts +13 -0
- package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
- package/src/utils/__tests__/deriveId.test.ts +28 -0
- package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
- package/src/utils/createStrictContext.ts +49 -0
- package/src/utils/deriveId.ts +31 -0
- package/src/utils/getKeyToActionMap.ts +95 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FocusEvent,
|
|
3
|
+
PointerEvent,
|
|
4
|
+
Ref,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
CarouselAutoplay,
|
|
15
|
+
CarouselContextValue,
|
|
16
|
+
CarouselIds,
|
|
17
|
+
CarouselImperativeApi,
|
|
18
|
+
CarouselSnapAlign,
|
|
19
|
+
CarouselTransition,
|
|
20
|
+
CarouselTranslations,
|
|
21
|
+
} from "../types";
|
|
22
|
+
|
|
23
|
+
const EMPTY_IDS: CarouselIds = {};
|
|
24
|
+
|
|
25
|
+
const DEFAULT_AUTOPLAY_DELAY_MS = 4000;
|
|
26
|
+
|
|
27
|
+
const DEFAULT_TRANSLATIONS: Required<CarouselTranslations> = {
|
|
28
|
+
slideLabel: ({ index, total }) => `${index} of ${total}`,
|
|
29
|
+
indicatorLabel: ({ index }) => `Slide ${index}`,
|
|
30
|
+
startSlideshow: "Start automatic slide show",
|
|
31
|
+
stopSlideshow: "Stop automatic slide show",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function resolveAutoplay(autoplay: CarouselAutoplay | undefined): {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
delay: number;
|
|
37
|
+
} {
|
|
38
|
+
if (autoplay === true)
|
|
39
|
+
return { enabled: true, delay: DEFAULT_AUTOPLAY_DELAY_MS };
|
|
40
|
+
if (autoplay && typeof autoplay === "object")
|
|
41
|
+
return { enabled: true, delay: autoplay.delay };
|
|
42
|
+
return { enabled: false, delay: DEFAULT_AUTOPLAY_DELAY_MS };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type UseCarouselRootProps = {
|
|
46
|
+
/** Uncontrolled seed for the active page. Defaults to `0`. */
|
|
47
|
+
defaultPage?: number;
|
|
48
|
+
/** Controlled active page. When provided, the hook is in controlled
|
|
49
|
+
* mode and defers all state changes back through `onPageChange`. */
|
|
50
|
+
page?: number;
|
|
51
|
+
/** Required when `page` is provided. Invoked with the next page
|
|
52
|
+
* value the Root would like to advance to. */
|
|
53
|
+
onPageChange?: (page: number) => void;
|
|
54
|
+
/** Uncontrolled seed for the playing flag. Defaults to `false`. */
|
|
55
|
+
defaultPlaying?: boolean;
|
|
56
|
+
/** Controlled playing flag. When provided, the hook is in controlled
|
|
57
|
+
* mode and defers all state changes back through `onPlayingChange`. */
|
|
58
|
+
playing?: boolean;
|
|
59
|
+
/** Required when `playing` is provided. Invoked with the proposed
|
|
60
|
+
* next playing value. */
|
|
61
|
+
onPlayingChange?: (playing: boolean) => void;
|
|
62
|
+
/** Autoplay configuration — see {@link CarouselAutoplay}. */
|
|
63
|
+
autoplay?: CarouselAutoplay;
|
|
64
|
+
/** Number of slides visible per page. Defaults to `1`. */
|
|
65
|
+
slidesPerPage?: number;
|
|
66
|
+
/** Slides advanced per Prev/Next click — `"auto"` (default) is
|
|
67
|
+
* `slidesPerPage`. */
|
|
68
|
+
slidesPerMove?: number | "auto";
|
|
69
|
+
/** Override the default user-visible strings — see
|
|
70
|
+
* {@link CarouselTranslations}. */
|
|
71
|
+
translations?: CarouselTranslations;
|
|
72
|
+
/** Custom DOM ids for the rendered sub-components — see
|
|
73
|
+
* {@link CarouselIds}. */
|
|
74
|
+
ids?: CarouselIds;
|
|
75
|
+
/** Visual transition mode — see {@link CarouselTransition}. */
|
|
76
|
+
transition?: CarouselTransition;
|
|
77
|
+
/** Scroll-snap alignment — see {@link CarouselSnapAlign}. */
|
|
78
|
+
snapAlign?: CarouselSnapAlign;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Owns the Root-side state for a Carousel: the slide registration map,
|
|
83
|
+
* the ordered list of registered slide keys, and the active page.
|
|
84
|
+
*
|
|
85
|
+
* Slide keys are tracked as `useState` so registration and unregistration
|
|
86
|
+
* trigger a re-render — descendants that depend on slide order, count, or
|
|
87
|
+
* the active page (e.g. each `Carousel.Slide`'s `data-index` /
|
|
88
|
+
* `data-total` / `data-state`, and the prev/next triggers' `disabled`
|
|
89
|
+
* attribute) update automatically when slides mount and unmount.
|
|
90
|
+
*
|
|
91
|
+
* The active page supports two modes, statically discriminated at the
|
|
92
|
+
* `CarouselRootProps` level:
|
|
93
|
+
*
|
|
94
|
+
* - **Uncontrolled** — pass `defaultPage` (or omit it for `0`); the hook
|
|
95
|
+
* owns updates internally via the `next` / `previous` callbacks.
|
|
96
|
+
* - **Controlled** — pass `page` and `onPageChange`; the hook defers
|
|
97
|
+
* every change back through `onPageChange` and reads the live value
|
|
98
|
+
* from the `page` prop on every render.
|
|
99
|
+
*
|
|
100
|
+
* `next` and `previous` clamp at the ends — `next` is a no-op on the
|
|
101
|
+
* last page and `previous` a no-op on the first. `canGoNext` /
|
|
102
|
+
* `canGoPrevious` on the published context drive the `disabled`
|
|
103
|
+
* attribute on the prev/next triggers.
|
|
104
|
+
*/
|
|
105
|
+
export function useCarouselRoot(
|
|
106
|
+
{
|
|
107
|
+
defaultPage = 0,
|
|
108
|
+
page,
|
|
109
|
+
onPageChange,
|
|
110
|
+
defaultPlaying = false,
|
|
111
|
+
playing,
|
|
112
|
+
onPlayingChange,
|
|
113
|
+
autoplay,
|
|
114
|
+
slidesPerPage = 1,
|
|
115
|
+
slidesPerMove = "auto",
|
|
116
|
+
translations,
|
|
117
|
+
ids = EMPTY_IDS,
|
|
118
|
+
transition = "slide",
|
|
119
|
+
snapAlign = "start",
|
|
120
|
+
}: UseCarouselRootProps = {},
|
|
121
|
+
imperativeRef?: Ref<CarouselImperativeApi>,
|
|
122
|
+
) {
|
|
123
|
+
const { enabled: autoplayEnabled, delay: autoplayDelay } =
|
|
124
|
+
resolveAutoplay(autoplay);
|
|
125
|
+
const slidesRef = useRef<Map<string, HTMLDivElement>>(new Map());
|
|
126
|
+
const [slideKeys, setSlideKeys] = useState<string[]>([]);
|
|
127
|
+
const [internalPage, setInternalPage] = useState(defaultPage);
|
|
128
|
+
const isControlled = page !== undefined;
|
|
129
|
+
const rawPage = isControlled ? (page as number) : internalPage;
|
|
130
|
+
const total = slideKeys.length;
|
|
131
|
+
const effectiveSlidesPerMove =
|
|
132
|
+
slidesPerMove === "auto" ? slidesPerPage : slidesPerMove;
|
|
133
|
+
// "auto" mode keeps the existing ceil(total / slidesPerPage) formula
|
|
134
|
+
// and accepts a partial last page; numeric mode uses
|
|
135
|
+
// floor((total - slidesPerPage) / slidesPerMove) + 1 so the active
|
|
136
|
+
// window is always full. Math.ceil(0 / N) === 0 so the empty-slide
|
|
137
|
+
// case still gives totalPages === 0.
|
|
138
|
+
const totalPages =
|
|
139
|
+
total === 0
|
|
140
|
+
? 0
|
|
141
|
+
: total <= slidesPerPage
|
|
142
|
+
? 1
|
|
143
|
+
: slidesPerMove === "auto"
|
|
144
|
+
? Math.ceil(total / slidesPerPage)
|
|
145
|
+
: Math.floor((total - slidesPerPage) / effectiveSlidesPerMove) + 1;
|
|
146
|
+
|
|
147
|
+
// In controlled mode an out-of-range page prop is a consumer error —
|
|
148
|
+
// throw loudly so it surfaces during development rather than shipping
|
|
149
|
+
// as a silent no-op carousel.
|
|
150
|
+
if (isControlled && totalPages > 0 && (rawPage < 0 || rawPage >= totalPages)) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Carousel: page index ${rawPage} is out of range (totalPages: ${totalPages})`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
// In uncontrolled mode, clamp silently: the page can temporarily
|
|
156
|
+
// outlive totalPages during slide re-registration (HMR, tab switch)
|
|
157
|
+
// where slides unmount and re-register one by one, briefly lowering
|
|
158
|
+
// totalPages below the previously-valid current page.
|
|
159
|
+
const currentPage =
|
|
160
|
+
!isControlled && totalPages > 0
|
|
161
|
+
? Math.max(0, Math.min(rawPage, totalPages - 1))
|
|
162
|
+
: rawPage;
|
|
163
|
+
|
|
164
|
+
const [internalPlaying, setInternalPlaying] = useState(defaultPlaying);
|
|
165
|
+
const isPlayingControlled = playing !== undefined;
|
|
166
|
+
const currentPlaying = isPlayingControlled ? playing : internalPlaying;
|
|
167
|
+
|
|
168
|
+
// Tracked separately so a focus move between two descendants of the
|
|
169
|
+
// Root (e.g. tabbing from Prev to Next) doesn't release the pause —
|
|
170
|
+
// mouseLeave only fires when the pointer exits the Root's outer
|
|
171
|
+
// boundary, and onBlur's relatedTarget tells us whether focus is
|
|
172
|
+
// still inside.
|
|
173
|
+
const [hovered, setHovered] = useState(false);
|
|
174
|
+
const [focused, setFocused] = useState(false);
|
|
175
|
+
// Touch is tracked separately because mouseenter doesn't always
|
|
176
|
+
// fire on touch devices, and pointerdown/up are filtered to
|
|
177
|
+
// pointerType === "touch" so mouse interaction stays on the
|
|
178
|
+
// hover-pause path.
|
|
179
|
+
const [touchActive, setTouchActive] = useState(false);
|
|
180
|
+
|
|
181
|
+
// Per the WAI-ARIA APG carousel example: when the user explicitly
|
|
182
|
+
// resumes the slideshow via PlayPauseTrigger, the hover/focus pause
|
|
183
|
+
// is suspended for the lifetime of that playing session — otherwise
|
|
184
|
+
// they'd fight a pause every time the pointer was already over the
|
|
185
|
+
// carousel. The ref is set inside togglePlaying() on a false→true
|
|
186
|
+
// transition and reset by the effect below when playing flips back
|
|
187
|
+
// to false. External (non-trigger) playing changes don't set it.
|
|
188
|
+
const userInitiatedPlayRef = useRef(false);
|
|
189
|
+
const suspended =
|
|
190
|
+
(hovered || focused || touchActive) && !userInitiatedPlayRef.current;
|
|
191
|
+
|
|
192
|
+
// Boundary derivation: navigation requires at least one page. The
|
|
193
|
+
// ends clamp at the last page (which, with slidesPerPage > 1, is
|
|
194
|
+
// generally before the last slide).
|
|
195
|
+
const canGoPrevious = totalPages > 0 && currentPage > 0;
|
|
196
|
+
const canGoNext = totalPages > 0 && currentPage < totalPages - 1;
|
|
197
|
+
|
|
198
|
+
const registerSlide = useCallback(
|
|
199
|
+
(key: string, element: HTMLDivElement | null) => {
|
|
200
|
+
if (element) {
|
|
201
|
+
slidesRef.current.set(key, element);
|
|
202
|
+
} else {
|
|
203
|
+
slidesRef.current.delete(key);
|
|
204
|
+
}
|
|
205
|
+
setSlideKeys(Array.from(slidesRef.current.keys()));
|
|
206
|
+
},
|
|
207
|
+
[],
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// next/previous are only reachable via Carousel.NextTrigger /
|
|
211
|
+
// Carousel.PreviousTrigger — both are disabled by the HTML disabled
|
|
212
|
+
// attribute when canGoNext / canGoPrevious is false, so the click
|
|
213
|
+
// never fires at boundaries. Guards become reachable (and necessary)
|
|
214
|
+
// once the imperative API or autoplay land; they're added then.
|
|
215
|
+
const isProgrammaticScrollRef = useRef(false);
|
|
216
|
+
|
|
217
|
+
const next = useCallback(() => {
|
|
218
|
+
if (currentPage >= totalPages - 1) return;
|
|
219
|
+
isProgrammaticScrollRef.current = true;
|
|
220
|
+
const target = currentPage + 1;
|
|
221
|
+
if (isControlled) {
|
|
222
|
+
onPageChange?.(target);
|
|
223
|
+
} else {
|
|
224
|
+
setInternalPage(target);
|
|
225
|
+
}
|
|
226
|
+
}, [currentPage, totalPages, isControlled, onPageChange]);
|
|
227
|
+
|
|
228
|
+
const previous = useCallback(() => {
|
|
229
|
+
if (currentPage <= 0) return;
|
|
230
|
+
isProgrammaticScrollRef.current = true;
|
|
231
|
+
const target = currentPage - 1;
|
|
232
|
+
if (isControlled) {
|
|
233
|
+
onPageChange?.(target);
|
|
234
|
+
} else {
|
|
235
|
+
setInternalPage(target);
|
|
236
|
+
}
|
|
237
|
+
}, [currentPage, isControlled, onPageChange]);
|
|
238
|
+
|
|
239
|
+
const goTo = useCallback(
|
|
240
|
+
(target: number) => {
|
|
241
|
+
if (isControlled) {
|
|
242
|
+
onPageChange?.(target);
|
|
243
|
+
} else {
|
|
244
|
+
setInternalPage(target);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
[isControlled, onPageChange],
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Autoplay timer. Schedules a single setTimeout per active page; when
|
|
251
|
+
// next() runs it bumps currentPage, which retriggers the effect with
|
|
252
|
+
// a fresh timer. canGoNext gates the schedule so autoplay stops at
|
|
253
|
+
// the last page.
|
|
254
|
+
// The suspended flag pauses the timer while the user is hovering or
|
|
255
|
+
// has focus inside the Root, per WCAG 2.2.2.
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (!autoplayEnabled || !currentPlaying || !canGoNext || suspended) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const id = setTimeout(() => {
|
|
261
|
+
next();
|
|
262
|
+
}, autoplayDelay);
|
|
263
|
+
return () => clearTimeout(id);
|
|
264
|
+
}, [
|
|
265
|
+
autoplayEnabled,
|
|
266
|
+
currentPlaying,
|
|
267
|
+
canGoNext,
|
|
268
|
+
suspended,
|
|
269
|
+
autoplayDelay,
|
|
270
|
+
next,
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
// Handlers spread onto the Root <section>. mouseEnter / mouseLeave
|
|
274
|
+
// fire once at the outer boundary (they don't bubble through inner
|
|
275
|
+
// hovers). onFocus / onBlur in React do bubble; relatedTarget on
|
|
276
|
+
// onBlur tells us whether focus is moving to another descendant —
|
|
277
|
+
// in which case we keep `focused` true.
|
|
278
|
+
const onMouseEnter = useCallback(() => setHovered(true), []);
|
|
279
|
+
const onMouseLeave = useCallback(() => setHovered(false), []);
|
|
280
|
+
const onFocus = useCallback(() => setFocused(true), []);
|
|
281
|
+
const onBlur = useCallback((event: FocusEvent<HTMLElement>) => {
|
|
282
|
+
const next = event.relatedTarget;
|
|
283
|
+
if (!next || !event.currentTarget.contains(next)) {
|
|
284
|
+
setFocused(false);
|
|
285
|
+
}
|
|
286
|
+
}, []);
|
|
287
|
+
const onPointerDown = useCallback((event: PointerEvent<HTMLElement>) => {
|
|
288
|
+
if (event.pointerType === "touch") setTouchActive(true);
|
|
289
|
+
}, []);
|
|
290
|
+
// pointerup / pointercancel always release the suspension — only
|
|
291
|
+
// pointerdown is gated on pointerType, so a non-touch release is a
|
|
292
|
+
// no-op anyway (touchActive was already false).
|
|
293
|
+
const onPointerUp = useCallback(() => setTouchActive(false), []);
|
|
294
|
+
const onPointerCancel = useCallback(() => setTouchActive(false), []);
|
|
295
|
+
|
|
296
|
+
const rootHandlers = useMemo(
|
|
297
|
+
() => ({
|
|
298
|
+
onMouseEnter,
|
|
299
|
+
onMouseLeave,
|
|
300
|
+
onFocus,
|
|
301
|
+
onBlur,
|
|
302
|
+
onPointerDown,
|
|
303
|
+
onPointerUp,
|
|
304
|
+
onPointerCancel,
|
|
305
|
+
}),
|
|
306
|
+
[
|
|
307
|
+
onMouseEnter,
|
|
308
|
+
onMouseLeave,
|
|
309
|
+
onFocus,
|
|
310
|
+
onBlur,
|
|
311
|
+
onPointerDown,
|
|
312
|
+
onPointerUp,
|
|
313
|
+
onPointerCancel,
|
|
314
|
+
],
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const play = useCallback(() => {
|
|
318
|
+
userInitiatedPlayRef.current = true;
|
|
319
|
+
if (isPlayingControlled) {
|
|
320
|
+
onPlayingChange?.(true);
|
|
321
|
+
} else {
|
|
322
|
+
setInternalPlaying(true);
|
|
323
|
+
}
|
|
324
|
+
}, [isPlayingControlled, onPlayingChange]);
|
|
325
|
+
|
|
326
|
+
const pause = useCallback(() => {
|
|
327
|
+
if (isPlayingControlled) {
|
|
328
|
+
onPlayingChange?.(false);
|
|
329
|
+
} else {
|
|
330
|
+
setInternalPlaying(false);
|
|
331
|
+
}
|
|
332
|
+
}, [isPlayingControlled, onPlayingChange]);
|
|
333
|
+
|
|
334
|
+
const togglePlaying = useCallback(() => {
|
|
335
|
+
if (currentPlaying) pause();
|
|
336
|
+
else play();
|
|
337
|
+
}, [currentPlaying, play, pause]);
|
|
338
|
+
|
|
339
|
+
const [refreshTick, setRefreshTick] = useState(0);
|
|
340
|
+
const refresh = useCallback(() => setRefreshTick((t) => t + 1), []);
|
|
341
|
+
|
|
342
|
+
const getProgress = useCallback(
|
|
343
|
+
() => ({
|
|
344
|
+
page: currentPage,
|
|
345
|
+
totalPages,
|
|
346
|
+
value: totalPages > 1 ? currentPage / (totalPages - 1) : 0,
|
|
347
|
+
}),
|
|
348
|
+
[currentPage, totalPages],
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Visibility tracking is a ref (not state) because callers read on
|
|
352
|
+
// demand via isInView() and the IO callback in useCarouselViewport
|
|
353
|
+
// mutates many entries per tick — re-rendering on each addition
|
|
354
|
+
// would be wasteful and unnecessary.
|
|
355
|
+
const visibleSlideIndicesRef = useRef<Set<number>>(new Set());
|
|
356
|
+
const setSlideInView = useCallback((slideIndex: number, inView: boolean) => {
|
|
357
|
+
if (inView) visibleSlideIndicesRef.current.add(slideIndex);
|
|
358
|
+
else visibleSlideIndicesRef.current.delete(slideIndex);
|
|
359
|
+
}, []);
|
|
360
|
+
const isInView = useCallback(
|
|
361
|
+
(slideIndex: number) => visibleSlideIndicesRef.current.has(slideIndex),
|
|
362
|
+
[],
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
useImperativeHandle(
|
|
366
|
+
imperativeRef,
|
|
367
|
+
() => ({
|
|
368
|
+
next,
|
|
369
|
+
previous,
|
|
370
|
+
goTo,
|
|
371
|
+
play,
|
|
372
|
+
pause,
|
|
373
|
+
refresh,
|
|
374
|
+
getProgress,
|
|
375
|
+
isInView,
|
|
376
|
+
}),
|
|
377
|
+
[next, previous, goTo, play, pause, refresh, getProgress, isInView],
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
// Reset the user-initiated flag when the playing session ends, so a
|
|
381
|
+
// subsequent external (non-trigger) flip to playing=true doesn't
|
|
382
|
+
// inherit the bypass.
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
if (!currentPlaying) {
|
|
385
|
+
userInitiatedPlayRef.current = false;
|
|
386
|
+
}
|
|
387
|
+
}, [currentPlaying]);
|
|
388
|
+
|
|
389
|
+
const isAutoRotating = autoplayEnabled && currentPlaying;
|
|
390
|
+
|
|
391
|
+
const resolvedTranslations = useMemo<Required<CarouselTranslations>>(
|
|
392
|
+
() => ({ ...DEFAULT_TRANSLATIONS, ...translations }),
|
|
393
|
+
[translations],
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const contextValue = useMemo<CarouselContextValue>(
|
|
397
|
+
() => ({
|
|
398
|
+
registerSlide,
|
|
399
|
+
slidesRef,
|
|
400
|
+
slideKeys,
|
|
401
|
+
slidesPerPage,
|
|
402
|
+
effectiveSlidesPerMove,
|
|
403
|
+
totalPages,
|
|
404
|
+
currentPage,
|
|
405
|
+
canGoNext,
|
|
406
|
+
canGoPrevious,
|
|
407
|
+
next,
|
|
408
|
+
previous,
|
|
409
|
+
goTo,
|
|
410
|
+
playing: currentPlaying,
|
|
411
|
+
togglePlaying,
|
|
412
|
+
autoplayEnabled,
|
|
413
|
+
isAutoRotating,
|
|
414
|
+
translations: resolvedTranslations,
|
|
415
|
+
ids,
|
|
416
|
+
transition,
|
|
417
|
+
snapAlign,
|
|
418
|
+
refreshTick,
|
|
419
|
+
visibleSlideIndicesRef,
|
|
420
|
+
setSlideInView,
|
|
421
|
+
isProgrammaticScrollRef,
|
|
422
|
+
}),
|
|
423
|
+
[
|
|
424
|
+
registerSlide,
|
|
425
|
+
slidesRef,
|
|
426
|
+
slideKeys,
|
|
427
|
+
slidesPerPage,
|
|
428
|
+
effectiveSlidesPerMove,
|
|
429
|
+
totalPages,
|
|
430
|
+
currentPage,
|
|
431
|
+
canGoNext,
|
|
432
|
+
canGoPrevious,
|
|
433
|
+
next,
|
|
434
|
+
previous,
|
|
435
|
+
goTo,
|
|
436
|
+
currentPlaying,
|
|
437
|
+
togglePlaying,
|
|
438
|
+
autoplayEnabled,
|
|
439
|
+
isAutoRotating,
|
|
440
|
+
resolvedTranslations,
|
|
441
|
+
ids,
|
|
442
|
+
transition,
|
|
443
|
+
snapAlign,
|
|
444
|
+
refreshTick,
|
|
445
|
+
setSlideInView,
|
|
446
|
+
],
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
return { contextValue, rootHandlers };
|
|
450
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useId } from "react";
|
|
2
|
+
|
|
3
|
+
import { useCarouselContext } from "./useCarouselContext";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Per-slide hook: self-registers with the Root so the Root can compute
|
|
7
|
+
* the live slide count, and exposes the slide's zero-based `index` plus
|
|
8
|
+
* the current `total`.
|
|
9
|
+
*
|
|
10
|
+
* The returned `slideRef` is a callback ref — attached to the rendered
|
|
11
|
+
* slide element so registration runs synchronously during commit. Using
|
|
12
|
+
* a callback ref (rather than `useEffect`) keeps DOM-attachment and
|
|
13
|
+
* registration in lockstep, which matters when slides mount or unmount
|
|
14
|
+
* during a render pass driven by an external state change.
|
|
15
|
+
*/
|
|
16
|
+
export function useCarouselSlide() {
|
|
17
|
+
const {
|
|
18
|
+
registerSlide,
|
|
19
|
+
slideKeys,
|
|
20
|
+
slidesPerPage,
|
|
21
|
+
effectiveSlidesPerMove,
|
|
22
|
+
currentPage,
|
|
23
|
+
} = useCarouselContext();
|
|
24
|
+
const slideKey = useId();
|
|
25
|
+
|
|
26
|
+
const slideRef = useCallback(
|
|
27
|
+
(element: HTMLDivElement | null) => {
|
|
28
|
+
registerSlide(slideKey, element);
|
|
29
|
+
},
|
|
30
|
+
[registerSlide, slideKey],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const index = slideKeys.indexOf(slideKey);
|
|
34
|
+
const total = slideKeys.length;
|
|
35
|
+
// The active "page" covers a window of slidesPerPage slides starting
|
|
36
|
+
// at pageOffset = currentPage * effectiveSlidesPerMove. With
|
|
37
|
+
// slidesPerMove="auto" this collapses to non-overlapping page-sized
|
|
38
|
+
// groups; with a numeric slidesPerMove the windows overlap.
|
|
39
|
+
const pageOffset = currentPage * effectiveSlidesPerMove;
|
|
40
|
+
const isActive =
|
|
41
|
+
index >= 0 && index >= pageOffset && index < pageOffset + slidesPerPage;
|
|
42
|
+
const state: "active" | "inactive" = isActive ? "active" : "inactive";
|
|
43
|
+
|
|
44
|
+
return { slideRef, index, total, state };
|
|
45
|
+
}
|