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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (567) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +12 -12
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +11 -5
  5. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +2 -4
  10. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  11. package/dist/ButtonGroup/ButtonGroup.js +0 -1
  12. package/dist/Calendar/Calendar.d.ts +5 -4
  13. package/dist/Calendar/Calendar.d.ts.map +1 -1
  14. package/dist/Calendar/Calendar.js +82 -43
  15. package/dist/Calendar/hooks/index.d.ts +0 -2
  16. package/dist/Calendar/hooks/index.d.ts.map +1 -1
  17. package/dist/Calendar/hooks/index.js +1 -2
  18. package/dist/Calendar/index.d.ts +3 -3
  19. package/dist/Calendar/index.d.ts.map +1 -1
  20. package/dist/Calendar/index.js +1 -1
  21. package/dist/Calendar/styles.d.ts +3 -9
  22. package/dist/Calendar/styles.d.ts.map +1 -1
  23. package/dist/Calendar/styles.js +3 -11
  24. package/dist/Carousel/Carousel.d.ts.map +1 -1
  25. package/dist/Carousel/Carousel.js +17 -3
  26. package/dist/Center/Center.d.ts +11 -1
  27. package/dist/Center/Center.d.ts.map +1 -1
  28. package/dist/Center/Center.js +9 -3
  29. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  30. package/dist/Chat/ChatComposerInput.js +0 -1
  31. package/dist/Chat/ChatMessageList.d.ts +14 -1
  32. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  33. package/dist/Chat/ChatMessageList.js +2 -0
  34. package/dist/Chat/useTriggerMenu.d.ts +8 -1
  35. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  36. package/dist/Chat/useTriggerMenu.js +15 -3
  37. package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +51 -9
  40. package/dist/CheckboxList/CheckboxList.d.ts +13 -1
  41. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  42. package/dist/CheckboxList/CheckboxList.js +39 -9
  43. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  44. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  45. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  46. package/dist/CheckboxList/CheckboxListItem.js +0 -1
  47. package/dist/Citation/Citation.d.ts.map +1 -1
  48. package/dist/Citation/Citation.js +9 -5
  49. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  50. package/dist/CodeBlock/CodeBlock.js +51 -37
  51. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  52. package/dist/CommandPalette/CommandPalette.js +5 -5
  53. package/dist/ContextMenu/ContextMenu.d.ts +4 -5
  54. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  55. package/dist/ContextMenu/ContextMenu.js +93 -11
  56. package/dist/DateInput/DateInput.d.ts +23 -1
  57. package/dist/DateInput/DateInput.d.ts.map +1 -1
  58. package/dist/DateInput/DateInput.js +167 -99
  59. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  60. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  61. package/dist/DateRangeInput/DateRangeInput.js +42 -9
  62. package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
  63. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  64. package/dist/DateTimeInput/DateTimeInput.js +78 -15
  65. package/dist/Dialog/Dialog.d.ts.map +1 -1
  66. package/dist/Dialog/Dialog.js +12 -0
  67. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
  68. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  69. package/dist/DropdownMenu/DropdownMenu.js +42 -14
  70. package/dist/Field/Field.d.ts +19 -2
  71. package/dist/Field/Field.d.ts.map +1 -1
  72. package/dist/Field/Field.js +4 -0
  73. package/dist/Field/FieldLabel.d.ts +21 -2
  74. package/dist/Field/FieldLabel.d.ts.map +1 -1
  75. package/dist/Field/FieldLabel.js +41 -26
  76. package/dist/FileInput/FileInput.d.ts +22 -1
  77. package/dist/FileInput/FileInput.d.ts.map +1 -1
  78. package/dist/FileInput/FileInput.js +56 -10
  79. package/dist/Grid/Grid.d.ts +11 -1
  80. package/dist/Grid/Grid.d.ts.map +1 -1
  81. package/dist/Grid/Grid.js +32 -6
  82. package/dist/InputGroup/InputGroup.d.ts +4 -3
  83. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  84. package/dist/InputGroup/InputGroup.js +16 -6
  85. package/dist/InputGroup/InputGroupContext.d.ts +4 -0
  86. package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
  87. package/dist/InputGroup/InputGroupContext.js +2 -2
  88. package/dist/InputGroup/groupStyles.d.ts +1 -1
  89. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  90. package/dist/InputGroup/groupStyles.js +3 -3
  91. package/dist/InputGroup/index.d.ts +1 -0
  92. package/dist/InputGroup/index.d.ts.map +1 -1
  93. package/dist/Kbd/Kbd.d.ts.map +1 -1
  94. package/dist/Kbd/Kbd.js +33 -1
  95. package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
  96. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  97. package/dist/Layer/layerAnimations.stylex.js +8 -4
  98. package/dist/Layer/useLayer.d.ts +10 -0
  99. package/dist/Layer/useLayer.d.ts.map +1 -1
  100. package/dist/Layer/useLayer.js +64 -9
  101. package/dist/Layout/Layout.d.ts +4 -11
  102. package/dist/Layout/Layout.d.ts.map +1 -1
  103. package/dist/Layout/Layout.js +10 -11
  104. package/dist/Layout/LayoutFooter.d.ts +2 -2
  105. package/dist/Layout/LayoutFooter.d.ts.map +1 -1
  106. package/dist/Layout/LayoutHeader.d.ts +2 -2
  107. package/dist/Layout/LayoutHeader.d.ts.map +1 -1
  108. package/dist/Layout/LayoutPanel.d.ts +2 -2
  109. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  110. package/dist/Layout/padding.stylex.d.ts +51 -0
  111. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  112. package/dist/Layout/padding.stylex.js +63 -0
  113. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  114. package/dist/Lightbox/Lightbox.js +4 -2
  115. package/dist/Link/Link.d.ts +16 -1
  116. package/dist/Link/Link.d.ts.map +1 -1
  117. package/dist/Link/Link.js +18 -7
  118. package/dist/Markdown/Markdown.d.ts.map +1 -1
  119. package/dist/Markdown/Markdown.js +7 -0
  120. package/dist/MoreMenu/MoreMenu.d.ts +1 -7
  121. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  122. package/dist/MoreMenu/MoreMenu.js +0 -2
  123. package/dist/MultiSelector/MultiSelector.d.ts +27 -2
  124. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  125. package/dist/MultiSelector/MultiSelector.js +141 -42
  126. package/dist/MultiSelector/hooks.d.ts +12 -1
  127. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  128. package/dist/MultiSelector/hooks.js +14 -1
  129. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  130. package/dist/NavMenu/NavHeadingMenu.js +34 -3
  131. package/dist/NumberInput/NumberInput.d.ts +23 -2
  132. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  133. package/dist/NumberInput/NumberInput.js +62 -12
  134. package/dist/Pagination/Pagination.d.ts +1 -1
  135. package/dist/Pagination/Pagination.d.ts.map +1 -1
  136. package/dist/Pagination/Pagination.js +15 -1
  137. package/dist/Popover/Popover.d.ts +18 -2
  138. package/dist/Popover/Popover.d.ts.map +1 -1
  139. package/dist/Popover/Popover.js +5 -2
  140. package/dist/Popover/usePopover.d.ts +38 -3
  141. package/dist/Popover/usePopover.d.ts.map +1 -1
  142. package/dist/Popover/usePopover.js +24 -9
  143. package/dist/PowerSearch/PowerSearch.d.ts +11 -1
  144. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  145. package/dist/PowerSearch/PowerSearch.js +6 -1
  146. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  147. package/dist/ProgressBar/ProgressBar.js +3 -5
  148. package/dist/RadioList/RadioList.d.ts +20 -2
  149. package/dist/RadioList/RadioList.d.ts.map +1 -1
  150. package/dist/RadioList/RadioList.js +105 -9
  151. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  152. package/dist/RadioList/RadioListItem.js +13 -2
  153. package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
  154. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  155. package/dist/SegmentedControl/SegmentedControl.js +78 -44
  156. package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
  157. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  158. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  159. package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
  160. package/dist/Selector/Selector.d.ts +24 -1
  161. package/dist/Selector/Selector.d.ts.map +1 -1
  162. package/dist/Selector/Selector.js +122 -43
  163. package/dist/Selector/hooks.d.ts +8 -1
  164. package/dist/Selector/hooks.d.ts.map +1 -1
  165. package/dist/Selector/hooks.js +14 -1
  166. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  167. package/dist/SideNav/SideNavHeading.js +2 -0
  168. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  169. package/dist/SideNav/SideNavItem.js +1 -1
  170. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  171. package/dist/Skeleton/Skeleton.js +8 -2
  172. package/dist/Slider/Slider.d.ts +21 -0
  173. package/dist/Slider/Slider.d.ts.map +1 -1
  174. package/dist/Slider/Slider.js +36 -9
  175. package/dist/Spinner/Spinner.d.ts.map +1 -1
  176. package/dist/Spinner/Spinner.js +1 -1
  177. package/dist/Stack/Stack.d.ts +38 -23
  178. package/dist/Stack/Stack.d.ts.map +1 -1
  179. package/dist/Stack/Stack.js +25 -1
  180. package/dist/Stack/StackItem.d.ts +12 -23
  181. package/dist/Stack/StackItem.d.ts.map +1 -1
  182. package/dist/Stack/StackItem.js +8 -1
  183. package/dist/Switch/Switch.d.ts +23 -2
  184. package/dist/Switch/Switch.d.ts.map +1 -1
  185. package/dist/Switch/Switch.js +41 -11
  186. package/dist/TabList/Tab.d.ts.map +1 -1
  187. package/dist/TabList/Tab.js +6 -0
  188. package/dist/TabList/TabList.d.ts +16 -4
  189. package/dist/TabList/TabList.d.ts.map +1 -1
  190. package/dist/TabList/TabList.js +87 -10
  191. package/dist/TabList/TabListContext.d.ts +5 -0
  192. package/dist/TabList/TabListContext.d.ts.map +1 -1
  193. package/dist/TabList/TabListContext.js +5 -0
  194. package/dist/TabList/TabMenu.d.ts.map +1 -1
  195. package/dist/TabList/TabMenu.js +6 -1
  196. package/dist/Table/BaseTable.d.ts.map +1 -1
  197. package/dist/Table/BaseTable.js +11 -8
  198. package/dist/Table/Table.d.ts.map +1 -1
  199. package/dist/Table/Table.js +10 -1
  200. package/dist/Table/TableCell.d.ts +8 -1
  201. package/dist/Table/TableCell.d.ts.map +1 -1
  202. package/dist/Table/TableCell.js +10 -10
  203. package/dist/Table/TableHeaderCell.d.ts +8 -1
  204. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  205. package/dist/Table/TableHeaderCell.js +16 -16
  206. package/dist/Table/index.d.ts +2 -1
  207. package/dist/Table/index.d.ts.map +1 -1
  208. package/dist/Table/index.js +2 -2
  209. package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
  210. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  211. package/dist/Table/plugins/pagination/paginateData.js +8 -3
  212. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  213. package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
  214. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  215. package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
  216. package/dist/Table/tableContextMenu.d.ts +33 -0
  217. package/dist/Table/tableContextMenu.d.ts.map +1 -0
  218. package/dist/Table/tableContextMenu.js +133 -0
  219. package/dist/Table/types.d.ts +66 -0
  220. package/dist/Table/types.d.ts.map +1 -1
  221. package/dist/TextArea/TextArea.d.ts +22 -1
  222. package/dist/TextArea/TextArea.d.ts.map +1 -1
  223. package/dist/TextArea/TextArea.js +41 -9
  224. package/dist/TextInput/TextInput.d.ts +23 -2
  225. package/dist/TextInput/TextInput.d.ts.map +1 -1
  226. package/dist/TextInput/TextInput.js +55 -10
  227. package/dist/TimeInput/TimeInput.d.ts +23 -1
  228. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  229. package/dist/TimeInput/TimeInput.js +147 -76
  230. package/dist/Toast/Toast.d.ts.map +1 -1
  231. package/dist/Toast/Toast.js +14 -0
  232. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  233. package/dist/Toast/ToastViewport.js +131 -3
  234. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  235. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  236. package/dist/Tokenizer/Tokenizer.js +26 -4
  237. package/dist/Toolbar/Toolbar.d.ts +3 -3
  238. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  239. package/dist/Toolbar/Toolbar.js +51 -7
  240. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  241. package/dist/Tooltip/useTooltip.js +56 -9
  242. package/dist/TopNav/TopNavHeading.d.ts +7 -1
  243. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  244. package/dist/TopNav/TopNavHeading.js +8 -0
  245. package/dist/TreeList/TreeList.d.ts +1 -1
  246. package/dist/TreeList/TreeList.d.ts.map +1 -1
  247. package/dist/TreeList/TreeList.js +72 -2
  248. package/dist/TreeList/TreeListItem.d.ts +11 -1
  249. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  250. package/dist/TreeList/TreeListItem.js +51 -12
  251. package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
  252. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
  253. package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
  254. package/dist/Typeahead/BaseTypeahead.d.ts +13 -0
  255. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  256. package/dist/Typeahead/BaseTypeahead.js +69 -6
  257. package/dist/Typeahead/Typeahead.d.ts +26 -3
  258. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  259. package/dist/Typeahead/Typeahead.js +65 -29
  260. package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
  261. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  262. package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
  263. package/dist/VisuallyHidden/index.d.ts +11 -0
  264. package/dist/VisuallyHidden/index.d.ts.map +1 -0
  265. package/dist/VisuallyHidden/index.js +13 -0
  266. package/dist/astryx.css +18 -0
  267. package/dist/astryx.umd.js +55 -55
  268. package/dist/astryx.umd.js.map +4 -4
  269. package/dist/hooks/focusableSelector.d.ts +22 -0
  270. package/dist/hooks/focusableSelector.d.ts.map +1 -0
  271. package/dist/hooks/focusableSelector.js +24 -0
  272. package/dist/hooks/index.d.ts +12 -2
  273. package/dist/hooks/index.d.ts.map +1 -1
  274. package/dist/hooks/index.js +7 -2
  275. package/dist/hooks/useAnnounce.d.ts +40 -0
  276. package/dist/hooks/useAnnounce.d.ts.map +1 -0
  277. package/dist/hooks/useAnnounce.js +142 -0
  278. package/dist/hooks/useClickableContainer.d.ts +16 -1
  279. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  280. package/dist/hooks/useClickableContainer.js +16 -4
  281. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  282. package/dist/hooks/useEntryAnimation.js +4 -4
  283. package/dist/hooks/useFocusTrap.d.ts +18 -0
  284. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  285. package/dist/hooks/useFocusTrap.js +87 -3
  286. package/dist/hooks/useGridFocus.d.ts +88 -1
  287. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  288. package/dist/hooks/useGridFocus.js +261 -62
  289. package/dist/hooks/useKeyboardHint.d.ts +82 -0
  290. package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
  291. package/dist/hooks/useKeyboardHint.js +220 -0
  292. package/dist/hooks/useListFocus.d.ts +78 -7
  293. package/dist/hooks/useListFocus.d.ts.map +1 -1
  294. package/dist/hooks/useListFocus.js +305 -52
  295. package/dist/hooks/useLongPress.d.ts +21 -0
  296. package/dist/hooks/useLongPress.d.ts.map +1 -0
  297. package/dist/hooks/useLongPress.js +89 -0
  298. package/dist/hooks/useTreeFocus.d.ts +157 -0
  299. package/dist/hooks/useTreeFocus.d.ts.map +1 -0
  300. package/dist/hooks/useTreeFocus.js +337 -0
  301. package/dist/hooks/useTypeahead.d.ts +63 -0
  302. package/dist/hooks/useTypeahead.d.ts.map +1 -0
  303. package/dist/hooks/useTypeahead.js +117 -0
  304. package/dist/index.d.ts +1 -0
  305. package/dist/index.d.ts.map +1 -1
  306. package/dist/index.js +1 -0
  307. package/dist/utils/dateTypes.d.ts +12 -0
  308. package/dist/utils/dateTypes.d.ts.map +1 -1
  309. package/dist/utils/dateTypes.js +41 -1
  310. package/dist/utils/index.d.ts +4 -0
  311. package/dist/utils/index.d.ts.map +1 -1
  312. package/dist/utils/index.js +3 -1
  313. package/dist/utils/inputAria.d.ts +29 -0
  314. package/dist/utils/inputAria.d.ts.map +1 -0
  315. package/dist/utils/inputAria.js +30 -0
  316. package/dist/utils/timeParser.d.ts.map +1 -1
  317. package/dist/utils/timeParser.js +5 -2
  318. package/docs.mjs +2 -2
  319. package/package.json +12 -8
  320. package/src/Avatar/Avatar.test.tsx +37 -0
  321. package/src/Avatar/Avatar.tsx +10 -5
  322. package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
  323. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
  324. package/src/Button/Button.tsx +3 -16
  325. package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
  326. package/src/ButtonGroup/ButtonGroup.tsx +0 -1
  327. package/src/Calendar/Calendar.doc.mjs +4 -4
  328. package/src/Calendar/Calendar.test.tsx +168 -30
  329. package/src/Calendar/Calendar.tsx +121 -66
  330. package/src/Calendar/hooks/index.ts +0 -6
  331. package/src/Calendar/index.ts +1 -3
  332. package/src/Calendar/styles.ts +4 -9
  333. package/src/Carousel/Carousel.doc.mjs +7 -1
  334. package/src/Carousel/Carousel.test.tsx +20 -0
  335. package/src/Carousel/Carousel.tsx +16 -1
  336. package/src/Center/Center.doc.mjs +12 -2
  337. package/src/Center/Center.tsx +28 -3
  338. package/src/Chat/ChatComposerInput.test.tsx +26 -7
  339. package/src/Chat/ChatComposerInput.tsx +0 -1
  340. package/src/Chat/ChatDictationButton.doc.mjs +1 -1
  341. package/src/Chat/ChatMessageList.doc.mjs +8 -0
  342. package/src/Chat/ChatMessageList.test.tsx +18 -0
  343. package/src/Chat/ChatMessageList.tsx +16 -0
  344. package/src/Chat/useTriggerMenu.tsx +22 -4
  345. package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
  346. package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
  347. package/src/CheckboxInput/CheckboxInput.tsx +78 -6
  348. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  349. package/src/CheckboxList/CheckboxList.test.tsx +192 -26
  350. package/src/CheckboxList/CheckboxList.tsx +56 -3
  351. package/src/CheckboxList/CheckboxListContext.tsx +7 -0
  352. package/src/CheckboxList/CheckboxListItem.tsx +0 -3
  353. package/src/Citation/Citation.doc.mjs +10 -0
  354. package/src/Citation/Citation.test.tsx +121 -0
  355. package/src/Citation/Citation.tsx +15 -3
  356. package/src/CodeBlock/CodeBlock.test.tsx +115 -0
  357. package/src/CodeBlock/CodeBlock.tsx +63 -33
  358. package/src/CommandPalette/CommandPalette.test.tsx +47 -1
  359. package/src/CommandPalette/CommandPalette.tsx +9 -21
  360. package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
  361. package/src/ContextMenu/ContextMenu.test.tsx +155 -1
  362. package/src/ContextMenu/ContextMenu.tsx +113 -16
  363. package/src/DateInput/DateInput.doc.mjs +286 -56
  364. package/src/DateInput/DateInput.test.tsx +248 -2
  365. package/src/DateInput/DateInput.tsx +196 -100
  366. package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
  367. package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
  368. package/src/DateRangeInput/DateRangeInput.tsx +63 -5
  369. package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
  370. package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
  371. package/src/DateTimeInput/DateTimeInput.tsx +125 -17
  372. package/src/Dialog/Dialog.doc.mjs +2 -2
  373. package/src/Dialog/Dialog.tsx +12 -0
  374. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
  375. package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
  376. package/src/DropdownMenu/DropdownMenu.tsx +52 -18
  377. package/src/Field/Field.doc.mjs +59 -18
  378. package/src/Field/Field.test.tsx +32 -0
  379. package/src/Field/Field.tsx +23 -5
  380. package/src/Field/FieldLabel.tsx +55 -17
  381. package/src/FileInput/FileInput.doc.mjs +17 -0
  382. package/src/FileInput/FileInput.test.tsx +283 -49
  383. package/src/FileInput/FileInput.tsx +76 -4
  384. package/src/Grid/Grid.doc.mjs +17 -4
  385. package/src/Grid/Grid.test.tsx +43 -30
  386. package/src/Grid/Grid.tsx +38 -3
  387. package/src/Icon/Icon.doc.mjs +3 -3
  388. package/src/InputGroup/InputGroup.doc.mjs +111 -23
  389. package/src/InputGroup/InputGroup.test.tsx +257 -60
  390. package/src/InputGroup/InputGroup.tsx +23 -5
  391. package/src/InputGroup/InputGroupContext.ts +9 -4
  392. package/src/InputGroup/groupStyles.ts +7 -1
  393. package/src/InputGroup/index.ts +1 -0
  394. package/src/Kbd/Kbd.test.tsx +22 -4
  395. package/src/Kbd/Kbd.tsx +35 -3
  396. package/src/Layer/layerAnimations.stylex.ts +20 -4
  397. package/src/Layer/useLayer.doc.mjs +5 -0
  398. package/src/Layer/useLayer.test.tsx +111 -0
  399. package/src/Layer/useLayer.tsx +90 -9
  400. package/src/Layout/Layout.tsx +8 -16
  401. package/src/Layout/LayoutFooter.doc.mjs +4 -4
  402. package/src/Layout/LayoutFooter.tsx +3 -3
  403. package/src/Layout/LayoutHeader.doc.mjs +4 -4
  404. package/src/Layout/LayoutHeader.tsx +3 -3
  405. package/src/Layout/LayoutPanel.tsx +3 -3
  406. package/src/Layout/padding.stylex.ts +52 -0
  407. package/src/Lightbox/Lightbox.doc.mjs +9 -0
  408. package/src/Lightbox/Lightbox.test.tsx +53 -5
  409. package/src/Lightbox/Lightbox.tsx +9 -4
  410. package/src/Link/Link.doc.mjs +14 -0
  411. package/src/Link/Link.test.tsx +50 -0
  412. package/src/Link/Link.tsx +39 -4
  413. package/src/Markdown/Markdown.test.tsx +11 -0
  414. package/src/Markdown/Markdown.tsx +7 -0
  415. package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
  416. package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
  417. package/src/MoreMenu/MoreMenu.tsx +0 -9
  418. package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
  419. package/src/MultiSelector/MultiSelector.test.tsx +375 -8
  420. package/src/MultiSelector/MultiSelector.tsx +194 -48
  421. package/src/MultiSelector/hooks.ts +27 -0
  422. package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
  423. package/src/NavMenu/NavHeadingMenu.tsx +50 -3
  424. package/src/NumberInput/NumberInput.doc.mjs +17 -0
  425. package/src/NumberInput/NumberInput.test.tsx +242 -40
  426. package/src/NumberInput/NumberInput.tsx +89 -11
  427. package/src/Pagination/Pagination.doc.mjs +3 -3
  428. package/src/Pagination/Pagination.test.tsx +117 -5
  429. package/src/Pagination/Pagination.tsx +21 -1
  430. package/src/Popover/Popover.doc.mjs +26 -0
  431. package/src/Popover/Popover.test.tsx +121 -0
  432. package/src/Popover/Popover.tsx +23 -2
  433. package/src/Popover/usePopover.doc.mjs +9 -3
  434. package/src/Popover/usePopover.tsx +70 -12
  435. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  436. package/src/PowerSearch/PowerSearch.test.tsx +97 -1
  437. package/src/PowerSearch/PowerSearch.tsx +16 -3
  438. package/src/ProgressBar/ProgressBar.test.tsx +34 -26
  439. package/src/ProgressBar/ProgressBar.tsx +3 -4
  440. package/src/RadioList/RadioList.doc.mjs +9 -0
  441. package/src/RadioList/RadioList.test.tsx +252 -16
  442. package/src/RadioList/RadioList.tsx +166 -10
  443. package/src/RadioList/RadioListItem.tsx +14 -2
  444. package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
  445. package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
  446. package/src/SegmentedControl/SegmentedControl.tsx +102 -54
  447. package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
  448. package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
  449. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  450. package/src/Selector/Selector.doc.mjs +26 -0
  451. package/src/Selector/Selector.test.tsx +387 -15
  452. package/src/Selector/Selector.tsx +175 -67
  453. package/src/Selector/hooks.ts +23 -0
  454. package/src/SideNav/SideNav.test.tsx +48 -0
  455. package/src/SideNav/SideNavHeading.tsx +8 -2
  456. package/src/SideNav/SideNavItem.tsx +2 -4
  457. package/src/Skeleton/Skeleton.test.tsx +25 -0
  458. package/src/Skeleton/Skeleton.tsx +11 -1
  459. package/src/Slider/Slider.doc.mjs +15 -0
  460. package/src/Slider/Slider.test.tsx +155 -2
  461. package/src/Slider/Slider.tsx +53 -5
  462. package/src/Spinner/Spinner.tsx +7 -1
  463. package/src/Stack/Stack.doc.mjs +77 -0
  464. package/src/Stack/Stack.test.tsx +55 -5
  465. package/src/Stack/Stack.tsx +73 -24
  466. package/src/Stack/StackItem.test.tsx +23 -0
  467. package/src/Stack/StackItem.tsx +20 -23
  468. package/src/Switch/Switch.doc.mjs +15 -0
  469. package/src/Switch/Switch.test.tsx +162 -27
  470. package/src/Switch/Switch.tsx +61 -21
  471. package/src/TabList/Tab.tsx +6 -0
  472. package/src/TabList/TabList.doc.mjs +6 -0
  473. package/src/TabList/TabList.test.tsx +251 -0
  474. package/src/TabList/TabList.tsx +103 -12
  475. package/src/TabList/TabListContext.ts +7 -3
  476. package/src/TabList/TabMenu.tsx +6 -3
  477. package/src/Table/BaseTable.tsx +21 -17
  478. package/src/Table/Table.doc.mjs +2 -0
  479. package/src/Table/Table.test.tsx +10 -0
  480. package/src/Table/Table.tsx +8 -0
  481. package/src/Table/TableCell.tsx +25 -23
  482. package/src/Table/TableHeaderCell.tsx +28 -29
  483. package/src/Table/index.ts +3 -2
  484. package/src/Table/plugins/pagination/paginateData.ts +10 -3
  485. package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
  486. package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
  487. package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
  488. package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
  489. package/src/Table/tableContextMenu.test.tsx +157 -0
  490. package/src/Table/tableContextMenu.tsx +142 -0
  491. package/src/Table/types.ts +74 -0
  492. package/src/Table/useTableColumnResize.doc.mjs +3 -3
  493. package/src/Table/useTableStickyColumns.doc.mjs +2 -2
  494. package/src/Text/Text.doc.mjs +18 -18
  495. package/src/TextArea/TextArea.doc.mjs +17 -0
  496. package/src/TextArea/TextArea.test.tsx +167 -35
  497. package/src/TextArea/TextArea.tsx +62 -15
  498. package/src/TextInput/TextInput.doc.mjs +17 -0
  499. package/src/TextInput/TextInput.test.tsx +168 -34
  500. package/src/TextInput/TextInput.tsx +77 -9
  501. package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
  502. package/src/TimeInput/TimeInput.doc.mjs +340 -60
  503. package/src/TimeInput/TimeInput.test.tsx +255 -7
  504. package/src/TimeInput/TimeInput.tsx +176 -70
  505. package/src/Timestamp/Timestamp.doc.mjs +19 -19
  506. package/src/Toast/Toast.tsx +14 -0
  507. package/src/Toast/ToastViewport.test.tsx +214 -0
  508. package/src/Toast/ToastViewport.tsx +167 -21
  509. package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
  510. package/src/Tokenizer/Tokenizer.test.tsx +105 -1
  511. package/src/Tokenizer/Tokenizer.tsx +37 -1
  512. package/src/Toolbar/Toolbar.test.tsx +109 -6
  513. package/src/Toolbar/Toolbar.tsx +59 -5
  514. package/src/Tooltip/Tooltip.test.tsx +68 -0
  515. package/src/Tooltip/useTooltip.tsx +55 -8
  516. package/src/TopNav/TopNav.test.tsx +50 -0
  517. package/src/TopNav/TopNavHeading.tsx +17 -1
  518. package/src/TreeList/TreeList.test.tsx +295 -6
  519. package/src/TreeList/TreeList.tsx +73 -1
  520. package/src/TreeList/TreeListItem.tsx +51 -5
  521. package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
  522. package/src/Typeahead/BaseTypeahead.tsx +93 -9
  523. package/src/Typeahead/Typeahead.doc.mjs +141 -30
  524. package/src/Typeahead/Typeahead.test.tsx +331 -1
  525. package/src/Typeahead/Typeahead.tsx +105 -33
  526. package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
  527. package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
  528. package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
  529. package/src/VisuallyHidden/index.ts +15 -0
  530. package/src/docs-script.test.mjs +50 -0
  531. package/src/hooks/focusableSelector.ts +25 -0
  532. package/src/hooks/index.ts +28 -2
  533. package/src/hooks/useAnnounce.test.tsx +101 -0
  534. package/src/hooks/useAnnounce.ts +168 -0
  535. package/src/hooks/useClickableContainer.ts +16 -4
  536. package/src/hooks/useEntryAnimation.ts +16 -4
  537. package/src/hooks/useFocusTrap.test.tsx +132 -0
  538. package/src/hooks/useFocusTrap.ts +98 -4
  539. package/src/hooks/useGridFocus.doc.mjs +40 -2
  540. package/src/hooks/useGridFocus.test.tsx +132 -0
  541. package/src/hooks/useGridFocus.ts +343 -67
  542. package/src/hooks/useKeyboardHint.doc.mjs +101 -0
  543. package/src/hooks/useKeyboardHint.test.tsx +70 -0
  544. package/src/hooks/useKeyboardHint.tsx +332 -0
  545. package/src/hooks/useListFocus.doc.mjs +50 -10
  546. package/src/hooks/useListFocus.test.tsx +375 -0
  547. package/src/hooks/useListFocus.ts +395 -57
  548. package/src/hooks/useLongPress.test.tsx +134 -0
  549. package/src/hooks/useLongPress.ts +119 -0
  550. package/src/hooks/useTreeFocus.doc.mjs +140 -0
  551. package/src/hooks/useTreeFocus.test.tsx +214 -0
  552. package/src/hooks/useTreeFocus.ts +559 -0
  553. package/src/hooks/useTypeahead.test.tsx +109 -0
  554. package/src/hooks/useTypeahead.ts +183 -0
  555. package/src/index.ts +1 -0
  556. package/src/theme/derivedVarRegistry.test.ts +10 -5
  557. package/src/utils/dateTypes.test.ts +40 -0
  558. package/src/utils/dateTypes.ts +40 -0
  559. package/src/utils/index.ts +9 -0
  560. package/src/utils/inputAria.test.ts +39 -0
  561. package/src/utils/inputAria.ts +58 -0
  562. package/src/utils/timeParser.test.ts +10 -0
  563. package/src/utils/timeParser.ts +5 -2
  564. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
  565. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
  566. package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
  567. package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
