@astryxdesign/core 0.1.2 → 0.1.3

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
@@ -62,6 +62,12 @@ export const docs = {
62
62
  type: 'boolean',
63
63
  description: 'Whether the input is disabled.',
64
64
  },
65
+ {
66
+ name: 'disabledMessage',
67
+ type: 'string',
68
+ description:
69
+ 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (the field becomes read-only). Use this instead of wrapping a disabled NumberInput in Tooltip.',
70
+ },
65
71
  {
66
72
  name: 'placeholder',
67
73
  type: 'string',
@@ -166,6 +172,7 @@ export const docs = {
166
172
  { guidance: true, description: 'Show units (e.g. "%" or "GB") so users know what the number represents.' },
167
173
  { guidance: false, description: 'Use NumberInput for free-form text that happens to contain numbers; use TextInput instead.' },
168
174
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
175
+ { guidance: false, description: "Wrap a disabled NumberInput in Tooltip to explain why it's disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead." },
169
176
  ],
170
177
  anatomy: [
171
178
  {name: 'Label', required: true, description: 'The label for the number input.'},
@@ -236,6 +243,12 @@ export const docsZh = {
236
243
  type: 'boolean',
237
244
  description: '输入框是否禁用。',
238
245
  },
246
+ {
247
+ name: 'disabledMessage',
248
+ type: 'string',
249
+ description:
250
+ '说明输入框被禁用的原因。与 isDisabled 一起使用时,在悬停/键盘聚焦时显示工具提示,并通过 aria-disabled 保持输入框可聚焦(字段变为只读)。请使用此属性,而不是用 Tooltip 包裹已禁用的 NumberInput。',
251
+ },
239
252
  {
240
253
  name: 'placeholder',
241
254
  type: 'string',
@@ -339,6 +352,7 @@ export const docsZh = {
339
352
  { guidance: true, description: 'Show units (e.g. "%" or "GB") so users know what the number represents.' },
340
353
  { guidance: false, description: 'Use NumberInput for free-form text that happens to contain numbers; use TextInput instead.' },
341
354
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
355
+ { guidance: false, description: "Wrap a disabled NumberInput in Tooltip to explain why it's disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead." },
342
356
  ],
343
357
  anatomy: [
344
358
  {name: 'Label', required: true, description: 'The label for the number input.'},
@@ -361,6 +375,7 @@ export const docsDense = {
361
375
  { guidance: true, description: 'Show units (e.g. "%" or "GB") so users know what the number represents.' },
362
376
  { guidance: false, description: 'Use NumberInput for free-form text that happens to contain numbers; use TextInput instead.' },
363
377
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
378
+ { guidance: false, description: "Wrap a disabled NumberInput in Tooltip to explain why it's disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead." },
364
379
  ],
365
380
  anatomy: [
366
381
  {name: 'Label', required: true, description: 'The label for the number input.'},
@@ -380,6 +395,8 @@ export const docsDense = {
380
395
  isOptional: 'Field optional (mutually exclusive w/ isRequired).',
381
396
  isRequired: 'Field required (mutually exclusive w/ isOptional).',
382
397
  isDisabled: 'Input disabled.',
398
+ disabledMessage:
399
+ 'Explains why input is disabled. With isDisabled, shows tooltip on hover/focus + keeps input focusable via aria-disabled (field becomes read-only). Use instead of wrapping a disabled NumberInput in Tooltip.',
383
400
  placeholder: 'Placeholder text.',
384
401
  labelTooltip: 'Tooltip text in info icon at label end.',
385
402
  startIcon: 'Icon at input start.',
@@ -9,17 +9,46 @@
9
9
  * SYNC: When NumberInput.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen, fireEvent} from '@testing-library/react';
12
+ import {describe, it, expect, vi, beforeEach} from 'vitest';
13
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {HashtagIcon} from '@heroicons/react/24/outline';
16
16
  import {NumberInput} from './NumberInput';
17
17
 
18
+ // Mock showPopover/hidePopover since jsdom does not implement them. Used by the
19
+ // disabledMessage tooltip.
20
+ beforeEach(() => {
21
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
22
+ this.setAttribute('popover-open', '');
23
+ const event = new Event('toggle', {bubbles: false});
24
+ Object.defineProperty(event, 'newState', {value: 'open'});
25
+ this.dispatchEvent(event);
26
+ });
27
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
28
+ this.removeAttribute('popover-open');
29
+ const event = new Event('toggle', {bubbles: false});
30
+ Object.defineProperty(event, 'newState', {value: 'closed'});
31
+ this.dispatchEvent(event);
32
+ });
33
+ const originalMatches = HTMLElement.prototype.matches;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ (HTMLElement.prototype as any).matches = function (
36
+ selector: string,
37
+ ): boolean {
38
+ if (selector === ':popover-open') {
39
+ return this.hasAttribute('popover-open');
40
+ }
41
+ return originalMatches.call(this, selector);
42
+ };
43
+ });
44
+
45
+ // jsdom popover content is in the DOM but may not be "visible" in the
46
+ // accessibility tree. Use hidden: true to find it.
47
+ const h = {hidden: true} as const;
48
+
18
49
  describe('NumberInput', () => {
19
50
  it('renders with label', () => {
20
- render(
21
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
22
- );
51
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
23
52
  expect(screen.getByLabelText('Quantity')).toBeInTheDocument();
24
53
  });
25
54
 
@@ -41,9 +70,7 @@ describe('NumberInput', () => {
41
70
  });
42
71
 
43
72
  it('displays null for null value', () => {
44
- render(
45
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
46
- );
73
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
47
74
  expect(screen.getByRole('spinbutton')).toHaveValue(null);
48
75
  });
49
76
 
@@ -103,9 +130,7 @@ describe('NumberInput', () => {
103
130
  });
104
131
 
105
132
  it('does not set aria-required when isRequired is false', () => {
106
- render(
107
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
108
- );
133
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
109
134
  expect(screen.getByRole('spinbutton')).not.toHaveAttribute('aria-required');
110
135
  });
111
136
 
@@ -139,9 +164,7 @@ describe('NumberInput', () => {
139
164
  });
140
165
 
141
166
  it('is not disabled by default', () => {
142
- render(
143
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
144
- );
167
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
145
168
  expect(screen.getByRole('spinbutton')).not.toBeDisabled();
146
169
  });
147
170
 
@@ -176,12 +199,7 @@ describe('NumberInput', () => {
176
199
 
177
200
  it('sets max attribute', () => {
178
201
  render(
179
- <NumberInput
180
- label="Age"
181
- value={null}
182
- onChange={() => {}}
183
- max={120}
184
- />,
202
+ <NumberInput label="Age" value={null} onChange={() => {}} max={120} />,
185
203
  );
186
204
  expect(screen.getByRole('spinbutton')).toHaveAttribute('max', '120');
187
205
  });
@@ -204,11 +222,7 @@ describe('NumberInput', () => {
204
222
  const user = userEvent.setup();
205
223
  const handleChange = vi.fn();
206
224
  render(
207
- <NumberInput
208
- label="Quantity"
209
- value={null}
210
- onChange={handleChange}
211
- />,
225
+ <NumberInput label="Quantity" value={null} onChange={handleChange} />,
212
226
  );
213
227
 
214
228
  const input = screen.getByRole('spinbutton');
@@ -497,6 +511,68 @@ describe('NumberInput', () => {
497
511
  });
498
512
  });
499
513
 
514
+ describe('invalid typed input feedback (WCAG 3.3.1)', () => {
515
+ it('sets aria-invalid="true" when typed input is unparseable', async () => {
516
+ const user = userEvent.setup();
517
+ render(
518
+ <NumberInput
519
+ label="Count"
520
+ value={null}
521
+ onChange={() => {}}
522
+ isIntegerOnly
523
+ />,
524
+ );
525
+
526
+ const input = screen.getByRole('spinbutton');
527
+ await user.click(input);
528
+ // "3.5" is invalid when isIntegerOnly is set
529
+ await user.type(input, '3.5');
530
+
531
+ expect(input).toHaveAttribute('aria-invalid', 'true');
532
+ });
533
+
534
+ it('does not set aria-invalid when typed input is valid', async () => {
535
+ const user = userEvent.setup();
536
+ render(<NumberInput label="Count" value={null} onChange={() => {}} />);
537
+
538
+ const input = screen.getByRole('spinbutton');
539
+ await user.click(input);
540
+ await user.type(input, '42');
541
+
542
+ expect(input).not.toHaveAttribute('aria-invalid');
543
+ });
544
+
545
+ it('announces an alert message when typed input is invalid', async () => {
546
+ const user = userEvent.setup();
547
+ render(
548
+ <NumberInput
549
+ label="Count"
550
+ value={null}
551
+ onChange={() => {}}
552
+ isIntegerOnly
553
+ />,
554
+ );
555
+
556
+ const input = screen.getByRole('spinbutton');
557
+ await user.click(input);
558
+ await user.type(input, '3.5');
559
+
560
+ expect(screen.getByRole('alert')).toHaveTextContent('Invalid number');
561
+ });
562
+
563
+ it('does not announce an alert message when input is valid', async () => {
564
+ const user = userEvent.setup();
565
+ render(<NumberInput label="Count" value={null} onChange={() => {}} />);
566
+
567
+ const input = screen.getByRole('spinbutton');
568
+ await user.click(input);
569
+ await user.type(input, '42');
570
+
571
+ expect(screen.getByRole('alert')).toHaveTextContent('');
572
+ expect(screen.queryByText('Invalid number')).not.toBeInTheDocument();
573
+ });
574
+ });
575
+
500
576
  it('renders tooltip info icon when labelTooltip is provided', () => {
501
577
  render(
502
578
  <NumberInput
@@ -510,9 +586,7 @@ describe('NumberInput', () => {
510
586
  });
511
587
 
512
588
  it('does not render tooltip icon when labelTooltip is not provided', () => {
513
- render(
514
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
515
- );
589
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
516
590
  expect(document.querySelector('svg')).not.toBeInTheDocument();
517
591
  });
518
592
 
@@ -530,9 +604,7 @@ describe('NumberInput', () => {
530
604
  });
531
605
 
532
606
  it('does not focus when hasAutoFocus is false', () => {
533
- render(
534
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
535
- );
607
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
536
608
  expect(screen.getByRole('spinbutton')).not.toHaveFocus();
537
609
  });
538
610
  });
@@ -554,9 +626,7 @@ describe('NumberInput', () => {
554
626
  });
555
627
 
556
628
  it('does not set name attribute when htmlName is not provided', () => {
557
- render(
558
- <NumberInput label="Quantity" value={null} onChange={() => {}} />,
559
- );
629
+ render(<NumberInput label="Quantity" value={null} onChange={() => {}} />);
560
630
  expect(screen.getByRole('spinbutton')).not.toHaveAttribute('name');
561
631
  });
562
632
  });
@@ -590,12 +660,7 @@ describe('NumberInput', () => {
590
660
 
591
661
  it('does not show clear button when value is null', () => {
592
662
  render(
593
- <NumberInput
594
- label="Qty"
595
- value={null}
596
- onChange={() => {}}
597
- hasClear
598
- />,
663
+ <NumberInput label="Qty" value={null} onChange={() => {}} hasClear />,
599
664
  );
600
665
  expect(
601
666
  screen.queryByRole('button', {name: 'Clear Qty'}),
@@ -664,4 +729,141 @@ describe('NumberInput', () => {
664
729
  expect(input).toHaveFocus();
665
730
  });
666
731
  });
732
+
733
+ describe('disabledMessage', () => {
734
+ it('shows the reason tooltip on hover when disabled with a reason', async () => {
735
+ render(
736
+ <NumberInput
737
+ label="Quantity"
738
+ value={5}
739
+ onChange={() => {}}
740
+ isDisabled
741
+ disabledMessage="You need the Editor role"
742
+ />,
743
+ );
744
+
745
+ const input = screen.getByRole('spinbutton');
746
+ const container = input.parentElement as HTMLElement;
747
+ const tooltip = screen.getByRole('tooltip', h);
748
+ expect(tooltip).toHaveTextContent('You need the Editor role');
749
+
750
+ fireEvent.mouseEnter(container);
751
+ await waitFor(() => {
752
+ expect(tooltip).toHaveAttribute('popover-open');
753
+ });
754
+
755
+ fireEvent.mouseLeave(container);
756
+ await waitFor(() => {
757
+ expect(tooltip).not.toHaveAttribute('popover-open');
758
+ });
759
+ });
760
+
761
+ it('shows the reason tooltip on keyboard focus', async () => {
762
+ const user = userEvent.setup();
763
+ render(
764
+ <NumberInput
765
+ label="Quantity"
766
+ value={5}
767
+ onChange={() => {}}
768
+ isDisabled
769
+ disabledMessage="You need the Editor role"
770
+ />,
771
+ );
772
+
773
+ const tooltip = screen.getByRole('tooltip', h);
774
+ await user.tab();
775
+ expect(screen.getByRole('spinbutton')).toHaveFocus();
776
+ await waitFor(() => {
777
+ expect(tooltip).toHaveAttribute('popover-open');
778
+ });
779
+ });
780
+
781
+ it('does not render a tooltip when not disabled', () => {
782
+ render(
783
+ <NumberInput
784
+ label="Quantity"
785
+ value={5}
786
+ onChange={() => {}}
787
+ disabledMessage="You need the Editor role"
788
+ />,
789
+ );
790
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
791
+ });
792
+
793
+ it('does not render a tooltip when disabled without a reason', () => {
794
+ render(
795
+ <NumberInput
796
+ label="Quantity"
797
+ value={5}
798
+ onChange={() => {}}
799
+ isDisabled
800
+ />,
801
+ );
802
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
803
+ });
804
+
805
+ it('keeps the input focusable via aria-disabled when a reason is provided', () => {
806
+ render(
807
+ <NumberInput
808
+ label="Quantity"
809
+ value={5}
810
+ onChange={() => {}}
811
+ isDisabled
812
+ disabledMessage="You need the Editor role"
813
+ />,
814
+ );
815
+ const input = screen.getByRole('spinbutton');
816
+ expect(input).not.toBeDisabled();
817
+ expect(input).toHaveAttribute('aria-disabled', 'true');
818
+ expect(input).toHaveAttribute('readonly');
819
+ });
820
+
821
+ it('links the reason tooltip from the input via aria-describedby', () => {
822
+ render(
823
+ <NumberInput
824
+ label="Quantity"
825
+ value={5}
826
+ onChange={() => {}}
827
+ isDisabled
828
+ disabledMessage="You need the Editor role"
829
+ />,
830
+ );
831
+ const input = screen.getByRole('spinbutton');
832
+ const tooltip = screen.getByRole('tooltip', h);
833
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
834
+ });
835
+
836
+ it('blocks value changes while focusable-disabled', async () => {
837
+ const user = userEvent.setup();
838
+ const onChange = vi.fn();
839
+ render(
840
+ <NumberInput
841
+ label="Quantity"
842
+ value={5}
843
+ onChange={onChange}
844
+ isDisabled
845
+ disabledMessage="You need the Editor role"
846
+ />,
847
+ );
848
+
849
+ const input = screen.getByRole('spinbutton');
850
+ await user.click(input);
851
+ await user.keyboard('9');
852
+ expect(onChange).not.toHaveBeenCalled();
853
+ });
854
+
855
+ it('remains natively disabled when disabled without a reason', () => {
856
+ render(
857
+ <NumberInput
858
+ label="Quantity"
859
+ value={5}
860
+ onChange={() => {}}
861
+ isDisabled
862
+ />,
863
+ );
864
+ const input = screen.getByRole('spinbutton');
865
+ expect(input).toBeDisabled();
866
+ expect(input).not.toHaveAttribute('aria-disabled');
867
+ });
868
+ });
667
869
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file NumberInput.tsx
7
- * @input Uses React, useId, useState, useMemo, useCallback, Field, Icon
7
+ * @input Uses React, useId, useState, useMemo, useCallback, Field, Icon, InputGroupContext
8
8
  * @output Exports NumberInput component, NumberInputProps
9
9
  * @position Core implementation; consumed by index.ts, tested by NumberInput.test.tsx
10
10
  *
@@ -46,6 +46,9 @@ import {
46
46
  inputStatusFocusWithinStyles,
47
47
  } from '../Field';
48
48
  import {Icon, renderIconSlot, type IconType} from '../Icon';
49
+ import {VisuallyHidden} from '../VisuallyHidden';
50
+ import {useTooltip} from '../Tooltip';
51
+ import {getInputARIA} from '../utils';
49
52
  import {useSize} from '../SizeContext/SizeContext';
50
53
  import {useInputContainer} from '../hooks/useInputContainer';
51
54
  import {useInputGroup} from '../InputGroup/InputGroupContext';
@@ -167,6 +170,27 @@ interface NumberInputPropsBase extends Omit<
167
170
  * @default false
168
171
  */
169
172
  isDisabled?: boolean;
173
+ /**
174
+ * Explains why the input is disabled. When set together with `isDisabled`,
175
+ * the input shows a tooltip with this text on hover and keyboard focus, and
176
+ * stays focusable (via `aria-disabled`) so the reason is discoverable by
177
+ * keyboard and assistive technology. The field cannot be edited (it becomes
178
+ * read-only) while disabled.
179
+ *
180
+ * Use this instead of wrapping a disabled input in `Tooltip` — disabled
181
+ * controls don't emit the pointer events an external tooltip needs.
182
+ *
183
+ * @example
184
+ * ```
185
+ * <NumberInput
186
+ * label="Quantity"
187
+ * value={quantity}
188
+ * isDisabled
189
+ * disabledMessage="Editing is locked while the order is processing"
190
+ * />
191
+ * ```
192
+ */
193
+ disabledMessage?: string;
170
194
  /**
171
195
  * Icon to display at the start of the input.
172
196
  * Accepts a ReactNode (e.g. `<Icon icon={SearchIcon} />`) or an SVG icon component directly.
@@ -346,6 +370,7 @@ export function NumberInput({
346
370
  isOptional = false,
347
371
  isRequired = false,
348
372
  isDisabled = false,
373
+ disabledMessage,
349
374
  startIcon,
350
375
  labelIcon,
351
376
  status,
@@ -376,6 +401,7 @@ export function NumberInput({
376
401
  }: NumberInputProps) {
377
402
  const size = useSize(sizeProp, 'md');
378
403
  const id = useId();
404
+ const inputLabelID = useId();
379
405
  const descriptionID = useId();
380
406
  const statusMessageID = useId();
381
407
  const inputRef = useRef<HTMLInputElement | null>(null);
@@ -385,6 +411,20 @@ export function NumberInput({
385
411
  // Pending input while user is typing (null = show formatted value)
386
412
  const [pendingInput, setPendingInput] = useState<string | null>(null);
387
413
 
414
+ // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
415
+ // tooltip listeners attach to the input container (which already exists) and
416
+ // the input stays perceivable via aria-disabled instead of the native
417
+ // disabled attribute. The field is made read-only so it can't be typed into,
418
+ // and value mutation is blocked by the isDisabled guard in the handlers.
419
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
420
+ const disabledMessageTooltip = useTooltip({
421
+ placement: 'above',
422
+ // The container div is not naturally focusable; focusin bubbles up from
423
+ // the input, so always attach focus listeners.
424
+ focusTrigger: 'always',
425
+ isEnabled: showsDisabledMessage,
426
+ });
427
+
388
428
  const statusIconMap: Record<InputStatusType, IconName> = {
389
429
  warning: 'warning',
390
430
  error: 'error',
@@ -400,13 +440,15 @@ export function NumberInput({
400
440
  success: 'success',
401
441
  };
402
442
 
403
- const ariaDescribedBy =
443
+ const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
444
+ inputLabelID,
404
445
  [
405
446
  description ? descriptionID : null,
406
447
  status?.message ? statusMessageID : null,
407
- ]
408
- .filter(Boolean)
409
- .join(' ') || undefined;
448
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
449
+ ],
450
+ inputGroup,
451
+ );
410
452
 
411
453
  // Display value: pending input if typing, otherwise the raw value
412
454
  // Note: With type="number", we can't use formatted display values
@@ -431,6 +473,12 @@ export function NumberInput({
431
473
  // Handle input text change - update immediately if valid
432
474
  const handleInputChange = useCallback(
433
475
  (e: React.ChangeEvent<HTMLInputElement>) => {
476
+ // Value can't change while showing a disabled message (the field is
477
+ // read-only and non-native-disabled), but guard the handler too so the
478
+ // pending value and onChange never fire.
479
+ if (isDisabled) {
480
+ return;
481
+ }
434
482
  const newValue = e.target.value;
435
483
  setPendingInput(newValue);
436
484
 
@@ -440,7 +488,7 @@ export function NumberInput({
440
488
  onChange(parsed);
441
489
  }
442
490
  },
443
- [value, onChange, min, max, isIntegerOnly],
491
+ [value, onChange, min, max, isIntegerOnly, isDisabled],
444
492
  );
445
493
 
446
494
  // Handle focus
@@ -522,7 +570,13 @@ export function NumberInput({
522
570
 
523
571
  const inputWrapper = (
524
572
  <div
525
- ref={containerRef}
573
+ ref={el => {
574
+ containerRef.current = el;
575
+ // Anchor + hover/focus listeners for the disabled-message tooltip.
576
+ // Handlers are gated internally by isEnabled, and anchor names
577
+ // compose, so attaching unconditionally is safe.
578
+ disabledMessageTooltip.ref(el);
579
+ }}
526
580
  onClick={handleWrapperClick}
527
581
  onMouseUp={handleWrapperMouseUp}
528
582
  {...mergeProps(
@@ -542,6 +596,7 @@ export function NumberInput({
542
596
  style,
543
597
  )}>
544
598
  {startIcon && renderIconSlot(startIcon, {size: 'sm', color: 'secondary'})}
599
+ {inputGroup && <VisuallyHidden id={inputLabelID}>{label}</VisuallyHidden>}
545
600
  <input
546
601
  {...rest}
547
602
  ref={mergeRefs(ref, inputRef)}
@@ -555,7 +610,12 @@ export function NumberInput({
555
610
  onBlur={handleBlur}
556
611
  onKeyDown={handleKeyDown}
557
612
  placeholder={placeholder}
558
- disabled={isDisabled}
613
+ // With a disabledMessage the input keeps focusability via aria-disabled
614
+ // so the reason is focus-discoverable; readOnly + the handler guards
615
+ // keep the value from changing.
616
+ disabled={isDisabled && !showsDisabledMessage}
617
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
618
+ readOnly={showsDisabledMessage || undefined}
559
619
  autoFocus={hasAutoFocus}
560
620
  data-autofocus={hasAutoFocus || undefined}
561
621
  min={min ?? undefined}
@@ -563,8 +623,10 @@ export function NumberInput({
563
623
  step={step ?? undefined}
564
624
  aria-describedby={ariaDescribedBy}
565
625
  aria-required={isRequired === true ? 'true' : undefined}
566
- aria-invalid={status?.type === 'error' ? 'true' : undefined}
567
- aria-label={inputGroup ? label : undefined}
626
+ aria-invalid={
627
+ status?.type === 'error' || !isInputValid ? 'true' : undefined
628
+ }
629
+ aria-labelledby={ariaLabelledBy}
568
630
  {...stylex.props(
569
631
  styles.input,
570
632
  isDisabled && styles.inputDisabled,
@@ -572,6 +634,14 @@ export function NumberInput({
572
634
  )}
573
635
  />
574
636
  {units && <span {...stylex.props(styles.units)}>{units}</span>}
637
+ {/*
638
+ Live region announcing invalid typed input to assistive technology.
639
+ The value silently reverts on blur, so without this a screen-reader
640
+ user would get no feedback that their entry was rejected (WCAG 3.3.1).
641
+ */}
642
+ <VisuallyHidden as="div" role="alert" aria-live="assertive">
643
+ {!isInputValid ? 'Invalid number' : ''}
644
+ </VisuallyHidden>
575
645
  {hasClear && value != null && !isDisabled && (
576
646
  <button
577
647
  type="button"
@@ -592,7 +662,13 @@ export function NumberInput({
592
662
  );
593
663
 
594
664
  if (inputGroup) {
595
- return inputWrapper;
665
+ return (
666
+ <>
667
+ {inputWrapper}
668
+ {showsDisabledMessage &&
669
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
670
+ </>
671
+ );
596
672
  }
597
673
 
598
674
  return (
@@ -618,6 +694,8 @@ export function NumberInput({
618
694
  labelTooltip={labelTooltip}
619
695
  width={width}>
620
696
  {inputWrapper}
697
+ {showsDisabledMessage &&
698
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
621
699
  </Field>
622
700
  );
623
701
  }
@@ -47,7 +47,7 @@ export const docs = {
47
47
  {
48
48
  name: 'pageSize',
49
49
  type: 'number',
50
- description: 'Number of items per page.',
50
+ description: 'Number of items per page. Coerced to a positive integer; non-finite values fall back to the default.',
51
51
  default: '10',
52
52
  },
53
53
  {
@@ -175,7 +175,7 @@ export const docsZh = {
175
175
  {
176
176
  name: 'pageSize',
177
177
  type: 'number',
178
- description: '每页项目数。',
178
+ description: '每页项目数。强制转换为正整数;非有限值回退到默认值。',
179
179
  default: '10',
180
180
  },
181
181
  {
@@ -277,7 +277,7 @@ export const docsDense = {
277
277
  totalItems: 'Total items. Calculates page count. Precedence over totalPages.',
278
278
  totalPages: 'Total pages. Use when page count known but not item count.',
279
279
  hasMore: 'More pages exist after current. For cursor-based pagination.',
280
- pageSize: 'Items per page.',
280
+ pageSize: 'Items per page; coerced to positive integer, non-finite falls back to default',
281
281
  pageSizeOptions: 'Page size options. Shows selector dropdown when provided.',
282
282
  onPageSizeChange: 'Called on page size change. Auto resets to page 1.',
283
283
  variant: 'Display between prev/next buttons.',