@primitiv-ui/react 0.1.28 → 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
@@ -7,8 +7,20 @@ import { ComponentProps, ReactNode, RefObject } from "react";
7
7
  * rejects shapes that supply both or neither.
8
8
  */
9
9
  export type CarouselRootLabelProps =
10
- | { ariaLabel: string; ariaLabelledBy?: never }
11
- | { ariaLabel?: never; ariaLabelledBy: string };
10
+ | {
11
+ /** Accessible name for the carousel region, announced as
12
+ * `aria-label`. Prefer a short, human-readable description (e.g.
13
+ * `"Featured products"`). Mutually exclusive with `ariaLabelledBy`. */
14
+ ariaLabel: string;
15
+ ariaLabelledBy?: never;
16
+ }
17
+ | {
18
+ ariaLabel?: never;
19
+ /** Id of an existing element to name the carousel region, set as
20
+ * `aria-labelledby`. Use when a visible heading already labels the
21
+ * carousel. Mutually exclusive with `ariaLabel`. */
22
+ ariaLabelledBy: string;
23
+ };
12
24
 
13
25
  /**
14
26
  * Uncontrolled page state — the Root owns the active page internally,
@@ -87,18 +99,46 @@ export type CarouselRootPlayingStateProps =
87
99
  export type CarouselAutoplay = boolean | { delay: number };
88
100
 
89
101
  /**
90
- * Visual transition mode for the viewport.
102
+ * Visual transition mode for the viewport. The resolved value is
103
+ * published on the Root as `data-transition` so consumer CSS can switch
104
+ * the visual off a single hook.
91
105
  *
92
106
  * - `"slide"` (default) — relies on native CSS scroll-snap; the
93
107
  * Viewport scrolls programmatically when the page changes and
94
108
  * listens for `scrollsnapchange` to update React state when the
95
109
  * user swipes.
96
- * - `"none"` — the Viewport installs no scroll wiring at all.
97
- * Consumer CSS owns the visual transition (e.g. crossfade,
98
- * dissolve) via the `data-state` hook on each slide, which still
99
- * flips with the active page.
110
+ * - `"fade"` — installs no scroll wiring (like `"none"`), but names the
111
+ * intent so a styled surface can ship a crossfade by default: stack
112
+ * the slides and cross-fade opacity off the per-slide `data-state`
113
+ * hook. Native swipe/drag and peek don't apply (there's no scroll);
114
+ * controls, indicators, and keyboard paging still work.
115
+ * - `"none"` — the Viewport installs no scroll wiring at all, and the
116
+ * styled surface ships no default visual: consumer CSS owns the
117
+ * transition entirely via the `data-state` hook on each slide, which
118
+ * still flips with the active page.
100
119
  */
101
- export type CarouselTransition = "slide" | "none";
120
+ export type CarouselTransition = "slide" | "fade" | "none";
121
+
122
+ /**
123
+ * Resolved wrap-around navigation mode — the value published as the
124
+ * `data-loop` styling hook on the Root. The consumer-facing `loop` prop
125
+ * accepts `boolean | "wrap" | "infinite"` and resolves to this:
126
+ *
127
+ * - `"none"` — navigation clamps at the ends (`loop` omitted / `false`).
128
+ * - `"wrap"` — *semantic* wrapping (`loop` / `loop={true}` / `loop="wrap"`):
129
+ * Next/Previous and autoplay wrap past the ends, the triggers never
130
+ * disable, but wrapping last→first smooth-scrolls the whole track back
131
+ * (a visible rewind, the same path `Home`/`End` take).
132
+ * - `"infinite"` — continuous infinite (`loop="infinite"`): the same wrap
133
+ * page model, plus a cloned edge buffer and a native-scroll recentre so
134
+ * the wrap is a continuous glide with no visible rewind.
135
+ *
136
+ * Both `"wrap"` and `"infinite"` share the page model (the wrap
137
+ * arithmetic in `next`/`previous` and the never-disable boundaries);
138
+ * `"infinite"` layers the clone/scroll behaviour on top, keyed off the
139
+ * `data-loop="infinite"` hook.
140
+ */
141
+ export type CarouselLoopMode = "none" | "wrap" | "infinite";
102
142
 
