@primitiv-ui/react 0.1.0

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 (585) hide show
  1. package/README.md +79 -0
  2. package/package.json +59 -0
  3. package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
  4. package/src/AccessibleIcon/README.md +42 -0
  5. package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
  6. package/src/AccessibleIcon/index.ts +2 -0
  7. package/src/AccessibleIcon/types.ts +8 -0
  8. package/src/Accordion/Accordion.tsx +412 -0
  9. package/src/Accordion/AccordionContext.ts +12 -0
  10. package/src/Accordion/README.md +202 -0
  11. package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
  12. package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
  13. package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
  14. package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
  15. package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
  16. package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
  17. package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
  18. package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
  19. package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
  20. package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
  21. package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
  22. package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
  23. package/src/Accordion/hooks/index.ts +6 -0
  24. package/src/Accordion/hooks/useAccordionContext.ts +1 -0
  25. package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
  26. package/src/Accordion/hooks/useAccordionItem.ts +22 -0
  27. package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
  28. package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
  29. package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
  30. package/src/Accordion/index.ts +1 -0
  31. package/src/Accordion/types.ts +81 -0
  32. package/src/Alert/Alert.tsx +43 -0
  33. package/src/Alert/README.md +54 -0
  34. package/src/Alert/__tests__/Alert.test.tsx +28 -0
  35. package/src/Alert/index.ts +2 -0
  36. package/src/Alert/types.ts +5 -0
  37. package/src/Avatar/Avatar.tsx +149 -0
  38. package/src/Avatar/AvatarContext.ts +20 -0
  39. package/src/Avatar/README.md +116 -0
  40. package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
  41. package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
  42. package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
  43. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
  44. package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
  45. package/src/Avatar/hooks/index.ts +2 -0
  46. package/src/Avatar/hooks/useAvatarContext.ts +1 -0
  47. package/src/Avatar/hooks/useAvatarImage.ts +40 -0
  48. package/src/Avatar/index.ts +3 -0
  49. package/src/Avatar/types.ts +44 -0
  50. package/src/Breadcrumb/Breadcrumb.tsx +234 -0
  51. package/src/Breadcrumb/README.md +111 -0
  52. package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
  53. package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
  54. package/src/Breadcrumb/index.ts +2 -0
  55. package/src/Breadcrumb/types.ts +22 -0
  56. package/src/Button/Button.tsx +95 -0
  57. package/src/Button/README.md +112 -0
  58. package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
  59. package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
  60. package/src/Button/__tests__/Button.contract.test.tsx +72 -0
  61. package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
  62. package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
  63. package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
  64. package/src/Button/index.ts +2 -0
  65. package/src/Button/types.ts +8 -0
  66. package/src/Carousel/Carousel.tsx +708 -0
  67. package/src/Carousel/CarouselContext.ts +11 -0
  68. package/src/Carousel/README.md +848 -0
  69. package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
  70. package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
  71. package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
  72. package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
  73. package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
  74. package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
  75. package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
  76. package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
  77. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
  78. package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
  79. package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
  80. package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
  81. package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
  82. package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
  83. package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
  84. package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
  85. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
  86. package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
  87. package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
  88. package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
  89. package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
  90. package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
  91. package/src/Carousel/hooks/index.ts +4 -0
  92. package/src/Carousel/hooks/useCarouselContext.ts +13 -0
  93. package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
  94. package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
  95. package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
  96. package/src/Carousel/index.ts +3 -0
  97. package/src/Carousel/types.ts +400 -0
  98. package/src/Checkbox/Checkbox.tsx +228 -0
  99. package/src/Checkbox/CheckboxContext.ts +12 -0
  100. package/src/Checkbox/README.md +156 -0
  101. package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
  102. package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
  103. package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
  104. package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
  105. package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
  106. package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
  107. package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
  108. package/src/Checkbox/hooks/index.ts +2 -0
  109. package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
  110. package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
  111. package/src/Checkbox/index.ts +1 -0
  112. package/src/Checkbox/types.ts +33 -0
  113. package/src/CheckboxCard/CheckboxCard.tsx +208 -0
  114. package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
  115. package/src/CheckboxCard/README.md +114 -0
  116. package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
  117. package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
  118. package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
  119. package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
  120. package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
  121. package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
  122. package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
  123. package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
  124. package/src/CheckboxCard/hooks/index.ts +2 -0
  125. package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
  126. package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
  127. package/src/CheckboxCard/index.ts +3 -0
  128. package/src/CheckboxCard/types.ts +33 -0
  129. package/src/Collapsible/Collapsible.tsx +316 -0
  130. package/src/Collapsible/CollapsibleContext.ts +7 -0
  131. package/src/Collapsible/README.md +174 -0
  132. package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
  133. package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
  134. package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
  135. package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
  136. package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
  137. package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
  138. package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
  139. package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
  140. package/src/Collapsible/hooks/index.ts +2 -0
  141. package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
  142. package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
  143. package/src/Collapsible/index.ts +1 -0
  144. package/src/Collapsible/types.ts +48 -0
  145. package/src/ContextMenu/ContextMenu.tsx +1004 -0
  146. package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
  147. package/src/ContextMenu/ContextMenuContext.ts +21 -0
  148. package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
  149. package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
  150. package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
  151. package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
  152. package/src/ContextMenu/README.md +275 -0
  153. package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
  154. package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
  155. package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
  156. package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
  157. package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
  158. package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
  159. package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
  160. package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
  161. package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
  162. package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
  163. package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
  164. package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
  165. package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
  166. package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
  167. package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
  168. package/src/ContextMenu/constants.ts +4 -0
  169. package/src/ContextMenu/index.ts +1 -0
  170. package/src/ContextMenu/types.ts +199 -0
  171. package/src/DirectionProvider/DirectionContext.ts +21 -0
  172. package/src/DirectionProvider/DirectionProvider.tsx +31 -0
  173. package/src/DirectionProvider/README.md +62 -0
  174. package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
  175. package/src/DirectionProvider/index.ts +3 -0
  176. package/src/DirectionProvider/types.ts +10 -0
  177. package/src/Divider/Divider.tsx +57 -0
  178. package/src/Divider/README.md +57 -0
  179. package/src/Divider/__tests__/Divider.test.tsx +41 -0
  180. package/src/Divider/index.ts +1 -0
  181. package/src/Divider/types.ts +5 -0
  182. package/src/Dropdown/Dropdown.tsx +842 -0
  183. package/src/Dropdown/DropdownContentContext.ts +15 -0
  184. package/src/Dropdown/DropdownContext.ts +17 -0
  185. package/src/Dropdown/DropdownGroupContext.ts +8 -0
  186. package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
  187. package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
  188. package/src/Dropdown/DropdownSubContext.ts +15 -0
  189. package/src/Dropdown/README.md +284 -0
  190. package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
  191. package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
  192. package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
  193. package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
  194. package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
  195. package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
  196. package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
  197. package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
  198. package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
  199. package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
  200. package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
  201. package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
  202. package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
  203. package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
  204. package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
  205. package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
  206. package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
  207. package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
  208. package/src/Dropdown/constants.ts +4 -0
  209. package/src/Dropdown/hooks/index.ts +9 -0
  210. package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
  211. package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
  212. package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
  213. package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
  214. package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
  215. package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
  216. package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
  217. package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
  218. package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
  219. package/src/Dropdown/index.ts +1 -0
  220. package/src/Dropdown/types.ts +200 -0
  221. package/src/EmptyState/EmptyState.tsx +245 -0
  222. package/src/EmptyState/README.md +129 -0
  223. package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
  224. package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
  225. package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
  226. package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
  227. package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
  228. package/src/EmptyState/index.ts +2 -0
  229. package/src/EmptyState/types.ts +21 -0
  230. package/src/Field/Field.tsx +239 -0
  231. package/src/Field/FieldContext.ts +22 -0
  232. package/src/Field/README.md +167 -0
  233. package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
  234. package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
  235. package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
  236. package/src/Field/hooks/index.ts +2 -0
  237. package/src/Field/hooks/useFieldContext.ts +1 -0
  238. package/src/Field/hooks/useFieldProps.ts +57 -0
  239. package/src/Field/index.ts +2 -0
  240. package/src/Field/types.ts +33 -0
  241. package/src/Fieldset/Fieldset.tsx +104 -0
  242. package/src/Fieldset/README.md +74 -0
  243. package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
  244. package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
  245. package/src/Fieldset/index.ts +2 -0
  246. package/src/Fieldset/types.ts +5 -0
  247. package/src/Input/Input.tsx +120 -0
  248. package/src/Input/README.md +180 -0
  249. package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
  250. package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
  251. package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
  252. package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
  253. package/src/Input/index.ts +2 -0
  254. package/src/Input/types.ts +7 -0
  255. package/src/InputGroup/InputGroup.tsx +228 -0
  256. package/src/InputGroup/README.md +178 -0
  257. package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
  258. package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
  259. package/src/InputGroup/index.ts +2 -0
  260. package/src/InputGroup/types.ts +13 -0
  261. package/src/MillerColumns/MillerColumns.tsx +329 -0
  262. package/src/MillerColumns/MillerColumnsContext.ts +25 -0
  263. package/src/MillerColumns/README.md +278 -0
  264. package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
  265. package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
  266. package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
  267. package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
  268. package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
  269. package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
  270. package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
  271. package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
  272. package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
  273. package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
  274. package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
  275. package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
  276. package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
  277. package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
  278. package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
  279. package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
  280. package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
  281. package/src/MillerColumns/hooks/index.ts +7 -0
  282. package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
  283. package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
  284. package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
  285. package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
  286. package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
  287. package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
  288. package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
  289. package/src/MillerColumns/index.ts +3 -0
  290. package/src/MillerColumns/types.ts +93 -0
  291. package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
  292. package/src/MillerColumns/utils.ts +75 -0
  293. package/src/Modal/Modal.tsx +474 -0
  294. package/src/Modal/ModalContext.ts +13 -0
  295. package/src/Modal/README.md +207 -0
  296. package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
  297. package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
  298. package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
  299. package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
  300. package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
  301. package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
  302. package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
  303. package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
  304. package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
  305. package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
  306. package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
  307. package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
  308. package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
  309. package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
  310. package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
  311. package/src/Modal/hooks/index.ts +4 -0
  312. package/src/Modal/hooks/useModalContent.ts +62 -0
  313. package/src/Modal/hooks/useModalContext.ts +1 -0
  314. package/src/Modal/hooks/useModalRoot.ts +81 -0
  315. package/src/Modal/hooks/useModalTrigger.ts +25 -0
  316. package/src/Modal/index.ts +3 -0
  317. package/src/Modal/types.ts +76 -0
  318. package/src/Portal/Portal.tsx +28 -0
  319. package/src/Portal/README.md +70 -0
  320. package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
  321. package/src/Portal/index.ts +2 -0
  322. package/src/Portal/types.ts +6 -0
  323. package/src/Progress/Progress.tsx +178 -0
  324. package/src/Progress/ProgressContext.ts +15 -0
  325. package/src/Progress/README.md +112 -0
  326. package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
  327. package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
  328. package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
  329. package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
  330. package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
  331. package/src/Progress/hooks/index.ts +2 -0
  332. package/src/Progress/hooks/useProgressContext.ts +1 -0
  333. package/src/Progress/hooks/useProgressRoot.ts +45 -0
  334. package/src/Progress/index.ts +3 -0
  335. package/src/Progress/types.ts +43 -0
  336. package/src/RadioCard/README.md +133 -0
  337. package/src/RadioCard/RadioCard.tsx +334 -0
  338. package/src/RadioCard/RadioCardContext.ts +23 -0
  339. package/src/RadioCard/RadioCardItemContext.ts +10 -0
  340. package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
  341. package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
  342. package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
  343. package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
  344. package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
  345. package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
  346. package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
  347. package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
  348. package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
  349. package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
  350. package/src/RadioCard/hooks/index.ts +3 -0
  351. package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
  352. package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
  353. package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
  354. package/src/RadioCard/index.ts +4 -0
  355. package/src/RadioCard/types.ts +51 -0
  356. package/src/RadioGroup/README.md +185 -0
  357. package/src/RadioGroup/RadioGroup.tsx +353 -0
  358. package/src/RadioGroup/RadioGroupContext.ts +23 -0
  359. package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
  360. package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
  361. package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
  362. package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
  363. package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
  364. package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
  365. package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
  366. package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
  367. package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
  368. package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
  369. package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
  370. package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
  371. package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
  372. package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
  373. package/src/RadioGroup/hooks/index.ts +3 -0
  374. package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
  375. package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
  376. package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
  377. package/src/RadioGroup/index.ts +1 -0
  378. package/src/RadioGroup/types.ts +51 -0
  379. package/src/Select/README.md +203 -0
  380. package/src/Select/Select.tsx +204 -0
  381. package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
  382. package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
  383. package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
  384. package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
  385. package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
  386. package/src/Select/__tests__/Select.group.test.tsx +42 -0
  387. package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
  388. package/src/Select/index.ts +2 -0
  389. package/src/Select/types.ts +89 -0
  390. package/src/SkipNav/README.md +87 -0
  391. package/src/SkipNav/SkipNav.tsx +116 -0
  392. package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
  393. package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
  394. package/src/SkipNav/index.ts +1 -0
  395. package/src/SkipNav/types.ts +26 -0
  396. package/src/Slider/README.md +215 -0
  397. package/src/Slider/Slider.tsx +308 -0
  398. package/src/Slider/SliderContext.ts +24 -0
  399. package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
  400. package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
  401. package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
  402. package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
  403. package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
  404. package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
  405. package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
  406. package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
  407. package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
  408. package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
  409. package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
  410. package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
  411. package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
  412. package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
  413. package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
  414. package/src/Slider/hooks/index.ts +3 -0
  415. package/src/Slider/hooks/useSliderContext.ts +1 -0
  416. package/src/Slider/hooks/useSliderRoot.ts +197 -0
  417. package/src/Slider/hooks/useSliderThumb.ts +77 -0
  418. package/src/Slider/index.ts +3 -0
  419. package/src/Slider/types.ts +48 -0
  420. package/src/Slider/utils.ts +155 -0
  421. package/src/Slot/Slot.tsx +158 -0
  422. package/src/Slot/__tests__/Slot.test.tsx +163 -0
  423. package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
  424. package/src/Slot/composeEventHandlers.ts +38 -0
  425. package/src/Slot/index.ts +3 -0
  426. package/src/Slot/types.ts +5 -0
  427. package/src/Status/README.md +50 -0
  428. package/src/Status/Status.tsx +44 -0
  429. package/src/Status/__tests__/Status.test.tsx +28 -0
  430. package/src/Status/index.ts +2 -0
  431. package/src/Status/types.ts +5 -0
  432. package/src/Switch/README.md +121 -0
  433. package/src/Switch/Switch.tsx +167 -0
  434. package/src/Switch/SwitchContext.ts +10 -0
  435. package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
  436. package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
  437. package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
  438. package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
  439. package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
  440. package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
  441. package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
  442. package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
  443. package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
  444. package/src/Switch/hooks/index.ts +2 -0
  445. package/src/Switch/hooks/useSwitchContext.ts +1 -0
  446. package/src/Switch/hooks/useSwitchRoot.ts +28 -0
  447. package/src/Switch/index.ts +3 -0
  448. package/src/Switch/types.ts +37 -0
  449. package/src/Table/README.md +205 -0
  450. package/src/Table/Table.tsx +380 -0
  451. package/src/Table/__tests__/Table.Body.test.tsx +61 -0
  452. package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
  453. package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
  454. package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
  455. package/src/Table/__tests__/Table.Head.test.tsx +61 -0
  456. package/src/Table/__tests__/Table.Header.test.tsx +73 -0
  457. package/src/Table/__tests__/Table.Root.test.tsx +49 -0
  458. package/src/Table/__tests__/Table.Row.test.tsx +67 -0
  459. package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
  460. package/src/Table/index.ts +1 -0
  461. package/src/Table/types.ts +63 -0
  462. package/src/Tabs/README.md +110 -0
  463. package/src/Tabs/Tabs.tsx +434 -0
  464. package/src/Tabs/TabsContext.ts +13 -0
  465. package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
  466. package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
  467. package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
  468. package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
  469. package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
  470. package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
  471. package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
  472. package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
  473. package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
  474. package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
  475. package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
  476. package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
  477. package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
  478. package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
  479. package/src/Tabs/hooks/index.ts +4 -0
  480. package/src/Tabs/hooks/useTabsContent.ts +27 -0
  481. package/src/Tabs/hooks/useTabsContext.ts +1 -0
  482. package/src/Tabs/hooks/useTabsRoot.ts +148 -0
  483. package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
  484. package/src/Tabs/index.ts +3 -0
  485. package/src/Tabs/types.ts +99 -0
  486. package/src/Tabs/utils.ts +8 -0
  487. package/src/Textarea/README.md +98 -0
  488. package/src/Textarea/Textarea.tsx +93 -0
  489. package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
  490. package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
  491. package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
  492. package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
  493. package/src/Textarea/index.ts +2 -0
  494. package/src/Textarea/types.ts +7 -0
  495. package/src/Toggle/README.md +97 -0
  496. package/src/Toggle/Toggle.tsx +81 -0
  497. package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
  498. package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
  499. package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
  500. package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
  501. package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
  502. package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
  503. package/src/Toggle/index.ts +2 -0
  504. package/src/Toggle/types.ts +23 -0
  505. package/src/ToggleGroup/README.md +137 -0
  506. package/src/ToggleGroup/ToggleGroup.tsx +298 -0
  507. package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
  508. package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
  509. package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
  510. package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
  511. package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
  512. package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
  513. package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
  514. package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
  515. package/src/ToggleGroup/hooks/index.ts +2 -0
  516. package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
  517. package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
  518. package/src/ToggleGroup/index.ts +2 -0
  519. package/src/ToggleGroup/types.ts +72 -0
  520. package/src/Tooltip/README.md +214 -0
  521. package/src/Tooltip/Tooltip.tsx +260 -0
  522. package/src/Tooltip/TooltipContext.ts +20 -0
  523. package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
  524. package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
  525. package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
  526. package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
  527. package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
  528. package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
  529. package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
  530. package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
  531. package/src/Tooltip/hooks/index.ts +4 -0
  532. package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
  533. package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
  534. package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
  535. package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
  536. package/src/Tooltip/index.ts +1 -0
  537. package/src/Tooltip/types.ts +64 -0
  538. package/src/Tree/README.md +339 -0
  539. package/src/Tree/Tree.tsx +571 -0
  540. package/src/Tree/TreeContext.ts +24 -0
  541. package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
  542. package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
  543. package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
  544. package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
  545. package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
  546. package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
  547. package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
  548. package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
  549. package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
  550. package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
  551. package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
  552. package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
  553. package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
  554. package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
  555. package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
  556. package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
  557. package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
  558. package/src/Tree/hooks/index.ts +3 -0
  559. package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
  560. package/src/Tree/hooks/useTreePath.ts +68 -0
  561. package/src/Tree/hooks/useTreeRoot.ts +279 -0
  562. package/src/Tree/index.ts +3 -0
  563. package/src/Tree/types.ts +224 -0
  564. package/src/Tree/utils.ts +59 -0
  565. package/src/VisuallyHidden/README.md +58 -0
  566. package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
  567. package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
  568. package/src/VisuallyHidden/index.ts +2 -0
  569. package/src/VisuallyHidden/types.ts +5 -0
  570. package/src/hooks/index.ts +3 -0
  571. package/src/hooks/useCollection.ts +74 -0
  572. package/src/hooks/useControllableState.ts +81 -0
  573. package/src/hooks/useRovingTabindex.ts +178 -0
  574. package/src/index.ts +38 -0
  575. package/src/test/intersectionObserverPolyfill.ts +83 -0
  576. package/src/test/popoverPolyfill.ts +86 -0
  577. package/src/test/scrollPolyfill.ts +23 -0
  578. package/src/types.ts +13 -0
  579. package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
  580. package/src/utils/__tests__/deriveId.test.ts +28 -0
  581. package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
  582. package/src/utils/createStrictContext.ts +49 -0
  583. package/src/utils/deriveId.ts +31 -0
  584. package/src/utils/getKeyToActionMap.ts +95 -0
  585. package/src/utils/index.ts +3 -0
