@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.
Files changed (307) hide show
  1. package/README.md +4 -0
  2. package/package.json +6 -1
  3. package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
  4. package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
  5. package/src/AccessibleIcon/types.ts +15 -2
  6. package/src/Accordion/Accordion.tsx +25 -4
  7. package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
  8. package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
  9. package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
  10. package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
  11. package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
  12. package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
  13. package/src/Accordion/types.ts +72 -21
  14. package/src/Alert/Alert.tsx +36 -7
  15. package/src/Alert/__tests__/Alert.test.tsx +5 -0
  16. package/src/Alert/types.ts +18 -2
  17. package/src/Avatar/Avatar.tsx +99 -15
  18. package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
  19. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
  20. package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
  21. package/src/Avatar/hooks/useAvatarImage.ts +16 -0
  22. package/src/Avatar/types.ts +15 -3
  23. package/src/Breadcrumb/Breadcrumb.tsx +136 -18
  24. package/src/Breadcrumb/README.md +47 -0
  25. package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
  26. package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
  27. package/src/Breadcrumb/types.ts +73 -7
  28. package/src/Button/Button.tsx +2 -0
  29. package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
  30. package/src/Button/types.ts +21 -0
  31. package/src/Carousel/Carousel.tsx +263 -15
  32. package/src/Carousel/README.md +749 -72
  33. package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
  34. package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
  35. package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
  36. package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
  37. package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
  38. package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
  39. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +88 -0
  40. package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
  41. package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
  42. package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
  43. package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
  44. package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
  45. package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
  46. package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
  47. package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
  48. package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
  49. package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
  50. package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
  51. package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
  52. package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
  53. package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
  54. package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
  55. package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
  56. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
  57. package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
  58. package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
  59. package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
  60. package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
  61. package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
  62. package/src/Carousel/hooks/index.ts +1 -0
  63. package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
  64. package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
  65. package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
  66. package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
  67. package/src/Carousel/loopEngine.ts +54 -0
  68. package/src/Carousel/types.ts +457 -35
  69. package/src/Checkbox/Checkbox.tsx +9 -1
  70. package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
  71. package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
  72. package/src/Checkbox/types.ts +19 -0
  73. package/src/CheckboxCard/CheckboxCard.tsx +13 -4
  74. package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
  75. package/src/CheckboxCard/types.ts +65 -17
  76. package/src/Collapsible/Collapsible.tsx +49 -3
  77. package/src/Collapsible/README.md +40 -0
  78. package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
  79. package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
  80. package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
  81. package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
  82. package/src/Collapsible/types.ts +89 -22
  83. package/src/ContextMenu/ContextMenu.tsx +270 -4
  84. package/src/ContextMenu/types.ts +148 -6
  85. package/src/DirectionProvider/DirectionProvider.tsx +41 -9
  86. package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
  87. package/src/DirectionProvider/types.ts +28 -3
  88. package/src/Divider/Divider.tsx +24 -10
  89. package/src/Divider/__tests__/Divider.test.tsx +5 -0
  90. package/src/Divider/types.ts +10 -3
  91. package/src/Drawer/Drawer.tsx +315 -0
  92. package/src/Drawer/README.md +124 -0
  93. package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
  94. package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
  95. package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
  96. package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
  97. package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
  98. package/src/Drawer/index.ts +2 -0
  99. package/src/Drawer/types.ts +66 -0
  100. package/src/Dropdown/Dropdown.tsx +117 -0
  101. package/src/Dropdown/types.ts +241 -11
  102. package/src/EmptyState/EmptyState.tsx +74 -4
  103. package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
  104. package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
  105. package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
  106. package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
  107. package/src/EmptyState/types.ts +49 -6
  108. package/src/Field/Field.tsx +23 -0
  109. package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
  110. package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
  111. package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
  112. package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
  113. package/src/Field/types.ts +60 -12
  114. package/src/Fieldset/Fieldset.tsx +54 -18
  115. package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
  116. package/src/Fieldset/types.ts +20 -2
  117. package/src/Input/Input.tsx +14 -9
  118. package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
  119. package/src/Input/types.ts +19 -2
  120. package/src/InputGroup/InputGroup.tsx +11 -3
  121. package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
  122. package/src/InputGroup/types.ts +26 -5
  123. package/src/MillerColumns/MillerColumns.tsx +86 -19
  124. package/src/MillerColumns/types.ts +83 -27
  125. package/src/Modal/Modal.tsx +31 -4
  126. package/src/Modal/README.md +19 -10
  127. package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
  128. package/src/Modal/hooks/useModalContent.ts +32 -0
  129. package/src/Modal/types.ts +110 -29
  130. package/src/NavigationMenu/NavigationMenu.tsx +890 -0
  131. package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
  132. package/src/NavigationMenu/README.md +355 -0
  133. package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
  134. package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
  135. package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
  136. package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
  137. package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
  138. package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
  139. package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
  140. package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
  141. package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
  142. package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
  143. package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
  144. package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
  145. package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
  146. package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
  147. package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
  148. package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
  149. package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
  150. package/src/NavigationMenu/constants.ts +5 -0
  151. package/src/NavigationMenu/hooks/index.ts +7 -0
  152. package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
  153. package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
  154. package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
  155. package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
  156. package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
  157. package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
  158. package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
  159. package/src/NavigationMenu/index.ts +3 -0
  160. package/src/NavigationMenu/types.ts +259 -0
  161. package/src/NavigationMenu/utils.ts +89 -0
  162. package/src/Popover/Popover.tsx +427 -0
  163. package/src/Popover/PopoverContext.ts +13 -0
  164. package/src/Popover/README.md +106 -0
  165. package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
  166. package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
  167. package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
  168. package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
  169. package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
  170. package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
  171. package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
  172. package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
  173. package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
  174. package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
  175. package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
  176. package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
  177. package/src/Popover/hooks/index.ts +3 -0
  178. package/src/Popover/hooks/usePopoverContent.ts +98 -0
  179. package/src/Popover/hooks/usePopoverRoot.ts +84 -0
  180. package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
  181. package/src/Popover/index.ts +2 -0
  182. package/src/Popover/types.ts +197 -0
  183. package/src/Portal/Portal.tsx +50 -10
  184. package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
  185. package/src/Portal/types.ts +5 -0
  186. package/src/Progress/Progress.tsx +33 -1
  187. package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
  188. package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
  189. package/src/Progress/types.ts +20 -4
  190. package/src/Radio/Radio.tsx +9 -1
  191. package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
  192. package/src/Radio/hooks/useRadioRoot.ts +3 -0
  193. package/src/Radio/types.ts +20 -0
  194. package/src/RadioCard/RadioCard.tsx +11 -0
  195. package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
  196. package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
  197. package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
  198. package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
  199. package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
  200. package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
  201. package/src/RadioCard/types.ts +95 -22
  202. package/src/RadioGroup/RadioGroup.tsx +11 -0
  203. package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
  204. package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
  205. package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
  206. package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
  207. package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
  208. package/src/RadioGroup/types.ts +73 -9
  209. package/src/SegmentedControl/README.md +154 -0
  210. package/src/SegmentedControl/SegmentedControl.tsx +309 -0
  211. package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
  212. package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
  213. package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
  214. package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
  215. package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
  216. package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
  217. package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
  218. package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
  219. package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
  220. package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
  221. package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
  222. package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
  223. package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
  224. package/src/SegmentedControl/hooks/index.ts +2 -0
  225. package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
  226. package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +86 -0
  227. package/src/SegmentedControl/index.ts +2 -0
  228. package/src/SegmentedControl/types.ts +138 -0
  229. package/src/Select/README.md +122 -157
  230. package/src/Select/Select.tsx +595 -68
  231. package/src/Select/SelectContext.ts +38 -0
  232. package/src/Select/SelectItemIndicatorContext.ts +29 -0
  233. package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
  234. package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
  235. package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
  236. package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
  237. package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
  238. package/src/Select/__tests__/Select.form.test.tsx +38 -0
  239. package/src/Select/__tests__/Select.group.test.tsx +6 -6
  240. package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
  241. package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
  242. package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
  243. package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
  244. package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
  245. package/src/Select/__tests__/Select.placeholder.test.tsx +4 -4
  246. package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
  247. package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
  248. package/src/Select/__tests__/Select.selection.test.tsx +104 -0
  249. package/src/Select/__tests__/Select.separator.test.tsx +71 -0
  250. package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
  251. package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
  252. package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
  253. package/src/Select/constants.ts +8 -0
  254. package/src/Select/hooks/index.ts +2 -0
  255. package/src/Select/hooks/useSelectContent.ts +149 -0
  256. package/src/Select/hooks/useSelectRoot.ts +149 -0
  257. package/src/Select/types.ts +199 -31
  258. package/src/SkipNav/SkipNav.tsx +69 -29
  259. package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
  260. package/src/SkipNav/types.ts +17 -9
  261. package/src/Slider/Slider.tsx +33 -1
  262. package/src/Slider/SliderContext.ts +28 -1
  263. package/src/Slider/types.ts +157 -9
  264. package/src/Slot/Slot.tsx +94 -19
  265. package/src/Slot/__tests__/Slot.test.tsx +110 -0
  266. package/src/Slot/composeEventHandlers.ts +26 -2
  267. package/src/Slot/types.ts +16 -0
  268. package/src/Status/Status.tsx +36 -15
  269. package/src/Status/__tests__/Status.test.tsx +5 -0
  270. package/src/Status/types.ts +12 -1
  271. package/src/Switch/Switch.tsx +15 -2
  272. package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
  273. package/src/Switch/hooks/useSwitchRoot.ts +3 -0
  274. package/src/Switch/types.ts +49 -22
  275. package/src/Table/Table.tsx +44 -10
  276. package/src/Table/types.ts +66 -17
  277. package/src/Tabs/Tabs.tsx +8 -0
  278. package/src/Tabs/types.ts +29 -4
  279. package/src/Textarea/Textarea.tsx +9 -1
  280. package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
  281. package/src/Textarea/types.ts +20 -3
  282. package/src/Toggle/Toggle.tsx +51 -13
  283. package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
  284. package/src/Toggle/types.ts +54 -4
  285. package/src/ToggleGroup/ToggleGroup.tsx +14 -1
  286. package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
  287. package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
  288. package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
  289. package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
  290. package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
  291. package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
  292. package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
  293. package/src/ToggleGroup/types.ts +55 -2
  294. package/src/Tooltip/Tooltip.tsx +98 -20
  295. package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
  296. package/src/Tooltip/hooks/useTooltipProvider.ts +13 -1
  297. package/src/Tooltip/types.ts +89 -8
  298. package/src/Tree/Tree.tsx +187 -33
  299. package/src/Tree/types.ts +54 -12
  300. package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
  301. package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
  302. package/src/VisuallyHidden/types.ts +11 -1
  303. package/src/index.ts +4 -0
  304. package/src/test/intersectionObserverPolyfill.ts +7 -0
  305. package/src/test/resizeObserverPolyfill.ts +80 -0
  306. package/src/utils/focusable.ts +15 -0
  307. package/src/utils/index.ts +1 -0
