@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
@@ -0,0 +1,162 @@
1
+ import "./dialog-polyfill";
2
+
3
+ import { createEvent, fireEvent, render, screen } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+
6
+ import { Modal } from "../Modal";
7
+
8
+ function getDialog() {
9
+ return document.querySelector("dialog") as HTMLDialogElement;
10
+ }
11
+
12
+ // WAI-ARIA APG Modal Dialog pattern: Tab must cycle *within* the dialog and
13
+ // never reach the browser chrome. A native <dialog> inerts the background but
14
+ // does not wrap focus, so Modal.Content adds a boundary Tab-wrap trap. These
15
+ // tests render a Trigger (outside the dialog) in the wrap cases so that,
16
+ // without the trap, a native wrap would land on the Trigger — proving the
17
+ // assertion is driven by the trap, not by jsdom's own tab order.
18
+ describe("Modal focus trap — Tab cycles within the dialog", () => {
19
+ it("wraps focus from the last element back to the first on Tab", async () => {
20
+ // Arrange
21
+ const user = userEvent.setup();
22
+ render(
23
+ <Modal.Root defaultOpen>
24
+ <Modal.Trigger>Open</Modal.Trigger>
25
+ <Modal.Content>
26
+ <button type="button">First</button>
27
+ <button type="button">Second</button>
28
+ <button type="button">Third</button>
29
+ </Modal.Content>
30
+ </Modal.Root>,
31
+ );
32
+ screen.getByRole("button", { name: "Third" }).focus();
33
+
34
+ // Act
35
+ await user.tab();
36
+
37
+ // Assert
38
+ expect(screen.getByRole("button", { name: "First" })).toHaveFocus();
39
+ });
40
+
41
+ it("leaves an interior forward Tab to native handling (no wrap)", async () => {
42
+ // Arrange
43
+ const user = userEvent.setup();
44
+ render(
45
+ <Modal.Root defaultOpen>
46
+ <Modal.Content>
47
+ <button type="button">First</button>
48
+ <button type="button">Second</button>
49
+ <button type="button">Third</button>
50
+ </Modal.Content>
51
+ </Modal.Root>,
52
+ );
53
+ screen.getByRole("button", { name: "First" }).focus();
54
+
55
+ // Act
56
+ await user.tab();
57
+
58
+ // Assert
59
+ expect(screen.getByRole("button", { name: "Second" })).toHaveFocus();
60
+ });
61
+
62
+ it("wraps focus from the first element to the last on Shift+Tab", async () => {
63
+ // Arrange
64
+ const user = userEvent.setup();
65
+ render(
66
+ <Modal.Root defaultOpen>
67
+ <Modal.Trigger>Open</Modal.Trigger>
68
+ <Modal.Content>
69
+ <button type="button">First</button>
70
+ <button type="button">Second</button>
71
+ <button type="button">Third</button>
72
+ </Modal.Content>
73
+ </Modal.Root>,
74
+ );
75
+ screen.getByRole("button", { name: "First" }).focus();
76
+
77
+ // Act
78
+ await user.tab({ shift: true });
79
+
80
+ // Assert
81
+ expect(screen.getByRole("button", { name: "Third" })).toHaveFocus();
82
+ });
83
+
84
+ it("leaves an interior Shift+Tab to native handling (no wrap)", async () => {
85
+ // Arrange
86
+ const user = userEvent.setup();
87
+ render(
88
+ <Modal.Root defaultOpen>
89
+ <Modal.Content>
90
+ <button type="button">First</button>
91
+ <button type="button">Second</button>
92
+ <button type="button">Third</button>
93
+ </Modal.Content>
94
+ </Modal.Root>,
95
+ );
96
+ screen.getByRole("button", { name: "Second" }).focus();
97
+
98
+ // Act
99
+ await user.tab({ shift: true });
100
+
101
+ // Assert
102
+ expect(screen.getByRole("button", { name: "First" })).toHaveFocus();
103
+ });
104
+
105
+ it("ignores non-Tab keys (typing is never intercepted)", () => {
106
+ // Arrange
107
+ render(
108
+ <Modal.Root defaultOpen>
109
+ <Modal.Content>
110
+ <button type="button">First</button>
111
+ <button type="button">Second</button>
112
+ </Modal.Content>
113
+ </Modal.Root>,
114
+ );
115
+ const first = screen.getByRole("button", { name: "First" });
116
+ first.focus();
117
+
118
+ // Act
119
+ const event = createEvent.keyDown(first, { key: "a" });
120
+ fireEvent(first, event);
121
+
122
+ // Assert
123
+ expect(first).toHaveFocus();
124
+ expect(event.defaultPrevented).toBe(false);
125
+ });
126
+
127
+ it("blocks Tab when the dialog has no focusable content", () => {
128
+ // Arrange
129
+ render(
130
+ <Modal.Root defaultOpen>
131
+ <Modal.Content>Just text, nothing to focus.</Modal.Content>
132
+ </Modal.Root>,
133
+ );
134
+ const dialog = getDialog();
135
+
136
+ // Act
137
+ const event = createEvent.keyDown(dialog, { key: "Tab" });
138
+ fireEvent(dialog, event);
139
+
140
+ // Assert — Tab is swallowed so focus cannot escape to the browser chrome.
141
+ expect(event.defaultPrevented).toBe(true);
142
+ });
143
+
144
+ it("stays inactive while the dialog is closed", () => {
145
+ // Arrange — Content always renders its <dialog>; without defaultOpen it is
146
+ // mounted but closed, so the trap must not fire.
147
+ render(
148
+ <Modal.Root>
149
+ <Modal.Content>Just text, nothing to focus.</Modal.Content>
150
+ </Modal.Root>,
151
+ );
152
+ const dialog = getDialog();
153
+ expect(dialog.open).toBe(false);
154
+
155
+ // Act
156
+ const event = createEvent.keyDown(dialog, { key: "Tab" });
157
+ fireEvent(dialog, event);
158
+
159
+ // Assert — the guard returns before the empty-content block would preventDefault.
160
+ expect(event.defaultPrevented).toBe(false);
161
+ });
162
+ });
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef } from "react";
2
2
 