@@ -0,0 +1,68 @@
1
+ import { useTreeContext } from "../TreeContext";
2
+
3
+ import type { TreePathSegment } from "../types";
4
+
5
+ /**
6
+ * Returns the root-to-leaf ancestor chain of a tree value as an array
7
+ * of {@link TreePathSegment}s, or an empty array if the value has
8
+ * never been registered in the tree.
9
+ *
10
+ * Must be called from a component rendered inside `Tree.Root`.
11
+ *
12
+ * The segments are ordered **root → leaf** so the last entry is the
13
+ * queried value itself. Each segment exposes its `label` (the `label`
14
+ * prop passed to `Tree.Item` / `Tree.Branch`, or `null` when omitted),
15
+ * `isBranch`, `disabled` and `depth` — enough to render breadcrumbs,
16
+ * outline trails, or location indicators without a parallel data
17
+ * lookup.
18
+ *
19
+ * Paths survive a branch collapsing without `forceMount`: the tree
20
+ * keeps a durable copy of every node's metadata so ancestry remains
21
+ * resolvable even after descendants unmount. A value never mounted
22
+ * (e.g. a pre-selected item whose branch has not yet opened) returns
23
+ * an empty array.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * function CurrentPath({ value }: { value: string }) {
28
+ * const path = useTreePath(value);
29
+ * return <>{path.map((s) => s.label ?? s.value).join(" / ")}</>;
30
+ * }
31
+ * ```
32
+ */
33
+ export function useTreePath(value: string): TreePathSegment[] {
34
+ const { getPath } = useTreeContext();
35
+ return getPath(value);
36
+ }
37
+
38
+ /**
39
+ * Returns one path per currently-selected value, in selection order.
40
+ * In single-selection mode the array has zero or one entry; in
41
+ * multiple-selection mode it has one entry per selected value.
42
+ *
43
+ * Each inner array is the same shape as the return of
44
+ * {@link useTreePath} — root → leaf segments with labels carried from
45
+ * the `label` prop.
46
+ *
47
+ * Used by `Tree.SelectionPath` for its default rendering, and
48
+ * available directly for fully-custom breadcrumb UIs.
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * function Crumbs() {
53
+ * const paths = useTreeSelectionPaths();
54
+ * if (paths.length === 0) return <span>Nothing selected</span>;
55
+ * return (
56
+ * <ul>
57
+ * {paths.map((path, i) => (
58
+ * <li key={i}>{path.map((s) => s.label ?? s.value).join(" / ")}</li>
59
+ * ))}
60
+ * </ul>
61
+ * );
62
+ * }
63
+ * ```
64
+ */
65
+ export function useTreeSelectionPaths(): TreePathSegment[][] {
66
+ const { selectedOrder, getPath } = useTreeContext();
67
+ return selectedOrder.map((value) => getPath(value));
68
+ }
@@ -0,0 +1,279 @@
1
+ import { useCallback, useId, useRef, useState } from "react";
2
+
3
+ import { useCollection, useControllableState } from "../../hooks";
4
+
5
+ import type {
6
+ SelectionMode,
7
+ TreeContextValue,
8
+ TreeNodeMeta,
9
+ TreePathSegment,
10
+ TreeSelectModifiers,
11
+ } from "../types";
12
+
13
+ /** Defensive cap to short-circuit a cycle in `parentValue` pointers. */
14
+ const MAX_PATH_DEPTH = 64;
15
+
16
+ export type UseTreeRootOptions = {
17
+ expandedValues: string[] | undefined;
18
+ defaultExpandedValues: string[] | undefined;
19
+ onExpandedChange: ((values: string[]) => void) | undefined;
20
+ selectionMode: SelectionMode;
21
+ selectedValue: string | null | undefined;
22
+ defaultSelectedValue: string | null | undefined;
23
+ onSelectedValueChange: ((value: string | null) => void) | undefined;
24
+ selectedValues: string[] | undefined;
25
+ defaultSelectedValues: string[] | undefined;
26
+ onSelectedValuesChange: ((values: string[]) => void) | undefined;
27
+ };
28
+
29
+ function singleToArray(value: string | null | undefined): string[] | undefined {
30
+ if (value === undefined) {
31
+ return undefined;
32
+ }
33
+ return value === null ? [] : [value];
34
+ }
35
+
36
+ /**
37
+ * Owns the Tree's expansion and selection state, the item collection
38
+ * used to compute the visible DFS order, and the anchor that pins
39
+ * Shift+click range selection.
40
+ */
41
+ export function useTreeRoot(options: UseTreeRootOptions): TreeContextValue {
42
+ const rootId = useId();
43
+
44
+ const [expandedValues, setExpandedValues] = useControllableState<string[]>(
45
+ options.expandedValues,
46
+ options.defaultExpandedValues ?? [],
47
+ options.onExpandedChange,
48
+ );
49
+
50
+ const normalisedSelectedValues =
51
+ options.selectionMode === "multiple"
52
+ ? options.selectedValues
53
+ : singleToArray(options.selectedValue);
54
+
55
+ const normalisedDefaultSelectedValues =
56
+ options.selectionMode === "multiple"
57
+ ? (options.defaultSelectedValues ?? [])
58
+ : (singleToArray(options.defaultSelectedValue) ?? []);
59
+
60
+ const handleSelectedValuesChange = useCallback(
61
+ (next: string[]) => {
62
+ if (options.selectionMode === "multiple") {
63
+ options.onSelectedValuesChange?.(next);
64
+ } else {
65
+ options.onSelectedValueChange?.(next[0]!);
66
+ }
67
+ },
68
+ [
69
+ options.selectionMode,
70
+ options.onSelectedValueChange,
71
+ options.onSelectedValuesChange,
72
+ ],
73
+ );
74
+
75
+ const [selectedValues, setSelectedValues] = useControllableState<string[]>(
76
+ normalisedSelectedValues,
77
+ normalisedDefaultSelectedValues,
78
+ handleSelectedValuesChange,
79
+ );
80
+
81
+ const {
82
+ register: registerCollectionNode,
83
+ itemsRef,
84
+ keys,
85
+ } = useCollection<string, TreeNodeMeta>();
86
+
87
+ // Durable copy of every node meta ever registered. Unlike the
88
+ // collection above (which deletes on unmount so visible-order /
89
+ // focus reflect mounted state) this map keeps the last-seen entry
90
+ // for each value so `getPath` resolves ancestry even when an
91
+ // ancestor branch has collapsed without `forceMount` and its
92
+ // descendants have unmounted.
93
+ const pathMetaRef = useRef<Map<string, TreeNodeMeta>>(new Map());
94
+ const [pathMetaVersion, setPathMetaVersion] = useState(0);
95
+
96
+ const registerNode = useCallback(
97
+ (value: string, meta: TreeNodeMeta | null) => {
98
+ if (meta !== null) {
99
+ pathMetaRef.current.set(value, meta);
100
+ setPathMetaVersion((current) => current + 1);
101
+ }
102
+ registerCollectionNode(value, meta);
103
+ },
104
+ [registerCollectionNode],
105
+ );
106
+
107
+ const getPath = useCallback(
108
+ (value: string): TreePathSegment[] => {
109
+ const segments: TreePathSegment[] = [];
110
+ let cursor: string | null = value;
111
+ let hops = 0;
112
+ while (cursor !== null && hops < MAX_PATH_DEPTH) {
113
+ const meta = pathMetaRef.current.get(cursor);
114
+ if (meta === undefined) {
115
+ return [];
116
+ }
117
+ segments.unshift({
118
+ value: meta.value,
119
+ label: meta.label,
120
+ isBranch: meta.isBranch,
121
+ disabled: meta.disabled,
122
+ depth: meta.depth,
123
+ });
124
+ cursor = meta.parentValue;
125
+ hops += 1;
126
+ }
127
+ return segments;
128
+ },
129
+ // `pathMetaVersion` bumps whenever the persistent map mutates,
130
+ // forcing context-value identity to change so consumers re-render
131
+ // and re-evaluate `getPath`.
132
+ [pathMetaVersion],
133
+ );
134
+
135
+ const anchorRef = useRef<string | null>(null);
136
+ const [activeValue, setActiveValue] = useState<string | null>(null);
137
+
138
+ const focusItem = useCallback(
139
+ (value: string) => {
140
+ itemsRef.current.get(value)?.element.focus();
141
+ },
142
+ [itemsRef],
143
+ );
144
+
145
+ const isNodeDisabled = useCallback(
146
+ (value: string) => itemsRef.current.get(value)?.disabled === true,
147
+ [itemsRef],
148
+ );
149
+
150
+ const isExpanded = useCallback(
151
+ (value: string) => expandedValues.includes(value),
152
+ [expandedValues],
153
+ );
154
+
155
+ const toggleExpanded = useCallback(
156
+ (value: string) => {
157
+ const open = expandedValues.includes(value);
158
+ setExpandedValues(
159
+ open
160
+ ? expandedValues.filter((current) => current !== value)
161
+ : [...expandedValues, value],
162
+ );
163
+ },
164
+ [expandedValues, setExpandedValues],
165
+ );
166
+
167
+ const isSelected = useCallback(
168
+ (value: string) => selectedValues.includes(value),
169
+ [selectedValues],
170
+ );
171
+
172
+ const getVisibleOrder = useCallback((): string[] => {
173
+ const childrenByParent = new Map<string | null, string[]>();
174
+ for (const key of keys) {
175
+ const meta = itemsRef.current.get(key)!;
176
+ const bucket = childrenByParent.get(meta.parentValue) ?? [];
177
+ bucket.push(meta.value);
178
+ childrenByParent.set(meta.parentValue, bucket);
179
+ }
180
+
181
+ const result: string[] = [];
182
+ const visit = (parent: string | null): void => {
183
+ for (const value of childrenByParent.get(parent) ?? []) {
184
+ result.push(value);
185
+ const meta = itemsRef.current.get(value);
186
+ if (meta?.isBranch && expandedValues.includes(value)) {
187
+ visit(value);
188
+ }
189
+ }
190
+ };
191
+ visit(null);
192
+ return result;
193
+ }, [keys, itemsRef, expandedValues]);
194
+
195
+ const select = useCallback(
196
+ (value: string, modifiers?: TreeSelectModifiers) => {
197
+ if (options.selectionMode === "single") {
198
+ if (selectedValues[0] === value) {
199
+ return;
200
+ }
201
+ setSelectedValues([value]);
202
+ anchorRef.current = value;
203
+ return;
204
+ }
205
+
206
+ const shift = modifiers?.shift === true;
207
+ const additive = modifiers?.meta === true || modifiers?.ctrl === true;
208
+ const alreadySelected = selectedValues.includes(value);
209
+
210
+ if (shift) {
211
+ const anchor = anchorRef.current ?? value;
212
+ const order = getVisibleOrder();
213
+ const anchorIndex = order.indexOf(anchor);
214
+ const valueIndex = order.indexOf(value);
215
+ if (anchorIndex === -1 || valueIndex === -1) {
216
+ setSelectedValues([value]);
217
+ return;
218
+ }
219
+ const [start, end] =
220
+ anchorIndex <= valueIndex
221
+ ? [anchorIndex, valueIndex]
222
+ : [valueIndex, anchorIndex];
223
+ const range = order
224
+ .slice(start, end + 1)
225
+ .filter((candidate) => !isNodeDisabled(candidate));
226
+ setSelectedValues(range);
227
+ return;
228
+ }
229
+
230
+ if (additive) {
231
+ setSelectedValues(
232
+ alreadySelected
233
+ ? selectedValues.filter((current) => current !== value)
234
+ : [...selectedValues, value],
235
+ );
236
+ anchorRef.current = value;
237
+ return;
238
+ }
239
+
240
+ if (selectedValues.length === 1 && alreadySelected) {
241
+ return;
242
+ }
243
+ setSelectedValues([value]);
244
+ anchorRef.current = value;
245
+ },
246
+ [
247
+ options.selectionMode,
248
+ selectedValues,
249
+ setSelectedValues,
250
+ getVisibleOrder,
251
+ isNodeDisabled,
252
+ ],
253
+ );
254
+
255
+ const visibleOrder = getVisibleOrder();
256
+ const defaultTabStop =
257
+ visibleOrder.find((candidate) => !isNodeDisabled(candidate)) ?? null;
258
+ const tabStop =
259
+ activeValue !== null && visibleOrder.includes(activeValue)
260
+ ? activeValue
261
+ : defaultTabStop;
262
+
263
+ return {
264
+ rootId,
265
+ selectionMode: options.selectionMode,
266
+ isExpanded,
267
+ toggleExpanded,
268
+ isSelected,
269
+ select,
270
+ registerNode,
271
+ getVisibleOrder,
272
+ isNodeDisabled,
273
+ tabStop,
274
+ setActiveValue,
275
+ focusItem,
276
+ getPath,
277
+ selectedOrder: selectedValues,
278
+ };
279
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./Tree";
2
+ export { useTreePath, useTreeSelectionPaths } from "./hooks/useTreePath";
3
+ export type { TreePathSegment } from "./types";
@@ -0,0 +1,224 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+
3
+ type TreeRootBaseProps = ComponentProps<"div"> & {
4
+ children: ReactNode;
5
+ };
6
+
7
+ type TreeRootUncontrolledExpansionProps = {
8
+ /** Branch values expanded on first render when uncontrolled. */
9
+ defaultExpandedValues?: string[];
10
+ expandedValues?: never;
11
+ onExpandedChange?: (values: string[]) => void;
12
+ };
13
+
14
+ type TreeRootControlledExpansionProps = {
15
+ defaultExpandedValues?: never;
16
+ /** The set of expanded branch values, owned by the consumer. */
17
+ expandedValues: string[];
18
+ onExpandedChange: (values: string[]) => void;
19
+ };
20
+
21
+ type TreeRootSingleUncontrolledSelectionProps = {
22
+ selectionMode?: "single";
23
+ /** The value selected on first render when uncontrolled. */
24
+ defaultSelectedValue?: string | null;
25
+ selectedValue?: never;
26
+ onSelectedValueChange?: (value: string | null) => void;
27
+ defaultSelectedValues?: never;
28
+ selectedValues?: never;
29
+ onSelectedValuesChange?: never;
30
+ };
31
+
32
+ type TreeRootSingleControlledSelectionProps = {
33
+ selectionMode?: "single";
34
+ defaultSelectedValue?: never;
35
+ /** The selected value, owned by the consumer. */
36
+ selectedValue: string | null;
37
+ onSelectedValueChange: (value: string | null) => void;
38
+ defaultSelectedValues?: never;
39
+ selectedValues?: never;
40
+ onSelectedValuesChange?: never;
41
+ };
42
+
43
+ type TreeRootMultipleUncontrolledSelectionProps = {
44
+ selectionMode: "multiple";
45
+ /** The values selected on first render when uncontrolled. */
46
+ defaultSelectedValues?: string[];
47
+ selectedValues?: never;
48
+ onSelectedValuesChange?: (values: string[]) => void;
49
+ defaultSelectedValue?: never;
50
+ selectedValue?: never;
51
+ onSelectedValueChange?: never;
52
+ };
53
+
54
+ type TreeRootMultipleControlledSelectionProps = {
55
+ selectionMode: "multiple";
56
+ defaultSelectedValues?: never;
57
+ /** The selected values, owned by the consumer. */
58
+ selectedValues: string[];
59
+ onSelectedValuesChange: (values: string[]) => void;
60
+ defaultSelectedValue?: never;
61
+ selectedValue?: never;
62
+ onSelectedValueChange?: never;
63
+ };
64
+
65
+ export type TreeRootProps = TreeRootBaseProps &
66
+ (TreeRootUncontrolledExpansionProps | TreeRootControlledExpansionProps) &
67
+ (
68
+ | TreeRootSingleUncontrolledSelectionProps
69
+ | TreeRootSingleControlledSelectionProps
70
+ | TreeRootMultipleUncontrolledSelectionProps
71
+ | TreeRootMultipleControlledSelectionProps
72
+ );
73
+
74
+ export type TreeItemProps = ComponentProps<"div"> & {
75
+ /** Stable identifier for this item, unique within the tree. */
76
+ value: string;
77
+ /**
78
+ * Optional display label for this item. Stored alongside the value
79
+ * in the tree's node registry so {@link useTreePath} and
80
+ * `Tree.SelectionPath` can surface it without an external lookup.
81
+ * Has no effect on what `Tree.Item` renders.
82
+ */
83
+ label?: string;
84
+ /** Disable selection and remove the item from roving navigation. */
85
+ disabled?: boolean;
86
+ /** Render the item as the supplied child element instead of `<div>`. */
87
+ asChild?: boolean;
88
+ children: ReactNode;
89
+ };
90
+
91
+ export type TreeBranchProps = Omit<ComponentProps<"div">, "ref"> & {
92
+ /** Stable identifier for this branch, unique within the tree. */
93
+ value: string;
94
+ /**
95
+ * Optional display label for this branch. Stored alongside the value
96
+ * in the tree's node registry so {@link useTreePath} and
97
+ * `Tree.SelectionPath` can surface it without an external lookup.
98
+ * Has no effect on what `Tree.Branch` renders.
99
+ */
100
+ label?: string;
101
+ /**
102
+ * Disable selection, expansion-toggling, and roving navigation for
103
+ * this branch. The branch and its current content remain rendered.
104
+ */
105
+ disabled?: boolean;
106
+ children: ReactNode;
107
+ };
108
+
109
+ export type TreeBranchControlProps = ComponentProps<"div"> & {
110
+ /** Render the control as the supplied child element instead of `<div>`. */
111
+ asChild?: boolean;
112
+ children: ReactNode;
113
+ };
114
+
115
+ export type TreeBranchContentProps = ComponentProps<"div"> & {
116
+ children: ReactNode;
117
+ /**
118
+ * Keep the content mounted while the branch is collapsed so CSS can
119
+ * animate it in and out. When collapsed it is hidden from assistive
120
+ * technology with `aria-hidden`.
121
+ */
122
+ forceMount?: boolean;
123
+ };
124
+
125
+ export type TreeBranchIndicatorProps = ComponentProps<"span"> & {
126
+ /** Render as the supplied child element instead of `<span>`. */
127
+ asChild?: boolean;
128
+ };
129
+
130
+ /** Arguments passed to the `Tree.SelectionPath` render-prop form. */
131
+ export type TreeSelectionPathRenderProps = {
132
+ /** One root-to-leaf chain per currently-selected value, in selection order. */
133
+ paths: TreePathSegment[][];
134
+ };
135
+
136
+ export type TreeSelectionPathProps = Omit<ComponentProps<"div">, "children"> & {
137
+ /**
138
+ * Either standard React children (ignored — the subcomponent does its
139
+ * own rendering) or a render-prop receiving the resolved selection
140
+ * paths so consumers can lay out custom markup.
141
+ */
142
+ children?: ReactNode | ((args: TreeSelectionPathRenderProps) => ReactNode);
143
+ /**
144
+ * Node passed to each `Breadcrumb.Separator` in the default rendering.
145
+ * Defaults to Breadcrumb's built-in `"/"` glyph.
146
+ */
147
+ separator?: ReactNode;
148
+ };
149
+
150
+ export type TreeLevelContextValue = {
151
+ /** Zero-based nesting depth — `0` for items directly inside `Tree.Root`. */
152
+ depth: number;
153
+ /** The value of the enclosing branch, or `null` at the tree root. */
154
+ parentValue: string | null;
155
+ };
156
+
157
+ export type TreeNodeMeta = {
158
+ value: string;
159
+ element: HTMLElement;
160
+ isBranch: boolean;
161
+ disabled: boolean;
162
+ depth: number;
163
+ parentValue: string | null;
164
+ label: string | null;
165
+ };
166
+
167
+ /**
168
+ * One segment of an ancestor chain returned by {@link useTreePath} or
169
+ * `TreeContextValue.getPath`. The array is ordered **root → leaf**, so
170
+ * the last segment is the queried item itself.
171
+ */
172
+ export type TreePathSegment = {
173
+ value: string;
174
+ label: string | null;
175
+ isBranch: boolean;
176
+ disabled: boolean;
177
+ depth: number;
178
+ };
179
+
180
+ export type SelectionMode = "single" | "multiple";
181
+
182
+ export type TreeSelectModifiers = {
183
+ meta: boolean;
184
+ ctrl: boolean;
185
+ shift: boolean;
186
+ };
187
+
188
+ export type TreeContextValue = {
189
+ /** Stable id shared across the tree, used to derive ARIA wiring ids. */
190
+ rootId: string;
191
+ selectionMode: SelectionMode;
192
+ isExpanded: (value: string) => boolean;
193
+ toggleExpanded: (value: string) => void;
194
+ isSelected: (value: string) => boolean;
195
+ select: (value: string, modifiers?: TreeSelectModifiers) => void;
196
+ registerNode: (value: string, meta: TreeNodeMeta | null) => void;
197
+ getVisibleOrder: () => string[];
198
+ isNodeDisabled: (value: string) => boolean;
199
+ /** Value of the treeitem currently holding the single roving tabstop. */
200
+ tabStop: string | null;
201
+ setActiveValue: (value: string) => void;
202
+ focusItem: (value: string) => void;
203
+ /**
204
+ * Returns the root-to-leaf chain of segments for the given value, or
205
+ * an empty array if the value has never been registered. Walks the
206
+ * persistent node registry so paths remain resolvable even when an
207
+ * ancestor branch has collapsed and its descendants unmounted.
208
+ */
209
+ getPath: (value: string) => TreePathSegment[];
210
+ /**
211
+ * The currently-selected values in selection order. Mirrors
212
+ * `selectedValues` in multiple mode and `[selectedValue]` (or `[]`)
213
+ * in single mode.
214
+ */
215
+ selectedOrder: readonly string[];
216
+ };
217
+
218
+ export type TreeItemContextValue = {
219
+ value: string;
220
+ expanded: boolean;
221
+ disabled: boolean;
222
+ /** DOM `id` of the branch's `BranchControl`, for `aria-labelledby`. */
223
+ controlId: string;
224
+ };
@@ -0,0 +1,59 @@
1
+ import { Children, Fragment, isValidElement } from "react";
2
+ import type { ReactElement, ReactNode } from "react";
3
+
4
+ import { TreeBranchControl, TreeBranchContent } from "./Tree";
5
+
6
+ /** Whether `node` is a `Tree.BranchControl` element. */
7
+ export function isBranchControlElement(node: ReactNode): node is ReactElement {
8
+ return isValidElement(node) && node.type === TreeBranchControl;
9
+ }
10
+
11
+ /** Whether `node` is a `Tree.BranchContent` element. */
12
+ export function isBranchContentElement(node: ReactNode): node is ReactElement {
13
+ return isValidElement(node) && node.type === TreeBranchContent;
14
+ }
15
+
16
+ /**
17
+ * Splits a `Tree.Branch`'s children into its single `<Tree.BranchControl>`
18
+ * (the clickable row) and its optional `<Tree.BranchContent>` (the nested
19
+ * group). Both are matched however deeply they are wrapped in fragments,
20
+ * since `Children.toArray` does not descend into fragments — so
21
+ * `{open && <BranchContent/>}` is partitioned the same as a bare element.
22
+ */
23
+ export function partitionBranchChildren(children: ReactNode): {
24
+ control: ReactElement;
25
+ content: ReactElement | null;
26
+ } {
27
+ let control: ReactElement | null = null;
28
+ let content: ReactElement | null = null;
29
+
30
+ const visit = (nodes: ReactNode): void => {
31
+ for (const child of Children.toArray(nodes)) {
32
+ if (isBranchControlElement(child)) {
33
+ if (control !== null) {
34
+ throw new Error(
35
+ "A Tree.Branch may contain at most one <Tree.BranchControl>.",
36
+ );
37
+ }
38
+ control = child;
39
+ } else if (isBranchContentElement(child)) {
40
+ if (content !== null) {
41
+ throw new Error(
42
+ "A Tree.Branch may contain at most one <Tree.BranchContent>.",
43
+ );
44
+ }
45
+ content = child;
46
+ } else if (isValidElement(child) && child.type === Fragment) {
47
+ visit((child.props as { children?: ReactNode }).children);
48
+ }
49
+ }
50
+ };
51
+
52
+ visit(children);
53
+
54
+ if (control === null) {
55
+ throw new Error("A Tree.Branch must contain a <Tree.BranchControl>.");
56
+ }
57
+
58
+ return { control, content };
59
+ }
@@ -0,0 +1,58 @@
1
+ # VisuallyHidden
2
+
3
+ Visually hides its children while keeping them in the accessibility tree,
4
+ implementing the standard
5
+ [screen-reader-only pattern](https://www.w3.org/WAI/WCAG21/Techniques/css/C7).
6
+
7
+ ```tsx
8
+ import { VisuallyHidden } from "@primitiv-ui/react";
9
+
10
+ <button>
11
+ <SearchIcon aria-hidden="true" />
12
+ <VisuallyHidden>Search</VisuallyHidden>
13
+ </button>;
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Notes |
19
+ | ----------- | ------------------------ | ------- | -------------------------------------------------- |
20
+ | `asChild` | `boolean` | `false` | Render the consumer's element instead of a `<span>` |
21
+ | `style` | `CSSProperties` | — | Merged on top of the clip styles |
22
+ | `className` | `string` | — | Applied directly to the rendered element |
23
+ | `...rest` | `ComponentProps<"span">` | — | All other `<span>` props, including `aria-*` |
24
+
25
+ ## Functional styles
26
+
27
+ Unlike other `@primitiv-ui/react` components, `VisuallyHidden` ships inline
28
+ styles — the clip rectangle that removes content from the visual layout
29
+ *is* the component's behaviour, not decoration:
30
+
31
+ ```css
32
+ position: absolute;
33
+ width: 1px;
34
+ height: 1px;
35
+ padding: 0;
36
+ margin: -1px;
37
+ overflow: hidden;
38
+ clip: rect(0 0 0 0);
39
+ clip-path: inset(50%);
40
+ white-space: nowrap;
41
+ border-width: 0;
42
+ ```
43
+
44
+ A consumer `style` is merged on top, so any individual property can still
45
+ be overridden.
46
+
47
+ ## asChild
48
+
49
+ Pass `asChild` to hide the consumer's own element instead of a `<span>` —
50
+ useful when the hidden content needs specific semantics:
51
+
52
+ ```tsx
53
+ <VisuallyHidden asChild>
54
+ <h2>Section heading</h2>
55
+ </VisuallyHidden>
56
+ ```
57
+
58
+ The clip styles are merged onto the child element via the `Slot` utility.