@@ -10,6 +10,7 @@ import { Slot } from "../Slot/index.ts";
10
10
  import { CarouselProvider } from "./CarouselContext";
11
11
  import {
12
12
  useCarouselContext,
13
+ useCarouselLoop,
13
14
  useCarouselRoot,
14
15
  useCarouselSlide,
15
16
  useCarouselViewport,
@@ -25,6 +26,7 @@ import type {
25
26
  CarouselIndicatorProps,
26
27
  CarouselIndicatorsProps,
27
28
  CarouselPlayPauseTriggerProps,
29
+ CarouselProgressTextProps,
28
30
  } from "./types";
29
31
 
30
32
  /**
@@ -42,6 +44,13 @@ import type {
42
44
  * The discriminated union on the props type rejects both-or-neither at
43
45
  * compile time.
44
46
  *
47
+ * **Orientation.** Pass `orientation="vertical"` to page along the block
48
+ * axis instead of the inline axis: the viewport scroll-snaps vertically,
49
+ * the viewport `ArrowDown` / `ArrowUp` keys advance / retreat (the
50
+ * horizontal arrows go inert), and the resolved value is published as
51
+ * `data-orientation` on the rendered `<section>` so consumer CSS can
52
+ * switch layout off a single hook. Defaults to `"horizontal"`.
53
+ *
45
54
  * Supports two **page-state modes**, statically discriminated at the type
46
55
  * level so only one of the two shapes is accepted by TypeScript:
47
56
  *
@@ -51,6 +60,12 @@ import type {
51
60
  * parent owns the active page; the component defers every state
52
61
  * change back through the callback.
53
62
  *
63
+ * The `ref` is an imperative handle ({@link CarouselImperativeApi}), not
64
+ * the DOM node — the `<section>`'s native attributes are still spread
65
+ * through from the remaining props.
66
+ *
67
+ * @extends HTMLElement
68
+ *
54
69
  * @example Labelled inline, uncontrolled
55
70
  * ```tsx
56
71
  * <Carousel.Root ariaLabel="Featured products" defaultPage={0}>…</Carousel.Root>
@@ -89,12 +104,20 @@ export const CarouselRoot: ForwardRefExoticComponent<
89
104
  playing,
90
105
  onPlayingChange,
91
106
  autoplay,
107
+ onAutoplayStatusChange,
92
108
  slidesPerPage,
93
109
  slidesPerMove,
94
110
  translations,
95
111
  ids,
96
112
  transition,
97
113
  snapAlign,
114
+ orientation,
115
+ loop,
116
+ allowMouseDrag,
117
+ onDragStatusChange,
118
+ onOverscrollStatusChange,
119
+ inViewThreshold,
120
+ snapType,
98
121
  children,
99
122
  ...rest
100
123
  },
@@ -109,12 +132,20 @@ export const CarouselRoot: ForwardRefExoticComponent<
109
132
  playing,
110
133
  onPlayingChange,
111
134
  autoplay,
135
+ onAutoplayStatusChange,
112
136
  slidesPerPage,
113
137
  slidesPerMove,
114
138
  translations,
115
139
  ids,
116
140
  transition,
117
141
  snapAlign,
142
+ orientation,
143
+ loop,
144
+ allowMouseDrag,
145
+ onDragStatusChange,
146
+ onOverscrollStatusChange,
147
+ inViewThreshold,
148
+ snapType,
118
149
  },
119
150
  imperativeRef,
120
151
  );
@@ -123,6 +154,9 @@ export const CarouselRoot: ForwardRefExoticComponent<
123
154
  <CarouselProvider value={contextValue}>
124
155
  <section
125
156
  aria-roledescription="carousel"
157
+ data-orientation={contextValue.orientation}
158
+ data-transition={contextValue.transition}
159
+ data-loop={contextValue.loop}
126
160
  className={className}
127
161
  {...(ariaLabel !== undefined && { "aria-label": ariaLabel })}
128
162
  {...(ariaLabelledBy !== undefined && {
@@ -160,7 +194,14 @@ CarouselRoot.displayName = "CarouselRoot";
160
194
  *
161
195
  * **Styling hooks.** `data-carousel-viewport` is set on the rendered
162
196
  * element. The component ships no styles — apply your own scroll-snap
163
- * recipe via this attribute.
197
+ * recipe via this attribute. `data-snap-type="mandatory" | "proximity"`
198
+ * mirrors the resolved root {@link CarouselRootProps.snapType | `snapType`}
199
+ * — scope `scroll-snap-type`'s strictness to it. `data-mouse-drag` is
200
+ * present whenever `allowMouseDrag` is `true` (a persistent hook — the
201
+ * natural place for a `cursor: grab` affordance, since showing that cursor
202
+ * when dragging isn't actually enabled would be misleading); `data-dragging`
203
+ * is additionally present only for the duration of an active drag (see
204
+ * below) — pair it with `cursor: grabbing`.
164
205
  *
165
206
  * **Keyboard navigation.** The Viewport is in the tab order
166
207
  * (`tabIndex={0}`) so keyboard users can reach the rotation control
@@ -177,6 +218,44 @@ CarouselRoot.displayName = "CarouselRoot";
177
218
  * target — focus inside a slide (e.g. on a link or form control) keeps
178
219
  * its native arrow-key semantics.
179
220
  *
221
+ * **Mouse click-and-drag** — opt-in via {@link CarouselRootProps.allowMouseDrag |
222
+ * `allowMouseDrag`} on `Carousel.Root` (default `false`; an
223
+ * unconditionally-on drag could conflict with a consumer's own
224
+ * drag-sensitive slide content). When enabled: click and hold, then drag,
225
+ * and the viewport scrolls like a swipe: `scrollLeft`/`scrollTop` track
226
+ * the pointer, amplified by a sensitivity multiplier (no momentum — the
227
+ * multiplier only scales the tracked delta, motion still stops dead on
228
+ * release), once the drag clears a small movement threshold — below it,
229
+ * nothing happens, so a plain click on a link/button inside a slide
230
+ * still reaches it. Release lets `scroll-snap-type` settle to the
231
+ * nearest slide (the same `scrollsnapchange` sync a touch swipe already
232
+ * drives). A `data-dragging` attribute is set for the duration of an
233
+ * active drag. Only `pointerType === "mouse"` is handled — touch/pen
234
+ * already scroll natively regardless of this prop.
235
+ *
236
+ * **Mouse-wheel scroll.** A physical wheel's vertical notches already
237
+ * scroll a `orientation="vertical"` carousel natively. On the default
238
+ * horizontal orientation, a plain vertical wheel notch (`deltaY`)
239
+ * translates into horizontal scroll — browsers only do this
240
+ * automatically when `Shift` is held. Stands down whenever `deltaX` is
241
+ * non-negligible, so a trackpad/Magic Mouse horizontal swipe (which
242
+ * already scrolls a horizontal viewport natively via `deltaX`) is never
243
+ * fought.
244
+ *
245
+ * **Overscroll.** Pushing against a boundary with nowhere further to go —
246
+ * via the keyboard, the wheel, or a mouse drag — fires the root's
247
+ * {@link CarouselRootProps.onOverscrollStatusChange | `onOverscrollStatusChange`}.
248
+ * A mouse drag past a boundary additionally sets a persistent
249
+ * `data-overscroll="start" | "end"` attribute on the Viewport for the
250
+ * duration (removed once the drag stops pushing past it, whether by
251
+ * reversing back within bounds or releasing) — the natural hook for a
252
+ * rubber-band resistance visual. Keyboard/wheel overscroll is a single
253
+ * instantaneous tap with no equivalent sustained DOM state. Native
254
+ * touch/swipe overscroll (OS-level rubber-banding) isn't covered — that's
255
+ * browser-owned scroll physics with no JS hook to observe it from.
256
+ *
257
+ * @extends HTMLDivElement
258
+ *
180
259
  * @example
181
260
  * ```tsx
182
261
  * <Carousel.Root ariaLabel="Featured products">
@@ -191,21 +270,79 @@ export function CarouselViewport({
191
270
  children,
192
271
  ...rest
193
272
  }: CarouselViewportProps): ReactElement {
194
- const { isAutoRotating, ids } = useCarouselContext();
195
- const { viewportRef, onKeyDown } = useCarouselViewport();
273
+ const { isAutoRotating, ids, allowMouseDrag, snapType, slidesPerPage } =
274
+ useCarouselContext();
275
+ const {
276
+ viewportRef,
277
+ onKeyDown,
278
+ onDragStart,
279
+ onPointerDown,
280
+ onPointerMove,
281
+ onPointerUp,
282
+ onPointerCancel,
283
+ onClickCapture,
284
+ } = useCarouselViewport();
285
+ // Infinite loop swaps the native scroll-snap viewport for a JS transform track
286
+ // (RFC 0018, clone-strip revision): the engine translates one contiguous track
287
+ // whose real slides are flanked by a full period of aria-hidden clones, so every
288
+ // seam is already-painted DOM and a settle-time re-base is invisible (no per-slide
289
+ // shifts, no native snap to fight — the two things that flashed on iOS). Every
290
+ // other mode renders the slides straight into the scroll viewport as before.
291
+ const { trackRef, headCloneRef, tailCloneRef, isInfinite, dragHandlers } =
292
+ useCarouselLoop();
293
+ // Infinite is transform-driven, so its own pointer engine (drag → fling) owns
294
+ // the pointer events; the scroll-snap modes keep useCarouselViewport's
295
+ // mouse-drag handlers.
296
+ const pointerHandlers = dragHandlers ?? {
297
+ onPointerDown,
298
+ onPointerMove,
299
+ onPointerUp,
300
+ onPointerCancel,
301
+ };
196
302
 
197
303
  return (
198
304
  <div
199
305
  ref={viewportRef}
200
306
  data-carousel-viewport=""
307
+ data-snap-type={snapType}
201
308
  tabIndex={0}
202
309
  className={className}
203
310
  aria-live={isAutoRotating ? "off" : "polite"}
204
311
  onKeyDown={onKeyDown}
312
+ onDragStart={onDragStart}
313
+ {...pointerHandlers}
314
+ onClickCapture={onClickCapture}
315
+ {...(allowMouseDrag && { "data-mouse-drag": "" })}
205
316
  {...(ids.viewport !== undefined && { id: ids.viewport })}
206
317
  {...rest}
207
318
  >
208
- {children}
319
+ {isInfinite ? (
320
+ <div
321
+ className="primitiv-carousel__track"
322
+ data-carousel-track=""
323
+ data-slides-per-page={slidesPerPage}
324
+ ref={trackRef}
325
+ >
326
+ {/* Clone buffers (a full period each side) — the engine fills these
327
+ display:contents holders with aria-hidden copies so the strip is
328
+ periodic and every seam is contiguous painted DOM. */}
329
+ <div
330
+ data-carousel-clones="head"
331
+ aria-hidden="true"
332
+ style={{ display: "contents" }}
333
+ ref={headCloneRef}
334
+ />
335
+ {children}
336
+ <div
337
+ data-carousel-clones="tail"
338
+ aria-hidden="true"
339
+ style={{ display: "contents" }}
340
+ ref={tailCloneRef}
341
+ />
342
+ </div>
343
+ ) : (
344
+ children
345
+ )}
209
346
  </div>