103
143
  /**
104
144
  * Scroll-snap alignment that the Viewport should target when
@@ -112,8 +152,135 @@ export type CarouselTransition = "slide" | "none";
112
152
  * `(viewportWidth − slideWidth) / 2`. Use this with
113
153
  * `scroll-snap-align: center` in consumer CSS (e.g. Cover Flow
114
154
  * layouts where slides are narrower than the Viewport).
155
+ * - `"end"` — scrolls so the first slide of the target page's
156
+ * **trailing** edge aligns with the Viewport's trailing edge. The
157
+ * target is offset inward by the full `viewportWidth − slideWidth`.
158
+ * Use with `scroll-snap-align: end`.
159
+ *
160
+ * Settable on `Carousel.Root` (the default for every slide) and
161
+ * overridden per-slide via {@link CarouselSlideProps.snapAlign} — e.g. a
162
+ * variable-width layout where only some slides should centre. Matches
163
+ * Ark UI's per-`Item` `snapAlign`.
164
+ */
165
+ export type CarouselSnapAlign = "start" | "center" | "end";
166
+
167
+ /**
168
+ * `scroll-snap-type` strictness the Viewport scrolls with.
169
+ *
170
+ * - `"mandatory"` (default) — the browser always rests on a snap point;
171
+ * scrolling can't stop mid-way between slides.
172
+ * - `"proximity"` — the browser only nudges toward a snap point when the
173
+ * scroll ends *near* one, otherwise leaving the scroll wherever it
174
+ * stopped — for a looser, free-scrolling feel (e.g. a gallery where
175
+ * forcing a rest on every slide feels too rigid).
176
+ *
177
+ * The resolved value is published on the Viewport as `data-snap-type` so
178
+ * consumer CSS can switch `scroll-snap-type`'s strictness off a single hook.
179
+ * Matches Ark UI's `snapType`.
180
+ */
181
+ export type CarouselSnapType = "mandatory" | "proximity";
182
+
183
+ /**
184
+ * Payload passed to `Carousel.Root`'s `onDragStatusChange` on every
185
+ * mouse-drag status transition (only reachable when `allowMouseDrag` is
186
+ * `true`):
187
+ *
188
+ * - `"dragging.start"` — the pointer just crossed the click-vs-drag
189
+ * movement threshold; a drag has begun.
190
+ * - `"dragging"` — a subsequent pointer move while already dragging.
191
+ * - `"dragging.end"` — the drag ended (`pointerup` or `pointercancel`).
192
+ *
193
+ * `page` is the live active page at the moment of the event; `isDragging`
194
+ * mirrors the imperative `isDragging()` snapshot at that moment (`true`
195
+ * for `"dragging.start"`/`"dragging"`, `false` for `"dragging.end"`).
196
+ * Matches Ark UI's `onDragStatusChange` shape.
197
+ */
198
+ export type CarouselDragStatus = {
199
+ type: "dragging.start" | "dragging" | "dragging.end";
200
+ page: number;
201
+ isDragging: boolean;
202
+ };
203
+
204
+ /**
205
+ * Payload passed to `Carousel.Root`'s `onOverscrollStatusChange` whenever
206
+ * the user pushes against a boundary that has nowhere further to go —
207
+ * detected off the same `canGoNext` / `canGoPrevious` page-boundary truth
208
+ * the rest of the primitive already uses, from three input sources the
209
+ * carousel directly owns the physics for:
210
+ *
211
+ * - `"keyboard"` — the forward/backward arrow key pressed while already
212
+ * at that boundary (`Home`/`End` are absolute jumps, not directional
213
+ * pushes, so they never fire this).
214
+ * - `"wheel"` — the existing horizontal-desktop wheel-to-scroll
215
+ * translation, blocked at a boundary.
216
+ * - `"drag"` — a mouse drag (`allowMouseDrag`) pushing past a boundary.
217
+ *
218
+ * `"keyboard"` and `"wheel"` are instantaneous taps with no meaningful
219
+ * distance — they always fire a single bare `"overscroll"` with
220
+ * `amount: 0`. `"drag"` is the one continuous, physically real case —
221
+ * it fires `"overscroll.start"` the moment the drag starts pushing past
222
+ * the boundary, `"overscroll"` on every subsequent move while still
223
+ * pushing (with the live `amount`, the drag delta past the boundary),
224
+ * and `"overscroll.end"` when the drag stops pushing past it (a reversal
225
+ * back within bounds, or the drag ending) — mirroring
226
+ * `CarouselDragStatus`'s shape. `page` is the active page at the moment
227
+ * of the event.
228
+ *
229
+ * Native touch/swipe overscroll (OS-level rubber-banding) is
230
+ * deliberately out of scope — that's browser-owned scroll physics with
231
+ * no JS hook to observe it from, consistent with the primitive's
232
+ * "native scroll is the source of truth" approach elsewhere.
115
233
  */
