@astryxdesign/core 0.4.6 → 0.4.7-canary.019ae5a

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 (486) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +3 -2
  4. package/dist/AspectRatio/AspectRatio.d.ts +24 -0
  5. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  6. package/dist/AspectRatio/AspectRatio.js +23 -4
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +15 -10
  9. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  10. package/dist/AvatarGroup/AvatarGroup.js +3 -2
  11. package/dist/Banner/Banner.d.ts +40 -15
  12. package/dist/Banner/Banner.d.ts.map +1 -1
  13. package/dist/Banner/Banner.js +51 -18
  14. package/dist/BottomSheet/BottomSheet.d.ts +1 -0
  15. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  16. package/dist/BottomSheet/BottomSheet.js +54 -15
  17. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +6 -0
  18. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -0
  19. package/dist/BottomSheet/BottomSheetEdgeTint.js +62 -0
  20. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  21. package/dist/BottomSheet/BottomSheetPanel.js +1 -0
  22. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +1 -0
  23. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  24. package/dist/BottomSheet/BottomSheetSwitcher.js +13 -6
  25. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  26. package/dist/BottomSheet/useSheetGestures.js +23 -5
  27. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  28. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
  29. package/dist/Button/Button.d.ts.map +1 -1
  30. package/dist/Button/Button.js +5 -4
  31. package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
  32. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  33. package/dist/ButtonGroup/ButtonGroup.js +26 -7
  34. package/dist/Calendar/Calendar.d.ts +3 -1
  35. package/dist/Calendar/Calendar.d.ts.map +1 -1
  36. package/dist/Calendar/Calendar.js +13 -14
  37. package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
  38. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
  39. package/dist/Calendar/getInitialFocusDate.js +62 -0
  40. package/dist/Carousel/Carousel.d.ts.map +1 -1
  41. package/dist/Carousel/Carousel.js +3 -3
  42. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposerInput.js +2 -3
  44. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  45. package/dist/Chat/ChatLayout.js +3 -3
  46. package/dist/Chat/ChatMessageList.d.ts +22 -3
  47. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  48. package/dist/Chat/ChatMessageList.js +6 -3
  49. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  50. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  51. package/dist/Chat/ChatTokenizedText.js +11 -6
  52. package/dist/Chat/useChatComposerTokens.js +1 -1
  53. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  54. package/dist/CheckboxInput/CheckboxInput.js +3 -2
  55. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  56. package/dist/ClickableCard/ClickableCard.js +3 -3
  57. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  58. package/dist/CommandPalette/CommandPaletteInput.js +3 -2
  59. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  60. package/dist/CommandPalette/CommandPaletteItem.js +3 -2
  61. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  62. package/dist/ContextMenu/ContextMenu.js +4 -3
  63. package/dist/DateInput/DateInput.d.ts.map +1 -1
  64. package/dist/DateInput/DateInput.js +3 -2
  65. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  66. package/dist/DateInput/TouchDateField.js +38 -4
  67. package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
  68. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  69. package/dist/DateTimeInput/DateTimeInput.js +406 -12
  70. package/dist/DateTimeInput/index.d.ts +1 -1
  71. package/dist/DateTimeInput/index.d.ts.map +1 -1
  72. package/dist/Dialog/Dialog.d.ts +7 -1
  73. package/dist/Dialog/Dialog.d.ts.map +1 -1
  74. package/dist/Dialog/Dialog.js +89 -58
  75. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  76. package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
  77. package/dist/FileInput/FileInput.d.ts.map +1 -1
  78. package/dist/FileInput/FileInput.js +3 -2
  79. package/dist/Heading/Heading.d.ts.map +1 -1
  80. package/dist/Heading/Heading.js +6 -2
  81. package/dist/HoverCard/HoverCard.d.ts +6 -0
  82. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  83. package/dist/HoverCard/useHoverCard.d.ts +5 -0
  84. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  85. package/dist/HoverCard/useHoverCard.js +67 -25
  86. package/dist/Item/Item.d.ts.map +1 -1
  87. package/dist/Item/Item.js +4 -2
  88. package/dist/Kbd/Kbd.d.ts.map +1 -1
  89. package/dist/Kbd/Kbd.js +10 -3
  90. package/dist/Layer/LayerDepthContext.d.ts +52 -0
  91. package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
  92. package/dist/Layer/LayerDepthContext.js +66 -0
  93. package/dist/Layer/index.d.ts +3 -0
  94. package/dist/Layer/index.d.ts.map +1 -1
  95. package/dist/Layer/index.js +4 -0
  96. package/dist/Layer/layerStack.d.ts +98 -0
  97. package/dist/Layer/layerStack.d.ts.map +1 -0
  98. package/dist/Layer/layerStack.js +306 -0
  99. package/dist/Layer/useLayerDismissal.d.ts +93 -0
  100. package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
  101. package/dist/Layer/useLayerDismissal.js +91 -0
  102. package/dist/Lightbox/Lightbox.d.ts +1 -0
  103. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  104. package/dist/Lightbox/Lightbox.js +103 -81
  105. package/dist/Markdown/index.d.ts +1 -1
  106. package/dist/Markdown/index.d.ts.map +1 -1
  107. package/dist/Markdown/parser.d.ts +38 -1
  108. package/dist/Markdown/parser.d.ts.map +1 -1
  109. package/dist/Markdown/parser.js +177 -29
  110. package/dist/MobileNav/MobileNav.d.ts +6 -1
  111. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  112. package/dist/MobileNav/MobileNav.js +61 -36
  113. package/dist/MultiSelector/MultiSelector.d.ts +15 -1
  114. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  115. package/dist/MultiSelector/MultiSelector.js +17 -6
  116. package/dist/MultiSelector/index.d.ts +1 -1
  117. package/dist/MultiSelector/index.d.ts.map +1 -1
  118. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  119. package/dist/NavMenu/NavHeadingMenu.js +3 -2
  120. package/dist/Outline/Outline.d.ts.map +1 -1
  121. package/dist/Outline/Outline.js +3 -2
  122. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  123. package/dist/OverflowList/OverflowList.js +3 -2
  124. package/dist/Overlay/Overlay.d.ts.map +1 -1
  125. package/dist/Overlay/Overlay.js +3 -2
  126. package/dist/Popover/usePopover.d.ts.map +1 -1
  127. package/dist/Popover/usePopover.js +20 -17
  128. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  129. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  130. package/dist/PowerSearch/PowerSearch.js +19 -6
  131. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  132. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  133. package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
  134. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  135. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  136. package/dist/PowerSearch/PowerSearchValueEditor.js +19 -9
  137. package/dist/PowerSearch/types.d.ts +2 -0
  138. package/dist/PowerSearch/types.d.ts.map +1 -1
  139. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  140. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  141. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  142. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  143. package/dist/Resizable/ResizeHandle.js +3 -2
  144. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  145. package/dist/SegmentedControl/SegmentedControl.js +3 -2
  146. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  147. package/dist/SelectableCard/SelectableCard.js +3 -3
  148. package/dist/Selector/Selector.d.ts.map +1 -1
  149. package/dist/Selector/Selector.js +5 -0
  150. package/dist/SideNav/SideNav.d.ts.map +1 -1
  151. package/dist/SideNav/SideNav.js +4 -3
  152. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  153. package/dist/SideNav/SideNavHeading.js +6 -5
  154. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  155. package/dist/SideNav/SideNavItem.js +4 -3
  156. package/dist/Slider/Slider.d.ts.map +1 -1
  157. package/dist/Slider/Slider.js +3 -3
  158. package/dist/Spinner/Spinner.d.ts +3 -3
  159. package/dist/Spinner/Spinner.d.ts.map +1 -1
  160. package/dist/Spinner/Spinner.js +130 -78
  161. package/dist/Stepper/Step.d.ts +140 -0
  162. package/dist/Stepper/Step.d.ts.map +1 -0
  163. package/dist/Stepper/Step.js +1045 -0
  164. package/dist/Stepper/StepStatus.d.ts +24 -0
  165. package/dist/Stepper/StepStatus.d.ts.map +1 -0
  166. package/dist/Stepper/StepStatus.js +1 -0
  167. package/dist/Stepper/Stepper.d.ts +93 -0
  168. package/dist/Stepper/Stepper.d.ts.map +1 -0
  169. package/dist/Stepper/Stepper.js +184 -0
  170. package/dist/Stepper/StepperContext.d.ts +41 -0
  171. package/dist/Stepper/StepperContext.d.ts.map +1 -0
  172. package/dist/Stepper/StepperContext.js +34 -0
  173. package/dist/Stepper/index.d.ts +8 -0
  174. package/dist/Stepper/index.d.ts.map +1 -0
  175. package/dist/Stepper/index.js +7 -0
  176. package/dist/Stepper/stepper.stylex.d.ts +15 -0
  177. package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
  178. package/dist/Stepper/stepper.stylex.js +20 -0
  179. package/dist/Switch/Switch.d.ts.map +1 -1
  180. package/dist/Switch/Switch.js +3 -2
  181. package/dist/TabList/Tab.d.ts +15 -2
  182. package/dist/TabList/Tab.d.ts.map +1 -1
  183. package/dist/TabList/Tab.js +32 -3
  184. package/dist/TabList/TabList.d.ts +46 -7
  185. package/dist/TabList/TabList.d.ts.map +1 -1
  186. package/dist/TabList/TabList.js +353 -23
  187. package/dist/TabList/TabListContext.d.ts +11 -1
  188. package/dist/TabList/TabListContext.d.ts.map +1 -1
  189. package/dist/TabList/TabListContext.js +11 -2
  190. package/dist/TabList/TabMenu.d.ts.map +1 -1
  191. package/dist/TabList/TabMenu.js +3 -2
  192. package/dist/TabList/index.d.ts +2 -2
  193. package/dist/TabList/index.d.ts.map +1 -1
  194. package/dist/Table/BaseTable.d.ts.map +1 -1
  195. package/dist/Table/BaseTable.js +4 -1
  196. package/dist/Table/TableRow.d.ts +16 -5
  197. package/dist/Table/TableRow.d.ts.map +1 -1
  198. package/dist/Table/TableRow.js +16 -5
  199. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  200. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
  201. package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
  202. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  203. package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
  204. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  205. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  206. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  207. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
  208. package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
  209. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  210. package/dist/Table/plugins/selection/useTableSelection.js +19 -5
  211. package/dist/Table/types.d.ts +22 -4
  212. package/dist/Table/types.d.ts.map +1 -1
  213. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  214. package/dist/Table/useBaseTablePlugins.js +5 -0
  215. package/dist/Text/Text.d.ts.map +1 -1
  216. package/dist/Text/Text.js +6 -2
  217. package/dist/TextArea/TextArea.d.ts.map +1 -1
  218. package/dist/TextArea/TextArea.js +3 -2
  219. package/dist/TextInput/TextInput.d.ts.map +1 -1
  220. package/dist/TextInput/TextInput.js +3 -2
  221. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  222. package/dist/TimeInput/TimeInput.js +3 -2
  223. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  224. package/dist/Timestamp/Timestamp.js +5 -3
  225. package/dist/Token/TokenLink.d.ts.map +1 -1
  226. package/dist/Token/TokenLink.js +2 -2
  227. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  228. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  229. package/dist/Tokenizer/Tokenizer.js +2 -0
  230. package/dist/Tooltip/Tooltip.d.ts +5 -0
  231. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  232. package/dist/Tooltip/useTooltip.d.ts +5 -0
  233. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  234. package/dist/Tooltip/useTooltip.js +39 -24
  235. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  236. package/dist/TopNav/TopNavHeading.js +3 -2
  237. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  238. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  239. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  240. package/dist/TopNav/TopNavMenu.js +4 -3
  241. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  242. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  243. package/dist/Typeahead/BaseTypeahead.js +68 -29
  244. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  245. package/dist/Typeahead/Typeahead.js +3 -2
  246. package/dist/astryx.css +72 -2
  247. package/dist/hooks/index.d.ts +1 -0
  248. package/dist/hooks/index.d.ts.map +1 -1
  249. package/dist/hooks/index.js +1 -0
  250. package/dist/hooks/useFocusTrap.d.ts +7 -4
  251. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  252. package/dist/hooks/useFocusTrap.js +60 -95
  253. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  254. package/dist/hooks/useHotkeys.js +10 -3
  255. package/dist/hooks/useListFocus.d.ts +5 -2
  256. package/dist/hooks/useListFocus.d.ts.map +1 -1
  257. package/dist/hooks/useListFocus.js +12 -6
  258. package/dist/hooks/useMergedRefs.d.ts +18 -0
  259. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  260. package/dist/hooks/useMergedRefs.js +25 -0
  261. package/dist/index.d.ts +1 -0
  262. package/dist/index.d.ts.map +1 -1
  263. package/dist/index.js +1 -0
  264. package/dist/utils/groupItems.d.ts +5 -1
  265. package/dist/utils/groupItems.d.ts.map +1 -1
  266. package/dist/utils/groupItems.js +9 -9
  267. package/dist/utils/index.d.ts +1 -1
  268. package/dist/utils/index.d.ts.map +1 -1
  269. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  270. package/dist/utils/sharedResizeObserver.js +10 -1
  271. package/dist/utils/timeParser.d.ts +1 -1
  272. package/dist/utils/timeParser.d.ts.map +1 -1
  273. package/dist/utils/timeParser.js +26 -12
  274. package/locales/en.json +31 -7
  275. package/locales/pseudo.json +18 -0
  276. package/package.json +8 -3
  277. package/src/AppShell/AppShell.tsx +3 -2
  278. package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
  279. package/src/AspectRatio/AspectRatio.test.tsx +57 -10
  280. package/src/AspectRatio/AspectRatio.tsx +40 -1
  281. package/src/Avatar/Avatar.doc.mjs +2 -1
  282. package/src/Avatar/Avatar.test.tsx +17 -0
  283. package/src/Avatar/Avatar.tsx +61 -38
  284. package/src/AvatarGroup/AvatarGroup.tsx +3 -2
  285. package/src/Banner/Banner.doc.mjs +13 -11
  286. package/src/Banner/Banner.test.tsx +102 -15
  287. package/src/Banner/Banner.tsx +72 -25
  288. package/src/BottomSheet/BottomSheet.test.tsx +123 -0
  289. package/src/BottomSheet/BottomSheet.tsx +36 -4
  290. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +225 -0
  291. package/src/BottomSheet/BottomSheetEdgeTint.tsx +82 -0
  292. package/src/BottomSheet/BottomSheetPanel.test.tsx +66 -0
  293. package/src/BottomSheet/BottomSheetPanel.tsx +19 -0
  294. package/src/BottomSheet/BottomSheetSwitcher.tsx +16 -2
  295. package/src/BottomSheet/useSheetGestures.test.ts +27 -0
  296. package/src/BottomSheet/useSheetGestures.ts +25 -5
  297. package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
  298. package/src/Button/Button.doc.mjs +22 -0
  299. package/src/Button/Button.test.tsx +13 -0
  300. package/src/Button/Button.tsx +5 -4
  301. package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
  302. package/src/ButtonGroup/ButtonGroup.test.tsx +391 -1
  303. package/src/ButtonGroup/ButtonGroup.tsx +23 -5
  304. package/src/Calendar/Calendar.doc.mjs +4 -3
  305. package/src/Calendar/Calendar.test.tsx +52 -0
  306. package/src/Calendar/Calendar.tsx +18 -15
  307. package/src/Calendar/getInitialFocusDate.test.ts +94 -0
  308. package/src/Calendar/getInitialFocusDate.ts +93 -0
  309. package/src/Carousel/Carousel.tsx +3 -2
  310. package/src/Chat/ChatComposerInput.tsx +5 -1
  311. package/src/Chat/ChatLayout.tsx +3 -2
  312. package/src/Chat/ChatMessageList.doc.mjs +9 -1
  313. package/src/Chat/ChatMessageList.test.tsx +46 -0
  314. package/src/Chat/ChatMessageList.tsx +28 -4
  315. package/src/Chat/ChatTokenizedText.tsx +17 -10
  316. package/src/Chat/useChatComposerTokens.ts +1 -1
  317. package/src/CheckboxInput/CheckboxInput.tsx +3 -2
  318. package/src/ClickableCard/ClickableCard.tsx +3 -2
  319. package/src/CodeBlock/CodeBlock.doc.mjs +6 -0
  320. package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
  321. package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
  322. package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
  323. package/src/ContextMenu/ContextMenu.tsx +4 -3
  324. package/src/DateInput/DateInput.tsx +3 -2
  325. package/src/DateInput/DateInputTouch.test.tsx +36 -0
  326. package/src/DateInput/TouchDateField.tsx +38 -4
  327. package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
  328. package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
  329. package/src/DateTimeInput/DateTimeInput.tsx +556 -24
  330. package/src/DateTimeInput/index.ts +1 -0
  331. package/src/Dialog/Dialog.doc.mjs +8 -3
  332. package/src/Dialog/Dialog.test.tsx +261 -1
  333. package/src/Dialog/Dialog.tsx +109 -58
  334. package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
  335. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
  336. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
  337. package/src/Field/Field.doc.mjs +13 -2
  338. package/src/FileInput/FileInput.tsx +3 -2
  339. package/src/Heading/Heading.test.tsx +23 -1
  340. package/src/Heading/Heading.tsx +6 -2
  341. package/src/HoverCard/HoverCard.doc.mjs +5 -0
  342. package/src/HoverCard/HoverCard.tsx +6 -0
  343. package/src/HoverCard/useHoverCard.tsx +81 -30
  344. package/src/Item/Item.tsx +5 -4
  345. package/src/Kbd/Kbd.test.tsx +17 -0
  346. package/src/Kbd/Kbd.tsx +10 -3
  347. package/src/Layer/LayerDepthContext.tsx +62 -0
  348. package/src/Layer/index.ts +9 -0
  349. package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
  350. package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
  351. package/src/Layer/layerStack.ts +362 -0
  352. package/src/Layer/useLayerDismissal.test.tsx +469 -0
  353. package/src/Layer/useLayerDismissal.ts +173 -0
  354. package/src/Layout/Layout.doc.mjs +2 -2
  355. package/src/Lightbox/Lightbox.doc.mjs +12 -0
  356. package/src/Lightbox/Lightbox.tsx +124 -102
  357. package/src/Link/Link.doc.mjs +39 -0
  358. package/src/Markdown/Markdown.doc.mjs +5 -0
  359. package/src/Markdown/incremental.test.ts +16 -2
  360. package/src/Markdown/index.ts +1 -0
  361. package/src/Markdown/parser.perf.test.ts +96 -1
  362. package/src/Markdown/parser.test.ts +145 -2
  363. package/src/Markdown/parser.ts +244 -29
  364. package/src/MobileNav/MobileNav.tsx +62 -36
  365. package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
  366. package/src/MultiSelector/MultiSelector.doc.mjs +27 -0
  367. package/src/MultiSelector/MultiSelector.test.tsx +106 -0
  368. package/src/MultiSelector/MultiSelector.tsx +50 -6
  369. package/src/MultiSelector/index.ts +1 -0
  370. package/src/NavMenu/NavHeadingMenu.tsx +3 -2
  371. package/src/NumberInput/NumberInput.doc.mjs +5 -0
  372. package/src/Outline/Outline.tsx +3 -2
  373. package/src/OverflowList/OverflowList.tsx +3 -2
  374. package/src/Overlay/Overlay.tsx +3 -2
  375. package/src/Popover/Popover.test.tsx +27 -1
  376. package/src/Popover/usePopover.tsx +31 -28
  377. package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
  378. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  379. package/src/PowerSearch/PowerSearch.tsx +34 -5
  380. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +201 -0
  381. package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
  382. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +47 -1
  383. package/src/PowerSearch/PowerSearchValueEditor.tsx +15 -0
  384. package/src/PowerSearch/types.ts +2 -0
  385. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  386. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  387. package/src/Resizable/ResizeHandle.tsx +3 -2
  388. package/src/SegmentedControl/SegmentedControl.tsx +3 -2
  389. package/src/SelectableCard/SelectableCard.tsx +3 -2
  390. package/src/Selector/Selector.doc.mjs +12 -1
  391. package/src/Selector/Selector.test.tsx +45 -1
  392. package/src/Selector/Selector.tsx +11 -1
  393. package/src/SideNav/SideNav.doc.mjs +6 -6
  394. package/src/SideNav/SideNav.tsx +4 -2
  395. package/src/SideNav/SideNavHeading.tsx +10 -10
  396. package/src/SideNav/SideNavItem.tsx +4 -2
  397. package/src/Slider/Slider.tsx +3 -2
  398. package/src/Spinner/Spinner.test.tsx +147 -1
  399. package/src/Spinner/Spinner.tsx +120 -101
  400. package/src/Stepper/Step.doc.mjs +98 -0
  401. package/src/Stepper/Step.tsx +1638 -0
  402. package/src/Stepper/StepStatus.ts +26 -0
  403. package/src/Stepper/Stepper.doc.mjs +370 -0
  404. package/src/Stepper/Stepper.test.tsx +1024 -0
  405. package/src/Stepper/Stepper.tsx +258 -0
  406. package/src/Stepper/StepperContext.ts +71 -0
  407. package/src/Stepper/index.ts +18 -0
  408. package/src/Stepper/stepper.stylex.ts +19 -0
  409. package/src/Switch/Switch.tsx +3 -2
  410. package/src/TabList/Tab.doc.mjs +13 -1
  411. package/src/TabList/Tab.tsx +67 -3
  412. package/src/TabList/TabList.doc.mjs +22 -6
  413. package/src/TabList/TabList.test.tsx +580 -4
  414. package/src/TabList/TabList.tsx +490 -22
  415. package/src/TabList/TabListContext.ts +13 -2
  416. package/src/TabList/TabMenu.tsx +3 -2
  417. package/src/TabList/index.ts +6 -2
  418. package/src/Table/BaseTable.tsx +6 -3
  419. package/src/Table/Table.doc.mjs +6 -1
  420. package/src/Table/Table.test.tsx +132 -14
  421. package/src/Table/TableBody.doc.mjs +40 -0
  422. package/src/Table/TableFooter.doc.mjs +40 -0
  423. package/src/Table/TableHeader.doc.mjs +40 -0
  424. package/src/Table/TableRow.tsx +16 -5
  425. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
  426. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
  427. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
  428. package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
  429. package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
  430. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
  431. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
  432. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
  433. package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
  434. package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
  435. package/src/Table/types.ts +22 -4
  436. package/src/Table/useBaseTablePlugins.ts +5 -0
  437. package/src/Table/useTableGroupedRows.doc.mjs +5 -4
  438. package/src/Table/useTablePagination.doc.mjs +7 -0
  439. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  440. package/src/Table/useTableSelection.doc.mjs +31 -0
  441. package/src/Text/Text.test.tsx +23 -1
  442. package/src/Text/Text.tsx +6 -2
  443. package/src/TextArea/TextArea.tsx +3 -2
  444. package/src/TextInput/TextInput.doc.mjs +10 -0
  445. package/src/TextInput/TextInput.tsx +3 -2
  446. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  447. package/src/TimeInput/TimeInput.tsx +2 -2
  448. package/src/Timestamp/Timestamp.tsx +4 -2
  449. package/src/Toast/Toast.doc.mjs +6 -0
  450. package/src/Token/TokenLink.tsx +2 -2
  451. package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
  452. package/src/Tokenizer/Tokenizer.tsx +4 -0
  453. package/src/Toolbar/Toolbar.doc.mjs +5 -0
  454. package/src/Tooltip/Tooltip.doc.mjs +5 -0
  455. package/src/Tooltip/Tooltip.test.tsx +32 -0
  456. package/src/Tooltip/Tooltip.tsx +5 -0
  457. package/src/Tooltip/useTooltip.tsx +47 -24
  458. package/src/TopNav/TopNavHeading.tsx +3 -2
  459. package/src/TopNav/TopNavMegaMenu.tsx +3 -2
  460. package/src/TopNav/TopNavMenu.tsx +4 -3
  461. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  462. package/src/Typeahead/BaseTypeahead.tsx +85 -33
  463. package/src/Typeahead/Typeahead.doc.mjs +5 -0
  464. package/src/Typeahead/Typeahead.test.tsx +49 -0
  465. package/src/Typeahead/Typeahead.tsx +3 -2
  466. package/src/hooks/index.ts +2 -0
  467. package/src/hooks/useContainerReveal.doc.mjs +2 -2
  468. package/src/hooks/useFocusTrap.test.tsx +14 -0
  469. package/src/hooks/useFocusTrap.ts +62 -111
  470. package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
  471. package/src/hooks/useHotkeys.test.ts +18 -0
  472. package/src/hooks/useHotkeys.ts +10 -3
  473. package/src/hooks/useListFocus.doc.mjs +2 -2
  474. package/src/hooks/useListFocus.test.tsx +65 -3
  475. package/src/hooks/useListFocus.ts +15 -7
  476. package/src/hooks/useMergedRefs.doc.mjs +79 -0
  477. package/src/hooks/useMergedRefs.test.tsx +62 -0
  478. package/src/hooks/useMergedRefs.ts +36 -0
  479. package/src/index.ts +1 -0
  480. package/src/theme/MediaTheme.doc.mjs +5 -5
  481. package/src/utils/groupItems.ts +12 -6
  482. package/src/utils/index.ts +1 -1
  483. package/src/utils/sharedResizeObserver.ts +10 -2
  484. package/src/utils/timeParser.test.ts +6 -1
  485. package/src/utils/timeParser.ts +35 -13
  486. package/src/__tests__/inputWidthContract.test.tsx +0 -69