210
347
  );
211
348
  }
@@ -238,10 +375,20 @@ CarouselViewport.displayName = "CarouselViewport";
238
375
  * recipe targets `[data-carousel-slide]`).
239
376
  * - `data-index="N"` — the slide's zero-based position in registration order.
240
377
  * - `data-total="N"` — the live total slide count.
378
+ * - `data-snap-align="start" | "center" | "end"` — present only on a page's
379
+ * leading slide (every slide when `slidesPerPage` is 1), valued from this
380
+ * slide's own {@link CarouselSlideProps.snapAlign | `snapAlign`} prop if
381
+ * set, else the root's resolved `snapAlign`. The registry stylesheet
382
+ * scopes `scroll-snap-align` to this hook, both so an interior slide of a
383
+ * multi-slide page is never a valid scroll-snap resting position, and so
384
+ * the native resting position agrees with `snapAlign` rather than always
385
+ * being `"start"`.
241
386
  *
242
387
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
243
388
  * elsewhere throws a descriptive error.
244
389
  *
390
+ * @extends HTMLDivElement
391
+ *
245
392
  * @example Auto-labelled
246
393
  * ```tsx
247
394
  * <Carousel.Viewport>
@@ -254,15 +401,23 @@ CarouselViewport.displayName = "CarouselViewport";
254
401
  * ```tsx