116
- export type CarouselSnapAlign = "start" | "center";
234
+ export type CarouselOverscrollStatus = {
235
+ type: "overscroll.start" | "overscroll" | "overscroll.end";
236
+ edge: "start" | "end";
237
+ source: "keyboard" | "wheel" | "drag";
238
+ amount: number;
239
+ page: number;
240
+ };
241
+
242
+ /**
243
+ * Payload passed to `Carousel.Root`'s `onAutoplayStatusChange` on every
244
+ * autoplay status transition (only reachable when `autoplay` is enabled):
245
+ *
246
+ * - `"autoplay.start"` — the timer just became active: autoplay is
247
+ * enabled, `playing` is `true`, another page remains, and the timer
248
+ * isn't suspended by hover/focus/touch.
249
+ * - `"autoplay"` — a scheduled tick fired and is about to advance the
250
+ * page, while the timer stays active.
251
+ * - `"autoplay.stop"` — the timer stopped running, whether because
252
+ * `playing` flipped `false`, the last page was reached, or
253
+ * hover/focus/touch suspended it.
254
+ *
255
+ * Fires on every tick, not just play/pause toggles. `page` is the active
256
+ * page at the moment of the event; `isPlaying` mirrors whether the timer
257
+ * is running right after the event (`true` for `"autoplay.start"`/
258
+ * `"autoplay"`, `false` for `"autoplay.stop"`). Matches Ark UI's
259
+ * `onAutoplayStatusChange` shape.
260
+ */
261
+ export type CarouselAutoplayStatus = {
262
+ type: "autoplay.start" | "autoplay" | "autoplay.stop";
263
+ page: number;
264
+ isPlaying: boolean;
265
+ };
266
+
267
+ /**
268
+ * Axis the carousel scrolls and paginates along.
269
+ *
270
+ * - `"horizontal"` (default) — slides lay out inline; the viewport
271
+ * scroll-snaps on the inline (x) axis. `Carousel.NextTrigger` /
272
+ * `Carousel.PreviousTrigger` and the viewport `ArrowRight` /
273
+ * `ArrowLeft` keys advance / retreat.
274
+ * - `"vertical"` — slides lay out in the block direction; the viewport
275
+ * scroll-snaps on the block (y) axis. The viewport `ArrowDown` /
276
+ * `ArrowUp` keys advance / retreat instead (the horizontal arrows are
277
+ * inert), and programmatic paging scrolls on the block axis.
278
+ *
279
+ * The resolved value is published on the Root as `data-orientation` so
280
+ * consumer CSS can switch layout (e.g. a column viewport with a
281
+ * `scroll-snap-type: y mandatory`) off a single hook.
282
+ */
283
+ export type CarouselOrientation = "horizontal" | "vertical";
117
284
 
118
285
  /**
119
286
  * Pin DOM `id`s on the rendered sub-components for SSR / hydration
@@ -151,6 +318,10 @@ export type CarouselTranslations = {
151
318
  /** Accessible name for `Carousel.PlayPauseTrigger` while playing.
152
319
  * Default: `"Stop automatic slide show"`. */
153
320
  stopSlideshow?: string;
321
+ /** Format used for `Carousel.ProgressText`'s default rendered content.
322
+ * Receives the 0-indexed active `page` and the live `totalPages`.
323
+ * Default: `({ page, totalPages }) => "${page + 1} of ${totalPages}"`. */
324
+ progressText?: (params: { page: number; totalPages: number }) => string;
154
325
  };
155
326
 
156
327
  /** Props for `Carousel.Root` — the labelled `<section>` wrapping the whole widget; combines label, page-state, and playing-state shapes with autoplay, transition, and layout options. */
@@ -170,13 +341,27 @@ export type CarouselRootProps = Omit<
170
341
  * greater than `1`, slides are grouped into pages of that size for
171
342
  * navigation purposes: indicators auto-render per page, boundary
172
343
  * clamp moves to the last page, and `Carousel.NextTrigger` /
173
- * `Carousel.PreviousTrigger` advance one page at a time. */
344
+ * `Carousel.PreviousTrigger` advance one page at a time. The **last
345
+ * page always end-aligns** (its window is full, flush with the track
346
+ * end) rather than leaving a partial page — a partial page's leading
347
+ * slide can't align to the viewport start, which desyncs the active
348
+ * page against the scroll. So 7 slides at `slidesPerPage={3}` are 3
349
+ * pages — `[0,1,2] [3,4,5] [4,5,6]` — the last shifted back by one to
350
+ * stay full. Coerced to an integer ≥ 1 — `0`, negative, fractional,
351
+ * and non-finite values are clamped (e.g. `2.5 → 2`, `0 → 1`) so the
352
+ * page maths never divides by zero. */
174
353
  slidesPerPage?: number;
175
354
  /** Number of slides advanced by `Carousel.NextTrigger` /
176
- * `Carousel.PreviousTrigger`. `"auto"` (default) advances one
177
- * full page at a time (= `slidesPerPage`); a number advances
178
- * exactly that many slides per click and pages are computed so
179
- * the visible window always stays full. */
355
+ * `Carousel.PreviousTrigger`. `"auto"` (default) advances one full page
356
+ * at a time (= `slidesPerPage`); a number advances exactly that many
357
+ * slides per click and pages are windowed so the visible window always
358
+ * stays full. A numeric move is coerced to an integer in
359
+ * `[1, slidesPerPage]` — it can neither drop below one nor skip past a
360
+ * page (which would orphan the slides in the gap) — and the **last page
361
+ * is end-aligned to the track end**, so every slide stays reachable even
362
+ * when the move doesn't divide the slide count evenly (e.g. 6 slides,
363
+ * `slidesPerPage={3}`, `slidesPerMove={2}` yields pages
364
+ * `[0,1,2] [2,3,4] [3,4,5]`). */
180
365
  slidesPerMove?: number | "auto";
