@astryxdesign/core 0.1.2 → 0.1.3-canary.6d57c8d

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 (567) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +12 -12
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +11 -5
  5. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +2 -4
  10. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  11. package/dist/ButtonGroup/ButtonGroup.js +0 -1
  12. package/dist/Calendar/Calendar.d.ts +5 -4
  13. package/dist/Calendar/Calendar.d.ts.map +1 -1
  14. package/dist/Calendar/Calendar.js +82 -43
  15. package/dist/Calendar/hooks/index.d.ts +0 -2
  16. package/dist/Calendar/hooks/index.d.ts.map +1 -1
  17. package/dist/Calendar/hooks/index.js +1 -2
  18. package/dist/Calendar/index.d.ts +3 -3
  19. package/dist/Calendar/index.d.ts.map +1 -1
  20. package/dist/Calendar/index.js +1 -1
  21. package/dist/Calendar/styles.d.ts +3 -9
  22. package/dist/Calendar/styles.d.ts.map +1 -1
  23. package/dist/Calendar/styles.js +3 -11
  24. package/dist/Carousel/Carousel.d.ts.map +1 -1
  25. package/dist/Carousel/Carousel.js +17 -3
  26. package/dist/Center/Center.d.ts +11 -1
  27. package/dist/Center/Center.d.ts.map +1 -1
  28. package/dist/Center/Center.js +9 -3
  29. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  30. package/dist/Chat/ChatComposerInput.js +0 -1
  31. package/dist/Chat/ChatMessageList.d.ts +14 -1
  32. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  33. package/dist/Chat/ChatMessageList.js +2 -0
  34. package/dist/Chat/useTriggerMenu.d.ts +8 -1
  35. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  36. package/dist/Chat/useTriggerMenu.js +15 -3
  37. package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +51 -9
  40. package/dist/CheckboxList/CheckboxList.d.ts +13 -1
  41. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  42. package/dist/CheckboxList/CheckboxList.js +39 -9
  43. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  44. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  45. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  46. package/dist/CheckboxList/CheckboxListItem.js +0 -1
  47. package/dist/Citation/Citation.d.ts.map +1 -1
  48. package/dist/Citation/Citation.js +9 -5
  49. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  50. package/dist/CodeBlock/CodeBlock.js +51 -37
  51. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  52. package/dist/CommandPalette/CommandPalette.js +5 -5
  53. package/dist/ContextMenu/ContextMenu.d.ts +4 -5
  54. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  55. package/dist/ContextMenu/ContextMenu.js +93 -11
  56. package/dist/DateInput/DateInput.d.ts +23 -1
  57. package/dist/DateInput/DateInput.d.ts.map +1 -1
  58. package/dist/DateInput/DateInput.js +167 -99
  59. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  60. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  61. package/dist/DateRangeInput/DateRangeInput.js +42 -9
  62. package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
  63. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  64. package/dist/DateTimeInput/DateTimeInput.js +78 -15
  65. package/dist/Dialog/Dialog.d.ts.map +1 -1
  66. package/dist/Dialog/Dialog.js +12 -0
  67. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
  68. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  69. package/dist/DropdownMenu/DropdownMenu.js +42 -14
  70. package/dist/Field/Field.d.ts +19 -2
  71. package/dist/Field/Field.d.ts.map +1 -1
  72. package/dist/Field/Field.js +4 -0
  73. package/dist/Field/FieldLabel.d.ts +21 -2
  74. package/dist/Field/FieldLabel.d.ts.map +1 -1
  75. package/dist/Field/FieldLabel.js +41 -26
  76. package/dist/FileInput/FileInput.d.ts +22 -1
  77. package/dist/FileInput/FileInput.d.ts.map +1 -1
  78. package/dist/FileInput/FileInput.js +56 -10
  79. package/dist/Grid/Grid.d.ts +11 -1
  80. package/dist/Grid/Grid.d.ts.map +1 -1
  81. package/dist/Grid/Grid.js +32 -6
  82. package/dist/InputGroup/InputGroup.d.ts +4 -3
  83. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  84. package/dist/InputGroup/InputGroup.js +16 -6
  85. package/dist/InputGroup/InputGroupContext.d.ts +4 -0
  86. package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
  87. package/dist/InputGroup/InputGroupContext.js +2 -2
  88. package/dist/InputGroup/groupStyles.d.ts +1 -1
  89. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  90. package/dist/InputGroup/groupStyles.js +3 -3
  91. package/dist/InputGroup/index.d.ts +1 -0
  92. package/dist/InputGroup/index.d.ts.map +1 -1
  93. package/dist/Kbd/Kbd.d.ts.map +1 -1
  94. package/dist/Kbd/Kbd.js +33 -1
  95. package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
  96. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  97. package/dist/Layer/layerAnimations.stylex.js +8 -4
  98. package/dist/Layer/useLayer.d.ts +10 -0
  99. package/dist/Layer/useLayer.d.ts.map +1 -1
  100. package/dist/Layer/useLayer.js +64 -9
  101. package/dist/Layout/Layout.d.ts +4 -11
  102. package/dist/Layout/Layout.d.ts.map +1 -1
  103. package/dist/Layout/Layout.js +10 -11
  104. package/dist/Layout/LayoutFooter.d.ts +2 -2
  105. package/dist/Layout/LayoutFooter.d.ts.map +1 -1
  106. package/dist/Layout/LayoutHeader.d.ts +2 -2
  107. package/dist/Layout/LayoutHeader.d.ts.map +1 -1
  108. package/dist/Layout/LayoutPanel.d.ts +2 -2
  109. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  110. package/dist/Layout/padding.stylex.d.ts +51 -0
  111. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  112. package/dist/Layout/padding.stylex.js +63 -0
  113. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  114. package/dist/Lightbox/Lightbox.js +4 -2
  115. package/dist/Link/Link.d.ts +16 -1
  116. package/dist/Link/Link.d.ts.map +1 -1
  117. package/dist/Link/Link.js +18 -7
  118. package/dist/Markdown/Markdown.d.ts.map +1 -1
  119. package/dist/Markdown/Markdown.js +7 -0
  120. package/dist/MoreMenu/MoreMenu.d.ts +1 -7
  121. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  122. package/dist/MoreMenu/MoreMenu.js +0 -2
  123. package/dist/MultiSelector/MultiSelector.d.ts +27 -2
  124. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  125. package/dist/MultiSelector/MultiSelector.js +141 -42
  126. package/dist/MultiSelector/hooks.d.ts +12 -1
  127. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  128. package/dist/MultiSelector/hooks.js +14 -1
  129. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  130. package/dist/NavMenu/NavHeadingMenu.js +34 -3
  131. package/dist/NumberInput/NumberInput.d.ts +23 -2
  132. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  133. package/dist/NumberInput/NumberInput.js +62 -12
  134. package/dist/Pagination/Pagination.d.ts +1 -1
  135. package/dist/Pagination/Pagination.d.ts.map +1 -1
  136. package/dist/Pagination/Pagination.js +15 -1
  137. package/dist/Popover/Popover.d.ts +18 -2
  138. package/dist/Popover/Popover.d.ts.map +1 -1
  139. package/dist/Popover/Popover.js +5 -2
  140. package/dist/Popover/usePopover.d.ts +38 -3
  141. package/dist/Popover/usePopover.d.ts.map +1 -1
  142. package/dist/Popover/usePopover.js +24 -9
  143. package/dist/PowerSearch/PowerSearch.d.ts +11 -1
  144. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  145. package/dist/PowerSearch/PowerSearch.js +6 -1
  146. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  147. package/dist/ProgressBar/ProgressBar.js +3 -5
  148. package/dist/RadioList/RadioList.d.ts +20 -2
  149. package/dist/RadioList/RadioList.d.ts.map +1 -1
  150. package/dist/RadioList/RadioList.js +105 -9
  151. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  152. package/dist/RadioList/RadioListItem.js +13 -2
  153. package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
  154. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  155. package/dist/SegmentedControl/SegmentedControl.js +78 -44
  156. package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
  157. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  158. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  159. package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
  160. package/dist/Selector/Selector.d.ts +24 -1
  161. package/dist/Selector/Selector.d.ts.map +1 -1
  162. package/dist/Selector/Selector.js +122 -43
  163. package/dist/Selector/hooks.d.ts +8 -1
  164. package/dist/Selector/hooks.d.ts.map +1 -1
  165. package/dist/Selector/hooks.js +14 -1
  166. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  167. package/dist/SideNav/SideNavHeading.js +2 -0
  168. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  169. package/dist/SideNav/SideNavItem.js +1 -1
  170. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  171. package/dist/Skeleton/Skeleton.js +8 -2
  172. package/dist/Slider/Slider.d.ts +21 -0
  173. package/dist/Slider/Slider.d.ts.map +1 -1
  174. package/dist/Slider/Slider.js +36 -9
  175. package/dist/Spinner/Spinner.d.ts.map +1 -1
  176. package/dist/Spinner/Spinner.js +1 -1
  177. package/dist/Stack/Stack.d.ts +38 -23
  178. package/dist/Stack/Stack.d.ts.map +1 -1
  179. package/dist/Stack/Stack.js +25 -1
  180. package/dist/Stack/StackItem.d.ts +12 -23
  181. package/dist/Stack/StackItem.d.ts.map +1 -1
  182. package/dist/Stack/StackItem.js +8 -1
  183. package/dist/Switch/Switch.d.ts +23 -2
  184. package/dist/Switch/Switch.d.ts.map +1 -1
  185. package/dist/Switch/Switch.js +41 -11
  186. package/dist/TabList/Tab.d.ts.map +1 -1
  187. package/dist/TabList/Tab.js +6 -0
  188. package/dist/TabList/TabList.d.ts +16 -4
  189. package/dist/TabList/TabList.d.ts.map +1 -1
  190. package/dist/TabList/TabList.js +87 -10
  191. package/dist/TabList/TabListContext.d.ts +5 -0
  192. package/dist/TabList/TabListContext.d.ts.map +1 -1
  193. package/dist/TabList/TabListContext.js +5 -0
  194. package/dist/TabList/TabMenu.d.ts.map +1 -1
  195. package/dist/TabList/TabMenu.js +6 -1
  196. package/dist/Table/BaseTable.d.ts.map +1 -1
  197. package/dist/Table/BaseTable.js +11 -8
  198. package/dist/Table/Table.d.ts.map +1 -1
  199. package/dist/Table/Table.js +10 -1
  200. package/dist/Table/TableCell.d.ts +8 -1
  201. package/dist/Table/TableCell.d.ts.map +1 -1
  202. package/dist/Table/TableCell.js +10 -10
  203. package/dist/Table/TableHeaderCell.d.ts +8 -1
  204. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  205. package/dist/Table/TableHeaderCell.js +16 -16
  206. package/dist/Table/index.d.ts +2 -1
  207. package/dist/Table/index.d.ts.map +1 -1
  208. package/dist/Table/index.js +2 -2
  209. package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
  210. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  211. package/dist/Table/plugins/pagination/paginateData.js +8 -3
  212. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  213. package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
  214. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  215. package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
  216. package/dist/Table/tableContextMenu.d.ts +33 -0
  217. package/dist/Table/tableContextMenu.d.ts.map +1 -0
  218. package/dist/Table/tableContextMenu.js +133 -0
  219. package/dist/Table/types.d.ts +66 -0
  220. package/dist/Table/types.d.ts.map +1 -1
  221. package/dist/TextArea/TextArea.d.ts +22 -1
  222. package/dist/TextArea/TextArea.d.ts.map +1 -1
  223. package/dist/TextArea/TextArea.js +41 -9
  224. package/dist/TextInput/TextInput.d.ts +23 -2
  225. package/dist/TextInput/TextInput.d.ts.map +1 -1
  226. package/dist/TextInput/TextInput.js +55 -10
  227. package/dist/TimeInput/TimeInput.d.ts +23 -1
  228. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  229. package/dist/TimeInput/TimeInput.js +147 -76
  230. package/dist/Toast/Toast.d.ts.map +1 -1
  231. package/dist/Toast/Toast.js +14 -0
  232. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  233. package/dist/Toast/ToastViewport.js +131 -3
  234. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  235. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  236. package/dist/Tokenizer/Tokenizer.js +26 -4
  237. package/dist/Toolbar/Toolbar.d.ts +3 -3
  238. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  239. package/dist/Toolbar/Toolbar.js +51 -7
  240. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  241. package/dist/Tooltip/useTooltip.js +56 -9
  242. package/dist/TopNav/TopNavHeading.d.ts +7 -1
  243. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  244. package/dist/TopNav/TopNavHeading.js +8 -0
  245. package/dist/TreeList/TreeList.d.ts +1 -1
  246. package/dist/TreeList/TreeList.d.ts.map +1 -1
  247. package/dist/TreeList/TreeList.js +72 -2
  248. package/dist/TreeList/TreeListItem.d.ts +11 -1
  249. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  250. package/dist/TreeList/TreeListItem.js +51 -12
  251. package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
  252. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
  253. package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
  254. package/dist/Typeahead/BaseTypeahead.d.ts +13 -0
  255. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  256. package/dist/Typeahead/BaseTypeahead.js +69 -6
  257. package/dist/Typeahead/Typeahead.d.ts +26 -3
  258. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  259. package/dist/Typeahead/Typeahead.js +65 -29
  260. package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
  261. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  262. package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
  263. package/dist/VisuallyHidden/index.d.ts +11 -0
  264. package/dist/VisuallyHidden/index.d.ts.map +1 -0
  265. package/dist/VisuallyHidden/index.js +13 -0
  266. package/dist/astryx.css +18 -0
  267. package/dist/astryx.umd.js +55 -55
  268. package/dist/astryx.umd.js.map +4 -4
  269. package/dist/hooks/focusableSelector.d.ts +22 -0
  270. package/dist/hooks/focusableSelector.d.ts.map +1 -0
  271. package/dist/hooks/focusableSelector.js +24 -0
  272. package/dist/hooks/index.d.ts +12 -2
  273. package/dist/hooks/index.d.ts.map +1 -1
  274. package/dist/hooks/index.js +7 -2
  275. package/dist/hooks/useAnnounce.d.ts +40 -0
  276. package/dist/hooks/useAnnounce.d.ts.map +1 -0
  277. package/dist/hooks/useAnnounce.js +142 -0
  278. package/dist/hooks/useClickableContainer.d.ts +16 -1
  279. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  280. package/dist/hooks/useClickableContainer.js +16 -4
  281. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  282. package/dist/hooks/useEntryAnimation.js +4 -4
  283. package/dist/hooks/useFocusTrap.d.ts +18 -0
  284. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  285. package/dist/hooks/useFocusTrap.js +87 -3
  286. package/dist/hooks/useGridFocus.d.ts +88 -1
  287. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  288. package/dist/hooks/useGridFocus.js +261 -62
  289. package/dist/hooks/useKeyboardHint.d.ts +82 -0
  290. package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
  291. package/dist/hooks/useKeyboardHint.js +220 -0
  292. package/dist/hooks/useListFocus.d.ts +78 -7
  293. package/dist/hooks/useListFocus.d.ts.map +1 -1
  294. package/dist/hooks/useListFocus.js +305 -52
  295. package/dist/hooks/useLongPress.d.ts +21 -0
  296. package/dist/hooks/useLongPress.d.ts.map +1 -0
  297. package/dist/hooks/useLongPress.js +89 -0
  298. package/dist/hooks/useTreeFocus.d.ts +157 -0
  299. package/dist/hooks/useTreeFocus.d.ts.map +1 -0
  300. package/dist/hooks/useTreeFocus.js +337 -0
  301. package/dist/hooks/useTypeahead.d.ts +63 -0
  302. package/dist/hooks/useTypeahead.d.ts.map +1 -0
  303. package/dist/hooks/useTypeahead.js +117 -0
  304. package/dist/index.d.ts +1 -0
  305. package/dist/index.d.ts.map +1 -1
  306. package/dist/index.js +1 -0
  307. package/dist/utils/dateTypes.d.ts +12 -0
  308. package/dist/utils/dateTypes.d.ts.map +1 -1
  309. package/dist/utils/dateTypes.js +41 -1
  310. package/dist/utils/index.d.ts +4 -0
  311. package/dist/utils/index.d.ts.map +1 -1
  312. package/dist/utils/index.js +3 -1
  313. package/dist/utils/inputAria.d.ts +29 -0
  314. package/dist/utils/inputAria.d.ts.map +1 -0
  315. package/dist/utils/inputAria.js +30 -0
  316. package/dist/utils/timeParser.d.ts.map +1 -1
  317. package/dist/utils/timeParser.js +5 -2
  318. package/docs.mjs +2 -2
  319. package/package.json +12 -8
  320. package/src/Avatar/Avatar.test.tsx +37 -0
  321. package/src/Avatar/Avatar.tsx +10 -5
  322. package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
  323. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
  324. package/src/Button/Button.tsx +3 -16
  325. package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
  326. package/src/ButtonGroup/ButtonGroup.tsx +0 -1
  327. package/src/Calendar/Calendar.doc.mjs +4 -4
  328. package/src/Calendar/Calendar.test.tsx +168 -30
  329. package/src/Calendar/Calendar.tsx +121 -66
  330. package/src/Calendar/hooks/index.ts +0 -6
  331. package/src/Calendar/index.ts +1 -3
  332. package/src/Calendar/styles.ts +4 -9
  333. package/src/Carousel/Carousel.doc.mjs +7 -1
  334. package/src/Carousel/Carousel.test.tsx +20 -0
  335. package/src/Carousel/Carousel.tsx +16 -1
  336. package/src/Center/Center.doc.mjs +12 -2
  337. package/src/Center/Center.tsx +28 -3
  338. package/src/Chat/ChatComposerInput.test.tsx +26 -7
  339. package/src/Chat/ChatComposerInput.tsx +0 -1
  340. package/src/Chat/ChatDictationButton.doc.mjs +1 -1
  341. package/src/Chat/ChatMessageList.doc.mjs +8 -0
  342. package/src/Chat/ChatMessageList.test.tsx +18 -0
  343. package/src/Chat/ChatMessageList.tsx +16 -0
  344. package/src/Chat/useTriggerMenu.tsx +22 -4
  345. package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
  346. package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
  347. package/src/CheckboxInput/CheckboxInput.tsx +78 -6
  348. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  349. package/src/CheckboxList/CheckboxList.test.tsx +192 -26
  350. package/src/CheckboxList/CheckboxList.tsx +56 -3
  351. package/src/CheckboxList/CheckboxListContext.tsx +7 -0
  352. package/src/CheckboxList/CheckboxListItem.tsx +0 -3
  353. package/src/Citation/Citation.doc.mjs +10 -0
  354. package/src/Citation/Citation.test.tsx +121 -0
  355. package/src/Citation/Citation.tsx +15 -3
  356. package/src/CodeBlock/CodeBlock.test.tsx +115 -0
  357. package/src/CodeBlock/CodeBlock.tsx +63 -33
  358. package/src/CommandPalette/CommandPalette.test.tsx +47 -1
  359. package/src/CommandPalette/CommandPalette.tsx +9 -21
  360. package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
  361. package/src/ContextMenu/ContextMenu.test.tsx +155 -1
  362. package/src/ContextMenu/ContextMenu.tsx +113 -16
  363. package/src/DateInput/DateInput.doc.mjs +286 -56
  364. package/src/DateInput/DateInput.test.tsx +248 -2
  365. package/src/DateInput/DateInput.tsx +196 -100
  366. package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
  367. package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
  368. package/src/DateRangeInput/DateRangeInput.tsx +63 -5
  369. package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
  370. package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
  371. package/src/DateTimeInput/DateTimeInput.tsx +125 -17
  372. package/src/Dialog/Dialog.doc.mjs +2 -2
  373. package/src/Dialog/Dialog.tsx +12 -0
  374. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
  375. package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
  376. package/src/DropdownMenu/DropdownMenu.tsx +52 -18
  377. package/src/Field/Field.doc.mjs +59 -18
  378. package/src/Field/Field.test.tsx +32 -0
  379. package/src/Field/Field.tsx +23 -5
  380. package/src/Field/FieldLabel.tsx +55 -17
  381. package/src/FileInput/FileInput.doc.mjs +17 -0
  382. package/src/FileInput/FileInput.test.tsx +283 -49
  383. package/src/FileInput/FileInput.tsx +76 -4
  384. package/src/Grid/Grid.doc.mjs +17 -4
  385. package/src/Grid/Grid.test.tsx +43 -30
  386. package/src/Grid/Grid.tsx +38 -3
  387. package/src/Icon/Icon.doc.mjs +3 -3
  388. package/src/InputGroup/InputGroup.doc.mjs +111 -23
  389. package/src/InputGroup/InputGroup.test.tsx +257 -60
  390. package/src/InputGroup/InputGroup.tsx +23 -5
  391. package/src/InputGroup/InputGroupContext.ts +9 -4
  392. package/src/InputGroup/groupStyles.ts +7 -1
  393. package/src/InputGroup/index.ts +1 -0
  394. package/src/Kbd/Kbd.test.tsx +22 -4
  395. package/src/Kbd/Kbd.tsx +35 -3
  396. package/src/Layer/layerAnimations.stylex.ts +20 -4
  397. package/src/Layer/useLayer.doc.mjs +5 -0
  398. package/src/Layer/useLayer.test.tsx +111 -0
  399. package/src/Layer/useLayer.tsx +90 -9
  400. package/src/Layout/Layout.tsx +8 -16
  401. package/src/Layout/LayoutFooter.doc.mjs +4 -4
  402. package/src/Layout/LayoutFooter.tsx +3 -3
  403. package/src/Layout/LayoutHeader.doc.mjs +4 -4
  404. package/src/Layout/LayoutHeader.tsx +3 -3
  405. package/src/Layout/LayoutPanel.tsx +3 -3
  406. package/src/Layout/padding.stylex.ts +52 -0
  407. package/src/Lightbox/Lightbox.doc.mjs +9 -0
  408. package/src/Lightbox/Lightbox.test.tsx +53 -5
  409. package/src/Lightbox/Lightbox.tsx +9 -4
  410. package/src/Link/Link.doc.mjs +14 -0
  411. package/src/Link/Link.test.tsx +50 -0
  412. package/src/Link/Link.tsx +39 -4
  413. package/src/Markdown/Markdown.test.tsx +11 -0
  414. package/src/Markdown/Markdown.tsx +7 -0
  415. package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
  416. package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
  417. package/src/MoreMenu/MoreMenu.tsx +0 -9
  418. package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
  419. package/src/MultiSelector/MultiSelector.test.tsx +375 -8
  420. package/src/MultiSelector/MultiSelector.tsx +194 -48
  421. package/src/MultiSelector/hooks.ts +27 -0
  422. package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
  423. package/src/NavMenu/NavHeadingMenu.tsx +50 -3
  424. package/src/NumberInput/NumberInput.doc.mjs +17 -0
  425. package/src/NumberInput/NumberInput.test.tsx +242 -40
  426. package/src/NumberInput/NumberInput.tsx +89 -11
  427. package/src/Pagination/Pagination.doc.mjs +3 -3
  428. package/src/Pagination/Pagination.test.tsx +117 -5
  429. package/src/Pagination/Pagination.tsx +21 -1
  430. package/src/Popover/Popover.doc.mjs +26 -0
  431. package/src/Popover/Popover.test.tsx +121 -0
  432. package/src/Popover/Popover.tsx +23 -2
  433. package/src/Popover/usePopover.doc.mjs +9 -3
  434. package/src/Popover/usePopover.tsx +70 -12
  435. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  436. package/src/PowerSearch/PowerSearch.test.tsx +97 -1
  437. package/src/PowerSearch/PowerSearch.tsx +16 -3
  438. package/src/ProgressBar/ProgressBar.test.tsx +34 -26
  439. package/src/ProgressBar/ProgressBar.tsx +3 -4
  440. package/src/RadioList/RadioList.doc.mjs +9 -0
  441. package/src/RadioList/RadioList.test.tsx +252 -16
  442. package/src/RadioList/RadioList.tsx +166 -10
  443. package/src/RadioList/RadioListItem.tsx +14 -2
  444. package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
  445. package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
  446. package/src/SegmentedControl/SegmentedControl.tsx +102 -54
  447. package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
  448. package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
  449. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  450. package/src/Selector/Selector.doc.mjs +26 -0
  451. package/src/Selector/Selector.test.tsx +387 -15
  452. package/src/Selector/Selector.tsx +175 -67
  453. package/src/Selector/hooks.ts +23 -0
  454. package/src/SideNav/SideNav.test.tsx +48 -0
  455. package/src/SideNav/SideNavHeading.tsx +8 -2
  456. package/src/SideNav/SideNavItem.tsx +2 -4
  457. package/src/Skeleton/Skeleton.test.tsx +25 -0
  458. package/src/Skeleton/Skeleton.tsx +11 -1
  459. package/src/Slider/Slider.doc.mjs +15 -0
  460. package/src/Slider/Slider.test.tsx +155 -2
  461. package/src/Slider/Slider.tsx +53 -5
  462. package/src/Spinner/Spinner.tsx +7 -1
  463. package/src/Stack/Stack.doc.mjs +77 -0
  464. package/src/Stack/Stack.test.tsx +55 -5
  465. package/src/Stack/Stack.tsx +73 -24
  466. package/src/Stack/StackItem.test.tsx +23 -0
  467. package/src/Stack/StackItem.tsx +20 -23
  468. package/src/Switch/Switch.doc.mjs +15 -0
  469. package/src/Switch/Switch.test.tsx +162 -27
  470. package/src/Switch/Switch.tsx +61 -21
  471. package/src/TabList/Tab.tsx +6 -0
  472. package/src/TabList/TabList.doc.mjs +6 -0
  473. package/src/TabList/TabList.test.tsx +251 -0
  474. package/src/TabList/TabList.tsx +103 -12
  475. package/src/TabList/TabListContext.ts +7 -3
  476. package/src/TabList/TabMenu.tsx +6 -3
  477. package/src/Table/BaseTable.tsx +21 -17
  478. package/src/Table/Table.doc.mjs +2 -0
  479. package/src/Table/Table.test.tsx +10 -0
  480. package/src/Table/Table.tsx +8 -0
  481. package/src/Table/TableCell.tsx +25 -23
  482. package/src/Table/TableHeaderCell.tsx +28 -29
  483. package/src/Table/index.ts +3 -2
  484. package/src/Table/plugins/pagination/paginateData.ts +10 -3
  485. package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
  486. package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
  487. package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
  488. package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
  489. package/src/Table/tableContextMenu.test.tsx +157 -0
  490. package/src/Table/tableContextMenu.tsx +142 -0
  491. package/src/Table/types.ts +74 -0
  492. package/src/Table/useTableColumnResize.doc.mjs +3 -3
  493. package/src/Table/useTableStickyColumns.doc.mjs +2 -2
  494. package/src/Text/Text.doc.mjs +18 -18
  495. package/src/TextArea/TextArea.doc.mjs +17 -0
  496. package/src/TextArea/TextArea.test.tsx +167 -35
  497. package/src/TextArea/TextArea.tsx +62 -15
  498. package/src/TextInput/TextInput.doc.mjs +17 -0
  499. package/src/TextInput/TextInput.test.tsx +168 -34
  500. package/src/TextInput/TextInput.tsx +77 -9
  501. package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
  502. package/src/TimeInput/TimeInput.doc.mjs +340 -60
  503. package/src/TimeInput/TimeInput.test.tsx +255 -7
  504. package/src/TimeInput/TimeInput.tsx +176 -70
  505. package/src/Timestamp/Timestamp.doc.mjs +19 -19
  506. package/src/Toast/Toast.tsx +14 -0
  507. package/src/Toast/ToastViewport.test.tsx +214 -0
  508. package/src/Toast/ToastViewport.tsx +167 -21
  509. package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
  510. package/src/Tokenizer/Tokenizer.test.tsx +105 -1
  511. package/src/Tokenizer/Tokenizer.tsx +37 -1
  512. package/src/Toolbar/Toolbar.test.tsx +109 -6
  513. package/src/Toolbar/Toolbar.tsx +59 -5
  514. package/src/Tooltip/Tooltip.test.tsx +68 -0
  515. package/src/Tooltip/useTooltip.tsx +55 -8
  516. package/src/TopNav/TopNav.test.tsx +50 -0
  517. package/src/TopNav/TopNavHeading.tsx +17 -1
  518. package/src/TreeList/TreeList.test.tsx +295 -6
  519. package/src/TreeList/TreeList.tsx +73 -1
  520. package/src/TreeList/TreeListItem.tsx +51 -5
  521. package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
  522. package/src/Typeahead/BaseTypeahead.tsx +93 -9
  523. package/src/Typeahead/Typeahead.doc.mjs +141 -30
  524. package/src/Typeahead/Typeahead.test.tsx +331 -1
  525. package/src/Typeahead/Typeahead.tsx +105 -33
  526. package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
  527. package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
  528. package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
  529. package/src/VisuallyHidden/index.ts +15 -0
  530. package/src/docs-script.test.mjs +50 -0
  531. package/src/hooks/focusableSelector.ts +25 -0
  532. package/src/hooks/index.ts +28 -2
  533. package/src/hooks/useAnnounce.test.tsx +101 -0
  534. package/src/hooks/useAnnounce.ts +168 -0
  535. package/src/hooks/useClickableContainer.ts +16 -4
  536. package/src/hooks/useEntryAnimation.ts +16 -4
  537. package/src/hooks/useFocusTrap.test.tsx +132 -0
  538. package/src/hooks/useFocusTrap.ts +98 -4
  539. package/src/hooks/useGridFocus.doc.mjs +40 -2
  540. package/src/hooks/useGridFocus.test.tsx +132 -0
  541. package/src/hooks/useGridFocus.ts +343 -67
  542. package/src/hooks/useKeyboardHint.doc.mjs +101 -0
  543. package/src/hooks/useKeyboardHint.test.tsx +70 -0
  544. package/src/hooks/useKeyboardHint.tsx +332 -0
  545. package/src/hooks/useListFocus.doc.mjs +50 -10
  546. package/src/hooks/useListFocus.test.tsx +375 -0
  547. package/src/hooks/useListFocus.ts +395 -57
  548. package/src/hooks/useLongPress.test.tsx +134 -0
  549. package/src/hooks/useLongPress.ts +119 -0
  550. package/src/hooks/useTreeFocus.doc.mjs +140 -0
  551. package/src/hooks/useTreeFocus.test.tsx +214 -0
  552. package/src/hooks/useTreeFocus.ts +559 -0
  553. package/src/hooks/useTypeahead.test.tsx +109 -0
  554. package/src/hooks/useTypeahead.ts +183 -0
  555. package/src/index.ts +1 -0
  556. package/src/theme/derivedVarRegistry.test.ts +10 -5
  557. package/src/utils/dateTypes.test.ts +40 -0
  558. package/src/utils/dateTypes.ts +40 -0
  559. package/src/utils/index.ts +9 -0
  560. package/src/utils/inputAria.test.ts +39 -0
  561. package/src/utils/inputAria.ts +58 -0
  562. package/src/utils/timeParser.test.ts +10 -0
  563. package/src/utils/timeParser.ts +5 -2
  564. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
  565. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
  566. package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
  567. package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file CodeBlock.test.tsx