255
402
  * <Carousel.Slide ariaLabel="Hand-picked for you">…</Carousel.Slide>
256
403
  * ```
404
+ *
405
+ * @example Per-slide snap alignment (overrides the root default)
406
+ * ```tsx
407
+ * <Carousel.Slide snapAlign="end">…</Carousel.Slide>
408
+ * ```
257
409
  */
258
410
  export function CarouselSlide({
259
411
  className = "",
260
412
  ariaLabel,
413
+ snapAlign: snapAlignOverride,
261
414
  children,
262
415
  ...rest
263
416
  }: CarouselSlideProps): ReactElement {
264
- const { slideRef, index, total, state } = useCarouselSlide();
417
+ const { slideRef, index, total, state, snapAlign } =
418
+ useCarouselSlide(snapAlignOverride);
265
419
  const { translations } = useCarouselContext();
420
+
266
421
  const autoLabel =
267
422
  index >= 0 && total > 0
268
423
  ? translations.slideLabel({ index: index + 1, total })
@@ -280,6 +435,7 @@ export function CarouselSlide({
280
435
  data-state={state}
281
436
  className={className}
282
437
  {...(label !== undefined && { "aria-label": label })}
438
+ {...(snapAlign !== undefined && { "data-snap-align": snapAlign })}
283
439
  {...rest}
284
440
  >
285
441
  {children}
@@ -306,6 +462,8 @@ CarouselSlide.displayName = "CarouselSlide";
306
462
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
307
463
  * elsewhere throws a descriptive error.
308
464
  *
465
+ * @extends HTMLButtonElement
466
+ *
309
467
  * @example
310
468
  * ```tsx