181
366
  /** Override the default user-visible strings the component owns —
182
367
  * see {@link CarouselTranslations}. Useful for i18n. */
@@ -191,6 +376,60 @@ export type CarouselRootProps = Omit<
191
376
  * `scroll-snap-align: center` on slides (e.g. Cover Flow layouts
192
377
  * where slides are narrower than the Viewport). */
193
378
  snapAlign?: CarouselSnapAlign;
379
+ /** Axis the carousel scrolls and paginates along — see
380
+ * {@link CarouselOrientation}. Defaults to `"horizontal"`. Switches
381
+ * the viewport scroll axis, the arrow-key bindings, and the
382
+ * `data-orientation` styling hook on the Root. */
383
+ orientation?: CarouselOrientation;
384
+ /** Wrap-around navigation mode — see {@link CarouselLoopMode}.
385
+ * Defaults to `false` (`"none"`). Accepts a boolean for ergonomics or a
386
+ * named mode:
387
+ *
388
+ * - `false` / omitted → `"none"` (clamp at the ends).
389
+ * - `true` / `"wrap"` → `"wrap"` (semantic wrap: Next/Previous and
390
+ * autoplay wrap past the ends and the triggers never disable, but the
391
+ * wrap last→first smooth-scrolls the track back — a visible rewind).
392
+ * - `"infinite"` → continuous infinite: the same wrap page model plus a
393
+ * cloned edge buffer and native-scroll recentre so the wrap is a
394
+ * continuous glide with no rewind.
395
+ *
396
+ * All wrapping requires more than one page — a single-page carousel has
397
+ * no wrap target, so the triggers stay disabled regardless. The resolved
398
+ * mode is published as `data-loop="none" | "wrap" | "infinite"` on the
399
+ * Root. */
400
+ loop?: boolean | "wrap" | "infinite";
401
+ /** Whether the Viewport supports mouse click-and-drag scrolling —
402
+ * the pointer tracks 1:1 into `scrollLeft`/`scrollTop` (no momentum)
403
+ * once past a small movement threshold, release lets the existing
404
+ * `scroll-snap-type` settle. Defaults to `false`: an unconditionally-on
405
+ * drag can conflict with a consumer's own drag-sensitive slide content
406
+ * (a nested carousel, a draggable card, a canvas), so it's opt-in.
407
+ * Touch/pen scrolling is unaffected either way — that's native,
408
+ * independent of this prop. */
409
+ allowMouseDrag?: boolean;
410
+ /** Fires on every mouse-drag status transition — see
411
+ * {@link CarouselDragStatus}. Matches Ark UI's `onDragStatusChange`.
412
+ * No-op unless `allowMouseDrag` is `true` (a drag can't start
413
+ * otherwise). */
414
+ onDragStatusChange?: (status: CarouselDragStatus) => void;
415
+ /** Fires whenever the user pushes against a boundary with nowhere
416
+ * further to go, from the keyboard, wheel, or a mouse drag — see
417
+ * {@link CarouselOverscrollStatus}. */
418
+ onOverscrollStatusChange?: (status: CarouselOverscrollStatus) => void;
419
+ /** Fires on every autoplay status transition — see
420
+ * {@link CarouselAutoplayStatus}. Matches Ark UI's
421
+ * `onAutoplayStatusChange`. Fires on every tick, not just play/pause
422
+ * toggles — useful for analytics. No-op unless `autoplay` is enabled. */
423
+ onAutoplayStatusChange?: (status: CarouselAutoplayStatus) => void;
424
+ /** Visibility threshold(s) the `isInView` fallback's
425
+ * `IntersectionObserver` uses — both as the observer's own `threshold`
426
+ * option and (for an array, the highest value) the cutoff a slide's
427
+ * `intersectionRatio` must clear to count as "in view". Matches Ark
428
+ * UI's `inViewThreshold` shape. Defaults to `0.6`. */
429
+ inViewThreshold?: number | number[];
430
+ /** `scroll-snap-type` strictness — see {@link CarouselSnapType}.
431
+ * Defaults to `"mandatory"`. */
432
+ snapType?: CarouselSnapType;
194
433
  };
195
434
 
196
435
  /**
@@ -209,12 +448,33 @@ export type CarouselContextValue = {
209
448
  /** Ordered list of currently-mounted slide keys. The slide's index is
210
449
  * its position in this array; the array's length is the total. */
211
450
  slideKeys: string[];
212
- /** Number of slides visible per page (default `1`). */
451
+ /** Number of slides visible per page — the consumer's `slidesPerPage`
452
+ * coerced to an integer ≥ 1 (0 / negative / non-finite → 1). */
213
453
  slidesPerPage: number;
214
454
  /** Resolved slides advanced per Prev/Next click — equal to
215
- * `slidesPerPage` when the consumer left `slidesPerMove="auto"`,
216
- * else the numeric value. */
455
+ * `slidesPerPage` when the consumer left `slidesPerMove="auto"`, else
456
+ * the numeric value coerced to an integer in `[1, slidesPerPage]` (a
457
+ * move can neither drop below one nor skip past a page). */
217
458
  effectiveSlidesPerMove: number;
