@primitiv-ui/react 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/README.md +19 -2
  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 +32 -11
  7. package/src/Accordion/README.md +2 -2
  8. package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
  9. package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
  10. package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
  11. package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
  12. package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
  13. package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
  14. package/src/Accordion/types.ts +72 -21
  15. package/src/Alert/Alert.tsx +36 -7
  16. package/src/Alert/__tests__/Alert.test.tsx +5 -0
  17. package/src/Alert/types.ts +18 -2
  18. package/src/Avatar/Avatar.tsx +99 -15
  19. package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
  20. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
  21. package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
  22. package/src/Avatar/hooks/useAvatarImage.ts +16 -0
  23. package/src/Avatar/types.ts +15 -3
  24. package/src/Breadcrumb/Breadcrumb.tsx +139 -21
  25. package/src/Breadcrumb/README.md +47 -0
  26. package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
  27. package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
  28. package/src/Breadcrumb/types.ts +73 -7
  29. package/src/Button/Button.tsx +2 -0
  30. package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
  31. package/src/Button/__tests__/Button.contract.test.tsx +1 -1
  32. package/src/Button/types.ts +21 -0
  33. package/src/Carousel/Carousel.tsx +268 -20
  34. package/src/Carousel/README.md +764 -87
  35. package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
  36. package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
  37. package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
  38. package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
  39. package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
  40. package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
  41. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +90 -2
  42. package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
  43. package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
  44. package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
  45. package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
  46. package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
  47. package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
  48. package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
  49. package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
  50. package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
  51. package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
  52. package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
  53. package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
  54. package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
  55. package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
  56. package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
  57. package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
  58. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
  59. package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
  60. package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
  61. package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
  62. package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
  63. package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
  64. package/src/Carousel/hooks/index.ts +1 -0
  65. package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
  66. package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
  67. package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
  68. package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
  69. package/src/Carousel/loopEngine.ts +54 -0
  70. package/src/Carousel/types.ts +458 -36
  71. package/src/Checkbox/Checkbox.tsx +11 -3
  72. package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
  73. package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
  74. package/src/Checkbox/types.ts +19 -0
  75. package/src/CheckboxCard/CheckboxCard.tsx +14 -5
  76. package/src/CheckboxCard/README.md +1 -1
  77. package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
  78. package/src/CheckboxCard/types.ts +65 -17
  79. package/src/Collapsible/Collapsible.tsx +49 -3
  80. package/src/Collapsible/README.md +41 -1
  81. package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
  82. package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
  83. package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
  84. package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
  85. package/src/Collapsible/types.ts +89 -22
  86. package/src/Combobox/Combobox.tsx +508 -0
  87. package/src/Combobox/ComboboxContext.ts +23 -0
  88. package/src/Combobox/README.md +170 -0
  89. package/src/Combobox/__tests__/Combobox.asChild.test.tsx +62 -0
  90. package/src/Combobox/__tests__/Combobox.basic-rendering.test.tsx +16 -0
  91. package/src/Combobox/__tests__/Combobox.content.test.tsx +36 -0
  92. package/src/Combobox/__tests__/Combobox.controlled-state.test.tsx +88 -0
  93. package/src/Combobox/__tests__/Combobox.empty.test.tsx +42 -0
  94. package/src/Combobox/__tests__/Combobox.ids.test.tsx +66 -0
  95. package/src/Combobox/__tests__/Combobox.item.test.tsx +76 -0
  96. package/src/Combobox/__tests__/Combobox.keyboard-interaction.test.tsx +196 -0
  97. package/src/Combobox/__tests__/Combobox.light-dismiss.test.tsx +101 -0
  98. package/src/Combobox/__tests__/Combobox.query.test.tsx +105 -0
  99. package/src/Combobox/hooks/index.ts +1 -0
  100. package/src/Combobox/hooks/useComboboxRoot.ts +2 -0
  101. package/src/Combobox/index.ts +3 -0
  102. package/src/Combobox/types.ts +133 -0
  103. package/src/ContextMenu/ContextMenu.tsx +270 -4
  104. package/src/ContextMenu/README.md +1 -1
  105. package/src/ContextMenu/types.ts +148 -6
  106. package/src/DirectionProvider/DirectionProvider.tsx +41 -9
  107. package/src/DirectionProvider/README.md +4 -4
  108. package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
  109. package/src/DirectionProvider/types.ts +28 -3
  110. package/src/Divider/Divider.tsx +24 -10
  111. package/src/Divider/__tests__/Divider.contract.test.tsx +1 -1
  112. package/src/Divider/__tests__/Divider.test.tsx +5 -0
  113. package/src/Divider/types.ts +10 -3
  114. package/src/Drawer/Drawer.tsx +315 -0
  115. package/src/Drawer/README.md +124 -0
  116. package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
  117. package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
  118. package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
  119. package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
  120. package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
  121. package/src/Drawer/index.ts +2 -0
  122. package/src/Drawer/types.ts +66 -0
  123. package/src/Dropdown/Dropdown.tsx +119 -2
  124. package/src/Dropdown/README.md +7 -5
  125. package/src/Dropdown/types.ts +241 -11
  126. package/src/EmptyState/EmptyState.tsx +74 -4
  127. package/src/EmptyState/README.md +2 -2
  128. package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
  129. package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
  130. package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
  131. package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
  132. package/src/EmptyState/types.ts +49 -6
  133. package/src/Field/Field.tsx +23 -0
  134. package/src/Field/README.md +1 -1
  135. package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
  136. package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
  137. package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
  138. package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
  139. package/src/Field/hooks/useFieldProps.ts +1 -1
  140. package/src/Field/types.ts +60 -12
  141. package/src/Fieldset/Fieldset.tsx +55 -19
  142. package/src/Fieldset/README.md +2 -2
  143. package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
  144. package/src/Fieldset/types.ts +20 -2
  145. package/src/Input/Input.tsx +15 -10
  146. package/src/Input/README.md +2 -2
  147. package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
  148. package/src/Input/types.ts +19 -2
  149. package/src/InputGroup/InputGroup.tsx +11 -3
  150. package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
  151. package/src/InputGroup/types.ts +26 -5
  152. package/src/Listbox/Listbox.tsx +475 -0
  153. package/src/Listbox/ListboxContext.ts +48 -0
  154. package/src/Listbox/README.md +248 -0
  155. package/src/Listbox/__tests__/Listbox.active-descendant.test.tsx +128 -0
  156. package/src/Listbox/__tests__/Listbox.asChild.test.tsx +91 -0
  157. package/src/Listbox/__tests__/Listbox.basic-rendering.test.tsx +16 -0
  158. package/src/Listbox/__tests__/Listbox.controlled-state.test.tsx +114 -0
  159. package/src/Listbox/__tests__/Listbox.disabled.test.tsx +90 -0
  160. package/src/Listbox/__tests__/Listbox.fixtures.ts +27 -0
  161. package/src/Listbox/__tests__/Listbox.group-label.test.tsx +97 -0
  162. package/src/Listbox/__tests__/Listbox.group.test.tsx +45 -0
  163. package/src/Listbox/__tests__/Listbox.keyboard-edge-cases.test.tsx +144 -0
  164. package/src/Listbox/__tests__/Listbox.keyboard-interaction.test.tsx +112 -0
  165. package/src/Listbox/__tests__/Listbox.modifier-keys.test.tsx +86 -0
  166. package/src/Listbox/__tests__/Listbox.multi-select-keyboard.test.tsx +368 -0
  167. package/src/Listbox/__tests__/Listbox.multiple-mode.test.tsx +91 -0
  168. package/src/Listbox/__tests__/Listbox.mutation-hardening.test.tsx +480 -0
  169. package/src/Listbox/__tests__/Listbox.orientation.test.tsx +59 -0
  170. package/src/Listbox/__tests__/Listbox.reading-direction.test.tsx +65 -0
  171. package/src/Listbox/__tests__/Listbox.reorder.test.tsx +78 -0
  172. package/src/Listbox/__tests__/Listbox.selection-follows-focus.test.tsx +64 -0
  173. package/src/Listbox/__tests__/Listbox.strict-context.test.tsx +21 -0
  174. package/src/Listbox/__tests__/Listbox.typeahead.test.tsx +123 -0
  175. package/src/Listbox/__tests__/Listbox.uncontrolled-state.test.tsx +48 -0
  176. package/src/Listbox/constants.ts +2 -0
  177. package/src/Listbox/hooks/index.ts +1 -0
  178. package/src/Listbox/hooks/useListboxRoot.ts +407 -0
  179. package/src/Listbox/index.ts +3 -0
  180. package/src/Listbox/types.ts +238 -0
  181. package/src/MillerColumns/MillerColumns.tsx +186 -45
  182. package/src/MillerColumns/README.md +90 -20
  183. package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +45 -0
  184. package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +4 -2
  185. package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +18 -3
  186. package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +48 -0
  187. package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +35 -2
  188. package/src/MillerColumns/__tests__/MillerColumns.keyboard-selection-state.test.tsx +96 -0
  189. package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +29 -4
  190. package/src/MillerColumns/__tests__/MillerColumns.preview-visibility.test.tsx +79 -0
  191. package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +189 -0
  192. package/src/MillerColumns/__tests__/MillerColumns.styled-parts.test.tsx +143 -0
  193. package/src/MillerColumns/__tests__/MillerColumns.typeahead.test.tsx +104 -0
  194. package/src/MillerColumns/constants.ts +2 -0
  195. package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +5 -2
  196. package/src/MillerColumns/hooks/useMillerColumnsItem.ts +29 -7
  197. package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +80 -10
  198. package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
  199. package/src/MillerColumns/index.ts +4 -0
  200. package/src/MillerColumns/types.ts +136 -30
  201. package/src/MillerColumns/utils.ts +69 -3
  202. package/src/Modal/Modal.tsx +35 -8
  203. package/src/Modal/README.md +24 -15
  204. package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
  205. package/src/Modal/hooks/useModalContent.ts +32 -0
  206. package/src/Modal/types.ts +110 -29
  207. package/src/NavigationMenu/NavigationMenu.tsx +890 -0
  208. package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
  209. package/src/NavigationMenu/README.md +355 -0
  210. package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
  211. package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
  212. package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
  213. package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
  214. package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
  215. package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
  216. package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
  217. package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
  218. package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
  219. package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
  220. package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
  221. package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
  222. package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
  223. package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
  224. package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
  225. package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
  226. package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
  227. package/src/NavigationMenu/constants.ts +5 -0
  228. package/src/NavigationMenu/hooks/index.ts +7 -0
  229. package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
  230. package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
  231. package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
  232. package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
  233. package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
  234. package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
  235. package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
  236. package/src/NavigationMenu/index.ts +3 -0
  237. package/src/NavigationMenu/types.ts +259 -0
  238. package/src/NavigationMenu/utils.ts +89 -0
  239. package/src/Popover/Popover.tsx +427 -0
  240. package/src/Popover/PopoverContext.ts +13 -0
  241. package/src/Popover/README.md +106 -0
  242. package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
  243. package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
  244. package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
  245. package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
  246. package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
  247. package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
  248. package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
  249. package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
  250. package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
  251. package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
  252. package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
  253. package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
  254. package/src/Popover/hooks/index.ts +3 -0
  255. package/src/Popover/hooks/usePopoverContent.ts +98 -0
  256. package/src/Popover/hooks/usePopoverRoot.ts +84 -0
  257. package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
  258. package/src/Popover/index.ts +2 -0
  259. package/src/Popover/types.ts +197 -0
  260. package/src/Portal/Portal.tsx +50 -10
  261. package/src/Portal/README.md +2 -2
  262. package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
  263. package/src/Portal/types.ts +5 -0
  264. package/src/Progress/Progress.tsx +33 -1
  265. package/src/Progress/README.md +1 -1
  266. package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
  267. package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
  268. package/src/Progress/types.ts +20 -4
  269. package/src/Radio/Radio.tsx +10 -2
  270. package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
  271. package/src/Radio/hooks/useRadioRoot.ts +3 -0
  272. package/src/Radio/types.ts +20 -0
  273. package/src/RadioCard/RadioCard.tsx +11 -0
  274. package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
  275. package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
  276. package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
  277. package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
  278. package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
  279. package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
  280. package/src/RadioCard/types.ts +95 -22
  281. package/src/RadioGroup/README.md +2 -2
  282. package/src/RadioGroup/RadioGroup.tsx +12 -1
  283. package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
  284. package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
  285. package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
  286. package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
  287. package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
  288. package/src/RadioGroup/types.ts +73 -9
  289. package/src/SegmentedControl/README.md +154 -0
  290. package/src/SegmentedControl/SegmentedControl.tsx +309 -0
  291. package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
  292. package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
  293. package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
  294. package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
  295. package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
  296. package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
  297. package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
  298. package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
  299. package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
  300. package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
  301. package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
  302. package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
  303. package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
  304. package/src/SegmentedControl/hooks/index.ts +2 -0
  305. package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
  306. package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +87 -0
  307. package/src/SegmentedControl/index.ts +2 -0
  308. package/src/SegmentedControl/types.ts +138 -0
  309. package/src/Select/README.md +122 -157
  310. package/src/Select/Select.tsx +596 -69
  311. package/src/Select/SelectContext.ts +38 -0
  312. package/src/Select/SelectItemIndicatorContext.ts +29 -0
  313. package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
  314. package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
  315. package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
  316. package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
  317. package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
  318. package/src/Select/__tests__/Select.form.test.tsx +38 -0
  319. package/src/Select/__tests__/Select.group.test.tsx +6 -6
  320. package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
  321. package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
  322. package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
  323. package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
  324. package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
  325. package/src/Select/__tests__/Select.placeholder.test.tsx +7 -7
  326. package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
  327. package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
  328. package/src/Select/__tests__/Select.selection.test.tsx +104 -0
  329. package/src/Select/__tests__/Select.separator.test.tsx +71 -0
  330. package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
  331. package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
  332. package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
  333. package/src/Select/constants.ts +8 -0
  334. package/src/Select/hooks/index.ts +2 -0
  335. package/src/Select/hooks/useSelectContent.ts +149 -0
  336. package/src/Select/hooks/useSelectRoot.ts +149 -0
  337. package/src/Select/types.ts +199 -31
  338. package/src/SkipNav/README.md +3 -3
  339. package/src/SkipNav/SkipNav.tsx +72 -32
  340. package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
  341. package/src/SkipNav/types.ts +17 -9
  342. package/src/Slider/README.md +37 -7
  343. package/src/Slider/Slider.tsx +42 -4
  344. package/src/Slider/SliderContext.ts +28 -1
  345. package/src/Slider/types.ts +157 -9
  346. package/src/Slot/Slot.tsx +94 -19
  347. package/src/Slot/__tests__/Slot.test.tsx +112 -2
  348. package/src/Slot/composeEventHandlers.ts +26 -2
  349. package/src/Slot/types.ts +16 -0
  350. package/src/SplitButton/README.md +225 -0
  351. package/src/SplitButton/SplitButton.tsx +457 -0
  352. package/src/SplitButton/SplitButtonContext.ts +26 -0
  353. package/src/SplitButton/__tests__/SplitButton.accessibility.test.tsx +55 -0
  354. package/src/SplitButton/__tests__/SplitButton.asChild.test.tsx +51 -0
  355. package/src/SplitButton/__tests__/SplitButton.basic-rendering.test.tsx +57 -0
  356. package/src/SplitButton/__tests__/SplitButton.data-attributes.test.tsx +28 -0
  357. package/src/SplitButton/__tests__/SplitButton.disabled.test.tsx +79 -0
  358. package/src/SplitButton/__tests__/SplitButton.error-handling.test.tsx +29 -0
  359. package/src/SplitButton/__tests__/SplitButton.keyboard-interaction.test.tsx +56 -0
  360. package/src/SplitButton/__tests__/SplitButton.state-modes.test.tsx +68 -0
  361. package/src/SplitButton/index.ts +2 -0
  362. package/src/SplitButton/types.ts +186 -0
  363. package/src/Status/Status.tsx +36 -15
  364. package/src/Status/__tests__/Status.test.tsx +5 -0
  365. package/src/Status/types.ts +12 -1
  366. package/src/Switch/README.md +1 -1
  367. package/src/Switch/Switch.tsx +17 -4
  368. package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
  369. package/src/Switch/hooks/useSwitchRoot.ts +3 -0
  370. package/src/Switch/types.ts +49 -22
  371. package/src/Table/README.md +110 -8
  372. package/src/Table/Table.tsx +246 -16
  373. package/src/Table/TableExpandableContext.ts +7 -0
  374. package/src/Table/__tests__/Table.DetailRow.test.tsx +83 -0
  375. package/src/Table/__tests__/Table.ExpandTrigger.test.tsx +102 -0
  376. package/src/Table/__tests__/Table.Expandable.test.tsx +121 -0
  377. package/src/Table/types.ts +191 -17
  378. package/src/Tabs/README.md +4 -4
  379. package/src/Tabs/Tabs.tsx +13 -4
  380. package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +58 -0
  381. package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +15 -0
  382. package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +24 -0
  383. package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +36 -0
  384. package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +17 -0
  385. package/src/Tabs/hooks/useTabsContent.ts +5 -0
  386. package/src/Tabs/hooks/useTabsRoot.ts +29 -5
  387. package/src/Tabs/hooks/useTabsTrigger.ts +7 -2
  388. package/src/Tabs/types.ts +29 -4
  389. package/src/Textarea/Textarea.tsx +9 -1
  390. package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
  391. package/src/Textarea/types.ts +20 -3
  392. package/src/Toggle/Toggle.tsx +51 -13
  393. package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
  394. package/src/Toggle/types.ts +54 -4
  395. package/src/ToggleGroup/README.md +4 -4
  396. package/src/ToggleGroup/ToggleGroup.tsx +14 -1
  397. package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
  398. package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
  399. package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
  400. package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
  401. package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
  402. package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
  403. package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
  404. package/src/ToggleGroup/types.ts +55 -2
  405. package/src/Tooltip/README.md +7 -7
  406. package/src/Tooltip/Tooltip.tsx +100 -22
  407. package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +50 -0
  408. package/src/Tooltip/__tests__/Tooltip.error-handling.test.tsx +37 -0
  409. package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
  410. package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +134 -0
  411. package/src/Tooltip/__tests__/Tooltip.skip-delay.test.tsx +216 -0
  412. package/src/Tooltip/__tests__/Tooltip.timer-coordination.test.tsx +223 -0
  413. package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
  414. package/src/Tooltip/hooks/useTooltipContent.ts +3 -0
  415. package/src/Tooltip/hooks/useTooltipProvider.ts +46 -8
  416. package/src/Tooltip/hooks/useTooltipRoot.ts +30 -6
  417. package/src/Tooltip/types.ts +89 -8
  418. package/src/Tree/README.md +69 -5
  419. package/src/Tree/Tree.tsx +188 -34
  420. package/src/Tree/__tests__/Tree.styled-wrapper.test.tsx +159 -0
  421. package/src/Tree/hooks/useTreePath.ts +33 -1
  422. package/src/Tree/index.ts +6 -1
  423. package/src/Tree/types.ts +54 -12
  424. package/src/Tree/utils.ts +46 -4
  425. package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
  426. package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
  427. package/src/VisuallyHidden/types.ts +11 -1
  428. package/src/hooks/index.ts +1 -0
  429. package/src/hooks/useTypeahead.ts +95 -0
  430. package/src/index.ts +10 -0
  431. package/src/test/intersectionObserverPolyfill.ts +7 -0
  432. package/src/test/resizeObserverPolyfill.ts +80 -0
  433. package/src/useLocalStorage/__tests__/useLocalStorage.reading.test.tsx +31 -0
  434. package/src/useLocalStorage/__tests__/useLocalStorage.resilience.test.tsx +97 -0
  435. package/src/useLocalStorage/__tests__/useLocalStorage.ssr.test.tsx +20 -0
  436. package/src/useLocalStorage/__tests__/useLocalStorage.writing.test.tsx +72 -0
  437. package/src/useLocalStorage/index.ts +1 -0
  438. package/src/useLocalStorage/useLocalStorage.ts +140 -0
  439. package/src/useMediaQuery/README.md +66 -0
  440. package/src/useMediaQuery/__tests__/useMediaQuery.matching.test.tsx +42 -0
  441. package/src/useMediaQuery/__tests__/useMediaQuery.ssr.test.tsx +19 -0
  442. package/src/useMediaQuery/__tests__/useMediaQuery.subscription.test.tsx +89 -0
  443. package/src/useMediaQuery/index.ts +1 -0
  444. package/src/useMediaQuery/useMediaQuery.ts +49 -0
  445. package/src/usePagination/README.md +170 -0
  446. package/src/usePagination/__tests__/usePagination.navigation.test.tsx +109 -0
  447. package/src/usePagination/__tests__/usePagination.page-count.test.tsx +56 -0
  448. package/src/usePagination/__tests__/usePagination.page-state.test.tsx +87 -0
  449. package/src/usePagination/__tests__/usePagination.range.test.tsx +82 -0
  450. package/src/usePagination/__tests__/usePagination.slice.test.tsx +59 -0
  451. package/src/usePagination/__tests__/usePagination.windowing.test.tsx +68 -0
  452. package/src/usePagination/index.ts +1 -0
  453. package/src/usePagination/usePagination.ts +292 -0
  454. package/src/utils/focusable.ts +15 -0
  455. package/src/utils/getKeyToActionMap.ts +1 -1
  456. package/src/utils/index.ts +1 -0