311
469
  * <Carousel.NextTrigger>Next</Carousel.NextTrigger>
@@ -373,6 +531,8 @@ CarouselNextTrigger.displayName = "CarouselNextTrigger";
373
531
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
374
532
  * elsewhere throws a descriptive error.
375
533
  *
534
+ * @extends HTMLButtonElement
535
+ *
376
536
  * @example
377
537
  * ```tsx
378
538
  * <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
@@ -439,6 +599,8 @@ CarouselPreviousTrigger.displayName = "CarouselPreviousTrigger";
439
599
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
440
600
  * elsewhere throws a descriptive error.
441
601
  *
602
+ * @extends HTMLDivElement
603
+ *
442
604
  * @example
443
605
  * ```tsx
444
606
  * <Carousel.IndicatorGroup label="Choose slide">
@@ -488,6 +650,15 @@ CarouselIndicatorGroup.displayName = "CarouselIndicatorGroup";
488
650
  * `aria-disabled="false"`. Both states also flip `data-state` between
489
651
  * `"active"` and `"inactive"` so consumer CSS can paint the active dot.
490
652
  *
653
+ * **`readOnly`.** When `true`, the indicator becomes a presentational-only
654
+ * progress dot instead of a navigation control: it renders a `<span>` (no
655
+ * button semantics), carries `aria-hidden="true"` (it's decorative, not
656
+ * announced), and clicking it no longer calls `goTo` — for a carousel whose
657
+ * only navigation is e.g. `allowMouseDrag`, where clickable dots would be a
658
+ * redundant/misleading control. It still tracks `data-state`, so CSS-driven
659
+ * progress display keeps working, and the consumer's own `onClick` (if
660
+ * passed) still fires — only the internal navigation is suppressed.
661
+ *
491
662
  * **Styling hooks.**