@@ -476,20 +476,17 @@ export function DropdownMenuSubMenu(
476
476
  );
477
477
 
478
478
  const endAffordance = hasSpinner ? (
479
- <span
480
- {...mergeProps(
481
- themeProps('dropdown-menu-indicator-icon'),
482
- stylex.props(triggerStyles.caret),
483
- )}>
479
+ <span {...stylex.props(triggerStyles.caret)}>
484
480
  <Spinner size="sm" />
485
481
  </span>
486
482
  ) : (
487
- <span
488
- {...mergeProps(
489
- themeProps('dropdown-menu-indicator-icon'),
490
- stylex.props(triggerStyles.caret),
491
- )}>
492
- <Icon icon="chevronRight" size="sm" color="secondary" />
483
+ <span {...stylex.props(triggerStyles.caret)}>
484
+ <Icon
485
+ icon="chevronRight"
486
+ size="sm"
487
+ color="secondary"
488
+ {...themeProps('dropdown-menu-indicator-icon')}
489
+ />
493
490
  </span>
494
491
  );
495
492
 
@@ -39,8 +39,8 @@ export const docs = {
39
39
  ],
40
40
  vars: [
41
41
  {name: '--_field-radius', description: 'Border radius of input fields', default: 'var(--radius-element)', private: true},
42
- {name: '--_input-clear-hit-inset', description: 'Outset of the clear (\u2715) button\u2019s invisible hit area, applied to a ::after overlay. 0 on a fine pointer; negative on a coarse one, which grows the 20px button to the 24px touch target without changing what is drawn.', default: '0px', private: true},
43
- {name: '--_input-clear-hit-content', description: 'Whether the clear (\u2715) button\u2019s invisible hit overlay exists. `none` on a fine pointer, so no ::after is generated and hover still reaches the glyph; `""` on a coarse one, where the overlay provides the 24px touch target.', default: 'none', private: true},
42
+ {name: '--_input-clear-hit-inset', description: 'Outset of the clear (\u2715) button\'s invisible hit area, applied to a ::after overlay. 0 on a fine pointer; negative on a coarse one, which grows the 20px button to the 24px touch target without changing what is drawn.', default: '0px', private: true},
43
+ {name: '--_input-clear-hit-content', description: 'Whether the clear (\u2715) button\'s invisible hit overlay exists. `none` on a fine pointer, so no ::after is generated and hover still reaches the glyph; `""` on a coarse one, where the overlay provides the 24px touch target.', default: 'none', private: true},
44
44
  ],