5
+ * @input Uses vitest, @testing-library/react, CodeBlock component
6
+ * @output Unit tests for CodeBlock (copy button, collapse, scroll region a11y)
7
+ * @position Testing; validates CodeBlock implementation
8
+ *
9
+ * SYNC: When CodeBlock.tsx changes, update tests to match new behavior
10
+ */
11
+
12
+ import {describe, it, expect, vi, beforeEach} from 'vitest';
13
+ import {render, screen, fireEvent} from '@testing-library/react';
14
+ import {CodeBlock} from './CodeBlock';
15
+
16
+ // A code sample long enough to exceed the default collapsible threshold (10).
17
+ const LONG_CODE = Array.from(
18
+ {length: 15},
19
+ (_, i) => `const line${i} = ${i};`,
20
+ ).join('\n');
21
+
22
+ describe('CodeBlock', () => {
23
+ beforeEach(() => {
24
+ // jsdom does not implement the async Clipboard API.
25
+ Object.assign(navigator, {
26
+ clipboard: {writeText: vi.fn().mockResolvedValue(undefined)},
27
+ });
28
+ });
29
+
30
+ it('renders the code', () => {
31
+ render(<CodeBlock code="const x = 1;" language="javascript" />);
32
+ expect(screen.getByText(/const/)).toBeInTheDocument();
33
+ });
34
+
35
+ it('makes the scroll container keyboard-focusable', () => {
36
+ render(<CodeBlock code="const x = 1;" language="javascript" />);
37
+ const region = screen.getByRole('group');
38
+ expect(region).toHaveAttribute('tabindex', '0');
39
+ expect(region).toHaveAttribute('aria-label', 'javascript');
40
+ });
41
+
42
+ it('labels the scroll region "Code" when no language label is shown', () => {
43
+ render(<CodeBlock code="hello" hasLanguageLabel={false} />);
44
+ const region = screen.getByRole('group');
45
+ expect(region).toHaveAttribute('tabindex', '0');
46
+ expect(region).toHaveAttribute('aria-label', 'Code');
47
+ });
48
+
49
+ it('copies code when the copy button is clicked', () => {
50
+ render(<CodeBlock code="const x = 1;" language="javascript" />);
51
+ const copyButton = screen.getByRole('button', {name: 'Copy code'});
52
+ fireEvent.click(copyButton);
53
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith('const x = 1;');
54
+ });
55
+
56
+ it('does NOT collapse the block when the copy button is clicked', () => {
57
+ render(
58
+ <CodeBlock
59
+ code={LONG_CODE}
60
+ language="javascript"
61
+ title="example"
62
+ isCollapsible
63
+ />,
64
+ );
65
+ // The collapsible header exposes aria-expanded.
66
+ const header = screen
67
+ .getAllByRole('button')
68
+ .find(el => el.hasAttribute('aria-expanded'));
69
+ expect(header).toBeTruthy();
70
+ expect(header).toHaveAttribute('aria-expanded', 'true');
71
+
72
+ const copyButton = screen.getByRole('button', {name: 'Copy code'});
73
+ fireEvent.click(copyButton);
74
+
75
+ // Clicking Copy must not toggle the collapsible header.
76
+ expect(header).toHaveAttribute('aria-expanded', 'true');
77
+ expect(navigator.clipboard.writeText).toHaveBeenCalled();
78
+ });
79
+
80
+ it('does not nest the copy button inside the collapsible header role="button"', () => {
81
+ render(
82
+ <CodeBlock
83
+ code={LONG_CODE}
84
+ language="javascript"
85
+ title="example"
86
+ isCollapsible
87
+ />,
88
+ );
89
+ const header = screen
90
+ .getAllByRole('button')
91
+ .find(el => el.hasAttribute('aria-expanded'));
92
+ const copyButton = screen.getByRole('button', {name: 'Copy code'});
93
+ expect(header).toBeTruthy();
94
+ // The copy button must be a sibling, not a descendant of the interactive
95
+ // header — nested interactive controls are invalid ARIA.
96
+ expect(header!.contains(copyButton)).toBe(false);
97
+ });
98
+
99
+ it('still toggles collapse when the header itself is clicked', () => {
100
+ render(
101
+ <CodeBlock
102
+ code={LONG_CODE}
103
+ language="javascript"
104
+ title="example"
105
+ isCollapsible
106
+ />,
107
+ );
108
+ const header = screen
109
+ .getAllByRole('button')
110
+ .find(el => el.hasAttribute('aria-expanded'))!;
111
+ expect(header).toHaveAttribute('aria-expanded', 'true');
112
+ fireEvent.click(header);
113
+ expect(header).toHaveAttribute('aria-expanded', 'false');
114
+ });
115
+ });
@@ -87,7 +87,7 @@ const styles = stylex.create({
87
87
  backgroundColor: 'var(--color-syntax-background)',
88
88
  overflow: 'hidden',
89
89
  },
90
- header: {
90
+ headerRow: {
91
91
  display: 'flex',
92
92
  alignItems: 'center',
93
93
  justifyContent: 'space-between',
@@ -97,6 +97,19 @@ const styles = stylex.create({
97
97
  top: 0,
98
98
  zIndex: 1,
99
99
  },
100
+ header: {
101
+ display: 'flex',
102
+ alignItems: 'center',
103
+ flex: 1,
104
+ minWidth: 0,
105
+ // Reset default <button> appearance for the collapsible title control.
106
+ padding: 0,
107
+ border: 'none',
108
+ backgroundColor: 'transparent',
109
+ color: 'inherit',
110
+ font: 'inherit',
111
+ textAlign: 'start',
112
+ },
100
113
  headerWithDivider: {
101
114
  paddingBlock: spacingVars['--spacing-2'],
102
115
  borderBottomWidth: borderVars['--border-width'],
@@ -469,7 +482,10 @@ function useTokenLines(
469
482
  // Span-mode code element
470
483
  // ---------------------------------------------------------------------------
471
484
 
472
- function buildSpanLine(lineText: string, tokens: SyntaxToken[]): React.ReactNode {
485
+ function buildSpanLine(
486
+ lineText: string,
487
+ tokens: SyntaxToken[],
488
+ ): React.ReactNode {
473
489
  if (tokens.length === 0) {
474
490
  return lineText || '\u200b';
475
491
  }
@@ -677,7 +693,9 @@ export function CodeBlock({
677
693
  const copyButtonEl = hasCopyButton ? (
678
694
  <button
679
695
  type="button"
680
- onClick={() => {
696
+ onClick={e => {
697
+ // Stop propagation so copying does not toggle the collapsible header.
698
+ e.stopPropagation();
681
699
  void handleCopy();
682
700
  }}
683
701
  aria-label={copied ? 'Copied' : 'Copy code'}
@@ -691,39 +709,44 @@ export function CodeBlock({
691
709
 
692
710
  const headerEl = showHeader ? (
693
711
  <div
694
- role={canCollapse ? 'button' : undefined}
695
- tabIndex={canCollapse ? 0 : undefined}
696
- aria-expanded={canCollapse ? !isCollapsed : undefined}
697
- onClick={canCollapse ? () => setIsCollapsed(prev => !prev) : undefined}
698
- onKeyDown={
699
- canCollapse
700
- ? (e: React.KeyboardEvent) => {
701
- if (e.key === 'Enter' || e.key === ' ') {
702
- e.preventDefault();
703
- setIsCollapsed(prev => !prev);
704
- }
705
- }
706
- : undefined
707
- }
708
712
  {...stylex.props(
709
- styles.header,
713
+ styles.headerRow,
710
714
  hasLineNumbers ? styles.headerWithDivider : styles.headerCompact,
711
- canCollapse && styles.headerCollapsible,
712
715
  )}>
713
- <span {...stylex.props(styles.headerTitle)}>
714
- {title}
715
- {title && languageLabel ? ' — ' : ''}
716
- {languageLabel}
717
- {canCollapse && (
718
- <span
719
- {...stylex.props(
720
- styles.collapseChevron,
721
- isCollapsed && styles.collapseChevronCollapsed,
722
- )}>
723
- <Icon icon="chevronDown" size="xsm" color="inherit" />
724
- </span>
725
- )}
726
- </span>
716
+ <div
717
+ role={canCollapse ? 'button' : undefined}
718
+ tabIndex={canCollapse ? 0 : undefined}
719
+ aria-expanded={canCollapse ? !isCollapsed : undefined}
720
+ onClick={canCollapse ? () => setIsCollapsed(prev => !prev) : undefined}
721
+ onKeyDown={
722
+ canCollapse
723
+ ? (e: React.KeyboardEvent) => {
724
+ if (e.key === 'Enter' || e.key === ' ') {
725
+ e.preventDefault();
726
+ setIsCollapsed(prev => !prev);
727
+ }
728
+ }
729
+ : undefined
730
+ }
731
+ {...stylex.props(
732
+ styles.header,
733
+ canCollapse && styles.headerCollapsible,
734
+ )}>
735
+ <span {...stylex.props(styles.headerTitle)}>
736
+ {title}
737
+ {title && languageLabel ? ' — ' : ''}
738
+ {languageLabel}
739
+ {canCollapse && (
740
+ <span
741
+ {...stylex.props(
742
+ styles.collapseChevron,
743
+ isCollapsed && styles.collapseChevronCollapsed,
744
+ )}>
745
+ <Icon icon="chevronDown" size="xsm" color="inherit" />
746
+ </span>
747
+ )}
748
+ </span>
749
+ </div>
727
750
  {copyButtonEl}
728
751
  </div>
729
752
  ) : null;
@@ -731,6 +754,13 @@ export function CodeBlock({
731
754
  const codeBody = (
732
755
  <div
733
756
  ref={scrollContainerRef}
757
+ // The scroll container is keyboard-focusable so keyboard users can
758
+ // scroll long or wide code that overflows the viewport. Uses
759
+ // role="group" (not "region") so multiple code blocks on a page don't
760
+ // create duplicate same-named landmarks (axe: landmark-unique).
761
+ tabIndex={0}
762
+ role="group"
763
+ aria-label={languageLabel ?? 'Code'}
734
764
  {...mergeProps(stylex.props(styles.scrollContainer), {
735
765
  style: scrollStyle,
736
766
  })}>
@@ -8,9 +8,10 @@
8
8
  */
9
9
 
10
10
  import {describe, it, expect, vi, beforeEach} from 'vitest';
11
- import {render, screen, waitFor} from '@testing-library/react';
11
+ import {render, screen, waitFor, fireEvent} from '@testing-library/react';
12
12
  import {CommandPalette} from './CommandPalette';
13
13
  import {createStaticSource} from '@astryxdesign/core/Typeahead';
14
+ import type {SearchSource, SearchableItem} from '@astryxdesign/core/Typeahead';
14
15
 
15
16
  const simpleSource = createStaticSource([
16
17
  {id: 'home', label: 'Home'},
@@ -223,4 +224,49 @@ describe('CommandPalette', () => {
223
224
  dialog.dispatchEvent(escapeEvent);
224
225
  expect(handleOpenChange).toHaveBeenCalledWith(false);
225
226
  });
227
+
228
+ it('keeps the empty state mounted while a no-result search is pending', async () => {
229
+ // A source whose searches resolve only when we release them, so we can
230
+ // observe the render output while a search transition is in flight.
231
+ const resolvers: ((items: SearchableItem[]) => void)[] = [];
232
+ const source: SearchSource = {
233
+ bootstrap: () => [],
234
+ async search(): Promise<SearchableItem[]> {
235
+ return new Promise<SearchableItem[]>(resolve => {
236
+ resolvers.push(resolve);
237
+ });
238
+ },
239
+ };
240
+
241
+ render(
242
+ <CommandPalette
243
+ isOpen={true}
244
+ onOpenChange={() => {}}
245
+ searchSource={source}
246
+ emptyBootstrapText="Type to search"
247
+ emptySearchText="No results"
248
+ />,
249
+ );
250
+
251
+ const input = screen.getByRole('combobox');
252
+
253
+ // First search: commits an empty result for query "z".
254
+ fireEvent.change(input, {target: {value: 'z'}});
255
+ await waitFor(() => expect(resolvers).toHaveLength(1));
256
+ resolvers[0]([]);
257
+ await waitFor(() =>
258
+ expect(screen.getByText('No results')).toBeInTheDocument(),
259
+ );
260
+
261
+ // Second keystroke while already empty: the empty state must remain in the
262
+ // DOM for the whole pending window — no unmount/remount flash.
263
+ fireEvent.change(input, {target: {value: 'zz'}});
264
+ expect(screen.getByText('No results')).toBeInTheDocument();
265
+ await waitFor(() => expect(resolvers).toHaveLength(2));
266
+ expect(screen.getByText('No results')).toBeInTheDocument();
267
+ resolvers[1]([]);
268
+ await waitFor(() =>
269
+ expect(screen.getByText('No results')).toBeInTheDocument(),
270
+ );
271
+ });
226
272
  });
@@ -24,12 +24,7 @@ import {
24
24
  type ReactNode,
25
25
  } from 'react';
26
26
  import {Dialog} from '../Dialog';
27
- import {
28
- Layout,
29
- LayoutHeader,
30
- LayoutContent,
31
- LayoutFooter,
32
- } from '../Layout';
27
+ import {Layout, LayoutHeader, LayoutContent, LayoutFooter} from '../Layout';
33
28
  import type {SearchSource, SearchableItem} from '../Typeahead';
34
29
  import {useCombobox} from '../Selector';
35
30
  import type {SelectorOptionData} from '../Selector';
@@ -133,9 +128,7 @@ function getGroup(item: SearchableItem): string | undefined {
133
128
  * When groups are present, items are ordered by group (preserving insertion order),
134
129
  * with ungrouped items at the end — matching the DefaultRenderer layout.
135
130
  */
136
- function buildSelectableItems(
137
- items: SearchableItem[],
138
- ): SelectorOptionData[] {
131
+ function buildSelectableItems(items: SearchableItem[]): SelectorOptionData[] {
139
132
  const hasGroups = items.some(item => getGroup(item) != null);
140
133
 
141
134
  if (!hasGroups) {
@@ -258,9 +251,7 @@ function ItemRenderer<T extends SearchableItem>({
258
251
  * />
259
252
  * ```
260
253
  */
261
- export function CommandPalette<
262
- T extends SearchableItem = SearchableItem,
263
- >({
254
+ export function CommandPalette<T extends SearchableItem = SearchableItem>({
264
255
  ref,
265
256
  isOpen,
266
257
  isInline,
@@ -488,13 +479,12 @@ export function CommandPalette<
488
479
  ],
489
480
  );
490
481
 
491
- // Empty state uses committed search (= what current results correspond to),
492
- // not optimisticSearch (= what the user typed).
493
- // While the transition is pending, search stays at '' so showEmptyBootstrap
494
- // remains true even after the user has started typing.
482
+ // `search` is the committed query the on-screen results correspond to (it
483
+ // still holds the previous query while a transition is pending). Keeping both
484
+ // flags ungated by `isPending` makes them exhaustive over the empty case, so
485
+ // the empty state is never unmounted and re-added mid-search (which flashed).
495
486
  const showEmptyBootstrap = search === '' && optimisticResults.length === 0;
496
- const showEmptySearch =
497
- !isPending && search !== '' && optimisticResults.length === 0;
487
+ const showEmptySearch = search !== '' && optimisticResults.length === 0;
498
488
 
499
489
  let listContent: ReactNode;
500
490
  if (showEmptyBootstrap) {
@@ -502,9 +492,7 @@ export function CommandPalette<
502
492
  <CommandPaletteEmpty>{emptyBootstrapText}</CommandPaletteEmpty>
503
493
  );
504
494
  } else if (showEmptySearch) {
505
- listContent = (
506
- <CommandPaletteEmpty>{emptySearchText}</CommandPaletteEmpty>
507
- );
495
+ listContent = <CommandPaletteEmpty>{emptySearchText}</CommandPaletteEmpty>;
508
496
  } else {
509
497
  listContent = (
510
498
  <ItemRenderer
@@ -53,10 +53,10 @@ export const docs = {
53
53
  default: "'md'",
54
54
  },
55
55
  {
56
- name: 'hasAutoFocus',
57
- type: 'boolean',
58
- description: 'Whether to auto-focus the first menu item when the menu opens. Set to false for inline showcases.',
59
- default: 'true',
56
+ name: 'label',
57
+ type: 'string',
58
+ description: 'Accessible name for the menu surface, announced when it opens.',
59
+ default: "'Context menu'",
60
60
  },
61
61
  {
62
62
  name: 'isDisabled',
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import {describe, it, expect, vi, beforeEach} from 'vitest';
12
- import {render, screen, fireEvent} from '@testing-library/react';
12
+ import {render, screen, fireEvent, act} from '@testing-library/react';
13
13
  import userEvent from '@testing-library/user-event';
14
14
  import {ContextMenu} from './ContextMenu';
15
15
  import {ContextMenuItem} from './index';
@@ -60,6 +60,52 @@ describe('ContextMenu', () => {
60
60
  expect(screen.getByRole('menu', {hidden: true})).toBeInTheDocument();
61
61
  });
62
62
 
63
+ it('typeahead focuses the matching menu item (menus-11)', () => {
64
+ render(
65
+ <ContextMenu items={[{label: 'Cut'}, {label: 'Copy'}, {label: 'Paste'}]}>
66
+ <div>Right-click me</div>
67
+ </ContextMenu>,
68
+ );
69
+ fireEvent.contextMenu(screen.getByText('Right-click me'));
70
+ const menu = screen.getByRole('menu', {hidden: true});
71
+ fireEvent.keyDown(menu, {key: 'p'});
72
+ expect(
73
+ screen.getByRole('menuitem', {name: 'Paste', hidden: true}),
74
+ ).toHaveFocus();
75
+ });
76
+
77
+ it('gives the menu an accessible name (menus-13)', () => {
78
+ render(
79
+ <ContextMenu items={[{label: 'Item 1'}]}>
80
+ <div>Right-click me</div>
81
+ </ContextMenu>,
82
+ );
83
+ // Defaults to "Context menu"; overridable via label.
84
+ expect(
85
+ screen.getByRole('menu', {name: 'Context menu', hidden: true}),
86
+ ).toBeInTheDocument();
87
+ });
88
+
89
+ it('uses a custom label', () => {
90
+ render(
91
+ <ContextMenu items={[{label: 'Item 1'}]} label="Row actions">
92
+ <div>Right-click me</div>
93
+ </ContextMenu>,
94
+ );
95
+ expect(
96
+ screen.getByRole('menu', {name: 'Row actions', hidden: true}),
97
+ ).toBeInTheDocument();
98
+ });
99
+
100
+ it('does not put aria-haspopup on the role-less trigger wrapper (menus-15)', () => {
101
+ render(
102
+ <ContextMenu items={[{label: 'Item 1'}]} data-testid="ctx">
103
+ <div>Right-click me</div>
104
+ </ContextMenu>,
105
+ );
106
+ expect(screen.getByTestId('ctx')).not.toHaveAttribute('aria-haspopup');
107
+ });
108
+
63
109
  it('opens menu on right-click', () => {
64
110
  render(
65
111
  <ContextMenu items={[{label: 'Item 1'}]}>
@@ -71,6 +117,48 @@ describe('ContextMenu', () => {
71
117
  expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
72
118
  });
73
119
 
120
+ it('closes on Escape even when opened without auto-focus', () => {
121
+ render(
122
+ <ContextMenu items={[{label: 'Item 1'}]}>
123
+ <div>Right-click me</div>
124
+ </ContextMenu>,
125
+ );
126
+
127
+ fireEvent.contextMenu(screen.getByText('Right-click me'));
128
+ expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
129
+ // Focus is not inside the menu, so the Escape path must be document-level.
130
+ fireEvent.keyDown(document, {key: 'Escape'});
131
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalled();
132
+ });
133
+
134
+ it('ignores Escape during IME composition', () => {
135
+ render(
136
+ <ContextMenu items={[{label: 'Item 1'}]}>
137
+ <div>Right-click me</div>
138
+ </ContextMenu>,
139
+ );
140
+
141
+ fireEvent.contextMenu(screen.getByText('Right-click me'));
142
+ fireEvent.keyDown(document, {key: 'Escape', isComposing: true});
143
+ expect(HTMLElement.prototype.hidePopover).not.toHaveBeenCalled();
144
+ });
145
+
146
+ it('restores focus to the trigger on close', () => {
147
+ render(
148
+ <ContextMenu items={[{label: 'Item 1'}]}>
149
+ <button type="button">Right-click me</button>
150
+ </ContextMenu>,
151
+ );
152
+
153
+ const trigger = screen.getByRole('button', {name: 'Right-click me'});
154
+ trigger.focus();
155
+ expect(trigger).toHaveFocus();
156
+
157
+ fireEvent.contextMenu(trigger);
158
+ fireEvent.keyDown(document, {key: 'Escape'});
159
+ expect(trigger).toHaveFocus();
160
+ });
161
+
74
162
  it('prevents default context menu on right-click', () => {
75
163
  render(
76
164
  <ContextMenu items={[{label: 'Item 1'}]}>
@@ -103,6 +191,72 @@ describe('ContextMenu', () => {
103
191
  );
104
192
  expect(screen.getByTestId('my-context-menu')).toBeInTheDocument();
105
193
  });
194
+
195
+ it('opens from a keyboard-invoked contextmenu (Shift+F10 / Menu key)', () => {
196
+ render(
197
+ <ContextMenu items={[{label: 'Item 1'}]} data-testid="ctx">
198
+ <div>Right-click me</div>
199
+ </ContextMenu>,
200
+ );
201
+ const trigger = screen.getByTestId('ctx');
202
+ // Anchor the trigger box so the rect fallback has a position to read.
203
+ trigger.getBoundingClientRect = () =>
204
+ ({
205
+ left: 40,
206
+ top: 10,
207
+ bottom: 30,
208
+ right: 100,
209
+ width: 60,
210
+ height: 20,
211
+ }) as DOMRect;
212
+ // Keyboard-initiated contextmenu: coords are (0,0) and detail is 0.
213
+ fireEvent.contextMenu(trigger, {clientX: 0, clientY: 0, detail: 0});
214
+ expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
215
+ });
216
+
217
+ it('opens on touch long-press', () => {
218
+ vi.useFakeTimers();
219
+ try {
220
+ render(
221
+ <ContextMenu items={[{label: 'Item 1'}]} data-testid="ctx">
222
+ <div>Long-press me</div>
223
+ </ContextMenu>,
224
+ );
225
+ const trigger = screen.getByTestId('ctx');
226
+ fireEvent.touchStart(trigger, {
227
+ touches: [{clientX: 20, clientY: 20}],
228
+ });
229
+ // Not open until the long-press threshold elapses.
230
+ expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
231
+ act(() => {
232
+ vi.advanceTimersByTime(500);
233
+ });
234
+ expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
235
+ } finally {
236
+ vi.useRealTimers();
237
+ }
238
+ });
239
+
240
+ it('cancels the long-press when the finger moves past the threshold', () => {
241
+ vi.useFakeTimers();
242
+ try {
243
+ render(
244
+ <ContextMenu items={[{label: 'Item 1'}]} data-testid="ctx">
245
+ <div>Long-press me</div>
246
+ </ContextMenu>,
247
+ );
248
+ const trigger = screen.getByTestId('ctx');
249
+ fireEvent.touchStart(trigger, {touches: [{clientX: 20, clientY: 20}]});
250
+ // Move past MOVE_CANCEL_PX (10px) — treated as a scroll, not a press.
251
+ fireEvent.touchMove(trigger, {touches: [{clientX: 20, clientY: 40}]});
252
+ act(() => {
253
+ vi.advanceTimersByTime(500);
254
+ });
255
+ expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
256
+ } finally {
257
+ vi.useRealTimers();
258
+ }
259
+ });
106
260
  });
107
261
 
108
262
  describe('ContextMenu items', () => {