@@ -46,6 +46,8 @@ import { ToggleGroupItemProps, ToggleGroupRootProps } from "./types";
46
46
  * element in place of the native `<div>`, with the group's `role`,
47
47
  * `data-orientation`, and ref merged in.
48
48
  *
49
+ * @extends HTMLDivElement
50
+ *
49
51
  * @example Single mode — text alignment
50
52
  * ```tsx
51
53
  * <ToggleGroup.Root type="single" defaultValue="left" aria-label="Alignment">
@@ -140,6 +142,7 @@ export function ToggleGroupRoot({
140
142
  }
141
143
 
142
144
  /** @internal */
145
+ // Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
143
146
  ToggleGroupRoot.displayName = "ToggleGroupRoot";
144
147
 
145
148
  /**
@@ -165,9 +168,19 @@ ToggleGroupRoot.displayName = "ToggleGroupRoot";
165
168
  *
166
169
  * **`asChild` prop.** Pass `asChild` to render any consumer element in
167
170
  * place of the native `<button>`, with the Item's `aria-pressed`,
168
- * `data-state`, `tabIndex`, and event handlers merged onto it.
171
+ * `data-state`, `tabIndex`, and event handlers merged onto it. The child
172
+ * must accept a `ref`. Useful for a link or a styled control that needs
173
+ * toggle-button semantics.
169
174
  *
170
175
  * @throws if rendered outside a `ToggleGroup.Root`.
176
+ *
177
+ * @extends HTMLButtonElement
178
+ *
179
+ * @example
180
+ * ```tsx
181
+ * <ToggleGroup.Item value="bold">Bold</ToggleGroup.Item>
182
+ * <ToggleGroup.Item value="strike" disabled>Strikethrough</ToggleGroup.Item>
183
+ * ```
171
184
  */
172
185
  export function ToggleGroupItem({
173
186
  value,
@@ -62,4 +62,24 @@ describe("ToggleGroup asChild composition", () => {
62
62
  expect(consumerClick).toHaveBeenCalledOnce();
63
63
  expect(item).toHaveAttribute("aria-pressed", "true");
64
64
  });
65
+
66
+ it("Item asChild activates on Space through the keyboard handler", async () => {
67
+ // A non-button element fires no native click on Space, so toggling can
68
+ // only come from the roving handler's activate mapping — the guarantee a
69
+ // native <button> would otherwise mask.
70
+ const user = userEvent.setup();
71
+ render(
72
+ <ToggleGroup.Root type="single" aria-label="Alignment">
73
+ <ToggleGroup.Item value="left" asChild>
74
+ <div>Left</div>
75
+ </ToggleGroup.Item>
76
+ </ToggleGroup.Root>,
77
+ );
78
+ const item = screen
79
+ .getByLabelText("Alignment")
80
+ .querySelector<HTMLElement>("[data-state]")!;
81
+ item.focus();
82
+ await user.keyboard(" ");
83
+ expect(item).toHaveAttribute("aria-pressed", "true");
84
+ });
65
85
  });
@@ -47,4 +47,13 @@ describe("ToggleGroup basic rendering", () => {
47
47
  "horizontal",
48
48
  );
49
49
  });
50
+
51
+ it("sets a displayName on the compound and Item", () => {
52
+ // Assert — empty displayNames would render each as anonymous in DevTools.
53
+ // `ToggleGroup`, `ToggleGroup.Root`, and the underlying function are one
54
+ // object (Object.assign compound), so the compound's "ToggleGroup" alias is
55
+ // the observable Root displayName; only Item is a distinct object.
56
+ expect(ToggleGroup.displayName).toBe("ToggleGroup");
57
+ expect(ToggleGroup.Item.displayName).toBe("ToggleGroupItem");
58
+ });
50
59
  });
@@ -0,0 +1,70 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { ToggleGroup } from "../ToggleGroup";
5
+
6
+ function ItemsFixture({ items }: { items: string[] }) {
7
+ return (
8
+ <ToggleGroup.Root type="single" aria-label="Alignment">
9
+ {items.map((v) => (
10
+ <ToggleGroup.Item key={v} value={v}>
11
+ {v}
12
+ </ToggleGroup.Item>
13
+ ))}
14
+ </ToggleGroup.Root>
15
+ );
16
+ }
17
+
18
+ function DisabledFixture({ disabledItem }: { disabledItem?: string }) {
19
+ return (
20
+ <ToggleGroup.Root type="single" aria-label="Alignment">
21
+ <ToggleGroup.Item value="left" disabled={disabledItem === "left"}>
22
+ Left
23
+ </ToggleGroup.Item>
24
+ <ToggleGroup.Item value="center" disabled={disabledItem === "center"}>
25
+ Center
26
+ </ToggleGroup.Item>
27
+ <ToggleGroup.Item value="right" disabled={disabledItem === "right"}>
28
+ Right
29
+ </ToggleGroup.Item>
30
+ </ToggleGroup.Root>
31
+ );
32
+ }
33
+
34
+ describe("ToggleGroup dynamic items — registration lifecycle", () => {
35
+ it("unregisters an unmounted item so the tab stop moves to the survivor", () => {
36
+ // Arrange — "a" is the first enabled item, so it holds the tab stop.
37
+ const { rerender } = render(<ItemsFixture items={["a", "b"]} />);
38
+ expect(screen.getByRole("button", { name: "a" })).toHaveAttribute(
39
+ "tabindex",
40
+ "0",
41
+ );
42
+
43
+ // Act — remove "a"; its effect cleanup must unregister it.
44
+ rerender(<ItemsFixture items={["b"]} />);
45
+
46
+ // Assert — with "a" gone from the registry, "b" is now the tab stop. A
47
+ // no-op cleanup would leave "a" registered ahead of "b", stranding the
48
+ // tab stop on the detached element and leaving "b" at tabindex -1.
49
+ expect(screen.getByRole("button", { name: "b" })).toHaveAttribute(
50
+ "tabindex",
51
+ "0",
52
+ );
53
+ });
54
+
55
+ it("re-registers an item's disabled flag when it changes after mount", async () => {
56
+ // Arrange — all three enabled; focus can travel Left → Center → Right.
57
+ const user = userEvent.setup();
58
+ const { rerender } = render(<DisabledFixture />);
59
+
60
+ // Act — disable Center after mount, then navigate rightward from Left.
61
+ rerender(<DisabledFixture disabledItem="center" />);
62
+ screen.getByRole("button", { name: "Left" }).focus();
63
+ await user.keyboard("{ArrowRight}");
64
+
65
+ // Assert — the registrar effect re-ran, so Center is skipped and focus
66
+ // lands on Right. A stale registration would keep Center navigable and
67
+ // strand focus (a disabled button cannot receive it).
68
+ expect(screen.getByRole("button", { name: "Right" })).toHaveFocus();
69
+ });
70
+ });
@@ -0,0 +1,23 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import { ToggleGroup } from "../ToggleGroup";
4
+ import { ToggleGroupContext } from "../ToggleGroupContext";
5
+
6
+ describe("ToggleGroup error handling", () => {
7
+ it("throws a helpful error when ToggleGroup.Item is rendered outside Root", () => {
8
+ // Arrange
9
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
10
+
11
+ // Assert
12
+ expect(() =>
13
+ render(<ToggleGroup.Item value="left">Left</ToggleGroup.Item>),
14
+ ).toThrow(/ToggleGroup\.Item must be rendered inside a ToggleGroup\.Root/);
15
+
16
+ error.mockRestore();
17
+ });
18
+
19
+ it("names the context for React DevTools", () => {
20
+ // Assert — an empty displayName would render the provider as anonymous.
21
+ expect(ToggleGroupContext.displayName).toBe("ToggleGroupContext");
22
+ });
23
+ });
@@ -1,4 +1,4 @@
1
- import { render, screen } from "@testing-library/react";
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
 
4
4
  import { ToggleGroup } from "../ToggleGroup";
@@ -45,6 +45,28 @@ describe("ToggleGroup keyboard — roving tabindex", () => {
45
45
  "-1",
46
46
  );
47
47
  });
48
+
49
+ it("moves the tab stop to the focused item (onFocus updates the home base)", () => {
50
+ // Arrange — nothing selected, so the first item is the initial tab stop.
51
+ render(<SingleFixture />);
52
+ expect(screen.getByRole("button", { name: "Left" })).toHaveAttribute(
53
+ "tabindex",
54
+ "0",
55
+ );
56
+
57
+ // Act — focusing another item should hand it the tab stop.
58
+ fireEvent.focus(screen.getByRole("button", { name: "Right" }));
59
+
60
+ // Assert — Right is now the sole tab stop; Left drops out of the sequence.
61
+ expect(screen.getByRole("button", { name: "Right" })).toHaveAttribute(
62
+ "tabindex",
63
+ "0",
64
+ );
65
+ expect(screen.getByRole("button", { name: "Left" })).toHaveAttribute(
66
+ "tabindex",
67
+ "-1",
68
+ );
69
+ });
48
70
  });
49
71
 
50
72
  describe("ToggleGroup keyboard — arrow key navigation (no auto-select)", () => {
@@ -0,0 +1,58 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { ToggleGroup } from "../ToggleGroup";
4
+
5
+ describe("ToggleGroup.Item ref forwarding", () => {
6
+ it("forwards the underlying button element to a consumer function ref", () => {
7
+ // Arrange
8
+ const received: (HTMLButtonElement | null)[] = [];
9
+ const functionRef = (node: HTMLButtonElement | null) => {
10
+ received.push(node);
11
+ };
12
+
13
+ // Act
14
+ render(
15
+ <ToggleGroup.Root type="single" aria-label="Alignment">
16
+ <ToggleGroup.Item value="left" ref={functionRef}>
17
+ Left
18
+ </ToggleGroup.Item>
19
+ </ToggleGroup.Root>,
20
+ );
21
+
22
+ // Assert
23
+ expect(received).toContain(screen.getByRole("button", { name: "Left" }));
24
+ });
25
+
26
+ it("re-attaches to a replacement ref when the ref prop changes", () => {
27
+ // Arrange — the item's ref memo must depend on `ref`, or a changed ref
28
+ // callback is never invoked with the element.
29
+ const firstReceived: (HTMLButtonElement | null)[] = [];
30
+ const secondReceived: (HTMLButtonElement | null)[] = [];
31
+ const firstRef = (node: HTMLButtonElement | null) => {
32
+ firstReceived.push(node);
33
+ };
34
+ const secondRef = (node: HTMLButtonElement | null) => {
35
+ secondReceived.push(node);
36
+ };
37
+
38
+ // Act — mount with the first ref, then re-render with a different one.
39
+ const { rerender } = render(
40
+ <ToggleGroup.Root type="single" aria-label="Alignment">
41
+ <ToggleGroup.Item value="left" ref={firstRef}>
42
+ Left
43
+ </ToggleGroup.Item>
44
+ </ToggleGroup.Root>,
45
+ );
46
+ rerender(
47
+ <ToggleGroup.Root type="single" aria-label="Alignment">
48
+ <ToggleGroup.Item value="left" ref={secondRef}>
49
+ Left
50
+ </ToggleGroup.Item>
51
+ </ToggleGroup.Root>,
52
+ );
53
+
54
+ // Assert — the replacement ref received the button.
55
+ const button = screen.getByRole("button", { name: "Left" });
56
+ expect(secondReceived).toContain(button);
57
+ });
58
+ });
@@ -42,7 +42,16 @@ export function useToggleGroupRoot({
42
42
  (itemValue: string) => {
43
43
  const isSelected = value.includes(itemValue);
44
44
  if (type === "single") {
45
- const next = isSelected ? [] : [itemValue];
45
+ // Deselecting clears the selection to the empty array. Stryker's
46
+ // ArrayDeclaration mutant fills this with a phantom value, but since
47
+ // that value can never equal a registered item's `value` — and single
48
+ // mode always overwrites the whole array on the next selection — no
49
+ // item ever reads it as pressed. The mutation is observationally
50
+ // identical, so it is isolated here to keep the disable off the tested
51
+ // `[itemValue]` literal below.
52
+ // Stryker disable next-line ArrayDeclaration
53
+ const cleared: string[] = [];
54
+ const next = isSelected ? cleared : [itemValue];
46
55
  setValue(next);
47
56
  (onValueChange as ((v: string | undefined) => void) | undefined)?.(
48
57
  isSelected ? undefined : itemValue,
@@ -68,6 +77,9 @@ export function useToggleGroupRoot({
68
77
  (itemValue: string, element: HTMLButtonElement | null, disabled = false) => {
69
78
  registerBase(itemValue, element ? { element, disabled } : null);
70
79
  },
80
+ // `registerBase` is a stable useCollection callback (its own deps never
81
+ // change), so emptying this array yields the identical memoised function.
82
+ // Stryker disable next-line ArrayDeclaration: equivalent — stable dependency.
71
83
  [registerBase],
72
84
  );
73
85
 
@@ -83,8 +95,16 @@ export function useToggleGroupRoot({
83
95
 
84
96
  const focusItem = useCallback(
85
97
  (itemValue: string) => {
98
+ // `itemValue` is always sourced from `navigable` (the enabled subset of
99
+ // `itemValues`), and useCollection's register() writes `itemsRef` and the
100
+ // `itemValues` state together — so a value that reaches here is guaranteed
101
+ // to have a live entry in the map.
102
+ // Stryker disable next-line OptionalChaining: unreachable given that invariant.
86
103
  itemsRef.current.get(itemValue)?.element.focus();
87
104
  },
105
+ // `itemsRef` is a stable RefObject, so emptying this array yields the
106
+ // identical memoised function.
107
+ // Stryker disable next-line ArrayDeclaration: equivalent — stable dependency.
88
108
  [itemsRef],
89
109
  );
90
110
 
@@ -7,12 +7,27 @@ import { HTMLAttributes, ButtonHTMLAttributes, ReactNode, Ref } from "react";
7
7
  */
8
8
  export type ToggleGroupRootBaseProps = Omit<
9
9
  HTMLAttributes<HTMLDivElement>,
10
- "dir"
10
+ "dir" | "defaultValue"
11
11
  > & {
12
+ /** Layout axis for keyboard navigation. `"horizontal"` binds
13
+ * ArrowLeft/ArrowRight; `"vertical"` binds ArrowUp/ArrowDown. Surfaces as
14
+ * `data-orientation` on the root for styling; it does not itself apply any
15
+ * flex/grid layout.
16
+ * @default "horizontal" */
12
17
  orientation?: "horizontal" | "vertical";
18
+ /** Reading direction. In `"rtl"` the horizontal arrow keys are mirrored so
19
+ * focus follows the visual order. Inherited from the nearest
20
+ * {@link DirectionProvider} when omitted, falling back to `"ltr"`. */
13
21
  dir?: "ltr" | "rtl";
22
+ /** Render a single consumer-supplied element in place of the native
23
+ * `<div>`, with the group's `role="group"`, `data-orientation`, and `ref`
24
+ * merged onto it via the {@link Slot} pattern.
25
+ * @default false */
14
26
  asChild?: boolean;
27
+ /** The {@link ToggleGroupItemProps | `ToggleGroup.Item`} elements that make
28
+ * up the segmented control. */
15
29
  children?: ReactNode;
30
+ /** Forwarded to the underlying `HTMLDivElement`. */
16
31
  ref?: Ref<HTMLDivElement>;
17
32
  };
18
33
 
@@ -21,9 +36,15 @@ export type ToggleGroupRootBaseProps = Omit<
21
36
  * initial value supplied via `defaultValue`.
22
37
  */
23
38
  export type SingleUncontrolledProps = {
39
+ /** Selects single-selection semantics: at most one item pressed at a time,
40
+ * and pressing the active item again clears the selection. */
24
41
  type: "single";
42
+ /** Value of the item pressed on first render. Omit to start with nothing
43
+ * pressed. */
25
44
  defaultValue?: string;
45
+ /** Forbidden in uncontrolled mode — use `defaultValue`. */
26
46
  value?: never;
47
+ /** Forbidden in uncontrolled mode. */
27
48
  onValueChange?: never;
28
49
  };
29
50
 
@@ -32,9 +53,16 @@ export type SingleUncontrolledProps = {
32
53
  * caller via `value` / `onValueChange`.
33
54
  */
34
55
  export type SingleControlledProps = {
56
+ /** Selects single-selection semantics: at most one item pressed at a time,
57
+ * and pressing the active item again clears the selection. */
35
58
  type: "single";
59
+ /** The currently pressed item's value, or `undefined` when none is pressed.
60
+ * Must be kept in sync by the caller via `onValueChange`. */
36
61
  value: string | undefined;
62
+ /** Called with the requested next value — the pressed item's value, or
63
+ * `undefined` when the active item is pressed again to deselect it. */
37
64
  onValueChange: (value: string | undefined) => void;
65
+ /** Forbidden in controlled mode — use `value`. */
38
66
  defaultValue?: never;
39
67
  };
40
68
 
@@ -43,9 +71,15 @@ export type SingleControlledProps = {
43
71
  * the initial set supplied via `defaultValue`.
44
72
  */
45
73
  export type MultipleUncontrolledProps = {
74
+ /** Selects multiple-selection semantics: any number of items can be pressed
75
+ * simultaneously and each toggles independently. */
46
76
  type: "multiple";
77
+ /** Values of the items pressed on first render. Omit to start with nothing
78
+ * pressed. */
47
79
  defaultValue?: string[];
80
+ /** Forbidden in uncontrolled mode — use `defaultValue`. */
48
81
  value?: never;
82
+ /** Forbidden in uncontrolled mode. */
49
83
  onValueChange?: never;
50
84
  };
51
85
 
@@ -54,9 +88,16 @@ export type MultipleUncontrolledProps = {
54
88
  * by the caller via `value` / `onValueChange`.
55
89
  */
56
90
  export type MultipleControlledProps = {
91
+ /** Selects multiple-selection semantics: any number of items can be pressed
92
+ * simultaneously and each toggles independently. */
57
93
  type: "multiple";
94
+ /** The full set of currently pressed item values. Must be kept in sync by
95
+ * the caller via `onValueChange`. */
58
96
  value: string[];
97
+ /** Called with the complete next array of pressed values whenever the user
98
+ * toggles any item. */
59
99
  onValueChange: (value: string[]) => void;
100
+ /** Forbidden in controlled mode — use `value`. */
60
101
  defaultValue?: never;
61
102
  };
62
103
 
@@ -81,11 +122,23 @@ export type ToggleGroupRootProps = ToggleGroupRootBaseProps &
81
122
  */
82
123
  export type ToggleGroupItemProps = Omit<
83
124
  ButtonHTMLAttributes<HTMLButtonElement>,
84
- "type"
125
+ "type" | "value"
85
126
  > & {
127
+ /** Identifies this item within the group. It is this string — not the
128
+ * visible label — that is compared against the group's pressed
129
+ * `value` / `defaultValue`; use `children` for the label. */
86
130
  value: string;
131
+ /** Forwards the native `disabled` attribute and removes the item from the
132
+ * roving tab order so arrow-key navigation skips it. Also sets
133
+ * `data-disabled=""` for CSS targeting.
134
+ * @default false */
87
135
  disabled?: boolean;
136
+ /** Render a single consumer-supplied element in place of the native
137
+ * `<button>`, with the item's `aria-pressed`, `data-state`, `tabIndex`, and
138
+ * event handlers merged onto it via the {@link Slot} pattern.
139
+ * @default false */
88
140
  asChild?: boolean;
141
+ /** Forwarded to the underlying `HTMLButtonElement`. */
89
142
  ref?: Ref<HTMLButtonElement>;
90
143
  };
91
144
 
@@ -60,12 +60,12 @@ The two shapes are statically discriminated at the type level.
60
60
 
61
61
  ```tsx