45
45
  derived: [
46
46
  {property: 'borderRadius', vars: ['--_field-radius']},
@@ -60,6 +60,17 @@ export const docs = {
60
60
  description: 'ID for the input element (used for the label htmlFor attribute).',
61
61
  required: true,
62
62
  },
63
+ {
64
+ name: 'labelID',
65
+ type: 'string',
66
+ description: 'ID applied to the label element itself for group accessibility.',
67
+ },
68
+ {
69
+ name: 'isGroupLabel',
70
+ type: 'boolean',
71
+ description: 'Renders the label as a span for control groups (radiogroup, checkbox list).',
72
+ default: 'false',
73
+ },
63
74
  {
64
75
  name: 'children',
65
76
  type: 'ReactNode',
@@ -54,13 +54,14 @@ export type {
54
54
  InputStatus as FileInputStatus,
55
55
  InputStatusType as FileInputStatusType,
56
56
  } from '../Field';
57
- import {mergeProps, mergeRefs} from '../utils';
57
+ import {mergeProps} from '../utils';
58
58
  import type {BaseProps} from '../BaseProps';
59
59
  import type {SizeValue} from '../utils/types';
60
60
  import {themeProps} from '../utils/themeProps';
61
61
  import {useTranslator} from '../i18n';
62
62
  import type {TranslatorFn} from '../i18n';
63
63
 
64
+ import {useMergedRefs} from '../hooks/useMergedRefs';
64
65
  function formatFileSize(bytes: number): string {
65
66
  if (bytes < 1024) {
66
67
  return `${bytes} B`;
@@ -827,7 +828,7 @@ export function FileInput({
827
828
  </VisuallyHidden>
828
829
  <input
829
830
  {...rest}
830
- ref={mergeRefs(ref, inputRef)}
831
+ ref={useMergedRefs(ref, inputRef)}
831
832
  id={id}
832
833
  type="file"
833
834
  accept={accept}
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import {render, screen} from '@testing-library/react';
9
- import {describe, it, expect} from 'vitest';
9
+ import {describe, it, expect, vi} from 'vitest';
10
10
  import {Heading} from './Heading';
11
11
 
12
12
  describe('Heading', () => {
@@ -16,6 +16,28 @@ describe('Heading', () => {
16
16
  expect(screen.getByText('Page Title')).toBeInTheDocument();
17
17
  });
18
18
 
19
+ it('keeps a forwarded ref attached across rerenders', () => {
20
+ const ref = vi.fn();
21
+ const {rerender} = render(
22
+ <Heading ref={ref} level={1}>
23
+ Before
24
+ </Heading>,
25
+ );
26
+
27
+ const element = screen.getByText('Before');
28
+ expect(ref).toHaveBeenLastCalledWith(element);
29
+ ref.mockClear();
30
+
31
+ rerender(
32
+ <Heading ref={ref} level={1}>
33
+ After
34
+ </Heading>,
35
+ );
36
+
37
+ expect(ref).not.toHaveBeenCalled();
38
+ expect(screen.getByText('After')).toBe(element);
39
+ });
40
+
19
41
  it('renders h1 for level 1', () => {
20
42
  render(<Heading level={1}>H1</Heading>);
21
43
  const element = screen.getByText('H1');
@@ -43,7 +43,8 @@ import {
43
43
  import {useTruncation} from '../Text/useTruncation';
44
44
  import {resolveStyleColor} from '../Text/Text';
45
45
  import type {LayerPlacement} from '../Layer';
46
- import {mergeProps, mergeRefs} from '../utils';
46
+ import {mergeProps} from '../utils';
47
+ import {useMergedRefs} from '../hooks/useMergedRefs';
47
48
  import type {BaseProps} from '../BaseProps';
48
49
  import {themeProps} from '../utils/themeProps';
49
50
 
@@ -242,13 +243,16 @@ export function Heading({
242
243
  // Ref for the heading element (used as tooltip anchor)
243
244
  const headingRef = useRef<HTMLHeadingElement>(null);
244
245
 
246
+ // Keep the merged ref stable across rerenders.
247
+ const mergedRef = useMergedRefs(ref, truncation.ref, headingRef);
248
+
245
249
  // Build inline style for -webkit-line-clamp (dynamic value)
246
250
  const inlineStyle = maxLines > 1 ? {WebkitLineClamp: maxLines} : undefined;
247
251
 
248
252
  return (
249
253
  <>
250
254
  <Component
251
- ref={mergeRefs(ref, truncation.ref, headingRef)}
255
+ ref={mergedRef}
252
256
  {...mergeProps(
253
257
  themeProps('heading', {level, color, ...(type && {type})}),
254
258
  stylex.props(
@@ -109,6 +109,11 @@ export const docs = {
109
109
  type: 'boolean',
110
110
  description: 'Whether the hover card should be shown on mount. Still dismissible.',
111
111
  },
112
+ {
113
+ name: 'isOpen',
114
+ type: 'boolean',
115
+ description: 'Controlled open state for the hover card.',
116
+ },
112
117
  ],
113
118
  },
114
119
  ],
@@ -144,6 +144,12 @@ export interface HoverCardProps extends Pick<
144
144
  * - `true`: force-show the hover card (hover/focus hide is suppressed)
145
145
  * - `false`: force-hide the hover card
146
146
  * - `undefined`: uncontrolled — hover/focus triggers manage visibility
147
+ *
148
+ * A controlled hover card still takes Escape when it is the top-most layer,
149
+ * and answers by calling `onOpenChange(false)` without hiding itself —
150
+ * closing is your update's decision, exactly as for a controlled Dialog.
151
+ * Ignore the call and the card stays, and so does the press: nothing
152
+ * underneath dismisses.
147
153
  */
148
154
  isOpen?: boolean;
149
155
 
@@ -31,6 +31,7 @@ import {
31
31
  type LayerTouchTrigger,
32
32
  } from '../Layer/useTouchTrigger';
33
33
  import {layerAnimations} from '../Layer/layerAnimations.stylex';
34
+ import {useLayerDismissal} from '../Layer/useLayerDismissal';
34
35
  import {
35
36
  colorVars,
36
37
  shadowVars,
@@ -135,6 +136,11 @@ export interface HoverCardOptions {
135
136
  * - `true`: force-show the hover card (hover/focus hide is suppressed)
136
137
  * - `false`: force-hide the hover card
137
138
  * - `undefined`: uncontrolled — hover/focus triggers manage visibility
139
+ *
140
+ * A controlled hover card still takes Escape when it is the top-most layer,
141
+ * and answers by calling `onHide` without hiding itself — closing is your
142
+ * update's decision, exactly as for a controlled Dialog. Ignore the call and
143
+ * the card stays, and so does the press: nothing underneath dismisses.
138
144
  */
139
145
  isOpen?: boolean;
140
146
 
@@ -409,19 +415,71 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
409
415
  [layer.id, scheduleHide],
410
416
  );
411
417
 
412
- const handleKeyDown = useCallback(
413
- (e: KeyboardEvent) => {
414
- if (e.key === 'Escape') {
415
- // Stop propagation so parent components don't react to the same Escape
416
- e.stopPropagation();
417
- // Hide immediately without refocusing (we're already on trigger)
418
- clearTimeouts();
419
- touch.clearTapOpen();
420
- layer.hide();
418
+ // Escape dismissal (WCAG 1.4.13) goes through the shared layer stack: a
419
+ // visible card is the top-most layer, so it takes the press and consumes it.
420
+ //
421
+ // This replaces a keydown listener on the TRIGGER that called
422
+ // stopPropagation() on any Escape while the trigger merely had focus — open
423
+ // card or not — so focusing a HoverCard trigger inside a Dialog silently ate
424
+ // the press that should have closed the Dialog. Presence is now answered from
425
+ // the DOM, so a closed card never claims a press.
426
+ // A controlled hover card stays on the stack and takes the press like any
427
+ // other layer, but answers it by reporting instead of hiding: `isOpen` is
428
+ // the consumer's value, so only their update may change it. Same contract as
429
+ // a controlled Dialog.
430
+ useLayerDismissal({
431
+ // Registered for the hook's lifetime rather than gated on `layer.isOpen`:
432
+ // that state can lag a frame behind the DOM, so a press arriving right after
433
+ // the layer appears would find nothing registered. Because this layer
434
+ // CONSUMES the press, a stale registration would be worse than a missed one
435
+ // — it would silently eat Escapes meant for the dialog underneath — so
436
+ // presence is answered from the DOM at press time instead of from state.
437
+ isActive: true,
438
+ isPresent: () => {
439
+ const el =
440
+ typeof document === 'undefined'
441
+ ? null
442
+ : document.getElementById(layer.id);
443
+ if (el == null) {
444
+ return false;
445
+ }
446
+ try {
447
+ return el.matches(':popover-open');
448
+ } catch {
449
+ // Browsers without the Popover API (and some test environments) cannot
450
+ // answer the selector; fall back to the hook's own state.
451
+ return layer.isOpen;
421
452
  }
422
453
  },
423
- [touch, clearTimeouts, layer],
424
- );
454
+ onDismiss: () => {
455
+ clearTimeouts();
456
+ touch.clearTapOpen();
457
+ // Controlled: report and stop. The close — and the focus restore that
458
+ // goes with it — happens in the controlled effect if and when the
459
+ // consumer flips `isOpen`.
460
+ if (isOpen !== undefined) {
461
+ onHide?.();
462
+ return;
463
+ }
464
+ // Only when the card itself held focus, which is the one case the
465
+ // content-level handler this replaced could run in. Refocusing
466
+ // unconditionally would drag the caret out of a field the user was
467
+ // typing in while a hover card happened to be up; arming the re-show
468
+ // guard unconditionally would swallow their next focus on the trigger,
469
+ // because a focus() on the already-focused trigger fires no focusin to
470
+ // clear it.
471
+ const card =
472
+ typeof document === 'undefined'
473
+ ? null
474
+ : document.getElementById(layer.id);
475
+ const hadFocus = card?.contains(document.activeElement) ?? false;
476
+ layer.hide();
477
+ if (hadFocus) {
478
+ isEscapeDismissingRef.current = true;
479
+ triggerRef.current?.focus();
480
+ }
481
+ },
482
+ });
425
483
 
426
484
  // Interaction ref that handles event listeners only
427
485
  const {handlePointerEnter} = touch;
@@ -436,7 +494,6 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
436
494
  'focusout',
437
495
  handleFocusOut as EventListener,
438
496
  );
439
- triggerRef.current.removeEventListener('keydown', handleKeyDown);
440
497
  triggerRef.current.removeEventListener(
441
498
  'pointerenter',
442
499
  handlePointerEnter,
@@ -465,9 +522,6 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
465
522
  el.addEventListener('focusin', handleFocusIn);
466
523
  el.addEventListener('focusout', handleFocusOut as EventListener);
467
524
  }
468
-
469
- // Attach keydown for Escape handling
470
- el.addEventListener('keydown', handleKeyDown);
471
525
  }
472
526
 
473
527
  triggerRef.current = el;
@@ -478,7 +532,6 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
478
532
  handleMouseLeave,
479
533
  handleFocusIn,
480
534
  handleFocusOut,
481
- handleKeyDown,
482
535
  handlePointerEnter,
483
536
  handlePointerDown,
484
537
  ],
@@ -518,7 +571,19 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
518
571
  layer.show();
519
572
  } else {
520
573
  clearTimeouts();
574
+ // A consumer closing the card while it holds focus would strand focus on
575
+ // <body>. Arming the re-show guard is what keeps the refocus from
576
+ // reopening the card through `handleFocusIn`, which shows on any focusin.
577
+ const card =
578
+ typeof document === 'undefined'
579
+ ? null
580
+ : document.getElementById(layer.id);
581
+ const hadFocus = card?.contains(document.activeElement) ?? false;
521
582
  layer.hide();
583
+ if (hadFocus) {
584
+ isEscapeDismissingRef.current = true;
585
+ triggerRef.current?.focus();
586
+ }
522
587
  }
523
588
  }, [isOpen, clearTimeouts, layer]);
524
589
 
@@ -576,20 +641,6 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
576
641
  isHoveringContentRef.current = false;
577
642
  scheduleHide();
578
643
  }}
579
- onKeyDown={e => {
580
- if (e.key === 'Escape') {
581
- // Stop propagation so parent components don't react to the same Escape
582
- e.stopPropagation();
583
- // Set flag to prevent re-show when we refocus trigger
584
- isEscapeDismissingRef.current = true;
585
- // Hide immediately
586
- clearTimeouts();
587
- touch.clearTapOpen();
588
- layer.hide();
589
- // Refocus the trigger
590
- triggerRef.current?.focus();
591
- }
592
- }}
593
644
  onBlur={e => {
594
645
  // Check if focus is moving back to the trigger or staying within content
595
646
  const relatedTarget = e.relatedTarget as HTMLElement | null;
package/src/Item/Item.tsx CHANGED
@@ -27,7 +27,8 @@ import {
27
27
  typeScaleVars,
28
28
  } from '../theme/tokens.stylex';
29
29
  import type {BaseProps} from '../BaseProps';
30
- import {mergeProps, mergeRefs} from '../utils';
30
+ import {mergeProps} from '../utils';
31
+ import {useMergedRefs} from '../hooks/useMergedRefs';
31
32
  import {computeTargetAndRel} from '../Link/computeTargetAndRel';
32
33
  import {useLinkComponent} from '../Link/useLinkComponent';
33
34
  import {useClickableContainer} from '../hooks/useClickableContainer';
@@ -579,11 +580,11 @@ export function Item({
579
580
  </>
580
581
  );
581
582
 
583
+ const mergedRef = useMergedRefs(ref, containerRef);
584
+
582
585
  return (
583
586
  <Component
584
- ref={
585
- (isDelegate ? mergeRefs(ref, containerRef) : ref) as React.Ref<never>
586
- }
587
+ ref={(isDelegate ? mergedRef : ref) as React.Ref<never>}
587
588
  {...restProps}
588
589
  aria-selected={(allowsAriaSelected && isSelected) || undefined}
589
590
  // aria-selected is invalid on roles that don't permit it (listitem, a
@@ -23,6 +23,7 @@ describe('Kbd', () => {
23
23
  value: originalPlatform,
24
24
  configurable: true,
25
25
  });
26
+ delete (navigator as {userAgentData?: unknown}).userAgentData;
26
27
  });
27
28
 
28
29
  it('renders a single key', () => {
@@ -54,6 +55,22 @@ describe('Kbd', () => {
54
55
  expect(screen.getByText('\u2318')).toBeInTheDocument(); // \u2318
55
56
  });
56
57
 
58
+ it('reads a blank userAgentData.platform as unknown, not as non-Mac', () => {
59
+ // Builds that rewrite their client-hints identity expose the key with an
60
+ // empty value; navigator.platform is the only surface left that answers.
61
+ Object.defineProperty(navigator, 'userAgentData', {
62
+ value: {platform: ''},
63
+ configurable: true,
64
+ });
65
+ Object.defineProperty(navigator, 'platform', {
66
+ value: 'MacIntel',
67
+ configurable: true,
68
+ });
69
+
70
+ render(<Kbd keys="mod" />);
71
+ expect(screen.getByText('\u2318')).toBeInTheDocument();
72
+ });
73
+
57
74
  it('maps modifier keys to symbols', () => {
58
75
  render(<Kbd keys="ctrl+alt+shift+k" />);
59
76
  expect(screen.getByText('\u2303')).toBeInTheDocument(); // \u2303
package/src/Kbd/Kbd.tsx CHANGED
@@ -124,8 +124,9 @@ function getServerPlatformSnapshot(): boolean {
124
124
 
125
125
  /**
126
126
  * Detects whether the current platform is macOS/iOS.
127
- * Prefers the User-Agent Client Hints API when available (modern Chrome/Edge),
128
- * falls back to navigator.platform (deprecated but universally supported).
127
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
128
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
129
+ * supported) when it is absent or blank.
129
130
  */
130
131
  function detectMac(): boolean {
131
132
  if (typeof navigator === 'undefined') {
@@ -134,7 +135,13 @@ function detectMac(): boolean {
134
135
  // Prefer User-Agent Client Hints API (not deprecated)
135
136
  const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
136
137
  if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
137
- return /mac/i.test((uaData as {platform: string}).platform ?? '');
138
+ const uaPlatform = (uaData as {platform?: unknown}).platform;
139
+ // A blank platform is no answer, not a negative one. Builds that rewrite
140
+ // their client-hints identity ship '', so fall through rather than
141
+ // reading it as "not Apple".
142
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
143
+ return /mac/i.test(uaPlatform);
144
+ }
138
145
  }
139
146
  // Fallback: navigator.platform (deprecated but still shipped everywhere)
140
147
  return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
@@ -0,0 +1,62 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file LayerDepthContext.tsx
7
+ * @input React context
8
+ * @output Exports LayerDepthContext, useLayerDepth, LayerDepthProvider
9
+ * @position Layer system; how the dismissal stack learns which layer is nested
10
+ * inside which.
11
+ *
12
+ * SYNC: When modified, update:
13
+ * - /packages/core/src/Layer/index.ts
14
+ * - /packages/core/src/Layer/useLayerDismissal.ts
15
+ *
16
+ * Nesting is read from the React tree rather than the DOM. Two reasons:
17
+ *
18
+ * - **Portals.** A nested overlay routinely renders into `document.body` or the
19
+ * native top layer, so DOM containment reports it as a sibling of the layer
20
+ * it is logically inside. React context flows through `createPortal`, so the
21
+ * tree keeps the relationship the DOM loses.
22
+ * - **Same-commit mounts.** Depth is fixed during render. Effect order is not
23
+ * available then and is misleading anyway: React runs child effects before
24
+ * parent effects, so an inner layer registers first and looks "older" than
25
+ * the outer layer that contains it.
26
+ *
27
+ * There is nothing to mount at the app root: the context defaults to 0, and
28
+ * each layer provides depth for its OWN content, so nesting composes on its own.
29
+ */
30
+
31
+ import {createContext, use, type ReactNode} from 'react';
32
+
33
+ /**
34
+ * How many layers deep the current subtree is. 0 at the app root; each layer
35
+ * increments it for its own content.
36
+ */
37
+ export const LayerDepthContext = createContext(0);
38
+ LayerDepthContext.displayName = 'LayerDepthContext';
39
+
40
+ /**
41
+ * Read the current nesting depth. A layer calls this to learn its OWN depth —
42
+ * the depth of the subtree it is rendered into, before it pushes its content
43
+ * one level deeper.
44
+ */
45
+ export function useLayerDepth(): number {
46
+ return use(LayerDepthContext);
47
+ }
48
+
49
+ /**
50
+ * Wrap a layer's content so any layer opened from inside it registers as
51
+ * nested. Takes no depth prop on purpose: it reads the ambient depth and adds
52
+ * one, so nesting composes without anyone tracking absolute numbers.
53
+ *
54
+ * Renders no DOM element — it is a context boundary only, which is why it takes
55
+ * no `xstyle`/`ref` and exposes no props interface.
56
+ */
57
+ export function LayerDepthProvider({children}: {children: ReactNode}) {
58
+ const depth = use(LayerDepthContext);
59
+ return <LayerDepthContext value={depth + 1}>{children}</LayerDepthContext>;
60
+ }
61
+
62
+ LayerDepthProvider.displayName = 'LayerDepthProvider';
@@ -29,6 +29,15 @@ export type {
29
29
  FixedLayerReturn,
30
30
  } from './useLayer';
31
31
 
32
+ // Layer dismissal stack — one Escape owner for every overlay family
33
+ export {useLayerDismissal} from './useLayerDismissal';
34
+ export type {
35
+ LayerEscapeBehavior,
36
+ UseLayerDismissalOptions,
37
+ UseLayerDismissalReturn,
38
+ } from './useLayerDismissal';
39
+ export {LayerDepthProvider} from './LayerDepthContext';
40
+
32
41
  // Shared touch behavior for the hover layers
33
42
  export {useTouchTrigger, isActionTrigger} from './useTouchTrigger';
34
43
  export type {