3
+ import { FOCUSABLE_SELECTOR } from "../../utils/index.ts";
3
4
  import { useModalContext } from "./useModalContext";
4
5
 
5
6
  export function useModalContent() {
@@ -48,13 +49,44 @@ export function useModalContent() {
48
49
  if (event.defaultPrevented) return;
49
50
  setOpen(false);
50
51
  };
52
+ const handleKeyDown = (event: KeyboardEvent) => {
53
+ // WAI-ARIA APG Modal Dialog: Tab must cycle within the dialog. A native
54
+ // modal <dialog> inerts the background (Tab can't reach the page) but
55
+ // does NOT wrap — Tab past the last element escapes to the browser
56
+ // chrome, then returns. Close only the two boundary gaps here; interior
57
+ // Tabs stay native so the browser's inert-aware order (radio groups,
58
+ // etc.) keeps working.
59
+ if (event.key !== "Tab") return;
60
+ if (!dialog.open) return;
61
+ const focusables = Array.from(
62
+ dialog.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
63
+ );
64
+ if (focusables.length === 0) {
65
+ // Nothing tabbable inside — block Tab so focus can't leak to the
66
+ // browser chrome (native <dialog> would otherwise let it out).
67
+ event.preventDefault();
68
+ return;
69
+ }
70
+ const first = focusables[0];
71
+ const last = focusables[focusables.length - 1];
72
+ const active = document.activeElement;
73
+ if (!event.shiftKey && active === last) {
74
+ event.preventDefault();
75
+ first.focus();
76
+ } else if (event.shiftKey && active === first) {
77
+ event.preventDefault();
78
+ last.focus();
79
+ }
80
+ };
51
81
  dialog.addEventListener("close", handleClose);
52
82
  dialog.addEventListener("cancel", handleCancel);
53
83
  dialog.addEventListener("pointerdown", handlePointerDown);
84
+ dialog.addEventListener("keydown", handleKeyDown);
54
85
  return () => {
55
86
  dialog.removeEventListener("close", handleClose);
56
87
  dialog.removeEventListener("cancel", handleCancel);
57
88
  dialog.removeEventListener("pointerdown", handlePointerDown);
89
+ dialog.removeEventListener("keydown", handleKeyDown);
58
90
  };
59
91
  }, [setOpen, contentCallbacksRef]);
60
92
 
