@primitiv-ui/react 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +6 -1
- package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
- package/src/AccessibleIcon/types.ts +15 -2
- package/src/Accordion/Accordion.tsx +25 -4
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
- package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
- package/src/Accordion/types.ts +72 -21
- package/src/Alert/Alert.tsx +36 -7
- package/src/Alert/__tests__/Alert.test.tsx +5 -0
- package/src/Alert/types.ts +18 -2
- package/src/Avatar/Avatar.tsx +99 -15
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
- package/src/Avatar/hooks/useAvatarImage.ts +16 -0
- package/src/Avatar/types.ts +15 -3
- package/src/Breadcrumb/Breadcrumb.tsx +136 -18
- package/src/Breadcrumb/README.md +47 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
- package/src/Breadcrumb/types.ts +73 -7
- package/src/Button/Button.tsx +2 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
- package/src/Button/types.ts +21 -0
- package/src/Carousel/Carousel.tsx +263 -15
- package/src/Carousel/README.md +749 -72
- package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
- package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
- package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
- package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
- package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +88 -0
- package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
- package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
- package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
- package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
- package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
- package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
- package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
- package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
- package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
- package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
- package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
- package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
- package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
- package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
- package/src/Carousel/hooks/index.ts +1 -0
- package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
- package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
- package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
- package/src/Carousel/loopEngine.ts +54 -0
- package/src/Carousel/types.ts +457 -35
- package/src/Checkbox/Checkbox.tsx +9 -1
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
- package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
- package/src/Checkbox/types.ts +19 -0
- package/src/CheckboxCard/CheckboxCard.tsx +13 -4
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
- package/src/CheckboxCard/types.ts +65 -17
- package/src/Collapsible/Collapsible.tsx +49 -3
- package/src/Collapsible/README.md +40 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
- package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
- package/src/Collapsible/types.ts +89 -22
- package/src/ContextMenu/ContextMenu.tsx +270 -4
- package/src/ContextMenu/types.ts +148 -6
- package/src/DirectionProvider/DirectionProvider.tsx +41 -9
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
- package/src/DirectionProvider/types.ts +28 -3
- package/src/Divider/Divider.tsx +24 -10
- package/src/Divider/__tests__/Divider.test.tsx +5 -0
- package/src/Divider/types.ts +10 -3
- package/src/Drawer/Drawer.tsx +315 -0
- package/src/Drawer/README.md +124 -0
- package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
- package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
- package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
- package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
- package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
- package/src/Drawer/index.ts +2 -0
- package/src/Drawer/types.ts +66 -0
- package/src/Dropdown/Dropdown.tsx +117 -0
- package/src/Dropdown/types.ts +241 -11
- package/src/EmptyState/EmptyState.tsx +74 -4
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
- package/src/EmptyState/types.ts +49 -6
- package/src/Field/Field.tsx +23 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
- package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
- package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
- package/src/Field/types.ts +60 -12
- package/src/Fieldset/Fieldset.tsx +54 -18
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
- package/src/Fieldset/types.ts +20 -2
- package/src/Input/Input.tsx +14 -9
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
- package/src/Input/types.ts +19 -2
- package/src/InputGroup/InputGroup.tsx +11 -3
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
- package/src/InputGroup/types.ts +26 -5
- package/src/MillerColumns/MillerColumns.tsx +86 -19
- package/src/MillerColumns/types.ts +83 -27
- package/src/Modal/Modal.tsx +31 -4
- package/src/Modal/README.md +19 -10
- package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
- package/src/Modal/hooks/useModalContent.ts +32 -0
- package/src/Modal/types.ts +110 -29
- package/src/NavigationMenu/NavigationMenu.tsx +890 -0
- package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
- package/src/NavigationMenu/README.md +355 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
- package/src/NavigationMenu/constants.ts +5 -0
- package/src/NavigationMenu/hooks/index.ts +7 -0
- package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
- package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
- package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
- package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
- package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
- package/src/NavigationMenu/index.ts +3 -0
- package/src/NavigationMenu/types.ts +259 -0
- package/src/NavigationMenu/utils.ts +89 -0
- package/src/Popover/Popover.tsx +427 -0
- package/src/Popover/PopoverContext.ts +13 -0
- package/src/Popover/README.md +106 -0
- package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
- package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
- package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
- package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
- package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
- package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
- package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
- package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
- package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
- package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
- package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
- package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
- package/src/Popover/hooks/index.ts +3 -0
- package/src/Popover/hooks/usePopoverContent.ts +98 -0
- package/src/Popover/hooks/usePopoverRoot.ts +84 -0
- package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
- package/src/Popover/index.ts +2 -0
- package/src/Popover/types.ts +197 -0
- package/src/Portal/Portal.tsx +50 -10
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
- package/src/Portal/types.ts +5 -0
- package/src/Progress/Progress.tsx +33 -1
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
- package/src/Progress/types.ts +20 -4
- package/src/Radio/Radio.tsx +9 -1
- package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
- package/src/Radio/hooks/useRadioRoot.ts +3 -0
- package/src/Radio/types.ts +20 -0
- package/src/RadioCard/RadioCard.tsx +11 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
- package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
- package/src/RadioCard/types.ts +95 -22
- package/src/RadioGroup/RadioGroup.tsx +11 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
- package/src/RadioGroup/types.ts +73 -9
- package/src/SegmentedControl/README.md +154 -0
- package/src/SegmentedControl/SegmentedControl.tsx +309 -0
- package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
- package/src/SegmentedControl/hooks/index.ts +2 -0
- package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
- package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +86 -0
- package/src/SegmentedControl/index.ts +2 -0
- package/src/SegmentedControl/types.ts +138 -0
- package/src/Select/README.md +122 -157
- package/src/Select/Select.tsx +595 -68
- package/src/Select/SelectContext.ts +38 -0
- package/src/Select/SelectItemIndicatorContext.ts +29 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
- package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
- package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
- package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
- package/src/Select/__tests__/Select.form.test.tsx +38 -0
- package/src/Select/__tests__/Select.group.test.tsx +6 -6
- package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
- package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
- package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
- package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
- package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +4 -4
- package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
- package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
- package/src/Select/__tests__/Select.selection.test.tsx +104 -0
- package/src/Select/__tests__/Select.separator.test.tsx +71 -0
- package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
- package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
- package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
- package/src/Select/constants.ts +8 -0
- package/src/Select/hooks/index.ts +2 -0
- package/src/Select/hooks/useSelectContent.ts +149 -0
- package/src/Select/hooks/useSelectRoot.ts +149 -0
- package/src/Select/types.ts +199 -31
- package/src/SkipNav/SkipNav.tsx +69 -29
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
- package/src/SkipNav/types.ts +17 -9
- package/src/Slider/Slider.tsx +33 -1
- package/src/Slider/SliderContext.ts +28 -1
- package/src/Slider/types.ts +157 -9
- package/src/Slot/Slot.tsx +94 -19
- package/src/Slot/__tests__/Slot.test.tsx +110 -0
- package/src/Slot/composeEventHandlers.ts +26 -2
- package/src/Slot/types.ts +16 -0
- package/src/Status/Status.tsx +36 -15
- package/src/Status/__tests__/Status.test.tsx +5 -0
- package/src/Status/types.ts +12 -1
- package/src/Switch/Switch.tsx +15 -2
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
- package/src/Switch/hooks/useSwitchRoot.ts +3 -0
- package/src/Switch/types.ts +49 -22
- package/src/Table/Table.tsx +44 -10
- package/src/Table/types.ts +66 -17
- package/src/Tabs/Tabs.tsx +8 -0
- package/src/Tabs/types.ts +29 -4
- package/src/Textarea/Textarea.tsx +9 -1
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
- package/src/Textarea/types.ts +20 -3
- package/src/Toggle/Toggle.tsx +51 -13
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
- package/src/Toggle/types.ts +54 -4
- package/src/ToggleGroup/ToggleGroup.tsx +14 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
- package/src/ToggleGroup/types.ts +55 -2
- package/src/Tooltip/Tooltip.tsx +98 -20
- package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +13 -1
- package/src/Tooltip/types.ts +89 -8
- package/src/Tree/Tree.tsx +187 -33
- package/src/Tree/types.ts +54 -12
- package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
- package/src/VisuallyHidden/types.ts +11 -1
- package/src/index.ts +4 -0
- package/src/test/intersectionObserverPolyfill.ts +7 -0
- package/src/test/resizeObserverPolyfill.ts +80 -0
- package/src/utils/focusable.ts +15 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Carousel } from "../index.ts";
|
|
4
|
+
|
|
5
|
+
// scroll-snap-align must only land on a page's *leading* slide when
|
|
6
|
+
// slidesPerPage > 1 — an interior slide is not a valid resting position,
|
|
7
|
+
// since the browser's mandatory snap would stop there and leave the
|
|
8
|
+
// viewport showing a straddled mix of two pages while currentPage/the
|
|
9
|
+
// indicators still claim a single clean page is active (the mouse-wheel /
|
|
10
|
+
// drag desync). data-snap-align is the CSS hook the registry stylesheet
|
|
11
|
+
// scopes scroll-snap-align to — its *value* mirrors the resolved root
|
|
12
|
+
// `snapAlign` ("center" default, "start"/"end"), so a user's native scroll
|
|
13
|
+
// snap lands wherever the programmatic scroll targets, instead of always
|
|
14
|
+
// snapping to "center" regardless of `snapAlign`.
|
|
15
|
+
describe("Carousel slide snap-align hook", () => {
|
|
16
|
+
it('should mark every slide data-snap-align="center" when slidesPerPage is 1 (default)', () => {
|
|
17
|
+
render(
|
|
18
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
19
|
+
<Carousel.Viewport>
|
|
20
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
21
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
22
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
23
|
+
</Carousel.Viewport>
|
|
24
|
+
</Carousel.Root>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
28
|
+
"data-snap-align",
|
|
29
|
+
"center",
|
|
30
|
+
);
|
|
31
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
32
|
+
"data-snap-align",
|
|
33
|
+
"center",
|
|
34
|
+
);
|
|
35
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
36
|
+
"data-snap-align",
|
|
37
|
+
"center",
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should only mark page-start slides when slidesPerPage > 1 (clean paging)", () => {
|
|
42
|
+
render(
|
|
43
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={2}>
|
|
44
|
+
<Carousel.Viewport>
|
|
45
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
46
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
47
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
48
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
49
|
+
</Carousel.Viewport>
|
|
50
|
+
</Carousel.Root>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
54
|
+
"data-snap-align",
|
|
55
|
+
"center",
|
|
56
|
+
);
|
|
57
|
+
expect(screen.getByTestId("slide-1")).not.toHaveAttribute(
|
|
58
|
+
"data-snap-align",
|
|
59
|
+
);
|
|
60
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
61
|
+
"data-snap-align",
|
|
62
|
+
"center",
|
|
63
|
+
);
|
|
64
|
+
expect(screen.getByTestId("slide-3")).not.toHaveAttribute(
|
|
65
|
+
"data-snap-align",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should mark the end-aligned last page's leading slide, even off the move step (uneven last page)", () => {
|
|
70
|
+
// perPage 2, 5 slides: offsets 0, 2, 3 (end-aligned) — slide 4 is never
|
|
71
|
+
// a page start (it only ever appears as page 2's *trailing* slide).
|
|
72
|
+
render(
|
|
73
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={2}>
|
|
74
|
+
<Carousel.Viewport>
|
|
75
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
76
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
77
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
78
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
79
|
+
<Carousel.Slide data-testid="slide-4" />
|
|
80
|
+
</Carousel.Viewport>
|
|
81
|
+
</Carousel.Root>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
85
|
+
"data-snap-align",
|
|
86
|
+
"center",
|
|
87
|
+
);
|
|
88
|
+
expect(screen.getByTestId("slide-1")).not.toHaveAttribute(
|
|
89
|
+
"data-snap-align",
|
|
90
|
+
);
|
|
91
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
92
|
+
"data-snap-align",
|
|
93
|
+
"center",
|
|
94
|
+
);
|
|
95
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
96
|
+
"data-snap-align",
|
|
97
|
+
"center",
|
|
98
|
+
);
|
|
99
|
+
expect(screen.getByTestId("slide-4")).not.toHaveAttribute(
|
|
100
|
+
"data-snap-align",
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should mark page-start slides data-snap-align="start" when the root snapAlign is "start"', () => {
|
|
105
|
+
render(
|
|
106
|
+
<Carousel.Root ariaLabel="Featured products" snapAlign="start">
|
|
107
|
+
<Carousel.Viewport>
|
|
108
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
109
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
110
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
111
|
+
</Carousel.Viewport>
|
|
112
|
+
</Carousel.Root>,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
116
|
+
"data-snap-align",
|
|
117
|
+
"start",
|
|
118
|
+
);
|
|
119
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
120
|
+
"data-snap-align",
|
|
121
|
+
"start",
|
|
122
|
+
);
|
|
123
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
124
|
+
"data-snap-align",
|
|
125
|
+
"start",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should let a slide\'s own snapAlign override the root default, including "start" and "end"', () => {
|
|
130
|
+
render(
|
|
131
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
132
|
+
<Carousel.Viewport>
|
|
133
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
134
|
+
<Carousel.Slide data-testid="slide-1" snapAlign="end" />
|
|
135
|
+
<Carousel.Slide data-testid="slide-2" snapAlign="start" />
|
|
136
|
+
</Carousel.Viewport>
|
|
137
|
+
</Carousel.Root>,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
141
|
+
"data-snap-align",
|
|
142
|
+
"center",
|
|
143
|
+
);
|
|
144
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
145
|
+
"data-snap-align",
|
|
146
|
+
"end",
|
|
147
|
+
);
|
|
148
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
149
|
+
"data-snap-align",
|
|
150
|
+
"start",
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should not publish a per-slide snapAlign override on a slide that isn't a valid snap-start position", () => {
|
|
155
|
+
render(
|
|
156
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={2}>
|
|
157
|
+
<Carousel.Viewport>
|
|
158
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
159
|
+
<Carousel.Slide data-testid="slide-1" snapAlign="end" />
|
|
160
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
161
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
162
|
+
</Carousel.Viewport>
|
|
163
|
+
</Carousel.Root>,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// slide-1 is an interior slide of page 0 (perPage 2) — no valid resting
|
|
167
|
+
// position, so its override must not leak into scroll-snap-align.
|
|
168
|
+
expect(screen.getByTestId("slide-1")).not.toHaveAttribute(
|
|
169
|
+
"data-snap-align",
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { act, render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
|
|
4
4
|
import { Carousel } from "../index.ts";
|
|
5
5
|
|
|
6
|
+
function fireScrollSnapChange(viewport: HTMLElement, snapTarget: HTMLElement) {
|
|
7
|
+
const event = new Event("scrollsnapchange", { bubbles: false });
|
|
8
|
+
Object.defineProperty(event, "snapTargetInline", {
|
|
9
|
+
value: snapTarget,
|
|
10
|
+
writable: false,
|
|
11
|
+
});
|
|
12
|
+
act(() => {
|
|
13
|
+
viewport.dispatchEvent(event);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
describe("Carousel numeric slidesPerMove", () => {
|
|
7
18
|
it("should render `floor((total - slidesPerPage) / slidesPerMove) + 1` indicators", () => {
|
|
8
19
|
render(
|
|
@@ -148,4 +159,171 @@ describe("Carousel numeric slidesPerMove", () => {
|
|
|
148
159
|
);
|
|
149
160
|
});
|
|
150
161
|
|
|
162
|
+
it("should end-align the last window so an inexact move leaves no slide unreachable", () => {
|
|
163
|
+
// 6 slides, perPage=3, move=2. A naive floor((6-3)/2)+1 = 2 pages
|
|
164
|
+
// ([0,1,2] [2,3,4]) orphans slide 5. End-aligning the last window
|
|
165
|
+
// adds a third page ([3,4,5]) so every slide is reachable.
|
|
166
|
+
render(
|
|
167
|
+
<Carousel.Root
|
|
168
|
+
ariaLabel="Featured products"
|
|
169
|
+
slidesPerPage={3}
|
|
170
|
+
slidesPerMove={2}
|
|
171
|
+
>
|
|
172
|
+
<Carousel.Viewport>
|
|
173
|
+
<Carousel.Slide />
|
|
174
|
+
<Carousel.Slide />
|
|
175
|
+
<Carousel.Slide />
|
|
176
|
+
<Carousel.Slide />
|
|
177
|
+
<Carousel.Slide />
|
|
178
|
+
<Carousel.Slide />
|
|
179
|
+
</Carousel.Viewport>
|
|
180
|
+
<Carousel.Indicators label="Choose page" />
|
|
181
|
+
</Carousel.Root>,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(
|
|
185
|
+
screen.getAllByRole("button", { name: /^Slide \d+$/ }),
|
|
186
|
+
).toHaveLength(3);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should snap the end-aligned last window to the track end", async () => {
|
|
190
|
+
const user = userEvent.setup();
|
|
191
|
+
render(
|
|
192
|
+
<Carousel.Root
|
|
193
|
+
ariaLabel="Featured products"
|
|
194
|
+
slidesPerPage={3}
|
|
195
|
+
slidesPerMove={2}
|
|
196
|
+
>
|
|
197
|
+
<Carousel.Viewport>
|
|
198
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
199
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
200
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
201
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
202
|
+
<Carousel.Slide data-testid="slide-4" />
|
|
203
|
+
<Carousel.Slide data-testid="slide-5" />
|
|
204
|
+
</Carousel.Viewport>
|
|
205
|
+
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
|
|
206
|
+
</Carousel.Root>,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// page 0 [0,1,2] → page 1 [2,3,4] → page 2 end-aligned to [3,4,5].
|
|
210
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
211
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
212
|
+
|
|
213
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
214
|
+
"data-state",
|
|
215
|
+
"active",
|
|
216
|
+
);
|
|
217
|
+
expect(screen.getByTestId("slide-5")).toHaveAttribute(
|
|
218
|
+
"data-state",
|
|
219
|
+
"active",
|
|
220
|
+
);
|
|
221
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
222
|
+
"data-state",
|
|
223
|
+
"inactive",
|
|
224
|
+
);
|
|
225
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeDisabled();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should clamp slidesPerMove to slidesPerPage so a move can't skip past a page", () => {
|
|
229
|
+
// move=3 > perPage=2 would leave a gap (window [0,1] then [3,4],
|
|
230
|
+
// skipping slide 2). Clamp move to perPage so windows stay contiguous:
|
|
231
|
+
// 6 slides → [0,1] [2,3] [4,5] → 3 pages.
|
|
232
|
+
render(
|
|
233
|
+
<Carousel.Root
|
|
234
|
+
ariaLabel="Featured products"
|
|
235
|
+
slidesPerPage={2}
|
|
236
|
+
slidesPerMove={3}
|
|
237
|
+
>
|
|
238
|
+
<Carousel.Viewport>
|
|
239
|
+
<Carousel.Slide />
|
|
240
|
+
<Carousel.Slide />
|
|
241
|
+
<Carousel.Slide />
|
|
242
|
+
<Carousel.Slide />
|
|
243
|
+
<Carousel.Slide />
|
|
244
|
+
<Carousel.Slide />
|
|
245
|
+
</Carousel.Viewport>
|
|
246
|
+
<Carousel.Indicators label="Choose page" />
|
|
247
|
+
</Carousel.Root>,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(
|
|
251
|
+
screen.getAllByRole("button", { name: /^Slide \d+$/ }),
|
|
252
|
+
).toHaveLength(3);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should advance by at most slidesPerPage when slidesPerMove exceeds it", async () => {
|
|
256
|
+
const user = userEvent.setup();
|
|
257
|
+
render(
|
|
258
|
+
<Carousel.Root
|
|
259
|
+
ariaLabel="Featured products"
|
|
260
|
+
slidesPerPage={2}
|
|
261
|
+
slidesPerMove={3}
|
|
262
|
+
>
|
|
263
|
+
<Carousel.Viewport>
|
|
264
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
265
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
266
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
267
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
268
|
+
</Carousel.Viewport>
|
|
269
|
+
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
|
|
270
|
+
</Carousel.Root>,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
274
|
+
|
|
275
|
+
// Clamped move=2 → window [2,3], not the [3,…] a move of 3 would give.
|
|
276
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
277
|
+
"data-state",
|
|
278
|
+
"active",
|
|
279
|
+
);
|
|
280
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
281
|
+
"data-state",
|
|
282
|
+
"active",
|
|
283
|
+
);
|
|
284
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
285
|
+
"data-state",
|
|
286
|
+
"inactive",
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should map a user swipe to the nearest window in numeric slidesPerMove mode", () => {
|
|
291
|
+
render(
|
|
292
|
+
<Carousel.Root
|
|
293
|
+
ariaLabel="Featured products"
|
|
294
|
+
slidesPerPage={3}
|
|
295
|
+
slidesPerMove={2}
|
|
296
|
+
>
|
|
297
|
+
<Carousel.Viewport data-testid="viewport">
|
|
298
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
299
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
300
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
301
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
302
|
+
<Carousel.Slide data-testid="slide-4" />
|
|
303
|
+
<Carousel.Slide data-testid="slide-5" />
|
|
304
|
+
</Carousel.Viewport>
|
|
305
|
+
</Carousel.Root>,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// The user swipes so slide 3 leads the viewport. With move=2 the nearest
|
|
309
|
+
// window start is the end-aligned last page [3,4,5], not floor(3/2)=1's
|
|
310
|
+
// [2,3,4].
|
|
311
|
+
fireScrollSnapChange(
|
|
312
|
+
screen.getByTestId("viewport"),
|
|
313
|
+
screen.getByTestId("slide-3"),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
317
|
+
"data-state",
|
|
318
|
+
"active",
|
|
319
|
+
);
|
|
320
|
+
expect(screen.getByTestId("slide-5")).toHaveAttribute(
|
|
321
|
+
"data-state",
|
|
322
|
+
"active",
|
|
323
|
+
);
|
|
324
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
325
|
+
"data-state",
|
|
326
|
+
"inactive",
|
|
327
|
+
);
|
|
328
|
+
});
|
|
151
329
|
});
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { act, render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
|
|
4
4
|
import { Carousel } from "../index.ts";
|
|
5
5
|
|
|
6
|
+
function fireScrollSnapChange(viewport: HTMLElement, snapTarget: HTMLElement) {
|
|
7
|
+
const event = new Event("scrollsnapchange", { bubbles: false });
|
|
8
|
+
Object.defineProperty(event, "snapTargetInline", {
|
|
9
|
+
value: snapTarget,
|
|
10
|
+
writable: false,
|
|
11
|
+
});
|
|
12
|
+
act(() => {
|
|
13
|
+
viewport.dispatchEvent(event);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
describe("Carousel slidesPerPage", () => {
|
|
7
18
|
it("should mark every slide on the active page as active when slidesPerPage > 1", () => {
|
|
8
19
|
render(
|
|
@@ -142,7 +153,12 @@ describe("Carousel slidesPerPage", () => {
|
|
|
142
153
|
);
|
|
143
154
|
});
|
|
144
155
|
|
|
145
|
-
it("should
|
|
156
|
+
it("should end-align the last page so its window is full (not a partial page that can't start-snap)", () => {
|
|
157
|
+
// perPage 2, 5 slides, last page. A naive offset of 2×2 = 4 leaves a
|
|
158
|
+
// single-slide page [4] whose leading slide can't align to the viewport
|
|
159
|
+
// start (there's nothing after it), so scroll-snap lands on slide 3 and the
|
|
160
|
+
// active page desyncs. End-aligning the last page to offset 3 → window
|
|
161
|
+
// [3,4], which snaps cleanly to the track end.
|
|
146
162
|
render(
|
|
147
163
|
<Carousel.Root
|
|
148
164
|
ariaLabel="Featured products"
|
|
@@ -159,25 +175,93 @@ describe("Carousel slidesPerPage", () => {
|
|
|
159
175
|
</Carousel.Root>,
|
|
160
176
|
);
|
|
161
177
|
|
|
162
|
-
expect(screen.getByTestId("slide-
|
|
178
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
163
179
|
"data-state",
|
|
164
180
|
"inactive",
|
|
165
181
|
);
|
|
166
|
-
expect(screen.getByTestId("slide-
|
|
182
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
167
183
|
"data-state",
|
|
168
|
-
"
|
|
184
|
+
"active",
|
|
169
185
|
);
|
|
170
|
-
expect(screen.getByTestId("slide-
|
|
186
|
+
expect(screen.getByTestId("slide-4")).toHaveAttribute(
|
|
171
187
|
"data-state",
|
|
172
|
-
"
|
|
188
|
+
"active",
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should reach the end-aligned last page in two clicks without desyncing (perPage 3, 7 slides)", async () => {
|
|
193
|
+
const user = userEvent.setup();
|
|
194
|
+
render(
|
|
195
|
+
<Carousel.Root
|
|
196
|
+
ariaLabel="Featured products"
|
|
197
|
+
slidesPerPage={3}
|
|
198
|
+
>
|
|
199
|
+
<Carousel.Viewport>
|
|
200
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
201
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
202
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
203
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
204
|
+
<Carousel.Slide data-testid="slide-4" />
|
|
205
|
+
<Carousel.Slide data-testid="slide-5" />
|
|
206
|
+
<Carousel.Slide data-testid="slide-6" />
|
|
207
|
+
</Carousel.Viewport>
|
|
208
|
+
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
|
|
209
|
+
</Carousel.Root>,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// 3 pages: [0,1,2] [3,4,5] [4,5,6] (last end-aligned). Two clicks reach it.
|
|
213
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
214
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
215
|
+
|
|
216
|
+
expect(screen.getByTestId("slide-4")).toHaveAttribute(
|
|
217
|
+
"data-state",
|
|
218
|
+
"active",
|
|
219
|
+
);
|
|
220
|
+
expect(screen.getByTestId("slide-6")).toHaveAttribute(
|
|
221
|
+
"data-state",
|
|
222
|
+
"active",
|
|
173
223
|
);
|
|
174
224
|
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
175
225
|
"data-state",
|
|
176
226
|
"inactive",
|
|
177
227
|
);
|
|
228
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeDisabled();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should map a swipe onto the end-aligned last page to the last page (not the page a floor would give)", () => {
|
|
232
|
+
render(
|
|
233
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={3}>
|
|
234
|
+
<Carousel.Viewport data-testid="viewport">
|
|
235
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
236
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
237
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
238
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
239
|
+
<Carousel.Slide data-testid="slide-4" />
|
|
240
|
+
<Carousel.Slide data-testid="slide-5" />
|
|
241
|
+
<Carousel.Slide data-testid="slide-6" />
|
|
242
|
+
</Carousel.Viewport>
|
|
243
|
+
</Carousel.Root>,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// The last page's leading slide is 4 (offset end-aligned to 7 − 3 = 4).
|
|
247
|
+
// A floor(4 / 3) would map it to page 1; the nearest-offset inverse maps it
|
|
248
|
+
// to the last page.
|
|
249
|
+
fireScrollSnapChange(
|
|
250
|
+
screen.getByTestId("viewport"),
|
|
251
|
+
screen.getByTestId("slide-4"),
|
|
252
|
+
);
|
|
253
|
+
|
|
178
254
|
expect(screen.getByTestId("slide-4")).toHaveAttribute(
|
|
179
255
|
"data-state",
|
|
180
256
|
"active",
|
|
181
257
|
);
|
|
258
|
+
expect(screen.getByTestId("slide-6")).toHaveAttribute(
|
|
259
|
+
"data-state",
|
|
260
|
+
"active",
|
|
261
|
+
);
|
|
262
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
263
|
+
"data-state",
|
|
264
|
+
"inactive",
|
|
265
|
+
);
|
|
182
266
|
});
|
|
183
267
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Carousel } from "../index.ts";
|
|
4
|
+
|
|
5
|
+
// scroll-snap-type strictness ("mandatory" forces a rest at a snap point;
|
|
6
|
+
// "proximity" only nudges toward one) is a Root-level prop, published as
|
|
7
|
+
// data-snap-type on the Viewport so the registry stylesheet can key off it —
|
|
8
|
+
// mirrors Ark UI's snapType.
|
|
9
|
+
describe("Carousel snapType", () => {
|
|
10
|
+
it('should default to data-snap-type="mandatory" on the viewport', () => {
|
|
11
|
+
render(
|
|
12
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
13
|
+
<Carousel.Viewport data-testid="viewport" />
|
|
14
|
+
</Carousel.Root>,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
expect(screen.getByTestId("viewport")).toHaveAttribute(
|
|
18
|
+
"data-snap-type",
|
|
19
|
+
"mandatory",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should reflect snapType="proximity" as data-snap-type on the viewport', () => {
|
|
24
|
+
render(
|
|
25
|
+
<Carousel.Root ariaLabel="Featured products" snapType="proximity">
|
|
26
|
+
<Carousel.Viewport data-testid="viewport" />
|
|
27
|
+
</Carousel.Root>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(screen.getByTestId("viewport")).toHaveAttribute(
|
|
31
|
+
"data-snap-type",
|
|
32
|
+
"proximity",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -18,6 +18,34 @@ function fireScrollSnapChange(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
describe("Carousel transition modes", () => {
|
|
21
|
+
it('should publish data-transition="slide" on the root by default (the CSS styling hook)', () => {
|
|
22
|
+
render(
|
|
23
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
24
|
+
<Carousel.Viewport>
|
|
25
|
+
<Carousel.Slide />
|
|
26
|
+
</Carousel.Viewport>
|
|
27
|
+
</Carousel.Root>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(
|
|
31
|
+
screen.getByRole("region", { name: "Featured products" }),
|
|
32
|
+
).toHaveAttribute("data-transition", "slide");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should publish data-transition="fade" when transition="fade" so CSS can crossfade off the hook', () => {
|
|
36
|
+
render(
|
|
37
|
+
<Carousel.Root ariaLabel="Featured products" transition="fade">
|
|
38
|
+
<Carousel.Viewport>
|
|
39
|
+
<Carousel.Slide />
|
|
40
|
+
</Carousel.Viewport>
|
|
41
|
+
</Carousel.Root>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(
|
|
45
|
+
screen.getByRole("region", { name: "Featured products" }),
|
|
46
|
+
).toHaveAttribute("data-transition", "fade");
|
|
47
|
+
});
|
|
48
|
+
|
|
21
49
|
it('should skip the programmatic scroll on page change when transition="none"', async () => {
|
|
22
50
|
const user = userEvent.setup();
|
|
23
51
|
render(
|
|
@@ -30,10 +58,7 @@ describe("Carousel transition modes", () => {
|
|
|
30
58
|
</Carousel.Root>,
|
|
31
59
|
);
|
|
32
60
|
|
|
33
|
-
const
|
|
34
|
-
screen.getByTestId("slide-1"),
|
|
35
|
-
"scrollIntoView",
|
|
36
|
-
);
|
|
61
|
+
const scrollToSpy = vi.spyOn(screen.getByTestId("viewport"), "scrollTo");
|
|
37
62
|
|
|
38
63
|
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
39
64
|
|
|
@@ -43,7 +68,7 @@ describe("Carousel transition modes", () => {
|
|
|
43
68
|
"data-state",
|
|
44
69
|
"active",
|
|
45
70
|
);
|
|
46
|
-
expect(
|
|
71
|
+
expect(scrollToSpy).not.toHaveBeenCalled();
|
|
47
72
|
});
|
|
48
73
|
|
|
49
74
|
it('should ignore scrollsnapchange events on the viewport when transition="none"', () => {
|
|
@@ -108,6 +108,26 @@ describe("Carousel translations", () => {
|
|
|
108
108
|
).toBeVisible();
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
it("should override the ProgressText format via translations.progressText", () => {
|
|
112
|
+
render(
|
|
113
|
+
<Carousel.Root
|
|
114
|
+
ariaLabel="Featured products"
|
|
115
|
+
translations={{
|
|
116
|
+
progressText: ({ page, totalPages }) =>
|
|
117
|
+
`Page ${page + 1} sur ${totalPages}`,
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<Carousel.Viewport>
|
|
121
|
+
<Carousel.Slide />
|
|
122
|
+
<Carousel.Slide />
|
|
123
|
+
</Carousel.Viewport>
|
|
124
|
+
<Carousel.ProgressText />
|
|
125
|
+
</Carousel.Root>,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(screen.getByText("Page 1 sur 2")).toBeVisible();
|
|
129
|
+
});
|
|
130
|
+
|
|
111
131
|
it("should fall back to defaults for any translation keys not specified", () => {
|
|
112
132
|
render(
|
|
113
133
|
<Carousel.Root
|
|
@@ -123,6 +143,7 @@ describe("Carousel translations", () => {
|
|
|
123
143
|
<Carousel.Indicator index={0} />
|
|
124
144
|
</Carousel.IndicatorGroup>
|
|
125
145
|
<Carousel.PlayPauseTrigger />
|
|
146
|
+
<Carousel.ProgressText />
|
|
126
147
|
</Carousel.Root>,
|
|
127
148
|
);
|
|
128
149
|
|
|
@@ -135,6 +156,8 @@ describe("Carousel translations", () => {
|
|
|
135
156
|
);
|
|
136
157
|
// indicatorLabel default still applied
|
|
137
158
|
expect(screen.getByRole("button", { name: "Slide 1" })).toBeVisible();
|
|
159
|
+
// progressText default still applied
|
|
160
|
+
expect(screen.getByText("1 of 2")).toBeVisible();
|
|
138
161
|
});
|
|
139
162
|
|
|
140
163
|
it("should apply translations.indicatorLabel to indicators auto-rendered by Carousel.Indicators", () => {
|