62
62
  // Uncontrolled
63
- <Tooltip.Root defaultOpen>…</Tooltip.Root>
63
+ <Tooltip.Root defaultOpen>...</Tooltip.Root>
64
64
 
65
65
  // Controlled
66
66
  const [open, setOpen] = useState(false);
67
67
  <Tooltip.Root open={open} onOpenChange={setOpen}>
68
- …
68
+ ...
69
69
  </Tooltip.Root>
70
70
  ```
71
71
 
@@ -82,7 +82,7 @@ before the normal delay resumes.
82
82
 
83
83
  ```tsx
84
84
  <Tooltip.Provider delayDuration={400} skipDelayDuration={150}>
85
- …
85
+ ...
86
86
  </Tooltip.Provider>
87
87
  ```
88
88
 
@@ -95,7 +95,7 @@ when the cursor leaves the trigger:
95
95
 
96
96
  ```tsx
97
97
  <Tooltip.Root disableHoverableContent>
98
- …
98
+ ...
99
99
  </Tooltip.Root>
100
100
  ```
101
101
 
@@ -114,7 +114,7 @@ tooltip open:
114
114
  event.preventDefault(); // keep open when clicking outside
115
115
  }}
116
116
  >
117
- …
117
+ ...
118
118
  </Tooltip.Content>
119
119
  ```
