@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
@@ -9,10 +9,26 @@
9
9
  * SYNC: When Pagination.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen, within, fireEvent, act} from '@testing-library/react';
12
+ import {describe, it, expect, vi, afterEach} from 'vitest';
13
+ import {
14
+ render,
15
+ screen,
16
+ within,
17
+ fireEvent,
18
+ act,
19
+ waitFor,
20
+ } from '@testing-library/react';
14
21
  import userEvent from '@testing-library/user-event';
15
22
  import {Pagination, generatePageRange} from './Pagination';
23
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
24
+
25
+ afterEach(() => {
26
+ __resetLiveRegionsForTest();
27
+ });
28
+
29
+ function politeRegion(): HTMLElement | null {
30
+ return document.querySelector('[data-astryx-live-region="polite"]');
31
+ }
16
32
 
17
33
  // =============================================================================
18
34
  // generatePageRange helper
@@ -239,6 +255,65 @@ describe('Pagination', () => {
239
255
  });
240
256
  });
241
257
 
258
+ // ---------------------------------------------------------------------------
259
+ // pageSize guarding
260
+ // ---------------------------------------------------------------------------
261
+
262
+ describe('pageSize guarding', () => {
263
+ it('does not crash the dots variant when pageSize is 0', () => {
264
+ render(
265
+ <Pagination
266
+ page={1}
267
+ onChange={() => {}}
268
+ totalItems={5}
269
+ pageSize={0}
270
+ variant="dots"
271
+ />,
272
+ );
273
+ const group = screen.getByRole('group', {name: 'Page indicators'});
274
+ expect(within(group).getAllByRole('button')).toHaveLength(5);
275
+ });
276
+
277
+ it('treats NaN pageSize as the default', () => {
278
+ render(
279
+ <Pagination
280
+ page={1}
281
+ onChange={() => {}}
282
+ totalItems={50}
283
+ pageSize={NaN}
284
+ variant="compact"
285
+ />,
286
+ );
287
+ expect(screen.getByText('Page 1 of 5')).toBeInTheDocument();
288
+ });
289
+
290
+ it('clamps negative pageSize to 1', () => {
291
+ render(
292
+ <Pagination
293
+ page={1}
294
+ onChange={() => {}}
295
+ totalItems={5}
296
+ pageSize={-10}
297
+ variant="compact"
298
+ />,
299
+ );
300
+ expect(screen.getByText('Page 1 of 5')).toBeInTheDocument();
301
+ });
302
+
303
+ it('floors fractional pageSize', () => {
304
+ render(
305
+ <Pagination
306
+ page={1}
307
+ onChange={() => {}}
308
+ totalItems={50}
309
+ pageSize={2.5}
310
+ variant="compact"
311
+ />,
312
+ );
313
+ expect(screen.getByText('Page 1 of 25')).toBeInTheDocument();
314
+ });
315
+ });
316
+
242
317
  // ---------------------------------------------------------------------------
243
318
  // Variant: dots
244
319
  // ---------------------------------------------------------------------------