@@ -8,23 +8,36 @@ import {
8
8
 
9
9
  /**
10
10
  * Imperative handle exposed on `Modal.Root`'s `ref`, letting callers open
11
- * and close the modal programmatically.
11
+ * and close the modal programmatically from outside the React subtree.
12
+ *
13
+ * In controlled mode these methods defer to `onOpenChange` rather than
14
+ * flipping any internal state — the parent stays the source of truth.
12
15
  */
13
16
  export type ModalImperativeApi = {
14
- /** Opens the modal. */
17
+ /** Opens the modal (uncontrolled: sets open; controlled: calls `onOpenChange(true)`). */
15
18
  open: () => void;
16
- /** Closes the modal. */
19
+ /** Closes the modal (uncontrolled: clears open; controlled: calls `onOpenChange(false)`). */
17
20
  close: () => void;
18
21
  };
19
22
 
20
23
  /**
21
- * Optional escape-hatch callbacks accepted by `Modal.Content` for
22
- * intercepting dismissal gestures.
24
+ * Optional escape-hatch callbacks accepted by
25
+ * {@link ModalContentProps | `Modal.Content`} for intercepting the two
26
+ * dismissal gestures the browser drives on a native `<dialog>`. Both receive
27
+ * the raw native event (not a synthetic React event).
23
28
  */
24
29
  export type ModalContentCallbacks = {
25
- /** Fires when the user presses Escape; call `preventDefault` to keep the modal open. */
30
+ /**
31
+ * Fires on the dialog's native `cancel` event when the user presses
32
+ * Escape. Call `event.preventDefault()` to veto closing and keep the
33
+ * modal open (e.g. to confirm unsaved changes first).
34
+ */
26
35
  onEscapeKeyDown?: (event: Event) => void;
27
- /** Fires on a pointer-down outside the content; call `preventDefault` to keep the modal open. */
36
+ /**
37
+ * Fires on a `pointerdown` whose coordinates land outside the dialog's
38
+ * bounding rect — i.e. on the browser-painted `::backdrop`. Call
39
+ * `event.preventDefault()` to veto closing and keep the modal open.
40
+ */
28
41
  onPointerDownOutside?: (event: PointerEvent) => void;
29
42
  };
30
43
 
@@ -51,75 +64,143 @@ export type ModalContextValue = {
51
64
  registerDescription: (id: string | undefined) => void;
52
65
  };
53
66
 
54
- /** Props for `Modal.Content` — native `<dialog>` props plus dismissal callbacks. */
67
+ /**
68
+ * Props for `Modal.Content` — every native `<dialog>` attribute plus the
69
+ * two {@link ModalContentCallbacks | dismissal callbacks}. `asChild` is
70
+ * intentionally absent: the native `<dialog>` is what supplies the inert
71
+ * background, top-layer stacking, and the Tab-wrap focus trap, so it cannot
72
+ * be swapped for another element.
73
+ */
55
74
  export type ModalContentProps = ComponentProps<"dialog"> &
56
75
  ModalContentCallbacks;
57
76
 
58
- /** `Modal.Root` props for the uncontrolled (self-managed) open state. */
77
+ /**
78
+ * `Modal.Root` props for the **uncontrolled** (self-managed) open state.
79
+ * The component owns the open flag internally. Mutually exclusive with
80
+ * {@link ControlledModalRootProps}.
81
+ */
59
82
  export type UncontrolledModalRootProps = {
60
- /** Initial open state when uncontrolled. */
83
+ /**
84
+ * Whether the modal is open on first render. The component owns the flag
85
+ * from then on. Omit for closed-on-mount.
86
+ * @default false
87
+ */
61
88
  defaultOpen?: boolean;
89
+ /** Forbidden in uncontrolled mode — use `defaultOpen` instead. */
62
90
  open?: never;
63
- /** Called whenever the open state changes. */
91
+ /**
92
+ * Called with the new open state whenever it changes (open or close).
93
+ * Optional in uncontrolled mode — useful purely to observe transitions.
94
+ */
64
95
  onOpenChange?: (open: boolean) => void;
65
96
  };
66
97
 
67
- /** `Modal.Root` props for the controlled open state. */
98
+ /**
99
+ * `Modal.Root` props for the **controlled** open state. The parent owns the
100
+ * flag and must keep it in sync via `onOpenChange`. Mutually exclusive with
101
+ * {@link UncontrolledModalRootProps}.
102
+ */
68
103
  export type ControlledModalRootProps = {
69
- /** Controlled open state. */
104
+ /** The current open state. Must be kept in sync by the parent via `onOpenChange`. */
70
105
  open: boolean;
71
- /** Called whenever the modal requests an open-state change. */
106
+ /**
107
+ * Called with the requested open state whenever the modal wants to open or
108
+ * close (trigger click, Close button, Esc, click-outside, imperative API).
109
+ * Required in controlled mode.
110
+ */
72
111
  onOpenChange: (open: boolean) => void;
112
+ /** Forbidden in controlled mode — use `open` instead. */
73
113
  defaultOpen?: never;
74
114
  };
75
115
 
76
- /** Props for `Modal.Root`, in either the controlled or uncontrolled mode. */
116
+ /**
117
+ * Props for {@link ModalRoot | `Modal.Root`}, in either the controlled or
118
+ * uncontrolled mode — resolves to
119
+ * {@link UncontrolledModalRootProps} | {@link ControlledModalRootProps}, so
120
+ * TypeScript accepts only one shape at a time.
121
+ */
77
122
  export type ModalRootProps = {
78
- /** Modal sub-components. */
123
+ /** The modal's sub-components (Trigger, Portal, Overlay, Content, ...). */
79
124
  children?: ReactNode;
80
125
  } & (UncontrolledModalRootProps | ControlledModalRootProps) & {
81
- /** Ref receiving the imperative open/close handle. */
126
+ /** Ref receiving the imperative {@link ModalImperativeApi | open/close handle}. */
82
127
  ref?: Ref<ModalImperativeApi>;
83
128
  };
84
129
 
85
130
  /** Props for `Modal.Trigger` — a `<button>` that opens the modal. */
86
131
  export type ModalTriggerProps = ComponentProps<"button"> & {
87
- /** Render into the consumer's own element instead of a `<button>`. */
132
+ /**
133
+ * Render into the consumer's own element (e.g. a router `<Link>`) instead
134
+ * of the default `<button>`, merging the trigger's ARIA wiring, composed
135
+ * event handlers, and ref onto it via the Slot pattern.
136
+ * @default false
137
+ */
88
138
  asChild?: boolean;
89
139
  };
90
140
 
91
141
  /** Props for `Modal.Close` — a `<button>` that closes the modal. */
92
142
  export type ModalCloseProps = ComponentProps<"button"> & {
93
- /** Render into the consumer's own element instead of a `<button>`. */
143
+ /**
144
+ * Render into the consumer's own element (e.g. an icon-only button)
145
+ * instead of the default `<button>`, merging the close behaviour in. The
146
+ * consumer's `onClick` runs first and can `event.preventDefault()` to veto.
147
+ * @default false
148
+ */
94
149
  asChild?: boolean;
95
150
  };
96
151
 
97
152
  /** Props for `Modal.Portal` — renders its children into `container`. */
98
153
  export type ModalPortalProps = {
99
- /** Content to portal. */
154
+ /** The dialog subtree to portal — typically `Modal.Overlay` + `Modal.Content`. */
100
155
  children?: ReactNode;
101
- /** Target element to portal into. Defaults to `document.body`. */
156
+ /**
157
+ * Target element to portal into.
158
+ * @default document.body
159
+ */
102
160
  container?: HTMLElement;
103
- /** Keep the portal mounted even while the modal is closed. */
161
+ /**
162
+ * Keep the portalled subtree mounted after `open` flips false, so a CSS
163
+ * exit animation keyed off `data-state="closed"` can play. Without it the
164
+ * portal unmounts as soon as the modal closes.
165
+ * @default false
166
+ */
104
167
  forceMount?: boolean;
105
168
  };
106
169
 
107
- /** Props for `Modal.Overlay` — the click-outside backdrop. */
170
+ /** Props for `Modal.Overlay` — the animation-friendly backdrop sibling. */
108
171
  export type ModalOverlayProps = ComponentProps<"div"> & {
109
- /** Render into the consumer's own element instead of a `<div>`. */
172
+ /**
173
+ * Render into the consumer's own element (e.g. a motion wrapper) instead of
174
+ * the default `<div>`, merging the overlay's `aria-hidden` and `data-state`
175
+ * onto it via the Slot pattern.
176
+ * @default false
177
+ */
110
178
  asChild?: boolean;
111
- /** Keep the overlay mounted even while the modal is closed. */
179
+ /**
180
+ * Keep the overlay mounted after `open` flips false so a CSS fade-out can
181
+ * play. Without it the overlay unmounts as soon as the modal closes.
182
+ * @default false
183
+ */
112
184
  forceMount?: boolean;
113
185
  };
114
186
 
115
- /** Props for `Modal.Title` — supplies the dialog's accessible name. */
187
+ /** Props for `Modal.Title` — supplies the dialog's accessible name (`aria-labelledby`). */
116
188
  export type ModalTitleProps = HTMLAttributes<HTMLElement> & {
117
- /** Render into the consumer's own element instead of an `<h2>`. */
189
+ /**
190
+ * Render into the consumer's own heading element (e.g. an `<h3>` for a
191
+ * nested dialog) instead of the default `<h2>`; the generated id is still
192
+ * registered for `aria-labelledby`.
193
+ * @default false
194
+ */
118
195
  asChild?: boolean;
119
196
  };
120
197
 
121
- /** Props for `Modal.Description` — supplies the dialog's accessible description. */
198
+ /** Props for `Modal.Description` — supplies the dialog's accessible description (`aria-describedby`). */
122
199
  export type ModalDescriptionProps = HTMLAttributes<HTMLElement> & {
123
- /** Render into the consumer's own element instead of a `<p>`. */
200
+ /**
201
+ * Render into the consumer's own element instead of the default `<p>`; the
202
+ * generated id is still registered for `aria-describedby`.
203
+ * @default false
204
+ */
124
205
  asChild?: boolean;
125
206
  };