459
+ /** Start slide index of the *last* page's window — `max(0, total -
460
+ * slidesPerPage)`. Every page's own offset is `min(page *
461
+ * effectiveSlidesPerMove, maxOffset)`; used to derive any page's window
462
+ * (not just the current one), e.g. the multi-slide `scroll-margin`
463
+ * extension that keeps native scroll-snap alignment consistent with the
464
+ * programmatic scroll for `slidesPerPage > 1`. */
465
+ maxOffset: number;
466
+ /** Start slide index of the currently-active window. In `"auto"`
467
+ * (paged) mode this is `currentPage * slidesPerPage` (the last page may
468
+ * be partial); in numeric (windowed) mode the last page is end-aligned
469
+ * — clamped to `total − slidesPerPage` — so every slide stays reachable
470
+ * even when the move doesn't divide the track evenly. Drives the slide
471
+ * `data-state` window and the viewport scroll target. */
472
+ currentPageOffset: number;
473
+ /** Map a leading slide index (e.g. a user swipe's snap target) back to
474
+ * its page index, clamped into `[0, totalPages − 1]`. Paged mode groups
475
+ * by `slidesPerPage`; windowed mode inverts the move offset to the
476
+ * nearest window start. */
477
+ pageForSlideIndex: (slideIndex: number) => number;
218
478
  /** Live total page count — `ceil(total / slidesPerPage)` in `"auto"`
219
479
  * mode (partial last page allowed), else
220
480
  * `floor((total - slidesPerPage) / effectiveSlidesPerMove) + 1`
@@ -231,13 +491,17 @@ export type CarouselContextValue = {
231
491
  /** `true` when there is a backward navigation target. Drives the
232
492
  * `disabled` attribute on `Carousel.PreviousTrigger`. */
233
493
  canGoPrevious: boolean;
234
- /** Advance the active page by one step. No-op when `!canGoNext`. */
235
- next: () => void;
236
- /** Retreat the active page by one step. No-op when `!canGoPrevious`. */
237
- previous: () => void;
494
+ /** Advance the active page by one step. No-op when `!canGoNext`. Pass
495
+ * `instant` to bypass the resolved smooth/reduced-motion scroll for
496
+ * just this one call — see {@link CarouselImperativeApi.next}. */
497
+ next: (instant?: boolean) => void;
498
+ /** Retreat the active page by one step. No-op when `!canGoPrevious`.
499
+ * See {@link CarouselContextValue.next} for the `instant` override. */
500
+ previous: (instant?: boolean) => void;
238
501
  /** Jump directly to `target` (zero-based page index). Used by
239
- * `Carousel.Indicator` to dispatch click-to-jump. */
240
- goTo: (target: number) => void;
502
+ * `Carousel.Indicator` to dispatch click-to-jump. See
503
+ * {@link CarouselContextValue.next} for the `instant` override. */
504
+ goTo: (target: number, instant?: boolean) => void;
241
505
  /** Whether autoplay is currently in the "playing" state. */
242
506
  playing: boolean;
243
507
  /** Toggles `playing`. Used by `Carousel.PlayPauseTrigger`. */