492
663
  * - `data-carousel-indicator` — CSS-targeting attribute.
493
664
  * - `data-state="active" | "inactive"` — tracks the current page.
@@ -495,15 +666,19 @@ CarouselIndicatorGroup.displayName = "CarouselIndicatorGroup";
495
666
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
496
667
  * elsewhere throws a descriptive error.
497
668
  *
669
+ * @extends HTMLButtonElement
670
+ *
498
671
  * @example
499
672
  * ```tsx
500
673
  * <Carousel.Indicator index={0} />
674
+ * <Carousel.Indicator index={0} readOnly />
501
675
  * ```
502
676
  */
503
677
  export function CarouselIndicator({
504
678
  className = "",
505
679
  index,
506
680
  onClick,
681
+ readOnly = false,
507
682
  asChild = false,
508
683
  children,
509
684
  ...rest
@@ -514,18 +689,22 @@ export function CarouselIndicator({
514
689
  const handleClick = useCallback(
515
690
  (event: MouseEvent<HTMLButtonElement>) => {
516
691
  onClick?.(event);
517
- goTo(index);
692
+ if (!readOnly) goTo(index);
518
693
  },
519
- [goTo, index, onClick],
694
+ [goTo, index, onClick, readOnly],
520
695
  );
521
696
 
522
697
  const indicatorProps = {
523
698
  className,
524
- "aria-label": translations.indicatorLabel({ index: index + 1 }),
525
- "aria-disabled": isActive,
526
699
  "data-carousel-indicator": "",
527
700
  "data-state": isActive ? "active" : "inactive",
528
701
  onClick: handleClick,
702
+ ...(readOnly
703
+ ? { "aria-hidden": true }
704
+ : {
705
+ "aria-label": translations.indicatorLabel({ index: index + 1 }),
706
+ "aria-disabled": isActive,
707
+ }),
529
708
  ...rest,
530
709
  };
531
710
 
@@ -533,6 +712,10 @@ export function CarouselIndicator({
533
712
  return <Slot {...indicatorProps}>{children}</Slot>;
534
713
  }
535
714
 
715
+ if (readOnly) {
716
+ return <span {...indicatorProps}>{children}</span>;
717
+ }
718
+
536
719
  return (
537
720
  <button type="button" {...indicatorProps}>
538
721
  {children}
@@ -555,23 +738,30 @@ CarouselIndicator.displayName = "CarouselIndicator";
555
738
  * thumbnail previews on hover), drop down to
556
739
  * `Carousel.IndicatorGroup` + `Carousel.Indicator` instead.
557
740
  *
741
+ * `readOnly` forwards to every generated indicator — see
742
+ * `Carousel.Indicator`'s own `readOnly` doc.
743
+ *
558
744
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
559
745
  * elsewhere throws a descriptive error.
560
746
  *
747
+ * @extends HTMLDivElement
748
+ *
561
749
  * @example
562
750
  * ```tsx
563
751
  * <Carousel.Indicators label="Choose slide" />
752
+ * <Carousel.Indicators label="Progress" readOnly />
564
753
  * ```
565
754
  */
566
- export function CarouselIndicators(
567
- props: CarouselIndicatorsProps,
568
- ): ReactElement {
755
+ export function CarouselIndicators({
756
+ readOnly,
757
+ ...props
758
+ }: CarouselIndicatorsProps): ReactElement {
569
759
  const { totalPages } = useCarouselContext();
570
760
 
571
761
  return (
572
762
  <CarouselIndicatorGroup {...props}>
573
763
  {Array.from({ length: totalPages }, (_, index) => (
574
- <CarouselIndicator key={index} index={index} />
764
+ <CarouselIndicator key={index} index={index} readOnly={readOnly} />
575
765
  ))}
576
766
  </CarouselIndicatorGroup>
577
767
  );
@@ -598,9 +788,23 @@ CarouselIndicators.displayName = "CarouselIndicators";
598
788
  * Static children also work — useful when you want a single icon and
599
789
  * style it via `[data-state]` selectors.
600
790
  *
791
+ * Requires autoplay to be enabled on `Carousel.Root` — a control that
792
+ * toggled a non-existent slideshow would be misleading, so rendering
793
+ * this trigger without an `autoplay` prop on the Root throws a
794
+ * descriptive error. Toggling `playing` to `true` starts the timer that
795
+ * advances the page on each tick (see {@link CarouselAutoplay}).
796
+ *
601
797
  * Must be rendered as a descendant of `Carousel.Root`; rendering it
602
- * elsewhere throws a descriptive error. The autoplay timer that
603
- * advances the page when `playing` flips to `true` lands in cycle 12.
798
+ * elsewhere throws a descriptive error.
799
+ *
800
+ * @extends HTMLButtonElement
801
+ *
802
+ * @example State-aware render prop
803
+ * ```tsx
804
+ * <Carousel.PlayPauseTrigger>
805
+ * {({ playing }) => (playing ? "Pause" : "Play")}
806
+ * </Carousel.PlayPauseTrigger>
807
+ * ```
604
808
  */
605
809
  export function CarouselPlayPauseTrigger({
606
810
  className = "",
@@ -654,6 +858,48 @@ export function CarouselPlayPauseTrigger({
654
858
  /** @internal */
655
859
  CarouselPlayPauseTrigger.displayName = "CarouselPlayPauseTrigger";
656
860
 
861
+ /**
862
+ * A `<span>` rendering the live active-page progress as text — `"1 of
863
+ * 3"` by default, via `translations.progressText`. Closes the gap
864
+ * between the imperative `getProgress()` data and an actual DOM node a
865
+ * consumer can drop in without wiring the announcement themselves.
866
+ *
867
+ * ```tsx
868
+ * <Carousel.ProgressText />
869
+ * ```
870
+ *
871
+ * Pass `children` to render custom content instead of the default
872
+ * translated text (e.g. an icon alongside the count) — the computed
873
+ * text is only used as a fallback.
874
+ *
875
+ * Must be rendered as a descendant of `Carousel.Root`; rendering it
876
+ * elsewhere throws a descriptive error (via the shared Carousel
877
+ * context guard).
878
+ *
879
+ * @extends HTMLSpanElement
880
+ *
881
+ * @example
882
+ * ```tsx
883
+ * <Carousel.ProgressText />
884
+ * ```
885
+ */
886
+ export function CarouselProgressText({
887
+ children,
888
+ ...rest
889
+ }: CarouselProgressTextProps): ReactElement {
890
+ const { currentPage, totalPages, translations } = useCarouselContext();
891
+
892
+ return (
893
+ <span {...rest}>
894
+ {children ??
895
+ translations.progressText({ page: currentPage, totalPages })}
896
+ </span>
897
+ );
898
+ }
899
+
900
+ /** @internal */
901
+ CarouselProgressText.displayName = "CarouselProgressText";
902
+
657
903
  /** Static-property shape of the compound {@link Carousel} export: the callable {@link CarouselRoot} plus its namespaced sub-components. */
658
904
  type CarouselCompound = typeof CarouselRoot & {
659
905
  Root: typeof CarouselRoot;
@@ -665,6 +911,7 @@ type CarouselCompound = typeof CarouselRoot & {
665
911
  Indicator: typeof CarouselIndicator;
666
912
  Indicators: typeof CarouselIndicators;
667
913
  PlayPauseTrigger: typeof CarouselPlayPauseTrigger;
914
+ ProgressText: typeof CarouselProgressText;
668
915
  };
669
916
 
670
917
  /**
@@ -717,6 +964,7 @@ const CarouselCompound: CarouselCompound = Object.assign(CarouselRoot, {
717
964
  Indicator: CarouselIndicator,
718
965
  Indicators: CarouselIndicators,
719
966
  PlayPauseTrigger: CarouselPlayPauseTrigger,
967
+ ProgressText: CarouselProgressText,
720
968
  });
721
969
 
722
970
  CarouselCompound.displayName = "Carousel";