120
120
 
@@ -178,7 +178,7 @@ subtree stays in the DOM while closed so a CSS animation can play on
178
178
 
179
179
  ```tsx
180
180
  <Tooltip.Portal forceMount>
181
- <Tooltip.Content forceMount>…</Tooltip.Content>
181
+ <Tooltip.Content forceMount>...</Tooltip.Content>
182
182
  </Tooltip.Portal>
183
183
  ```
184
184
 
@@ -199,7 +199,7 @@ child element; the child's own handlers run first.
199
199
  </Tooltip.Trigger>
200
200
 
201
201
  <Tooltip.Arrow asChild>
202
- <svg viewBox="0 0 10 5">…</svg>
202
+ <svg viewBox="0 0 10 5">...</svg>
203
203
  </Tooltip.Arrow>
204
204
  ```
205
205
 
@@ -24,11 +24,27 @@ import type {
24
24
  } from "./types";
25
25
 
26
26
  /**
27
- * Wraps one or more `Tooltip.Root` instances and provides shared
28
- * delay configuration. Required ancestor — rendering `Tooltip.Root`
29
- * without a Provider throws a context error.
27
+ * Wraps one or more {@link TooltipRoot | `Tooltip.Root`} instances and provides
28
+ * the shared delay configuration and group-wide open coordination. Required
29
+ * ancestor — rendering `Tooltip.Root` without a Provider throws a context
30
+ * error. Renders no DOM of its own (a `TooltipProviderContext` provider only).
30
31
  *
31
- * @example
32
+ * **Delay coordination.** Two timings govern how a group of tooltips feels:
33
+ *
34
+ * - `delayDuration` — how long a pointer must rest on a trigger before its
35
+ * tooltip opens (hover only; focus always opens immediately).
36
+ * - `skipDelayDuration` — the grace window after a tooltip closes during which
37
+ * moving to *another* trigger in the same Provider opens that tooltip
38
+ * instantly, with no `delayDuration` wait. Rest longer than this window and
39
+ * the next hover pays the full `delayDuration` again. This is what makes a
40
+ * toolbar of icon buttons feel responsive once the first tip has shown.
41
+ *
42
+ * Place a single Provider high in the tree (typically at the app root) so every
43
+ * tooltip shares one skip window. A per-instance
44
+ * {@link TooltipRootProps.delayDuration | `delayDuration`} on `Tooltip.Root`
45
+ * overrides the Provider's value for that one tooltip.
46
+ *
47
+ * @example App-wide provider
32
48
  * ```tsx