@@ -260,8 +524,27 @@ export type CarouselContextValue = {
260
524
  ids: CarouselIds;
261
525
  /** Resolved visual transition mode (defaults to `"slide"`). */
262
526
  transition: CarouselTransition;
263
- /** Resolved scroll-snap alignment (defaults to `"start"`). */
527
+ /** Resolved scroll-snap alignment (defaults to `"center"`). */
264
528
  snapAlign: CarouselSnapAlign;
529
+ /** Resolved scroll/pagination axis (defaults to `"horizontal"`).
530
+ * Drives the viewport scroll axis, the arrow-key bindings, and the
531
+ * `data-orientation` hook on the Root. */
532
+ orientation: CarouselOrientation;
533
+ /** Resolved wrap-around navigation mode (defaults to `"none"`) — see
534
+ * {@link CarouselLoopMode}. `"wrap"` and `"infinite"` both drive the
535
+ * boundary flags (`canGoNext`/`canGoPrevious` stay true past the ends
536
+ * when there is more than one page) and the wrap arithmetic in
537
+ * `next`/`previous`; `"infinite"` additionally signals the clone/scroll
538
+ * layer via the `data-loop` hook on the Root. */
539
+ loop: CarouselLoopMode;
540
+ /** Resolved mouse click-and-drag scrolling opt-in (defaults to
541
+ * `false`). */
542
+ allowMouseDrag: boolean;
543
+ /** Resolved `isInView` IntersectionObserver threshold(s) (defaults to
544
+ * `0.6`). */
545
+ inViewThreshold: number | number[];
546
+ /** Resolved `scroll-snap-type` strictness (defaults to `"mandatory"`). */
547
+ snapType: CarouselSnapType;
265
548
  /** Bumped by `refresh()` to force the viewport's scroll-align
266
549
  * effect to re-run without a page change. */
267
550
  refreshTick: number;
@@ -271,12 +554,55 @@ export type CarouselContextValue = {
271
554
  visibleSlideIndicesRef: RefObject<Set<number>>;
272
555
  /** Used by the Viewport hook to record visibility transitions. */
273
556
  setSlideInView: (slideIndex: number, inView: boolean) => void;
557
+ /** Used by the Viewport hook to report the live continuous scroll
558
+ * position, read on demand by the imperative `getScrollProgress()`. */
559
+ setScrollProgress: (value: number) => void;
560
+ /** Used by the Viewport hook to report a slide's live continuous
561
+ * center-distance, read on demand by the imperative
562
+ * `getSlideProgress()`. */
563
+ setSlideProgress: (slideIndex: number, value: number) => void;
274
564
  /** Set to `true` by `next()` and `previous()` the moment programmatic
275
565
  * navigation begins, and cleared by the Viewport hook once the scroll
276
566
  * animation settles (`scrollend` or a timeout fallback). The
277
567
  * IntersectionObserver callback checks this flag before calling `goTo`
278
568
  * so that IO entries firing mid-animation cannot undo the navigation. */
279
569
  isProgrammaticScrollRef: RefObject<boolean>;
570
+ /** One-shot override of the resolved smooth/reduced-motion
571
+ * `scrollBehavior`, set by `next()` / `previous()` / `goTo()` on every
572
+ * call (`true` when their optional `instant` argument was passed,
573
+ * `false` otherwise) and consumed — then reset to `false` — by the
574
+ * Viewport hook's scroll effect on its very next run, so an instant
575
+ * jump never outlives the single call that requested it. */
576
+ instantScrollRef: RefObject<boolean>;
577
+ /** One-shot hint for the infinite-loop engine, set to `true` by `goTo()` /
578
+ * `scrollToIndex()` (an absolute jump to a specific page — most visibly an
579
+ * indicator/thumbnail click) and to `false` by `next()` / `previous()` (a
580
+ * relative step, which should always take the loop's wrap shortcut).
581
+ * Consumed and reset by the engine's positioning effect on its very next
582
+ * run, so it never outlives the single navigation that requested it. */
583
+ directJumpRef: RefObject<boolean>;
584
+ /** Live mouse-drag state (only ever `true` when `allowMouseDrag` is
585
+ * `true`). Mutated by the Viewport hook and read by the imperative
586
+ * `isDragging()`. */
587
+ isDraggingRef: RefObject<boolean>;
588
+ /** Used by the Viewport hook to record drag start/end transitions. */
589
+ setDragging: (value: boolean) => void;
590
+ /** The consumer's `onDragStatusChange`, if provided — called by the
591
+ * Viewport hook on every drag status transition. */
592
+ onDragStatusChange?: (status: CarouselDragStatus) => void;
593
+ /** Live mouse-drag overscroll state (only ever `true` mid-drag, when
594
+ * `allowMouseDrag` is `true` and the drag is currently pushing past a
595
+ * boundary). Mutated by the Viewport hook and read by the imperative
596
+ * `isOverscrolling()`. Keyboard/wheel overscroll taps never set this —
597
+ * they're instantaneous, not a sustained state. */
598
+ isOverscrollingRef: RefObject<boolean>;
599
+ /** Used by the Viewport hook to record drag-overscroll start/end
600
+ * transitions. */
601
+ setOverscrolling: (value: boolean) => void;
602
+ /** The consumer's `onOverscrollStatusChange`, if provided — called by
603
+ * the Viewport hook whenever the keyboard, wheel, or a mouse drag
604
+ * pushes against a boundary with nowhere further to go. */
605
+ onOverscrollStatusChange?: (status: CarouselOverscrollStatus) => void;
280
606
  };
281
607
 
282
608
  /** Props for `Carousel.Viewport` — the scroll-snap slide container; accepts all native `<div>` props. */
@@ -289,6 +615,13 @@ export type CarouselSlideProps = Omit<ComponentProps<"div">, "aria-label"> & {
289
615
  * (e.g. `"Hand-picked for you"`). When omitted, slides are labelled
290
616
  * with their live index and total in registration order. */
291
617
  ariaLabel?: string;
618
+ /** Override the root's {@link CarouselRootProps.snapAlign | `snapAlign`}
619
+ * for this slide only — e.g. a variable-width layout where only some
620
+ * slides should centre or end-align. Only takes effect on a slide
621
+ * that's a valid scroll-snap resting position (a page's leading
622
+ * slide); an interior slide of a multi-slide page never snaps,
623
+ * regardless of this prop. Matches Ark UI's per-`Item` `snapAlign`. */
624
+ snapAlign?: CarouselSnapAlign;
292
625
  };
293
626
 
294
627
  /** Props for `Carousel.NextTrigger` — the button that advances one page; native `<button>` props plus `asChild`. */
@@ -316,14 +649,31 @@ export type CarouselIndicatorGroupProps = Omit<
316
649
  "label" | "aria-labelledby"
317
650
  > &
318
651
  (
319
- | { label: string; ariaLabelledBy?: never }
320
- | { label?: never; ariaLabelledBy: string }
652
+ | {
653
+ /** Accessible name for the indicator group, announced as
654
+ * `aria-label` (e.g. `"Choose slide"`). Mutually exclusive with
655
+ * `ariaLabelledBy`. */
656
+ label: string;
657
+ ariaLabelledBy?: never;
658
+ }
659
+ | {
660
+ label?: never;
661
+ /** Id of an existing element to name the indicator group, set as
662
+ * `aria-labelledby`. Mutually exclusive with `label`. */
663
+ ariaLabelledBy: string;
664
+ }
321
665
  );
322
666
 
323
667
  /** Props for `Carousel.Indicator` — a button that jumps to its target page; native `<button>` props plus the target `index` and `asChild`. */
324
668
  export type CarouselIndicatorProps = ComponentProps<"button"> & {
325
669
  /** Zero-based page this indicator targets. Clicking jumps to it. */
326
670
  index: number;
671
+ /** Renders a presentational-only `<span>` instead of a `<button>`:
672
+ * `aria-hidden="true"`, and clicking no longer calls `goTo`. Use for a
673
+ * progress display when navigation happens some other way (e.g. a
674
+ * carousel driven solely by `allowMouseDrag`). The consumer's own
675
+ * `onClick`, if passed, still fires. Default `false`. */
676
+ readOnly?: boolean;
327
677
  /** Render the child element instead of the default `<button>`.
328
678
  * Trigger props (onClick, aria-label, aria-disabled, data-state)
329
679
  * are merged onto the child via `Slot`. The child must accept a
@@ -338,12 +688,27 @@ export type CarouselIndicatorProps = ComponentProps<"button"> & {
338
688
  * fire as if the user had clicked.
339
689
  */
340
690
  export type CarouselImperativeApi = {
341
- /** Advance the active page by one. No-op on the last page. */
342
- next: () => void;
343
- /** Retreat the active page by one. No-op on the first page. */
344
- previous: () => void;
345
- /** Jump directly to `target` (zero-based page index). */
346
- goTo: (target: number) => void;
691
+ /** Advance the active page by one. No-op on the last page. Pass
692
+ * `instant` (default `false`) to bypass the resolved smooth/
693
+ * reduced-motion scroll for just this one call — e.g. an instant
694
+ * jump on initial deep-link vs. smooth for user-initiated
695
+ * navigation. Matches Ark UI's `scrollNext(instant?)`. */
696
+ next: (instant?: boolean) => void;
697
+ /** Retreat the active page by one. No-op on the first page. See
698
+ * `next`'s `instant` parameter. Matches Ark UI's
699
+ * `scrollPrev(instant?)`. */
700
+ previous: (instant?: boolean) => void;
701
+ /** Jump directly to `target` (zero-based page index). See `next`'s
702
+ * `instant` parameter. Matches Ark UI's `scrollTo(page, instant?)`. */
703
+ goTo: (target: number, instant?: boolean) => void;
704
+ /** Jump to the page containing the given zero-based **slide** index —
705
+ * slide granularity, distinct from `goTo`'s page granularity. In a
706
+ * multi-slide (`slidesPerPage > 1`) layout an interior slide isn't its
707
+ * own scroll-snap position, so this lands on that slide's *containing*
708
+ * page (the same mapping `Carousel.Indicator` uses internally). See
709
+ * `next`'s `instant` parameter. Matches Ark UI's
710
+ * `scrollToIndex(index, instant?)`. */
711
+ scrollToIndex: (slideIndex: number, instant?: boolean) => void;
347
712
  /** Set `playing` to `true`. Dismisses the hover/focus pause for the
348
713
  * lifetime of the resulting playing session. */
349
714
  play: () => void;
@@ -361,6 +726,42 @@ export type CarouselImperativeApi = {
361
726
  * visible in the viewport (per IntersectionObserver, ≥ 60%
362
727
  * intersection). Useful for lazy-loading slide content. */
363
728
  isInView: (slideIndex: number) => boolean;
729
+ /** The leading slide index each page snaps to, one entry per page
730
+ * (length `totalPages`). The last entry is the end-aligned offset
731
+ * (see the multi-slide page model), not necessarily a clean multiple
732
+ * of `slidesPerMove`. Useful for a custom progress bar or indicator
733
+ * that needs the raw offsets rather than just the page count. */
734
+ getPageSnapPoints: () => number[];
735
+ /** Live snapshot of whether a mouse drag is currently in progress
736
+ * (`allowMouseDrag`-gated — always `false` when it's off). Mirrors
737
+ * Ark UI's `api.isDragging`; see also `onDragStatusChange`. */
738
+ isDragging: () => boolean;
739
+ /** Live snapshot of whether a mouse drag is currently pushing past a
740
+ * boundary (`allowMouseDrag`-gated — always `false` when it's off, and
741
+ * never `true` for a keyboard/wheel overscroll tap, which is
742
+ * instantaneous rather than a sustained state). See
743
+ * `onOverscrollStatusChange`. */
744
+ isOverscrolling: () => boolean;
745
+ /** Continuous `[0, 1]` scroll position along the main axis — `0` at
746
+ * the start, `1` at the end, `0` when there's no overflow to scroll.
747
+ * Unlike `getProgress()`'s page-granular step function, this changes
748
+ * on every scroll tick (mirrored live onto the Viewport's
749
+ * `--carousel-progress` CSS custom property, rAF-batched, so a
750
+ * consumer can drive a progress bar from CSS alone without reading
751
+ * this at all). Robust under RTL without any `dir` check — RTL's
752
+ * standardized negative-`scrollLeft` convention is handled via
753
+ * `Math.abs`. */
754
+ getScrollProgress: () => number;
755
+ /** Continuous `[-1, 1]` signal for how far the slide at the zero-based
756
+ * index sits from the viewport's center along the main axis — `0` =
757
+ * centered, `±1` = the slide's center has reached the viewport edge
758
+ * (clamped beyond that). `0` for a never-registered/out-of-range
759
+ * index. Normalised by the *viewport's* half-extent, not the slide's
760
+ * own, so at `slidesPerPage > 1` a fully on-screen outer page member
761
+ * can already read a large magnitude — this is not equivalent to
762
+ * `isInView` flipping to `false`. Mirrored live onto that slide's
763
+ * `--slide-progress` CSS custom property, rAF-batched. */
764
+ getSlideProgress: (slideIndex: number) => number;
364
765
  };
365
766
 
366
767
  /** Live progress snapshot reported by the carousel: the active page, the total page count, and a normalised `value` in `[0, 1]`. */
@@ -382,9 +783,24 @@ export type CarouselIndicatorsProps = Omit<
382
783
  "label" | "aria-labelledby" | "children"
383
784
  > &
384
785
  (
385
- | { label: string; ariaLabelledBy?: never }
386
- | { label?: never; ariaLabelledBy: string }
387
- );
786
+ | {
787
+ /** Accessible name for the indicator group, announced as
788
+ * `aria-label` (e.g. `"Choose slide"`). Mutually exclusive with
789
+ * `ariaLabelledBy`. */
790
+ label: string;
791
+ ariaLabelledBy?: never;
792
+ }
793
+ | {
794
+ label?: never;
795
+ /** Id of an existing element to name the indicator group, set as
796
+ * `aria-labelledby`. Mutually exclusive with `label`. */
797
+ ariaLabelledBy: string;
798
+ }
799
+ ) & {
800
+ /** Forwarded to every generated `Carousel.Indicator`. See
801
+ * `CarouselIndicatorProps.readOnly`. Default `false`. */
802
+ readOnly?: boolean;
803
+ };
388
804
 
389
805
  /**
390
806
  * Render-prop or static children supported by
@@ -406,3 +822,9 @@ export type CarouselPlayPauseTriggerProps = Omit<
406
822
  * is not supported under `asChild`; pass a single element instead. */
407
823
  asChild?: boolean;
408
824
  };
825
+
826
+ /**
827
+ * Props for `Carousel.ProgressText` — renders the live active-page
828
+ * progress as text; native `<span>` props. `children`, if provided,
829
+ * overrides the default `translations.progressText` content. */
830
+ export type CarouselProgressTextProps = ComponentProps<"span">;
@@ -50,6 +50,8 @@ function dataStateOf(checked: CheckedState) {
50
50
  * mirror the input, but shipped CSS keys the mark off the input's native
51
51
  * `:checked` / `:indeterminate` instead; `data-state` is a convenience mirror.
52
52
  *
53
+ * @extends HTMLInputElement
54
+ *
53
55
  * @example Uncontrolled
54
56
  * ```tsx
55
57
  * <Checkbox.Root name="terms" value="accepted" defaultChecked aria-label="Accept terms">
@@ -120,7 +122,11 @@ export function CheckboxRoot(props: CheckboxRootProps): ReactElement {
120
122
  );
121
123
  }
122
124
 
123
- /** @internal */
125
+ // Runtime-dead: the compound alias below (same object via Object.assign)
126
+ // overwrites this to "Checkbox" at load, so the value is never observable. The
127
+ // assignment stays because it declares `displayName` on `typeof CheckboxRoot`,
128
+ // which TCheckboxCompound extends.
129
+ // Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
124
130
  CheckboxRoot.displayName = "CheckboxRoot";
125
131
 
126
132
  /**
@@ -138,6 +144,8 @@ CheckboxRoot.displayName = "CheckboxRoot";
138
144
  * (typically an `<svg>` tick) as the indicator itself, with `data-state`
139
145
  * merged onto that element rather than a wrapper.
140
146
  *
147
+ * @extends HTMLSpanElement
148
+ *
141
149
  * @example Icon as the indicator via `asChild`
142
150
  * ```tsx
143
151
  * <Checkbox.Indicator asChild>