@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
@@ -9,9 +9,12 @@
9
9
  * SYNC: When DateInput.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
+ import userEvent from '@testing-library/user-event';
14
15
  import {DateInput} from './DateInput';
16
+ import {InputGroup} from '../InputGroup';
17
+ import {InputGroupText} from '../InputGroup/InputGroupText';
15
18
 
16
19
  describe('DateInput', () => {
17
20
  it('renders with label', () => {
@@ -110,6 +113,43 @@ describe('DateInput', () => {
110
113
  expect(onChange).not.toHaveBeenCalled();
111
114
  });
112
115
 
116
+ it('sets aria-invalid="true" when typed input is unparseable', () => {
117
+ render(<DateInput label="Date" onChange={() => {}} />);
118
+
119
+ const input = screen.getByRole('combobox');
120
+ fireEvent.change(input, {target: {value: '13/45/2024'}});
121
+
122
+ expect(input).toHaveAttribute('aria-invalid', 'true');
123
+ });
124
+
125
+ it('does not set aria-invalid when typed input is a valid date', () => {
126
+ render(<DateInput label="Date" onChange={() => {}} />);
127
+
128
+ const input = screen.getByRole('combobox');
129
+ fireEvent.change(input, {target: {value: '03/15/2026'}});
130
+
131
+ expect(input).not.toHaveAttribute('aria-invalid');
132
+ });
133
+
134
+ it('announces an alert message when typed input is invalid', () => {
135
+ render(<DateInput label="Date" onChange={() => {}} />);
136
+
137
+ const input = screen.getByRole('combobox');
138
+ fireEvent.change(input, {target: {value: '13/45/2024'}});
139
+
140
+ expect(screen.getByRole('alert')).toHaveTextContent('Invalid date');
141
+ });
142
+
143
+ it('does not announce an alert message when input is valid', () => {
144
+ render(<DateInput label="Date" onChange={() => {}} />);
145
+
146
+ const input = screen.getByRole('combobox');
147
+ fireEvent.change(input, {target: {value: '03/15/2026'}});
148
+
149
+ expect(screen.getByRole('alert')).toHaveTextContent('');
150
+ expect(screen.queryByText('Invalid date')).not.toBeInTheDocument();
151
+ });
152
+
113
153
  it('reverts to previous value on blur when input is invalid', async () => {
114
154
  const onChange = vi.fn();
115
155
  render(<DateInput label="Date" value="2026-01-25" onChange={onChange} />);
@@ -280,6 +320,28 @@ describe('DateInput', () => {
280
320
  );
281
321
  });
282
322
 
323
+ it('opens the calendar popover on ArrowDown (keyboard, forms-13)', () => {
324
+ render(<DateInput label="Date" onChange={() => {}} />);
325
+ const input = screen.getByRole('combobox');
326
+ expect(input).toHaveAttribute('aria-expanded', 'false');
327
+ fireEvent.keyDown(input, {key: 'ArrowDown'});
328
+ expect(input).toHaveAttribute('aria-expanded', 'true');
329
+ });
330
+
331
+ it('opens the calendar popover on Alt+ArrowDown (keyboard, forms-13)', () => {
332
+ render(<DateInput label="Date" onChange={() => {}} />);
333
+ const input = screen.getByRole('combobox');
334
+ fireEvent.keyDown(input, {key: 'ArrowDown', altKey: true});
335
+ expect(input).toHaveAttribute('aria-expanded', 'true');
336
+ });
337
+
338
+ it('does not open on ArrowDown when disabled', () => {
339
+ render(<DateInput label="Date" isDisabled onChange={() => {}} />);
340
+ const input = screen.getByRole('combobox');
341
+ fireEvent.keyDown(input, {key: 'ArrowDown'});
342
+ expect(input).toHaveAttribute('aria-expanded', 'false');
343
+ });
344
+
283
345
  it('input has aria-haspopup="dialog"', () => {
284
346
  render(<DateInput label="Date" onChange={() => {}} />);
285
347
  expect(screen.getByRole('combobox')).toHaveAttribute(
@@ -585,4 +647,188 @@ describe('DateInput', () => {
585
647
  expect(input).toHaveValue('March 20, 2026');
586
648
  });
587
649
  });
650
+
651
+ describe('InputGroup', () => {
652
+ it('uses group ARIA and skips standalone Field chrome when grouped', () => {
653
+ render(
654
+ <InputGroup
655
+ label="Availability"
656
+ description="Choose a start date"
657
+ status={{type: 'error', message: 'Date is required'}}>
658
+ <InputGroupText>Starts</InputGroupText>
659
+ <DateInput label="Date" isLabelHidden onChange={() => {}} />
660
+ </InputGroup>,
661
+ );
662
+
663
+ const group = screen.getByRole('group', {name: 'Availability'});
664
+ const input = screen.getByRole('combobox', {
665
+ name: 'Availability Date',
666
+ });
667
+
668
+ expect(document.querySelectorAll('.astryx-field')).toHaveLength(1);
669
+ expect(input).toHaveAttribute('aria-labelledby');
670
+ expect(input.getAttribute('aria-labelledby')).toContain(
671
+ group.getAttribute('aria-labelledby'),
672
+ );
673
+ expect(input).toHaveAttribute(
674
+ 'aria-describedby',
675
+ group.getAttribute('aria-describedby'),
676
+ );
677
+ expect(input).not.toHaveAttribute('aria-invalid');
678
+ expect(screen.getByText('Date is required')).toBeInTheDocument();
679
+ });
680
+
681
+ it('preserves disabledMessage tooltip wiring when grouped', () => {
682
+ render(
683
+ <InputGroup label="Availability">
684
+ <InputGroupText>Starts</InputGroupText>
685
+ <DateInput
686
+ label="Date"
687
+ isLabelHidden
688
+ isDisabled
689
+ disabledMessage="Scheduling is locked"
690
+ onChange={() => {}}
691
+ />
692
+ </InputGroup>,
693
+ );
694
+
695
+ const input = screen.getByRole('combobox', {name: 'Availability Date'});
696
+ const tooltip = screen.getByRole('tooltip', {hidden: true});
697
+
698
+ expect(input).not.toBeDisabled();
699
+ expect(input).toHaveAttribute('aria-disabled', 'true');
700
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
701
+ expect(tooltip).toHaveTextContent('Scheduling is locked');
702
+ expect(
703
+ screen.getByRole('button', {name: 'Open calendar'}),
704
+ ).toBeDisabled();
705
+ });
706
+ });
707
+
708
+ describe('disabledMessage', () => {
709
+ // jsdom does not implement the Popover API used by the tooltip, so mock
710
+ // showPopover/hidePopover to toggle a `popover-open` attribute the tests
711
+ // can assert on.
712
+ beforeEach(() => {
713
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
714
+ this.setAttribute('popover-open', '');
715
+ });
716
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
717
+ this.removeAttribute('popover-open');
718
+ });
719
+ });
720
+
721
+ // jsdom popover content is in the DOM but not "visible" in the
722
+ // accessibility tree; use hidden: true to find it.
723
+ const h = {hidden: true} as const;
724
+
725
+ it('shows the reason tooltip on hover when disabled with a reason', async () => {
726
+ render(
727
+ <DateInput
728
+ label="Date"
729
+ isDisabled
730
+ disabledMessage="You need the Editor role"
731
+ />,
732
+ );
733
+
734
+ const container = screen.getByRole('combobox')
735
+ .parentElement as HTMLElement;
736
+ const tooltip = screen.getByRole('tooltip', h);
737
+ expect(tooltip).toHaveTextContent('You need the Editor role');
738
+
739
+ fireEvent.mouseEnter(container);
740
+ await waitFor(() => {
741
+ expect(tooltip).toHaveAttribute('popover-open');
742
+ });
743
+
744
+ fireEvent.mouseLeave(container);
745
+ await waitFor(() => {
746
+ expect(tooltip).not.toHaveAttribute('popover-open');
747
+ });
748
+ });
749
+
750
+ it('shows the reason tooltip on keyboard focus', async () => {
751
+ const user = userEvent.setup();
752
+ render(
753
+ <DateInput
754
+ label="Date"
755
+ isDisabled
756
+ disabledMessage="You need the Editor role"
757
+ />,
758
+ );
759
+
760
+ const tooltip = screen.getByRole('tooltip', h);
761
+ await user.tab();
762
+ expect(screen.getByRole('combobox')).toHaveFocus();
763
+ await waitFor(() => {
764
+ expect(tooltip).toHaveAttribute('popover-open');
765
+ });
766
+ });
767
+
768
+ it('does not render a tooltip when not disabled', () => {
769
+ render(
770
+ <DateInput label="Date" disabledMessage="You need the Editor role" />,
771
+ );
772
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
773
+ });
774
+
775
+ it('does not render a tooltip when disabled without a reason', () => {
776
+ render(<DateInput label="Date" isDisabled />);
777
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
778
+ });
779
+
780
+ it('keeps the input focusable via aria-disabled when a reason is provided', () => {
781
+ render(
782
+ <DateInput
783
+ label="Date"
784
+ isDisabled
785
+ disabledMessage="You need the Editor role"
786
+ />,
787
+ );
788
+ const input = screen.getByRole('combobox');
789
+ expect(input).not.toBeDisabled();
790
+ expect(input).toHaveAttribute('aria-disabled', 'true');
791
+ expect(input).toHaveAttribute('readonly');
792
+ });
793
+
794
+ it('links the reason tooltip from the input via aria-describedby', () => {
795
+ render(
796
+ <DateInput
797
+ label="Date"
798
+ isDisabled
799
+ disabledMessage="You need the Editor role"
800
+ />,
801
+ );
802
+ const input = screen.getByRole('combobox');
803
+ const tooltip = screen.getByRole('tooltip', h);
804
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
805
+ });
806
+
807
+ it('blocks value changes and opening while focusable-disabled', async () => {
808
+ const user = userEvent.setup();
809
+ const onChange = vi.fn();
810
+ render(
811
+ <DateInput
812
+ label="Date"
813
+ onChange={onChange}
814
+ isDisabled
815
+ disabledMessage="You need the Editor role"
816
+ />,
817
+ );
818
+
819
+ const input = screen.getByRole('combobox');
820
+ await user.click(input);
821
+ await user.type(input, '2026-03-15');
822
+ expect(input).toHaveValue('');
823
+ expect(input).toHaveAttribute('aria-expanded', 'false');
824
+ expect(onChange).not.toHaveBeenCalled();
825
+ });
826
+
827
+ it('remains natively disabled when disabled without a reason', () => {
828
+ render(<DateInput label="Date" isDisabled />);
829
+ const input = screen.getByRole('combobox');
830
+ expect(input).toBeDisabled();
831
+ expect(input).not.toHaveAttribute('aria-disabled');
832
+ });
833
+ });
588
834
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DateInput.tsx
7
- * @input Uses React, useId, useState, useCallback, useRef, Field, Icon, Calendar, usePopover
7
+ * @input Uses React, useId, useState, useCallback, useRef, Field, Icon, Calendar, usePopover, InputGroupContext
8
8
  * @output Exports DateInput component, DateInputProps
9
9
  * @position Core implementation; consumed by index.ts, tested by DateInput.test.tsx
10
10
  *
@@ -44,15 +44,16 @@ import {
44
44
  inputStatusFocusWithinStyles,
45
45
  } from '../Field';
46
46
  import {Icon} from '../Icon';
47
+ import {VisuallyHidden} from '../VisuallyHidden';
48
+ import {useInputGroup} from '../InputGroup/InputGroupContext';
49
+ import {groupStyles} from '../InputGroup/groupStyles';
50
+ import {useSize} from '../SizeContext/SizeContext';
47
51
  import {Spinner} from '../Spinner';
48
- import {
49
- Calendar,
50
- type ISODateString,
51
- type CalendarHandle,
52
- } from '../Calendar';
52
+ import {Calendar, type ISODateString, type CalendarHandle} from '../Calendar';
53
53
  import {useCalendarConstraints} from '../Calendar/hooks';
54
54
  import {usePopover} from '../Popover';
55
- import {parseDateInput} from '../utils';
55
+ import {useTooltip} from '../Tooltip';
56
+ import {getInputARIA, parseDateInput} from '../utils';
56
57
  import {
57
58
  plainDateFromISO,
58
59
  plainDateToISO,
@@ -177,6 +178,29 @@ export interface DateInputProps extends Omit<
177
178
  */
178
179
  isDisabled?: boolean;
179
180
 
181
+ /**
182
+ * Explains why the input is disabled. When set together with
183
+ * `isDisabled`, the input shows a tooltip with this text on hover and
184
+ * keyboard focus, and the field stays focusable (via `aria-disabled`)
185
+ * so the reason is discoverable by keyboard and assistive technology.
186
+ * Typing and calendar activation stay blocked.
187
+ *
188
+ * Use this instead of wrapping a disabled input in `Tooltip` — disabled
189
+ * controls don't emit the pointer events an external tooltip needs.
190
+ *
191
+ * @example
192
+ * ```
193
+ * <DateInput
194
+ * label="Event date"
195
+ * value={date}
196
+ * onChange={setDate}
197
+ * isDisabled
198
+ * disabledMessage="You need the Editor role to change this"
199
+ * />
200
+ * ```
201
+ */
202
+ disabledMessage?: string;
203
+
180
204
  /**
181
205
  * The selected date in ISO format (YYYY-MM-DD).
182
206
  */
@@ -279,6 +303,7 @@ export function DateInput({
279
303
  isOptional = false,
280
304
  isRequired = false,
281
305
  isDisabled = false,
306
+ disabledMessage,
282
307
  value,
283
308
  onChange,
284
309
  changeAction,
@@ -287,7 +312,7 @@ export function DateInput({
287
312
  max,
288
313
  dateConstraints,
289
314
  placeholder = 'Select a date',
290
- size = 'md',
315
+ size: sizeProp,
291
316
  status,
292
317
  labelTooltip,
293
318
  hasClear = false,
@@ -299,18 +324,37 @@ export function DateInput({
299
324
  ref,
300
325
  ...rest
301
326
  }: DateInputProps) {
327
+ const size = useSize(sizeProp, 'md');
302
328
  const id = useId();
329
+ const inputLabelID = useId();
303
330
  const descriptionID = useId();
304
331
  const statusMessageID = useId();
305
332
  const inputRef = useRef<HTMLInputElement | null>(null);
306
333
  const calendarRef = useRef<CalendarHandle | null>(null);
307
334
  const lastFiredValueRef = useRef<ISODateString | undefined>(undefined);
335
+ const inputGroup = useInputGroup();
308
336
 
309
337
  const [, startTransition] = useTransition();
310
338
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
311
339
  const isBusy = isLoading || optimisticValue !== value;
312
340
  const isEffectivelyDisabled = isDisabled || isBusy;
313
341
 
342
+ // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
343
+ // tooltip listeners attach to the input container (which already exists) and
344
+ // the text input stays perceivable via aria-disabled instead of the disabled
345
+ // attribute. Typing is blocked with readOnly and value mutation guards;
346
+ // calendar activation is blocked by the isEffectivelyDisabled guards. Only
347
+ // the persistent isDisabled state (not the transient busy state) surfaces a
348
+ // reason.
349
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
350
+ const disabledMessageTooltip = useTooltip({
351
+ placement: 'above',
352
+ // The container div is not naturally focusable; focusin bubbles up from
353
+ // the input, so always attach focus listeners.
354
+ focusTrigger: 'always',
355
+ isEnabled: showsDisabledMessage,
356
+ });
357
+
314
358
  // Status icon mapping
315
359
  const statusIconMap: Record<InputStatusType, IconName> = {
316
360
  warning: 'warning',
@@ -330,13 +374,15 @@ export function DateInput({
330
374
  // Constraint checking for text input validation (reuses calendar logic)
331
375
  const {isDateDisabled} = useCalendarConstraints({min, max, dateConstraints});
332
376
 
333
- const ariaDescribedBy =
377
+ const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
378
+ inputLabelID,
334
379
  [
335
380
  description ? descriptionID : null,
336
381
  status?.message ? statusMessageID : null,
337
- ]
338
- .filter(Boolean)
339
- .join(' ') || undefined;
382
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
383
+ ],
384
+ inputGroup,
385
+ );
340
386
 
341
387
  // Pending input while user is typing (null = show formatted value)
342
388
  const [pendingInput, setPendingInput] = useState<string | null>(null);
@@ -428,6 +474,11 @@ export function DateInput({
428
474
  // Handle input text change - update immediately if valid and allowed
429
475
  const handleInputChange = useCallback(
430
476
  (e: React.ChangeEvent<HTMLInputElement>) => {
477
+ // With a disabledMessage the input drops `disabled` for focusability, so
478
+ // guard value mutation explicitly (readOnly also blocks typing).
479
+ if (isEffectivelyDisabled) {
480
+ return;
481
+ }
431
482
  const newValue = e.target.value;
432
483
  setPendingInput(newValue);
433
484
 
@@ -445,7 +496,7 @@ export function DateInput({
445
496
  calendarRef.current?.navigateTo(parsedISO);
446
497
  }
447
498
  },
448
- [value, fireChange, isDateDisabled],
499
+ [value, fireChange, isDateDisabled, isEffectivelyDisabled],
449
500
  );
450
501
 
451
502
  // Commit pending input (shared by blur and Enter key)
@@ -483,14 +534,144 @@ export function DateInput({
483
534
  if (e.key === 'Escape' && popover.isOpen) {
484
535
  e.preventDefault();
485
536
  popover.hide();
537
+ } else if (
538
+ (e.key === 'ArrowDown' || (e.altKey && e.key === 'ArrowDown')) &&
539
+ !popover.isOpen
540
+ ) {
541
+ // APG combobox: ArrowDown (and Alt+ArrowDown) opens the calendar
542
+ // popover from the keyboard, keeping focus in the input (forms-13).
543
+ e.preventDefault();
544
+ if (!isEffectivelyDisabled) {
545
+ popover.show({skipAutoFocus: true});
546
+ }
486
547
  } else if (e.key === 'Enter') {
487
548
  e.preventDefault();
488
549
  commitPendingInput();
489
550
  }
490
551
  },
491
- [popover, commitPendingInput],
552
+ [popover, commitPendingInput, isEffectivelyDisabled],
553
+ );
554
+
555
+ const inputWrapper = (
556
+ <div
557
+ ref={el => {
558
+ popover.triggerRef(el);
559
+ // Anchor + hover/focus listeners for the disabled-message tooltip.
560
+ // Handlers are gated internally by isEnabled, and anchor names
561
+ // compose, so attaching unconditionally is safe.
562
+ disabledMessageTooltip.ref(el);
563
+ }}
564
+ {...rest}
565
+ {...mergeProps(
566
+ themeProps('date-input', {size, status: status?.type ?? null}),
567
+ stylex.props(
568
+ inputWrapperStyles.base,
569
+ sizeStyles[size],
570
+ isEffectivelyDisabled && inputWrapperStyles.disabled,
571
+ status && inputStatusBorderStyles[status.type],
572
+ status && inputStatusHoverShadowStyles[status.type],
573
+ status && inputStatusFocusWithinStyles[status.type],
574
+ inputGroup && groupStyles.inGroup,
575
+ xstyle,
576
+ ),
577
+ className,
578
+ style,
579
+ )}>
580
+ {inputGroup && <VisuallyHidden id={inputLabelID}>{label}</VisuallyHidden>}
581
+ <button
582
+ type="button"
583
+ onClick={handleToggle}
584
+ disabled={isEffectivelyDisabled}
585
+ aria-label={popover.isOpen ? 'Close calendar' : 'Open calendar'}
586
+ {...stylex.props(
587
+ styles.iconButton,
588
+ isEffectivelyDisabled && styles.iconButtonDisabled,
589
+ )}>
590
+ <Icon icon="calendar" size="sm" color="secondary" />
591
+ </button>
592
+ <input
593
+ ref={mergeRefs(ref, inputRef)}
594
+ id={id}
595
+ type="text"
596
+ role="combobox"
597
+ value={displayValue}
598
+ onChange={handleInputChange}
599
+ onBlur={handleBlur}
600
+ onClick={handleInputClick}
601
+ onKeyDown={handleInputKeyDown}
602
+ placeholder={placeholder}
603
+ // With a disabledMessage the input keeps focusability via
604
+ // aria-disabled so the reason is focus-discoverable; typing is
605
+ // blocked with readOnly and the mutation guards, and calendar
606
+ // activation is blocked by the isEffectivelyDisabled guards.
607
+ disabled={isEffectivelyDisabled && !showsDisabledMessage}
608
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
609
+ readOnly={showsDisabledMessage || undefined}
610
+ aria-labelledby={ariaLabelledBy}
611
+ aria-describedby={ariaDescribedBy}
612
+ aria-required={isRequired === true ? 'true' : undefined}
613
+ aria-invalid={
614
+ status?.type === 'error' || !isInputValid ? 'true' : undefined
615
+ }
616
+ aria-busy={isBusy || undefined}
617
+ aria-expanded={popover.isOpen}
618
+ aria-haspopup="dialog"
619
+ aria-controls={popover.isOpen ? popover.id : undefined}
620
+ aria-autocomplete="none"
621
+ autoComplete="off"
622
+ {...stylex.props(
623
+ styles.input,
624
+ isEffectivelyDisabled && styles.inputDisabled,
625
+ !isInputValid && styles.inputInvalid,
626
+ )}
627
+ />
628
+ {/*
629
+ Live region announcing invalid typed input to assistive technology.
630
+ The value silently reverts on blur, so without this a screen-reader
631
+ user would get no feedback that their entry was rejected (WCAG 3.3.1).
632
+ */}
633
+ <VisuallyHidden as="div" role="alert" aria-live="assertive">
634
+ {!isInputValid ? 'Invalid date' : ''}
635
+ </VisuallyHidden>
636
+ {hasClear && value !== undefined && !isEffectivelyDisabled && (
637
+ <button
638
+ type="button"
639
+ onClick={handleClear}
640
+ aria-label={`Clear ${label}`}
641
+ {...stylex.props(styles.iconButton)}>
642
+ <Icon icon="close" size="sm" color="secondary" />
643
+ </button>
644
+ )}
645
+ {isBusy && <Spinner size="sm" />}
646
+ {status && !inputGroup && (
647
+ <Icon
648
+ icon={statusIconMap[status.type]}
649
+ size="md"
650
+ color={statusIconColorMap[status.type]}
651
+ />
652
+ )}
653
+ {popover.render(
654
+ <Calendar
655
+ handleRef={calendarRef}
656
+ mode="single"
657
+ value={optimisticValue}
658
+ onChange={handleDateSelect}
659
+ min={min}
660
+ max={max}
661
+ dateConstraints={dateConstraints}
662
+ numberOfMonths={numberOfMonths}
663
+ />,
664
+ {placement: 'below', alignment: 'start'},
665
+ )}
666
+ {showsDisabledMessage &&
667
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
668
+ </div>
492
669
  );
493
670
 
671
+ if (inputGroup) {
672
+ return inputWrapper;
673
+ }
674
+
494
675
  return (
495
676
  <Field
496
677
  label={label}
@@ -512,92 +693,7 @@ export function DateInput({
512
693
  }
513
694
  labelTooltip={labelTooltip}
514
695
  width={width}>
515
- <div
516
- ref={popover.triggerRef}
517
- {...rest}
518
- {...mergeProps(
519
- themeProps('date-input', {size, status: status?.type ?? null}),
520
- stylex.props(
521
- inputWrapperStyles.base,
522
- sizeStyles[size],
523
- isEffectivelyDisabled && inputWrapperStyles.disabled,
524
- status && inputStatusBorderStyles[status.type],
525
- status && inputStatusHoverShadowStyles[status.type],
526
- status && inputStatusFocusWithinStyles[status.type],
527
- xstyle,
528
- ),
529
- className,
530
- style,
531
- )}>
532
- <button
533
- type="button"
534
- onClick={handleToggle}
535
- disabled={isEffectivelyDisabled}
536
- aria-label={popover.isOpen ? 'Close calendar' : 'Open calendar'}
537
- {...stylex.props(
538
- styles.iconButton,
539
- isEffectivelyDisabled && styles.iconButtonDisabled,
540
- )}>
541
- <Icon icon="calendar" size="sm" color="secondary" />
542
- </button>
543
- <input
544
- ref={mergeRefs(ref, inputRef)}
545
- id={id}
546
- type="text"
547
- role="combobox"
548
- value={displayValue}
549
- onChange={handleInputChange}
550
- onBlur={handleBlur}
551
- onClick={handleInputClick}
552
- onKeyDown={handleInputKeyDown}
553
- placeholder={placeholder}
554
- disabled={isEffectivelyDisabled}
555
- aria-describedby={ariaDescribedBy}
556
- aria-required={isRequired === true ? 'true' : undefined}
557
- aria-invalid={status?.type === 'error' ? 'true' : undefined}
558
- aria-busy={isBusy || undefined}
559
- aria-expanded={popover.isOpen}
560
- aria-haspopup="dialog"
561
- aria-controls={popover.isOpen ? popover.id : undefined}
562
- aria-autocomplete="none"
563
- autoComplete="off"
564
- {...stylex.props(
565
- styles.input,
566
- isEffectivelyDisabled && styles.inputDisabled,
567
- !isInputValid && styles.inputInvalid,
568
- )}
569
- />
570
- {hasClear && value !== undefined && !isEffectivelyDisabled && (
571
- <button
572
- type="button"
573
- onClick={handleClear}
574
- aria-label={`Clear ${label}`}
575
- {...stylex.props(styles.iconButton)}>
576
- <Icon icon="close" size="sm" color="secondary" />
577
- </button>
578
- )}
579
- {isBusy && <Spinner size="sm" />}
580
- {status && (
581
- <Icon
582
- icon={statusIconMap[status.type]}
583
- size="md"
584
- color={statusIconColorMap[status.type]}
585
- />
586
- )}
587
- </div>
588
- {popover.render(
589
- <Calendar
590
- handleRef={calendarRef}
591
- mode="single"
592
- value={optimisticValue}
593
- onChange={handleDateSelect}
594
- min={min}
595
- max={max}
596
- dateConstraints={dateConstraints}
597
- numberOfMonths={numberOfMonths}
598
- />,
599
- {placement: 'below', alignment: 'start'},
600
- )}
696
+ {inputWrapper}
601
697
  </Field>
602
698
  );
603
699
  }