33
49
  * <Tooltip.Provider delayDuration={400}>
34
50
  * <App />
@@ -55,13 +71,27 @@ export function TooltipProviderComponent({
55
71
  TooltipProviderComponent.displayName = "TooltipProvider";
56
72
 
57
73
  /**
58
- * The state boundary for a single tooltip. Owns the open/closed
59
- * state and wires it down to all sub-components via context.
74
+ * The state boundary for a single tooltip. Owns the open/closed state, runs the
75
+ * open-delay and grace-period timers, and wires everything down to the
76
+ * sub-components via `TooltipContext`. Renders no DOM of its own. Must be a
77
+ * descendant of {@link TooltipProviderComponent | `Tooltip.Provider`}.
60
78
  *
61
- * Supports two state modes, statically discriminated at the type level:
79
+ * **Controlled vs uncontrolled**, statically discriminated at the type level so
80
+ * TypeScript accepts only one shape:
62
81
  *
63
- * - **Uncontrolled** — pass `defaultOpen` or omit for closed-on-mount.
64
- * - **Controlled** — pass `open` (and optionally `onOpenChange`).
82
+ * - **Uncontrolled** — pass {@link UncontrolledTooltipRootProps.defaultOpen | `defaultOpen`}
83
+ * (or omit it for closed-on-mount). The component owns the flag; observe
84
+ * transitions with `onOpenChange`.
85
+ * - **Controlled** — pass {@link ControlledTooltipRootProps.open | `open`}
86
+ * (with an optional `onOpenChange`). The parent owns the flag; the delay and
87
+ * grace timers still run and drive `onOpenChange`, but the tooltip only moves
88
+ * when the parent updates `open`.
89
+ *
90
+ * **Timing overrides.** {@link TooltipRootProps.delayDuration | `delayDuration`}
91
+ * overrides the Provider's open delay for this tooltip only.
92
+ * {@link TooltipRootProps.disableHoverableContent | `disableHoverableContent`}
93
+ * removes the grace period, so the tooltip closes the instant the pointer
94
+ * leaves the trigger (the user cannot move into the content).
65
95
  *
66
96
  * @example
67
97
  * ```tsx
@@ -90,17 +120,28 @@ export function TooltipRoot({
90
120
  return <TooltipProvider value={contextValue}>{children}</TooltipProvider>;
91
121
  }
92
122
 
93
- /** @internal */
123
+ // Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
94
124
  TooltipRoot.displayName = "TooltipRoot";
95
125
 
96
126
  /**
97
- * The element that triggers the tooltip on hover and focus. Renders a
98
- * `<button type="button">` by default; use `asChild` to wrap a custom
99
- * element.
127
+ * The element that opens the tooltip. Renders a `<button type="button">` by
128
+ * default; pass `asChild` to project the trigger behaviour onto a custom
129
+ * element (a link, an icon button, etc.).
130
+ *
131
+ * **Open / close behaviour.** Pointer-enter opens after the effective
132
+ * `delayDuration` (immediately if the group's skip window is active);
133
+ * pointer-leave closes after a short grace period so the pointer can travel
134
+ * into hoverable content (immediately when `disableHoverableContent` is set on
135
+ * Root). Focus opens immediately and blur closes immediately, and `Escape`
136
+ * while focused closes immediately — matching the WAI-ARIA tooltip pattern.
137
+ * Consumer `onPointerEnter` / `onPointerLeave` / `onFocus` / `onBlur` /
138
+ * `onKeyDown` all compose (they run first).
139
+ *
140
+ * **ARIA wiring.**
141
+ * - `aria-describedby` points at {@link TooltipContent | `Tooltip.Content`}'s id.
142
+ * - `data-state="open" | "closed"` mirrors the tooltip state for CSS.
100
143
  *
101
- * ARIA wiring:
102
- * - `aria-describedby` points at `Tooltip.Content`'s id.
103
- * - `data-state="open" | "closed"` mirrors the tooltip state.
144
+ * @extends HTMLButtonElement
104
145
  *
105
146
  * @example
106
147
  * ```tsx
@@ -148,12 +189,13 @@ TooltipTrigger.displayName = "TooltipTrigger";
148
189
  *
149
190
  * By default the portal only renders while the tooltip is open. Pass
150
191
  * `forceMount` to keep the subtree mounted when closed — useful for
151
- * CSS exit animations driven by `data-state="closed"`.
192
+ * CSS exit animations driven by `data-state="closed"`. Renders no wrapper
193
+ * element of its own.
152
194
  *
153
195
  * @example
154
196
  * ```tsx
155
197
  * <Tooltip.Portal>
156
- * <Tooltip.Content>…</Tooltip.Content>
198
+ * <Tooltip.Content>...</Tooltip.Content>
157
199
  * </Tooltip.Portal>
158
200
  * ```
159
201
  */
@@ -192,6 +234,8 @@ TooltipPortal.displayName = "TooltipPortal";
192
234
  * `anchor-name` to the trigger and `position-anchor` / `position-area`
193
235
  * to the content in your own stylesheet.
194
236
  *
237
+ * @extends HTMLDivElement
238
+ *
195
239
  * @example
196
240
  * ```tsx
197
241
  * <Tooltip.Content className="tooltip__content">
@@ -233,6 +277,8 @@ TooltipContent.displayName = "TooltipContent";
233
277
  *
234
278
  * All positioning and styling is the consumer's responsibility via CSS.
235
279
  *
280
+ * @extends HTMLSpanElement
281
+ *
236
282
  * @example
237
283
  * ```tsx
238
284
  * <Tooltip.Content>
@@ -270,11 +316,43 @@ export type TooltipCompound = typeof TooltipRoot & {
270
316
  * Headless, accessible **Tooltip** — a compound component implementing the
271
317
  * [WAI-ARIA tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/):
272
318
  * a deferred, hoverable label anchored to a trigger, with a shared
273
- * {@link Tooltip.Provider} governing open / skip delays across a group. Zero
274
- * styles ship.
319
+ * {@link TooltipProviderComponent | `Tooltip.Provider`} governing open / skip
320
+ * delays across a group. Opens on hover (after a delay) and on focus
321
+ * (immediately); closes on blur, on `Escape`, and after a short grace period on
322
+ * pointer-leave so the pointer can travel into hoverable content. Zero styles
323
+ * ship; visual placement is your CSS concern (CSS anchor positioning).
324
+ *
325
+ * `Tooltip` is both callable (an alias of {@link TooltipRoot | `Tooltip.Root`})
326
+ * and carries its sub-components as static properties. A `Tooltip.Provider`
327
+ * ancestor is required — `Tooltip.Root` throws without one.
328
+ *
329
+ * - {@link TooltipProviderComponent | `Tooltip.Provider`} — shared delay config + group open coordination.
330
+ * - {@link TooltipRoot | `Tooltip.Root`} — per-tooltip state owner and timing.
331
+ * - {@link TooltipTrigger | `Tooltip.Trigger`} — `<button>` that opens on hover/focus.
332
+ * - {@link TooltipPortal | `Tooltip.Portal`} — optional portal to `document.body`.
333
+ * - {@link TooltipContent | `Tooltip.Content`} — the `role="tooltip"` panel.
334
+ * - {@link TooltipArrow | `Tooltip.Arrow`} — optional visual pointer.
335
+ *
336
+ * @example Minimal usage
337
+ * ```tsx
338
+ * import { Tooltip } from "@primitiv-ui/react";
339
+ *
340
+ * <Tooltip.Provider>
341
+ * <Tooltip.Root>
342
+ * <Tooltip.Trigger>Save</Tooltip.Trigger>
343
+ * <Tooltip.Portal>
344
+ * <Tooltip.Content>
345
+ * Save your changes
346
+ * <Tooltip.Arrow />
347
+ * </Tooltip.Content>
348
+ * </Tooltip.Portal>
349
+ * </Tooltip.Root>
350
+ * </Tooltip.Provider>;
351
+ * ```
275
352
  *
276
- * The default export is the `Root`; sub-components are attached as static
277
- * properties (`Tooltip.Provider`, `Tooltip.Trigger`, `Tooltip.Content`, …).
353
+ * @see {@link TooltipProviderComponent} for delay / skip-delay coordination.
354
+ * @see {@link TooltipRoot} for the controlled/uncontrolled state modes and timing overrides.
355
+ * @see {@link TooltipContent} for the grace period and the Escape / outside-pointer escape hatches.
278
356
  */
279
357
  const TooltipCompound: TooltipCompound = Object.assign(TooltipRoot, {
280
358
  Provider: TooltipProviderComponent,