@@ -9,8 +9,9 @@
9
9
  * SYNC: When DateTimeInput.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 {DateTimeInput} from './DateTimeInput';
15
16
  import type {ISODateTimeString} from './DateTimeInput';
16
17
 
@@ -20,6 +21,25 @@ describe('DateTimeInput', () => {
20
21
  expect(screen.getByLabelText('Meeting time')).toBeInTheDocument();
21
22
  });
22
23
 
24
+ it('derives the time input label from the field label (forms-15)', () => {
25
+ render(<DateTimeInput label="Meeting time" onChange={() => {}} />);
26
+ // Not a hardcoded "Time" — tied to the field label so it is localizable
27
+ // and unambiguous when multiple date-time fields share a page.
28
+ expect(screen.getByLabelText('Meeting time time')).toBeInTheDocument();
29
+ expect(screen.queryByLabelText('Time')).not.toBeInTheDocument();
30
+ });
31
+
32
+ it('uses an explicit timeLabel when provided', () => {
33
+ render(
34
+ <DateTimeInput
35
+ label="Meeting time"
36
+ timeLabel="Start time"
37
+ onChange={() => {}}
38
+ />,
39
+ );
40
+ expect(screen.getByLabelText('Start time')).toBeInTheDocument();
41
+ });
42
+
23
43
  it('renders with placeholder', () => {
24
44
  render(
25
45
  <DateTimeInput
@@ -31,10 +51,32 @@ describe('DateTimeInput', () => {
31
51
  expect(screen.getByPlaceholderText('Pick a date')).toBeInTheDocument();
32
52
  });
33
53
 
54
+ it('defaults the time portion placeholder to "Select a time"', () => {
55
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
56
+ expect(screen.getByPlaceholderText('Select a time')).toBeInTheDocument();
57
+ });
58
+
59
+ it('applies a custom timePlaceholder to the time portion', () => {
60
+ render(
61
+ <DateTimeInput
62
+ label="Meeting"
63
+ onChange={() => {}}
64
+ placeholder="Pick a date"
65
+ timePlaceholder="Pick a time"
66
+ />,
67
+ );
68
+ // Time portion uses the override; date portion keeps its own placeholder.
69
+ expect(screen.getByPlaceholderText('Pick a time')).toBeInTheDocument();
70
+ expect(screen.getByPlaceholderText('Pick a date')).toBeInTheDocument();
71
+ expect(
72
+ screen.queryByPlaceholderText('Select a time'),
73
+ ).not.toBeInTheDocument();
74
+ });
75
+
34
76
  it('renders both date and time inputs', () => {
35
77
  render(<DateTimeInput label="Meeting" onChange={() => {}} />);
36
78
  expect(screen.getByRole('combobox')).toBeInTheDocument();
37
- expect(screen.getByLabelText('Time')).toBeInTheDocument();
79
+ expect(screen.getByLabelText('Meeting time')).toBeInTheDocument();
38
80
  });
39
81
 
40
82
  it('displays formatted date in date input when value is provided', () => {
@@ -90,9 +132,7 @@ describe('DateTimeInput', () => {
90
132
  });
91
133
 
92
134
  it('visually hides label when isLabelHidden is true', () => {
93
- render(
94
- <DateTimeInput label="Meeting" isLabelHidden onChange={() => {}} />,
95
- );
135
+ render(<DateTimeInput label="Meeting" isLabelHidden onChange={() => {}} />);
96
136
  const label = screen.getByText('Meeting');
97
137
  expect(label).toBeInTheDocument();
98
138
  expect(screen.getByLabelText('Meeting')).toBeInTheDocument();
@@ -114,13 +154,13 @@ describe('DateTimeInput', () => {
114
154
  it('sets disabled on both inputs when isDisabled is true', () => {
115
155
  render(<DateTimeInput label="Meeting" isDisabled onChange={() => {}} />);
116
156
  expect(screen.getByRole('combobox')).toBeDisabled();
117
- expect(screen.getByLabelText('Time')).toBeDisabled();
157
+ expect(screen.getByLabelText('Meeting time')).toBeDisabled();
118
158
  });
119
159
 
120
160
  it('is not disabled by default', () => {
121
161
  render(<DateTimeInput label="Meeting" onChange={() => {}} />);
122
162
  expect(screen.getByRole('combobox')).not.toBeDisabled();
123
- expect(screen.getByLabelText('Time')).not.toBeDisabled();
163
+ expect(screen.getByLabelText('Meeting time')).not.toBeDisabled();
124
164
  });
125
165
 
126
166
  it('date input has role="combobox"', () => {
@@ -160,7 +200,7 @@ describe('DateTimeInput', () => {
160
200
  it('disables inputs and button when isLoading is true', () => {
161
201
  render(<DateTimeInput label="Meeting" isLoading onChange={() => {}} />);
162
202
  expect(screen.getByRole('combobox')).toBeDisabled();
163
- expect(screen.getByLabelText('Time')).toBeDisabled();
203
+ expect(screen.getByLabelText('Meeting time')).toBeDisabled();
164
204
  expect(screen.getByRole('button', {name: 'Open calendar'})).toBeDisabled();
165
205
  });
166
206
 
@@ -284,7 +324,7 @@ describe('DateTimeInput', () => {
284
324
  const onChange = vi.fn();
285
325
  render(<DateTimeInput label="Meeting" onChange={onChange} />);
286
326
 
287
- const timeInput = screen.getByLabelText('Time');
327
+ const timeInput = screen.getByLabelText('Meeting time');
288
328
  fireEvent.change(timeInput, {target: {value: '3:45 pm'}});
289
329
 
290
330
  expect(onChange).not.toHaveBeenCalled();
@@ -300,7 +340,7 @@ describe('DateTimeInput', () => {
300
340
  />,
301
341
  );
302
342
 
303
- const timeInput = screen.getByLabelText('Time');
343
+ const timeInput = screen.getByLabelText('Meeting time');
304
344
  fireEvent.change(timeInput, {target: {value: '3:45 pm'}});
305
345
 
306
346
  expect(onChange).toHaveBeenCalledWith('2026-03-15T15:45');
@@ -409,4 +449,232 @@ describe('DateTimeInput', () => {
409
449
  expect(dateInput).toHaveValue('March 20, 2026');
410
450
  });
411
451
  });
452
+
453
+ describe('invalid typed input feedback (WCAG 3.3.1)', () => {
454
+ it('sets aria-invalid="true" on the date input when typed date is unparseable', () => {
455
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
456
+
457
+ const dateInput = screen.getByRole('combobox');
458
+ fireEvent.change(dateInput, {target: {value: '13/45/2024'}});
459
+
460
+ expect(dateInput).toHaveAttribute('aria-invalid', 'true');
461
+ });
462
+
463
+ it('does not set aria-invalid on the date input when typed date is valid', () => {
464
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
465
+
466
+ const dateInput = screen.getByRole('combobox');
467
+ fireEvent.change(dateInput, {target: {value: '03/15/2026'}});
468
+
469
+ expect(dateInput).not.toHaveAttribute('aria-invalid');
470
+ });
471
+
472
+ it('announces an alert message when the typed date is invalid', () => {
473
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
474
+
475
+ const dateInput = screen.getByRole('combobox');
476
+ fireEvent.change(dateInput, {target: {value: '13/45/2024'}});
477
+
478
+ expect(screen.getByText('Invalid date')).toBeInTheDocument();
479
+ });
480
+
481
+ it('sets aria-invalid="true" on the time input when typed time is unparseable', () => {
482
+ render(
483
+ <DateTimeInput
484
+ label="Meeting"
485
+ value={'2026-03-15T10:00' as ISODateTimeString}
486
+ onChange={() => {}}
487
+ />,
488
+ );
489
+
490
+ const timeInput = screen.getByLabelText('Meeting time');
491
+ fireEvent.change(timeInput, {target: {value: '99:99 zz'}});
492
+
493
+ expect(timeInput).toHaveAttribute('aria-invalid', 'true');
494
+ });
495
+
496
+ it('does not set aria-invalid on the time input when typed time is valid', () => {
497
+ render(
498
+ <DateTimeInput
499
+ label="Meeting"
500
+ value={'2026-03-15T10:00' as ISODateTimeString}
501
+ onChange={() => {}}
502
+ />,
503
+ );
504
+
505
+ const timeInput = screen.getByLabelText('Meeting time');
506
+ fireEvent.change(timeInput, {target: {value: '3:45 pm'}});
507
+
508
+ expect(timeInput).not.toHaveAttribute('aria-invalid');
509
+ });
510
+
511
+ it('announces an alert message when the typed time is invalid', () => {
512
+ render(
513
+ <DateTimeInput
514
+ label="Meeting"
515
+ value={'2026-03-15T10:00' as ISODateTimeString}
516
+ onChange={() => {}}
517
+ />,
518
+ );
519
+
520
+ const timeInput = screen.getByLabelText('Meeting time');
521
+ fireEvent.change(timeInput, {target: {value: '99:99 zz'}});
522
+
523
+ expect(screen.getByText('Invalid time')).toBeInTheDocument();
524
+ });
525
+ });
526
+ describe('disabledMessage', () => {
527
+ // jsdom does not implement the Popover API used by the tooltip, so mock
528
+ // showPopover/hidePopover to toggle a `popover-open` attribute the tests
529
+ // can assert on.
530
+ beforeEach(() => {
531
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
532
+ this.setAttribute('popover-open', '');
533
+ });
534
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
535
+ this.removeAttribute('popover-open');
536
+ });
537
+ });
538
+
539
+ // jsdom popover content is in the DOM but not "visible" in the
540
+ // accessibility tree; use hidden: true to find it.
541
+ const h = {hidden: true} as const;
542
+
543
+ it('shows the reason tooltip on hover when disabled with a reason', async () => {
544
+ render(
545
+ <DateTimeInput
546
+ label="When"
547
+ onChange={() => {}}
548
+ isDisabled
549
+ disabledMessage="You need the Editor role"
550
+ />,
551
+ );
552
+
553
+ // The tooltip anchors on the outer row that wraps both inputs.
554
+ const dateInput = screen.getByRole('combobox');
555
+ const row = dateInput.parentElement?.parentElement as HTMLElement;
556
+ const tooltip = screen.getByRole('tooltip', h);
557
+ expect(tooltip).toHaveTextContent('You need the Editor role');
558
+
559
+ fireEvent.mouseEnter(row);
560
+ await waitFor(() => {
561
+ expect(tooltip).toHaveAttribute('popover-open');
562
+ });
563
+
564
+ fireEvent.mouseLeave(row);
565
+ await waitFor(() => {
566
+ expect(tooltip).not.toHaveAttribute('popover-open');
567
+ });
568
+ });
569
+
570
+ it('shows the reason tooltip on keyboard focus', async () => {
571
+ const user = userEvent.setup();
572
+ render(
573
+ <DateTimeInput
574
+ label="When"
575
+ onChange={() => {}}
576
+ isDisabled
577
+ disabledMessage="You need the Editor role"
578
+ />,
579
+ );
580
+
581
+ const tooltip = screen.getByRole('tooltip', h);
582
+ await user.tab();
583
+ expect(screen.getByRole('combobox')).toHaveFocus();
584
+ await waitFor(() => {
585
+ expect(tooltip).toHaveAttribute('popover-open');
586
+ });
587
+ });
588
+
589
+ it('does not render a tooltip when not disabled', () => {
590
+ render(
591
+ <DateTimeInput
592
+ label="When"
593
+ onChange={() => {}}
594
+ disabledMessage="You need the Editor role"
595
+ />,
596
+ );
597
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
598
+ });
599
+
600
+ it('does not render a tooltip when disabled without a reason', () => {
601
+ render(<DateTimeInput label="When" onChange={() => {}} isDisabled />);
602
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
603
+ });
604
+
605
+ it('keeps both inputs focusable via aria-disabled when a reason is provided', () => {
606
+ render(
607
+ <DateTimeInput
608
+ label="When"
609
+ onChange={() => {}}
610
+ isDisabled
611
+ disabledMessage="You need the Editor role"
612
+ />,
613
+ );
614
+ const dateInput = screen.getByRole('combobox');
615
+ const timeInput = screen.getByLabelText('When time');
616
+ expect(dateInput).not.toBeDisabled();
617
+ expect(dateInput).toHaveAttribute('aria-disabled', 'true');
618
+ expect(dateInput).toHaveAttribute('readonly');
619
+ expect(timeInput).not.toBeDisabled();
620
+ expect(timeInput).toHaveAttribute('aria-disabled', 'true');
621
+ expect(timeInput).toHaveAttribute('readonly');
622
+ });
623
+
624
+ it('links the reason tooltip from the date input via aria-describedby', () => {
625
+ render(
626
+ <DateTimeInput
627
+ label="When"
628
+ onChange={() => {}}
629
+ isDisabled
630
+ disabledMessage="You need the Editor role"
631
+ />,
632
+ );
633
+ const dateInput = screen.getByRole('combobox');
634
+ const tooltip = screen.getByRole('tooltip', h);
635
+ expect(dateInput.getAttribute('aria-describedby')).toContain(tooltip.id);
636
+ });
637
+
638
+ it('blocks value changes and opening while focusable-disabled', async () => {
639
+ const user = userEvent.setup();
640
+ const onChange = vi.fn();
641
+ render(
642
+ <DateTimeInput
643
+ label="When"
644
+ onChange={onChange}
645
+ isDisabled
646
+ disabledMessage="You need the Editor role"
647
+ />,
648
+ );
649
+
650
+ const dateInput = screen.getByRole('combobox');
651
+ await user.click(dateInput);
652
+ await user.type(dateInput, '2026-03-15');
653
+ expect(dateInput).toHaveValue('');
654
+ expect(dateInput).toHaveAttribute('aria-expanded', 'false');
655
+ expect(onChange).not.toHaveBeenCalled();
656
+ });
657
+
658
+ it('remains natively disabled when disabled without a reason', () => {
659
+ render(<DateTimeInput label="When" onChange={() => {}} isDisabled />);
660
+ const dateInput = screen.getByRole('combobox');
661
+ expect(dateInput).toBeDisabled();
662
+ expect(dateInput).not.toHaveAttribute('aria-disabled');
663
+ });
664
+
665
+ it('does not swap in the time format-hint placeholder on focus while disabled', () => {
666
+ render(
667
+ <DateTimeInput
668
+ label="When"
669
+ onChange={() => {}}
670
+ isDisabled
671
+ disabledMessage="You need the Editor role"
672
+ />,
673
+ );
674
+ const timeInput = screen.getByLabelText('When time');
675
+ timeInput.focus();
676
+ fireEvent.focus(timeInput);
677
+ expect(timeInput).toHaveAttribute('placeholder', 'Select a time');
678
+ });
679
+ });
412
680
  });
@@ -47,14 +47,12 @@ import {
47
47
  inputStatusFocusWithinStyles,
48
48
  } from '../Field';
49
49
  import {Icon} from '../Icon';
50
+ import {VisuallyHidden} from '../VisuallyHidden';
50
51
  import {Spinner} from '../Spinner';
51
- import {
52
- Calendar,
53
- type ISODateString,
54
- type CalendarHandle,
55
- } from '../Calendar';
52
+ import {Calendar, type ISODateString, type CalendarHandle} from '../Calendar';
56
53
  import {useCalendarConstraints} from '../Calendar/hooks';
57
54
  import {usePopover} from '../Popover';
55
+ import {useTooltip} from '../Tooltip';
58
56
  import {useInputContainer} from '../hooks/useInputContainer';
59
57
  import {
60
58
  type ISOTimeString,
@@ -213,6 +211,29 @@ export interface DateTimeInputProps extends Omit<
213
211
  */
214
212
  isDisabled?: boolean;
215
213
 
214
+ /**
215
+ * Explains why the input is disabled. When set together with
216
+ * `isDisabled`, the input shows a tooltip with this text on hover and
217
+ * keyboard focus, and the date and time fields stay focusable (via
218
+ * `aria-disabled`) so the reason is discoverable by keyboard and assistive
219
+ * technology. Typing and calendar activation stay blocked.
220
+ *
221
+ * Use this instead of wrapping a disabled input in `Tooltip` — disabled
222
+ * controls don't emit the pointer events an external tooltip needs.
223
+ *
224
+ * @example
225
+ * ```
226
+ * <DateTimeInput
227
+ * label="Meeting time"
228
+ * value={dateTime}
229
+ * onChange={setDateTime}
230
+ * isDisabled
231
+ * disabledMessage="You need the Editor role to change this"
232
+ * />
233
+ * ```
234
+ */
235
+ disabledMessage?: string;
236
+
216
237
  /**
217
238
  * The selected datetime in ISO 8601 format ("YYYY-MM-DDTHH:MM" or "YYYY-MM-DDTHH:MM:SS").
218
239
  */
@@ -278,11 +299,24 @@ export interface DateTimeInputProps extends Omit<
278
299
  hasClear?: boolean;
279
300
 
280
301
  /**
281
- * Placeholder text shown when no date is selected.
302
+ * Placeholder text shown in the date portion when no date is selected.
282
303
  * @default "Select a date"
283
304
  */
284
305
  placeholder?: string;
285
306
 
307
+ /**
308
+ * Placeholder text shown in the time portion when no time is selected.
309
+ * @default "Select a time"
310
+ */
311
+ timePlaceholder?: string;
312
+
313
+ /**
314
+ * Accessible label for the time portion of the field. Defaults to
315
+ * `"{label} time"` so it is tied to the field's own label and localizable,
316
+ * rather than a hardcoded English "Time".
317
+ */
318
+ timeLabel?: string;
319
+
286
320
  /**
287
321
  * The size of the inputs.
288
322
  * @default 'md'
@@ -373,6 +407,7 @@ export function DateTimeInput({
373
407
  isOptional = false,
374
408
  isRequired = false,
375
409
  isDisabled = false,
410
+ disabledMessage,
376
411
  value,
377
412
  onChange,
378
413
  changeAction,
@@ -385,6 +420,8 @@ export function DateTimeInput({
385
420
  timeIncrement = 1,
386
421
  hasClear = false,
387
422
  placeholder = 'Select a date',
423
+ timePlaceholder = 'Select a time',
424
+ timeLabel,
388
425
  size: sizeProp,
389
426
  status,
390
427
  labelTooltip,
@@ -412,6 +449,22 @@ export function DateTimeInput({
412
449
  const isBusy = isLoading || optimisticValue !== value;
413
450
  const isEffectivelyDisabled = isDisabled || isBusy;
414
451
 
452
+ // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
453
+ // tooltip listeners attach to the outer row container (which already exists)
454
+ // and both the date and time inputs stay perceivable via aria-disabled
455
+ // instead of the disabled attribute. Typing is blocked with readOnly and the
456
+ // value mutation guards; calendar activation is blocked by the
457
+ // isEffectivelyDisabled guards. Only the persistent isDisabled state (not the
458
+ // transient busy state) surfaces a reason.
459
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
460
+ const disabledMessageTooltip = useTooltip({
461
+ placement: 'above',
462
+ // The container div is not naturally focusable; focusin bubbles up from
463
+ // the inputs, so always attach focus listeners.
464
+ focusTrigger: 'always',
465
+ isEnabled: showsDisabledMessage,
466
+ });
467
+
415
468
  const statusIconMap: Record<InputStatusType, IconName> = {
416
469
  warning: 'warning',
417
470
  error: 'error',
@@ -431,6 +484,7 @@ export function DateTimeInput({
431
484
  [
432
485
  description ? descriptionID : null,
433
486
  status?.message ? statusMessageID : null,
487
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
434
488
  ]
435
489
  .filter(Boolean)
436
490
  .join(' ') || undefined;
@@ -520,12 +574,12 @@ export function DateTimeInput({
520
574
  return isTimeInRange(parsed, timeMin, timeMax);
521
575
  }, [timePendingInput, hasSeconds, timeMin, timeMax]);
522
576
 
523
- const timePlaceholder = useMemo(() => {
577
+ const resolvedTimePlaceholder = useMemo(() => {
524
578
  if (isTimeFocused && !timeDisplayValue) {
525
579
  return hourFormat === '12h' ? 'e.g., 2:30 PM' : 'e.g., 14:30';
526
580
  }
527
- return 'Select a time';
528
- }, [isTimeFocused, timeDisplayValue, hourFormat]);
581
+ return timePlaceholder;
582
+ }, [isTimeFocused, timeDisplayValue, hourFormat, timePlaceholder]);
529
583
 
530
584
  // --- Unified change handler ---
531
585
  const fireChange = useCallback(
@@ -598,6 +652,11 @@ export function DateTimeInput({
598
652
 
599
653
  const handleDateInputChange = useCallback(
600
654
  (e: React.ChangeEvent<HTMLInputElement>) => {
655
+ // With a disabledMessage the input drops `disabled` for focusability, so
656
+ // guard value mutation explicitly (readOnly also blocks typing).
657
+ if (isEffectivelyDisabled) {
658
+ return;
659
+ }
601
660
  const text = e.target.value;
602
661
  setDatePendingInput(text);
603
662
 
@@ -613,7 +672,7 @@ export function DateTimeInput({
613
672
  calendarRef.current?.navigateTo(parsedISO);
614
673
  }
615
674
  },
616
- [valueParts.date, isDateDisabled, handleDateChange],
675
+ [valueParts.date, isDateDisabled, handleDateChange, isEffectivelyDisabled],
617
676
  );
618
677
 
619
678
  const commitDatePendingInput = useCallback(() => {
@@ -666,6 +725,11 @@ export function DateTimeInput({
666
725
  // --- Time handlers ---
667
726
  const handleTimeInputChange = useCallback(
668
727
  (e: React.ChangeEvent<HTMLInputElement>) => {
728
+ // With a disabledMessage the input drops `disabled` for focusability, so
729
+ // guard value mutation explicitly (readOnly also blocks typing).
730
+ if (isEffectivelyDisabled) {
731
+ return;
732
+ }
669
733
  const text = e.target.value;
670
734
  setTimePendingInput(text);
671
735
 
@@ -690,10 +754,19 @@ export function DateTimeInput({
690
754
  valueParts.time,
691
755
  valueParts.date,
692
756
  fireChange,
757
+ isEffectivelyDisabled,
693
758
  ],
694
759
  );
695
760
 
696
- const handleTimeFocus = useCallback(() => setIsTimeFocused(true), []);
761
+ const handleTimeFocus = useCallback(() => {
762
+ // A disabled/busy input stays focusable (via aria-disabled) so its reason
763
+ // is discoverable, but it must not present editing affordances — keep the
764
+ // static placeholder rather than swapping in the format hint.
765
+ if (isEffectivelyDisabled) {
766
+ return;
767
+ }
768
+ setIsTimeFocused(true);
769
+ }, [isEffectivelyDisabled]);
697
770
 
698
771
  const handleTimeBlur = useCallback(() => {
699
772
  setIsTimeFocused(false);
@@ -788,6 +861,7 @@ export function DateTimeInput({
788
861
  statusVariant="detached"
789
862
  width={width}>
790
863
  <div
864
+ ref={disabledMessageTooltip.ref}
791
865
  {...rest}
792
866
  {...mergeProps(
793
867
  themeProps('date-time-input', {
@@ -832,10 +906,18 @@ export function DateTimeInput({
832
906
  onClick={handleDateInputClick}
833
907
  onKeyDown={handleDateKeyDown}
834
908
  placeholder={placeholder}
835
- disabled={isEffectivelyDisabled}
909
+ // With a disabledMessage the input keeps focusability via
910
+ // aria-disabled so the reason is focus-discoverable; typing is
911
+ // blocked with readOnly and the mutation guards, and calendar
912
+ // activation is blocked by the isEffectivelyDisabled guards.
913
+ disabled={isEffectivelyDisabled && !showsDisabledMessage}
914
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
915
+ readOnly={showsDisabledMessage || undefined}
836
916
  aria-describedby={ariaDescribedBy}
837
917
  aria-required={isRequired === true ? 'true' : undefined}
838
- aria-invalid={status?.type === 'error' ? 'true' : undefined}
918
+ aria-invalid={
919
+ status?.type === 'error' || !isDateInputValid ? 'true' : undefined
920
+ }
839
921
  aria-busy={isBusy || undefined}
840
922
  aria-expanded={popover.isOpen}
841
923
  aria-haspopup="dialog"
@@ -848,6 +930,15 @@ export function DateTimeInput({
848
930
  !isDateInputValid && styles.inputInvalid,
849
931
  )}
850
932
  />
933
+ {/*
934
+ Live region announcing invalid typed date input to assistive
935
+ technology. The value silently reverts on blur, so without this a
936
+ screen-reader user would get no feedback that their entry was
937
+ rejected (WCAG 3.3.1).
938
+ */}
939
+ <VisuallyHidden as="div" role="alert" aria-live="assertive">
940
+ {!isDateInputValid ? 'Invalid date' : ''}
941
+ </VisuallyHidden>
851
942
  {hasClear && value !== undefined && !isEffectivelyDisabled && (
852
943
  <button
853
944
  type="button"
@@ -893,17 +984,31 @@ export function DateTimeInput({
893
984
  onFocus={handleTimeFocus}
894
985
  onBlur={handleTimeBlur}
895
986
  onKeyDown={handleTimeKeyDown}
896
- placeholder={timePlaceholder}
897
- disabled={isEffectivelyDisabled}
898
- aria-label="Time"
987
+ placeholder={resolvedTimePlaceholder}
988
+ // With a disabledMessage the input keeps focusability via
989
+ // aria-disabled so the reason is focus-discoverable; typing is
990
+ // blocked with readOnly and the mutation guards.
991
+ disabled={isEffectivelyDisabled && !showsDisabledMessage}
992
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
993
+ readOnly={showsDisabledMessage || undefined}
994
+ aria-label={timeLabel ?? `${label} time`}
899
995
  aria-required={isRequired === true ? 'true' : undefined}
900
- aria-invalid={status?.type === 'error' ? 'true' : undefined}
996
+ aria-invalid={
997
+ status?.type === 'error' || !isTimeInputValid ? 'true' : undefined
998
+ }
901
999
  {...stylex.props(
902
1000
  styles.input,
903
1001
  isEffectivelyDisabled && styles.inputDisabled,
904
1002
  !isTimeInputValid && styles.inputInvalid,
905
1003
  )}
906
1004
  />
1005
+ {/*
1006
+ Live region announcing invalid typed time input to assistive
1007
+ technology (WCAG 3.3.1).
1008
+ */}
1009
+ <VisuallyHidden as="div" role="alert" aria-live="assertive">
1010
+ {!isTimeInputValid ? 'Invalid time' : ''}
1011
+ </VisuallyHidden>
907
1012
  </div>
908
1013
  </div>
909
1014
 
@@ -920,6 +1025,9 @@ export function DateTimeInput({
920
1025
  />,
921
1026
  {placement: 'below', alignment: 'start'},
922
1027
  )}
1028
+
1029
+ {showsDisabledMessage &&
1030
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
923
1031
  </Field>
924
1032
  );
925
1033
  }
@@ -40,13 +40,13 @@ export const docs = {
40
40
  {
41
41
  name: 'isOpen',
42
42
  type: 'boolean',
43
- description: 'Whether the dialog is open (required).',
43
+ description: 'Whether the dialog is open.',
44
44
  required: true,
45
45
  },
46
46
  {
47
47
  name: 'onOpenChange',
48
48
  type: '(isOpen: boolean) => unknown',
49
- description: 'Callback when dialog visibility changes (required).',
49
+ description: 'Callback when dialog visibility changes.',
50
50
  required: true,
51
51
  },
52
52
  {
@@ -20,6 +20,7 @@ import {useEffect, useMemo, useRef, type ReactNode} from 'react';
20
20
  import type {BaseProps} from '../BaseProps';
21
21
  import * as stylex from '@stylexjs/stylex';
22
22
  import {useScrollLock} from '../hooks/useScrollLock';
23
+ import {hasActiveFocusTrapEscape, isImeKeyEvent} from '../hooks/useFocusTrap';
23
24
  import {
24
25
  colorVars,
25
26
  radiusVars,
@@ -422,6 +423,12 @@ export function Dialog({
422
423
 
423
424
  const handleKeyDown = (event: KeyboardEvent) => {
424
425
  if (event.key === 'Escape') {
426
+ // Ignore IME composition-cancel, and defer to any popover/menu layered
427
+ // on top of this dialog so a single Escape closes only the top-most
428
+ // layer (the popover's own focus trap handles it and stops propagation).
429
+ if (isImeKeyEvent(event) || hasActiveFocusTrapEscape()) {
430
+ return;
431
+ }
425
432
  event.preventDefault();
426
433
  if (allowEscape) {
427
434
  onOpenChange(false);
@@ -447,6 +454,11 @@ export function Dialog({
447
454
  // Handle native cancel event (browser Escape handling)
448
455
  const handleCancel = (event: React.SyntheticEvent<HTMLDialogElement>) => {
449
456
  event.preventDefault();
457
+ // Defer to a popover/menu layered on top of this dialog; it will dismiss
458
+ // itself on the same Escape press.
459
+ if (hasActiveFocusTrapEscape()) {
460
+ return;
461
+ }
450
462
  if (allowEscape) {
451
463
  onOpenChange(false);
452
464
  }