@@ -304,6 +379,38 @@ describe('Pagination', () => {
304
379
  // ---------------------------------------------------------------------------
305
380
 
306
381
  describe('page change callbacks', () => {
382
+ it('does not announce on initial mount', () => {
383
+ render(<Pagination page={1} onChange={() => {}} totalPages={10} />);
384
+ expect(politeRegion()).toBeNull();
385
+ });
386
+
387
+ it('announces the new page politely when navigating', async () => {
388
+ const user = userEvent.setup();
389
+ render(<Pagination page={2} onChange={() => {}} totalPages={10} />);
390
+ await user.click(screen.getByRole('button', {name: 'Go to page 3'}));
391
+ await waitFor(() => {
392
+ expect(politeRegion()).toHaveTextContent('Page 3 of 10');
393
+ });
394
+ });
395
+
396
+ it('announces the next page when clicking next', async () => {
397
+ const user = userEvent.setup();
398
+ render(<Pagination page={2} onChange={() => {}} totalPages={5} />);
399
+ await user.click(screen.getByRole('button', {name: 'Go to next page'}));
400
+ await waitFor(() => {
401
+ expect(politeRegion()).toHaveTextContent('Page 3 of 5');
402
+ });
403
+ });
404
+
405
+ it('announces without a total when only hasMore is known', async () => {
406
+ const user = userEvent.setup();
407
+ render(<Pagination page={1} onChange={() => {}} hasMore />);
408
+ await user.click(screen.getByRole('button', {name: 'Go to next page'}));
409
+ await waitFor(() => {
410
+ expect(politeRegion()).toHaveTextContent('Page 2');
411
+ });
412
+ });
413
+
307
414
  it('calls onChange when clicking a page button', async () => {
308
415
  const user = userEvent.setup();
309
416
  const onChange = vi.fn();
@@ -395,7 +502,9 @@ describe('Pagination', () => {
395
502
  // reflects the page being navigated to.
396
503
  await user.click(screen.getByRole('button', {name: 'Go to next page'}));
397
504
  expect(changeAction).toHaveBeenCalledWith(2);
398
- expect(screen.getByText('Page 2 of 5')).toBeInTheDocument();
505
+ expect(
506
+ within(screen.getByRole('navigation')).getByText('Page 2 of 5'),
507
+ ).toBeInTheDocument();
399
508
 
400
509
  await act(async () => {
401
510
  resolveAction?.();
@@ -425,14 +534,17 @@ describe('Pagination', () => {
425
534
  );
426
535
 
427
536
  const next = screen.getByRole('button', {name: 'Go to next page'});
537
+ const nav = screen.getByRole('navigation');
428
538
  await act(async () => {
429
539
  fireEvent.click(next);
430
540
  });
431
- expect(screen.getByText('Page 2 of 5')).toBeInTheDocument();
541
+ // Scope to the nav landmark: the live region on document.body also
542
+ // carries the announced page text.
543
+ expect(within(nav).getByText('Page 2 of 5')).toBeInTheDocument();
432
544
  await act(async () => {
433
545
  fireEvent.click(next);
434
546
  });
435
- expect(screen.getByText('Page 3 of 5')).toBeInTheDocument();
547
+ expect(within(nav).getByText('Page 3 of 5')).toBeInTheDocument();
436
548
 
437
549
  expect(changeAction).toHaveBeenCalledTimes(2);
438
550
  expect(changeAction).toHaveBeenNthCalledWith(1, 2);
@@ -34,6 +34,7 @@ import {Button} from '../Button';
34
34
  import {Icon} from '../Icon';
35
35
  import {Selector} from '../Selector';
36
36
  import {Text} from '../Text';
37
+ import {useAnnounce} from '../hooks/useAnnounce';
37
38
  import {mergeProps} from '../utils';
38
39
  import type {BaseProps} from '../BaseProps';
39
40
  import {themeProps} from '../utils/themeProps';
@@ -335,7 +336,7 @@ export function Pagination({
335
336
  totalItems,
336
337
  totalPages: totalPagesProp,
337
338
  hasMore,
338
- pageSize = 10,
339
+ pageSize: pageSizeProp = 10,
339
340
  pageSizeOptions,
340
341
  onPageSizeChange,
341
342
  variant = 'pages',
@@ -351,6 +352,20 @@ export function Pagination({
351
352
  }: PaginationProps) {
352
353
  const [, startTransition] = useTransition();
353
354
 
355
+ // pageSize is typed as number, so 0, NaN, and negatives are valid at the
356
+ // type level but yield Infinity/NaN page counts, and
357
+ // Array.from({length: Infinity}) crashes the dots variant. Coerce to a
358
+ // positive integer; non-finite values fall back to the default.
359
+ const pageSize = Number.isFinite(pageSizeProp)
360
+ ? Math.max(1, Math.floor(pageSizeProp))
361
+ : 10;
362
+
363
+ // Announce page changes politely (navigation-10). The controls carry no
364
+ // live region, so page transitions were previously silent to screen readers.
365
+ // Only user-driven changes go through handlePageChange, so initial mount is
366
+ // never announced.
367
+ const announce = useAnnounce();
368
+
354
369
  // Track the page optimistically so rapid prev/next clicks advance from the
355
370
  // in-flight target instead of stalling on the last committed page.
356
371
  const [optimisticPage, setOptimisticPage] = useOptimistic(page);
@@ -384,6 +399,11 @@ export function Pagination({
384
399
  // Keep onChange urgent so controlled page state updates in the same commit
385
400
  // as the click; only the optimistic indicator and changeAction defer.
386
401
  onChange(newPage);
402
+ announce(
403
+ computedTotalPages != null
404
+ ? `Page ${newPage} of ${computedTotalPages}`
405
+ : `Page ${newPage}`,
406
+ );
387
407
  startTransition(async () => {
388
408
  setOptimisticPage(newPage);
389
409
  await changeAction?.(newPage);
@@ -90,6 +90,18 @@ export const docs = {
90
90
  description: 'Whether to auto-focus the first focusable element when the popover opens. Set to false for inline showcases or documentation previews.',
91
91
  default: 'true',
92
92
  },
93
+ {
94
+ name: 'hasLightDismiss',
95
+ type: 'boolean',
96
+ description: 'Whether clicking outside dismisses the popover. Set to false for surfaces that stay open until explicitly dismissed, like onboarding coachmarks.',
97
+ default: 'true',
98
+ },
99
+ {
100
+ name: 'hasEscapeDismiss',
101
+ type: 'boolean',
102
+ description: 'Whether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss={false}, since native light dismiss also closes on Escape.',
103
+ default: 'true',
104
+ },
93
105
  {
94
106
  name: 'xstyle',
95
107
  type: 'StyleXStyles',
@@ -219,6 +231,18 @@ export const docsZh = {
219
231
  description: '弹出框打开时是否自动聚焦第一个可聚焦元素。内联展示或文档预览设为 false。',
220
232
  default: 'true',
221
233
  },
234
+ {
235
+ name: 'hasLightDismiss',
236
+ type: 'boolean',
237
+ description: '点击外部是否关闭弹出框。需要显式关闭的界面(如新手引导提示)设为 false。',
238
+ default: 'true',
239
+ },
240
+ {
241
+ name: 'hasEscapeDismiss',
242
+ type: 'boolean',
243
+ description: '按 Escape 是否关闭弹出框。仅在 hasLightDismiss={false} 时生效,因为原生 light dismiss 行为同样响应 Escape。',
244
+ default: 'true',
245
+ },
222
246
  ],
223
247
  },
224
248
  ],
@@ -291,6 +315,8 @@ export const docsDense = {
291
315
  hasCloseButton: 'Whether to include hidden close button for accessibility.',
292
316
  closeButtonLabel: 'Label for hidden close button.',
293
317
  hasAutoFocus: 'Auto-focus first element on open; false for showcases.',
318
+ hasLightDismiss: 'Outside click dismisses; false for explicit-dismiss surfaces (coachmarks).',
319
+ hasEscapeDismiss: 'Escape dismisses; full effect only with hasLightDismiss=false.',
294
320
  },
295
321
  },
296
322
  ],
@@ -13,6 +13,7 @@ import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
13
13
  import {render, screen, fireEvent} from '@testing-library/react';
14
14
  import React, {useRef} from 'react';
15
15
  import {Popover} from './Popover';
16
+ import {Dialog} from '../Dialog';
16
17
 
17
18
  // Store original matches to restore later
18
19
  const originalMatches = HTMLElement.prototype.matches;
@@ -233,4 +234,124 @@ describe('Popover', () => {
233
234
  );
234
235
  warnSpy.mockRestore();
235
236
  });
237
+
238
+ describe('dismiss controls', () => {
239
+ // jsdom does not implement <dialog> showModal/close, needed by the
240
+ // host-Dialog fall-through test below.
241
+ beforeAll(() => {
242
+ HTMLDialogElement.prototype.showModal = vi.fn(function (
243
+ this: HTMLDialogElement,
244
+ ) {
245
+ this.setAttribute('open', '');
246
+ });
247
+ HTMLDialogElement.prototype.close = vi.fn(function (
248
+ this: HTMLDialogElement,
249
+ ) {
250
+ this.removeAttribute('open');
251
+ });
252
+ });
253
+
254
+ it('dismisses on Escape by default', () => {
255
+ render(
256
+ <Popover content={<span>Content</span>} label="Test">
257
+ <button type="button">Open</button>
258
+ </Popover>,
259
+ );
260
+ const trigger = screen.getByRole('button', {name: 'Open'});
261
+ fireEvent.click(trigger);
262
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
263
+
264
+ fireEvent.keyDown(document, {key: 'Escape'});
265
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
266
+ });
267
+
268
+ it('stays open on Escape when hasEscapeDismiss is false', () => {
269
+ render(
270
+ <Popover
271
+ content={<span>Content</span>}
272
+ label="Test"
273
+ hasLightDismiss={false}
274
+ hasEscapeDismiss={false}>
275
+ <button type="button">Open</button>
276
+ </Popover>,
277
+ );
278
+ const trigger = screen.getByRole('button', {name: 'Open'});
279
+ fireEvent.click(trigger);
280
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
281
+
282
+ fireEvent.keyDown(document, {key: 'Escape'});
283
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
284
+ });
285
+
286
+ it('still dismisses on Escape when only light dismiss is off', () => {
287
+ render(
288
+ <Popover
289
+ content={<span>Content</span>}
290
+ label="Test"
291
+ hasLightDismiss={false}>
292
+ <button type="button">Open</button>
293
+ </Popover>,
294
+ );
295
+ const trigger = screen.getByRole('button', {name: 'Open'});
296
+ fireEvent.click(trigger);
297
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
298
+
299
+ fireEvent.keyDown(document, {key: 'Escape'});
300
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
301
+ });
302
+
303
+ it('switches to popover="manual" when hasLightDismiss is false', () => {
304
+ const {unmount} = render(
305
+ <Popover content={<span>Content</span>} label="Test">
306
+ <button type="button">Open</button>
307
+ </Popover>,
308
+ );
309
+ fireEvent.click(screen.getByRole('button', {name: 'Open'}));
310
+ expect(document.querySelector('[popover]')).toHaveAttribute(
311
+ 'popover',
312
+ 'auto',
313
+ );
314
+ unmount();
315
+
316
+ render(
317
+ <Popover
318
+ content={<span>Content</span>}
319
+ label="Test"
320
+ hasLightDismiss={false}>
321
+ <button type="button">Open</button>
322
+ </Popover>,
323
+ );
324
+ fireEvent.click(screen.getByRole('button', {name: 'Open'}));
325
+ expect(document.querySelector('[popover]')).toHaveAttribute(
326
+ 'popover',
327
+ 'manual',
328
+ );
329
+ });
330
+
331
+ it('lets Escape fall through to a host Dialog when fully opted out', () => {
332
+ const onDialogOpenChange = vi.fn();
333
+ render(
334
+ <Dialog isOpen={true} onOpenChange={onDialogOpenChange} purpose="info">
335
+ <Popover
336
+ content={<span>Coachmark</span>}
337
+ label="Tip"
338
+ hasLightDismiss={false}
339
+ hasEscapeDismiss={false}>
340
+ <button type="button">Open tip</button>
341
+ </Popover>
342
+ </Dialog>,
343
+ );
344
+ const trigger = screen.getByRole('button', {name: 'Open tip'});
345
+ fireEvent.click(trigger);
346
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
347
+
348
+ // The Dialog listens for Escape on the dialog element, so fire from a
349
+ // node inside it. The popover registers no Escape handler, so
350
+ // hasActiveFocusTrapEscape() is false and the Dialog handles the press
351
+ // while the popover itself stays open.
352
+ fireEvent.keyDown(trigger, {key: 'Escape'});
353
+ expect(onDialogOpenChange).toHaveBeenCalledWith(false);
354
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
355
+ });
356
+ });
236
357
  });
@@ -186,6 +186,24 @@ export interface PopoverProps extends Pick<
186
186
  */
187
187
  hasAutoFocus?: boolean;
188
188
 
189
+ /**
190
+ * Whether clicking outside dismisses the popover.
191
+ * Set to `false` for surfaces that should stay open until explicitly
192
+ * dismissed, like onboarding coachmarks or multi-step flows.
193
+ * @default true
194
+ */
195
+ hasLightDismiss?: boolean;
196
+
197
+ /**
198
+ * Whether pressing Escape dismisses the popover.
199
+ *
200
+ * Only takes full effect together with `hasLightDismiss={false}`: with
201
+ * light dismiss on, the browser's native light dismiss also closes on
202
+ * Escape. Set both to `false` for explicit-dismiss-only surfaces.
203
+ * @default true
204
+ */
205
+ hasEscapeDismiss?: boolean;
206
+
189
207
  /**
190
208
  * Test ID for the popover container.
191
209
  */
@@ -239,7 +257,7 @@ const styles = stylex.create({
239
257
  * (immune to pressed-state transforms like `:active { scale(0.98) }`).
240
258
  *
241
259
  * Focus is trapped inside the popover when open.
242
- * Supports light dismiss (click outside or Escape to close).
260
+ * Supports light dismiss by default (click outside or Escape to close).
243
261
  *
244
262
  * For hover-triggered overlays, use {@link HoverCard} instead.
245
263
  *
@@ -277,6 +295,8 @@ export function Popover({
277
295
  hasCloseButton,
278
296
  closeButtonLabel,
279
297
  hasAutoFocus,
298
+ hasLightDismiss = true,
299
+ hasEscapeDismiss = true,
280
300
  xstyle,
281
301
  className,
282
302
  style,
@@ -299,7 +319,8 @@ export function Popover({
299
319
 
300
320
  const popover = usePopover({
301
321
  dialogLabel: label,
302
- hasLightDismiss: true,
322
+ hasLightDismiss,
323
+ hasEscapeDismiss,
303
324
  hasCloseButton,
304
325
  closeButtonLabel,
305
326
  hasAutoFocus,
@@ -11,10 +11,13 @@ export const docs = {
11
11
  {name: 'onHide', type: '() => void', description: 'Callback fired when the popover is hidden. Use this to return focus to the trigger when needed.'},
12
12
  {name: 'xstyle', type: 'StyleXStyles', description: 'StyleX styles applied to the popover content wrapper, after the default surface styles.'},
13
13
  {name: 'hasLightDismiss', type: 'boolean', description: 'Whether clicking outside dismisses the popover.', default: 'true'},
14
+ {name: 'hasEscapeDismiss', type: 'boolean', description: 'Whether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss: false, since native light dismiss also closes on Escape.', default: 'true'},
14
15
  {name: 'hasAutoFocus', type: 'boolean', description: 'Whether to automatically focus the first focusable element when opened.', default: 'true'},
15
16
  {name: 'hasCloseButton', type: 'boolean', description: 'Whether to include a hidden close button that appears for keyboard users.', default: 'true'},
16
17
  {name: 'closeButtonLabel', type: 'string', description: 'Accessible label for the hidden close button.', default: "'Close popover'"},
17
- {name: 'dialogLabel', type: 'string', description: 'Accessible label for the popover dialog. Provide one when there is no visible title.'},
18
+ {name: 'dialogLabel', type: 'string', description: 'Accessible label for the popover dialog (only applies when role is "dialog"). Provide one when there is no visible title.'},
19
+ {name: 'role', type: "'dialog' | 'none'", description: 'ARIA role on the content wrapper. Use "dialog" for genuine dialog content; use "none" for listbox/menu popups whose own content role should be exposed and whose trigger keeps DOM focus.', default: "'dialog'"},
20
+ {name: 'isModal', type: 'boolean', description: 'Whether a dialog-role popover is modal (aria-modal). Only applies when role is "dialog".', default: 'true'},
18
21
  {name: 'hasSurface', type: 'boolean', description: 'Whether to apply the default popover surface background, radius, and shadow.', default: 'true'},
19
22
  ],
20
23
  returns: [
@@ -27,7 +30,7 @@ export const docs = {
27
30
  {name: 'isOpen', type: 'boolean', description: 'Whether the popover is currently open.'},
28
31
  {name: 'id', type: 'string', description: 'Unique ID for aria-describedby or aria-controls.'},
29
32
  {name: 'render', type: '(children: ReactNode, props?: ContextRenderProps) => ReactNode', description: 'Render function for anchor-positioned popover content. Pass placement and alignment here.'},
30
- {name: 'triggerProps', type: '{aria-haspopup: "dialog"; aria-expanded: boolean; aria-controls: string}', description: 'ARIA attributes to spread onto the trigger element.'},
33
+ {name: 'triggerProps', type: '{aria-haspopup: "dialog" | "true"; aria-expanded: boolean; aria-controls: string}', description: 'ARIA attributes to spread onto the trigger element. aria-haspopup reflects the popover role.'},
31
34
  ],
32
35
  usage: {
33
36
  description: 'Headless hook for click-triggered popovers with focus trapping. Combines useLayer with useFocusTrap, auto-focus, light dismiss, Escape handling, and an optional hidden close button for accessible dialog-like popover behavior. Use for custom interactive floating content that needs keyboard navigation.',
@@ -51,10 +54,13 @@ export const docsDense = {
51
54
  onHide: 'fires when popover hides; use to return focus when needed.',
52
55
  xstyle: 'StyleX styles for content wrapper, after default surface.',
53
56
  hasLightDismiss: 'whether outside click dismisses popover.',
57
+ hasEscapeDismiss: 'whether Escape dismisses; full effect only w/ hasLightDismiss false.',
54
58
  hasAutoFocus: 'whether first focusable element receives focus on open.',
55
59
  hasCloseButton: 'whether hidden keyboard close button is included.',
56
60
  closeButtonLabel: 'label for hidden close button.',
57
- dialogLabel: 'accessible label for popover dialog.',
61
+ dialogLabel: 'accessible label for popover dialog (role="dialog" only).',
62
+ role: 'content wrapper ARIA role; "none" for listbox/menu popups.',
63
+ isModal: 'whether a dialog-role popover is modal (aria-modal).',
58
64
  hasSurface: 'apply default surface background/radius/shadow.',
59
65
  },
60
66
  returnDescriptions: {
@@ -109,6 +109,19 @@ export interface UsePopoverOptions {
109
109
  */
110
110
  hasLightDismiss?: boolean;
111
111
 
112
+ /**
113
+ * Whether pressing Escape dismisses the popover.
114
+ *
115
+ * Takes effect together with `hasLightDismiss: false`: with light dismiss
116
+ * on, the native popover uses `popover="auto"`, whose browser-level light
117
+ * dismiss also closes on Escape, so Escape handling stays registered to
118
+ * keep topmost-only dismissal intact. Set both to `false` for
119
+ * explicit-dismiss-only surfaces like onboarding coachmarks.
120
+ *
121
+ * @default true
122
+ */
123
+ hasEscapeDismiss?: boolean;
124
+
112
125
  /**
113
126
  * Whether to automatically focus the first focusable element when opened.
114
127
  * @default true
@@ -130,10 +143,35 @@ export interface UsePopoverOptions {
130
143
 
131
144
  /**
132
145
  * Accessible label for the dialog.
133
- * Required for screen readers to announce the dialog purpose.
146
+ * Required for screen readers to announce the dialog purpose
147
+ * (only applies when `role` is `'dialog'`).
134
148
  */
135
149
  dialogLabel?: string;
136
150
 
151
+ /**
152
+ * ARIA role stamped on the popover content wrapper.
153
+ *
154
+ * - `'dialog'` (default): the wrapper is a `role="dialog"` and, when
155
+ * `isModal` is true, carries `aria-modal`. Use for genuine dialog content.
156
+ * - `'none'`: the wrapper carries no role or `aria-modal`, so the popup's own
157
+ * content role (e.g. a child `role="listbox"` or `role="menu"`) is the
158
+ * exposed semantics. Use for comboboxes, listboxes, and menus — their
159
+ * trigger keeps DOM focus, so announcing an unnamed modal dialog around
160
+ * them is incorrect.
161
+ *
162
+ * @default 'dialog'
163
+ */
164
+ role?: 'dialog' | 'none';
165
+
166
+ /**
167
+ * Whether the dialog is modal (`aria-modal`). Only applies when `role` is
168
+ * `'dialog'`. Set to `false` for non-modal dialogs that do not inert the rest
169
+ * of the page.
170
+ *
171
+ * @default true
172
+ */
173
+ isModal?: boolean;
174
+
137
175
  /**
138
176
  * Whether to apply the default popover surface (background, border-radius,
139
177
  * box-shadow) to the content wrapper.
@@ -215,7 +253,7 @@ export interface UsePopoverReturn {
215
253
  * ARIA attributes to spread on the trigger element
216
254
  */
217
255
  triggerProps: {
218
- 'aria-haspopup': 'dialog';
256
+ 'aria-haspopup': 'dialog' | 'true';
219
257
  'aria-expanded': boolean;
220
258
  'aria-controls': string;
221
259
  };
@@ -228,7 +266,7 @@ export interface UsePopoverReturn {
228
266
  * - `useLayer` for popover positioning using CSS anchor positioning
229
267
  * - `useFocusTrap` for trapping focus within the popover content
230
268
  * - Auto-focus first element on open
231
- * - Escape key to close
269
+ * - Escape key to close (configurable via hasEscapeDismiss)
232
270
  * - Hidden close button that reveals on focus for accessibility
233
271
  *
234
272
  * The render function automatically wraps your content in a focus trap container
@@ -261,19 +299,20 @@ export interface UsePopoverReturn {
261
299
  * }
262
300
  * ```
263
301
  */
264
- export function usePopover(
265
- options: UsePopoverOptions = {},
266
- ): UsePopoverReturn {
302
+ export function usePopover(options: UsePopoverOptions = {}): UsePopoverReturn {
267
303
  const {
268
304
  onShow,
269
305
  onHide,
270
306
  xstyle,
271
307
  hasLightDismiss = true,
308
+ hasEscapeDismiss = true,
272
309
  hasAutoFocus = true,
273
310
  hasSurface = true,
274
311
  hasCloseButton = true,
275
312
  closeButtonLabel = 'Close popover',
276
313
  dialogLabel,
314
+ role = 'dialog',
315
+ isModal = true,
277
316
  } = options;
278
317
 
279
318
  // Track the trigger element for returning focus
@@ -290,10 +329,12 @@ export function usePopover(
290
329
  onHide,
291
330
  });
292
331
 
293
- // Focus trap for the popover content
332
+ // Focus trap for the popover content. Escape stays registered while light
333
+ // dismiss is on (native popover="auto" closes on Escape regardless), so a
334
+ // host Dialog keeps deferring to this trap instead of double-dismissing.
294
335
  const {containerRef: contentRef, focusFirst} = useFocusTrap<HTMLDivElement>({
295
336
  isActive: layer.isOpen,
296
- onEscape: layer.hide,
337
+ onEscape: hasEscapeDismiss || hasLightDismiss ? layer.hide : undefined,
297
338
  });
298
339
 
299
340
  // Auto-focus first element when popover opens (unless skipped)
@@ -339,20 +380,35 @@ export function usePopover(
339
380
 
340
381
  // ARIA attributes for the trigger
341
382
  const triggerProps = {
342
- 'aria-haspopup': 'dialog' as const,
383
+ 'aria-haspopup':
384
+ role === 'dialog' ? ('dialog' as const) : ('true' as const),
343
385
  'aria-expanded': layer.isOpen,
344
386
  'aria-controls': layer.id,
345
387
  };
346
388
 
389
+ // Dev-time guardrail: a dialog popover should always be labeled. Warn once
390
+ // per hook instance (in an effect) rather than on every render.
391
+ const warnedUnnamedDialogRef = useRef(false);
392
+ useEffect(() => {
393
+ if (role === 'dialog' && !dialogLabel && !warnedUnnamedDialogRef.current) {
394
+ warnedUnnamedDialogRef.current = true;
395
+ console.warn(
396
+ 'usePopover: role="dialog" without a `dialogLabel` renders an unnamed ' +
397
+ 'dialog. Pass `dialogLabel`, or use `role: "none"` for listbox/menu ' +
398
+ 'popups whose content already carries its own role.',
399
+ );
400
+ }
401
+ }, [role, dialogLabel]);
402
+
347
403
  // Wrapped render function that includes surface styles and optional hidden close button
348
404
  const render = useCallback(
349
405
  (children: ReactNode, props?: ContextRenderProps): ReactNode => {
350
406
  return layer.render(
351
407
  <div
352
408
  ref={contentRef}
353
- role="dialog"
354
- aria-modal="true"
355
- aria-label={dialogLabel}
409
+ role={role === 'dialog' ? 'dialog' : undefined}
410
+ aria-modal={role === 'dialog' && isModal ? true : undefined}
411
+ aria-label={role === 'dialog' ? dialogLabel : undefined}
356
412
  {...stylex.props(
357
413
  styles.contentWrapper,
358
414
  hasSurface && styles.surface,
@@ -379,6 +435,8 @@ export function usePopover(
379
435
  closeButtonLabel,
380
436
  contentRef,
381
437
  dialogLabel,
438
+ role,
439
+ isModal,
382
440
  xstyle,
383
441
  ],
384
442
  );