@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241

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 (414) hide show
  1. package/dist/Avatar/Avatar.d.ts +6 -1
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +46 -12
  4. package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
  5. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
  8. package/dist/Breadcrumbs/index.d.ts +1 -0
  9. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  10. package/dist/Breadcrumbs/index.js +2 -1
  11. package/dist/Calendar/Calendar.d.ts.map +1 -1
  12. package/dist/Calendar/Calendar.js +50 -9
  13. package/dist/Calendar/dayCellUtils.d.ts +4 -4
  14. package/dist/Calendar/dayCellUtils.js +2 -2
  15. package/dist/Calendar/styles.d.ts +30 -31
  16. package/dist/Calendar/styles.d.ts.map +1 -1
  17. package/dist/Calendar/styles.js +30 -31
  18. package/dist/Card/Card.js +1 -1
  19. package/dist/Carousel/Carousel.d.ts.map +1 -1
  20. package/dist/Carousel/Carousel.js +22 -12
  21. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  22. package/dist/Chat/ChatDictationButton.js +3 -1
  23. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  24. package/dist/Chat/ChatMessageBubble.js +8 -8
  25. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  26. package/dist/Chat/ChatToolCalls.js +10 -3
  27. package/dist/CheckboxInput/CheckboxInput.js +4 -4
  28. package/dist/Citation/Citation.d.ts +22 -1
  29. package/dist/Citation/Citation.d.ts.map +1 -1
  30. package/dist/Citation/Citation.js +20 -6
  31. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  32. package/dist/CodeBlock/CodeBlock.js +13 -7
  33. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  34. package/dist/Collapsible/Collapsible.js +3 -1
  35. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  36. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  37. package/dist/CommandPalette/CommandPalette.js +42 -3
  38. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  39. package/dist/ContextMenu/ContextMenu.js +14 -5
  40. package/dist/ContextMenu/index.d.ts +1 -0
  41. package/dist/ContextMenu/index.d.ts.map +1 -1
  42. package/dist/ContextMenu/index.js +3 -1
  43. package/dist/DateInput/DateInput.d.ts +1 -0
  44. package/dist/DateInput/DateInput.d.ts.map +1 -1
  45. package/dist/DateInput/DateInput.js +31 -19
  46. package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
  47. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  48. package/dist/DateRangeInput/DateRangeInput.js +20 -19
  49. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  50. package/dist/DateTimeInput/DateTimeInput.js +23 -18
  51. package/dist/Divider/Divider.d.ts +2 -2
  52. package/dist/Divider/Divider.d.ts.map +1 -1
  53. package/dist/Divider/Divider.js +13 -0
  54. package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
  55. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  56. package/dist/DropdownMenu/DropdownMenu.js +22 -5
  57. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
  59. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  60. package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
  61. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  62. package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
  63. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
  64. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
  65. package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
  66. package/dist/DropdownMenu/index.d.ts +10 -0
  67. package/dist/DropdownMenu/index.d.ts.map +1 -1
  68. package/dist/DropdownMenu/index.js +13 -0
  69. package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
  70. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  71. package/dist/DropdownMenu/menuItemHover.js +33 -0
  72. package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
  73. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
  74. package/dist/DropdownMenu/menuItemRoles.js +10 -1
  75. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  76. package/dist/DropdownMenu/renderDropdownItems.js +21 -6
  77. package/dist/Field/Field.d.ts +3 -1
  78. package/dist/Field/Field.d.ts.map +1 -1
  79. package/dist/Field/Field.js +4 -1
  80. package/dist/Field/inputStyles.stylex.d.ts +1 -0
  81. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  82. package/dist/Field/inputStyles.stylex.js +1 -0
  83. package/dist/FieldStatus/FieldStatus.d.ts +4 -1
  84. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  85. package/dist/FieldStatus/FieldStatus.js +4 -2
  86. package/dist/FileInput/FileInput.d.ts +1 -0
  87. package/dist/FileInput/FileInput.d.ts.map +1 -1
  88. package/dist/FileInput/FileInput.js +91 -65
  89. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  90. package/dist/HoverCard/useHoverCard.js +10 -4
  91. package/dist/Icon/Icon.d.ts +15 -1
  92. package/dist/Icon/Icon.d.ts.map +1 -1
  93. package/dist/Icon/Icon.js +19 -3
  94. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  95. package/dist/Lightbox/Lightbox.js +15 -9
  96. package/dist/List/List.d.ts.map +1 -1
  97. package/dist/List/List.js +6 -3
  98. package/dist/Markdown/Markdown.d.ts.map +1 -1
  99. package/dist/Markdown/Markdown.js +7 -0
  100. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  101. package/dist/MultiSelector/MultiSelector.js +65 -40
  102. package/dist/NumberInput/NumberInput.d.ts +1 -0
  103. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  104. package/dist/NumberInput/NumberInput.js +20 -17
  105. package/dist/Outline/Outline.js +2 -2
  106. package/dist/RadioList/RadioList.d.ts +2 -2
  107. package/dist/RadioList/RadioListItem.js +4 -4
  108. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  109. package/dist/Resizable/ResizeHandle.js +49 -19
  110. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  111. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  112. package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
  113. package/dist/Selector/Selector.d.ts.map +1 -1
  114. package/dist/Selector/Selector.js +58 -16
  115. package/dist/SideNav/SideNav.d.ts.map +1 -1
  116. package/dist/SideNav/SideNav.js +5 -5
  117. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  118. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  119. package/dist/SideNav/SideNavItem.d.ts +1 -1
  120. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  121. package/dist/SideNav/SideNavItem.js +12 -10
  122. package/dist/Slider/Slider.d.ts.map +1 -1
  123. package/dist/Slider/Slider.js +87 -34
  124. package/dist/Spinner/Spinner.d.ts +3 -2
  125. package/dist/Spinner/Spinner.d.ts.map +1 -1
  126. package/dist/Spinner/Spinner.js +11 -2
  127. package/dist/Switch/Switch.d.ts +8 -1
  128. package/dist/Switch/Switch.d.ts.map +1 -1
  129. package/dist/Switch/Switch.js +160 -91
  130. package/dist/TabList/TabList.d.ts +2 -11
  131. package/dist/TabList/TabList.d.ts.map +1 -1
  132. package/dist/TabList/TabList.js +5 -9
  133. package/dist/TabList/TabListContext.d.ts +0 -5
  134. package/dist/TabList/TabListContext.d.ts.map +1 -1
  135. package/dist/TabList/TabListContext.js +0 -5
  136. package/dist/Table/BaseTable.d.ts.map +1 -1
  137. package/dist/Table/BaseTable.js +30 -4
  138. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  139. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  140. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  141. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
  142. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  143. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  144. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  145. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +14 -6
  146. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  147. package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
  148. package/dist/Table/types.d.ts +32 -0
  149. package/dist/Table/types.d.ts.map +1 -1
  150. package/dist/TextArea/TextArea.d.ts +1 -0
  151. package/dist/TextArea/TextArea.d.ts.map +1 -1
  152. package/dist/TextArea/TextArea.js +16 -19
  153. package/dist/TextInput/TextInput.d.ts +1 -0
  154. package/dist/TextInput/TextInput.d.ts.map +1 -1
  155. package/dist/TextInput/TextInput.js +18 -17
  156. package/dist/TimeInput/TimeInput.d.ts +1 -0
  157. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  158. package/dist/TimeInput/TimeInput.js +15 -17
  159. package/dist/Timestamp/Timestamp.d.ts +31 -3
  160. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  161. package/dist/Timestamp/Timestamp.js +47 -76
  162. package/dist/Timestamp/formatInstant.d.ts +41 -0
  163. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  164. package/dist/Timestamp/formatInstant.js +177 -0
  165. package/dist/Timestamp/index.d.ts +1 -0
  166. package/dist/Timestamp/index.d.ts.map +1 -1
  167. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  168. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  169. package/dist/Timestamp/tooltipEntries.js +163 -0
  170. package/dist/Token/Token.d.ts +30 -3
  171. package/dist/Token/Token.d.ts.map +1 -1
  172. package/dist/Token/Token.js +17 -2
  173. package/dist/Token/index.d.ts +1 -1
  174. package/dist/Token/index.d.ts.map +1 -1
  175. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  176. package/dist/Tokenizer/Tokenizer.js +2 -2
  177. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  178. package/dist/Tooltip/useTooltip.js +17 -1
  179. package/dist/TopNav/TopNavItem.d.ts +3 -1
  180. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  181. package/dist/TopNav/TopNavItem.js +30 -3
  182. package/dist/TreeList/TreeList.d.ts.map +1 -1
  183. package/dist/TreeList/TreeList.js +1 -0
  184. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  185. package/dist/TreeList/TreeListBranches.js +8 -6
  186. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  187. package/dist/TreeList/TreeListItem.js +32 -14
  188. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  189. package/dist/Typeahead/BaseTypeahead.js +19 -10
  190. package/dist/Typeahead/Typeahead.js +1 -1
  191. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  192. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  193. package/dist/Typeahead/TypeaheadItem.js +20 -10
  194. package/dist/astryx.css +38 -14
  195. package/dist/astryx.umd.js +52 -52
  196. package/dist/astryx.umd.js.map +4 -4
  197. package/dist/hooks/index.d.ts +2 -0
  198. package/dist/hooks/index.d.ts.map +1 -1
  199. package/dist/hooks/index.js +1 -0
  200. package/dist/hooks/isRtlElement.d.ts +18 -0
  201. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  202. package/dist/hooks/isRtlElement.js +27 -0
  203. package/dist/hooks/useAnnounce.d.ts +3 -0
  204. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  205. package/dist/hooks/useAnnounce.js +39 -1
  206. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  207. package/dist/hooks/useFocusTrap.js +52 -11
  208. package/dist/hooks/useGridFocus.d.ts +6 -1
  209. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  210. package/dist/hooks/useGridFocus.js +12 -9
  211. package/dist/hooks/useHotkeys.d.ts +9 -0
  212. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  213. package/dist/hooks/useHotkeys.js +9 -0
  214. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  215. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  216. package/dist/hooks/useInputStatusIcon.js +158 -0
  217. package/dist/hooks/useListFocus.d.ts +39 -1
  218. package/dist/hooks/useListFocus.d.ts.map +1 -1
  219. package/dist/hooks/useListFocus.js +54 -9
  220. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  221. package/dist/theme/derivedVarRegistry.js +13 -0
  222. package/dist/utils/index.d.ts +1 -0
  223. package/dist/utils/index.d.ts.map +1 -1
  224. package/dist/utils/index.js +2 -1
  225. package/dist/utils/plainDate.d.ts +17 -0
  226. package/dist/utils/plainDate.d.ts.map +1 -1
  227. package/dist/utils/plainDate.js +23 -4
  228. package/dist/utils/rtlStyles.d.ts +24 -0
  229. package/dist/utils/rtlStyles.d.ts.map +1 -0
  230. package/dist/utils/rtlStyles.js +27 -0
  231. package/locales/en.json +76 -0
  232. package/locales/pseudo.json +57 -0
  233. package/package.json +6 -1
  234. package/src/Avatar/Avatar.doc.mjs +18 -4
  235. package/src/Avatar/Avatar.test.tsx +106 -0
  236. package/src/Avatar/Avatar.tsx +62 -10
  237. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  238. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  239. package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
  240. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  241. package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
  242. package/src/Breadcrumbs/index.ts +4 -0
  243. package/src/Button/Button.doc.mjs +0 -4
  244. package/src/Calendar/Calendar.test.tsx +170 -20
  245. package/src/Calendar/Calendar.tsx +73 -16
  246. package/src/Calendar/dayCellUtils.test.ts +18 -18
  247. package/src/Calendar/dayCellUtils.ts +2 -2
  248. package/src/Calendar/styles.ts +33 -39
  249. package/src/Card/Card.tsx +1 -1
  250. package/src/Carousel/Carousel.tsx +25 -6
  251. package/src/Chat/ChatComposer.doc.mjs +2 -2
  252. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  253. package/src/Chat/ChatDictationButton.tsx +7 -1
  254. package/src/Chat/ChatMessageBubble.tsx +15 -10
  255. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  256. package/src/Chat/ChatToolCalls.tsx +9 -2
  257. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  258. package/src/CheckboxInput/CheckboxInput.tsx +4 -4
  259. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  260. package/src/Citation/Citation.doc.mjs +4 -3
  261. package/src/Citation/Citation.test.tsx +97 -0
  262. package/src/Citation/Citation.tsx +43 -11
  263. package/src/CodeBlock/CodeBlock.tsx +35 -8
  264. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  265. package/src/Collapsible/Collapsible.test.tsx +7 -0
  266. package/src/Collapsible/Collapsible.tsx +16 -6
  267. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  268. package/src/CommandPalette/CommandPalette.tsx +41 -2
  269. package/src/ContextMenu/ContextMenu.test.tsx +42 -0
  270. package/src/ContextMenu/ContextMenu.tsx +13 -11
  271. package/src/ContextMenu/index.ts +5 -0
  272. package/src/DateInput/DateInput.doc.mjs +15 -5
  273. package/src/DateInput/DateInput.test.tsx +210 -3
  274. package/src/DateInput/DateInput.tsx +41 -29
  275. package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
  276. package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
  277. package/src/DateRangeInput/DateRangeInput.tsx +29 -27
  278. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  279. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  280. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  281. package/src/Divider/Divider.test.tsx +32 -8
  282. package/src/Divider/Divider.tsx +14 -1
  283. package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
  284. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  285. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
  286. package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
  287. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
  288. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  289. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
  290. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
  291. package/src/DropdownMenu/index.ts +16 -0
  292. package/src/DropdownMenu/menuItemHover.ts +39 -0
  293. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  294. package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
  295. package/src/Field/Field.tsx +14 -9
  296. package/src/Field/inputStyles.stylex.ts +2 -0
  297. package/src/FieldStatus/FieldStatus.tsx +10 -4
  298. package/src/FileInput/FileInput.doc.mjs +13 -7
  299. package/src/FileInput/FileInput.test.tsx +84 -16
  300. package/src/FileInput/FileInput.tsx +96 -79
  301. package/src/HoverCard/useHoverCard.tsx +15 -3
  302. package/src/Icon/Icon.doc.mjs +13 -0
  303. package/src/Icon/Icon.test.tsx +89 -0
  304. package/src/Icon/Icon.tsx +51 -3
  305. package/src/Lightbox/Lightbox.tsx +11 -3
  306. package/src/List/List.test.tsx +17 -29
  307. package/src/List/List.tsx +6 -1
  308. package/src/Markdown/Markdown.test.tsx +21 -0
  309. package/src/Markdown/Markdown.tsx +4 -0
  310. package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
  311. package/src/MultiSelector/MultiSelector.test.tsx +306 -3
  312. package/src/MultiSelector/MultiSelector.tsx +88 -57
  313. package/src/NumberInput/NumberInput.doc.mjs +11 -5
  314. package/src/NumberInput/NumberInput.test.tsx +48 -0
  315. package/src/NumberInput/NumberInput.tsx +25 -26
  316. package/src/Outline/Outline.test.tsx +12 -12
  317. package/src/Outline/Outline.tsx +2 -2
  318. package/src/RadioList/RadioList.doc.mjs +6 -0
  319. package/src/RadioList/RadioList.tsx +2 -2
  320. package/src/RadioList/RadioListItem.tsx +4 -4
  321. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  322. package/src/Resizable/ResizeHandle.tsx +70 -20
  323. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
  324. package/src/Selector/Selector.doc.mjs +8 -0
  325. package/src/Selector/Selector.test.tsx +328 -3
  326. package/src/Selector/Selector.tsx +70 -19
  327. package/src/SideNav/SideNav.tsx +1 -8
  328. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  329. package/src/SideNav/SideNavItem.tsx +5 -3
  330. package/src/Slider/Slider.doc.mjs +6 -0
  331. package/src/Slider/Slider.test.tsx +240 -12
  332. package/src/Slider/Slider.tsx +96 -23
  333. package/src/Spinner/Spinner.test.tsx +10 -8
  334. package/src/Spinner/Spinner.tsx +15 -5
  335. package/src/Switch/Switch.doc.mjs +142 -42
  336. package/src/Switch/Switch.test.tsx +22 -0
  337. package/src/Switch/Switch.tsx +90 -33
  338. package/src/TabList/TabList.doc.mjs +0 -6
  339. package/src/TabList/TabList.test.tsx +3 -15
  340. package/src/TabList/TabList.tsx +6 -17
  341. package/src/TabList/TabListContext.ts +0 -6
  342. package/src/Table/BaseTable.tsx +34 -2
  343. package/src/Table/Table.doc.mjs +11 -0
  344. package/src/Table/Table.test.tsx +85 -0
  345. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  346. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
  347. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  348. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  349. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +37 -8
  350. package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
  351. package/src/Table/types.ts +33 -0
  352. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  353. package/src/TextArea/TextArea.doc.mjs +11 -5
  354. package/src/TextArea/TextArea.tsx +17 -26
  355. package/src/TextInput/TextInput.doc.mjs +11 -5
  356. package/src/TextInput/TextInput.test.tsx +130 -3
  357. package/src/TextInput/TextInput.tsx +18 -25
  358. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  359. package/src/TimeInput/TimeInput.doc.mjs +11 -5
  360. package/src/TimeInput/TimeInput.tsx +14 -25
  361. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  362. package/src/Timestamp/Timestamp.test.tsx +371 -0
  363. package/src/Timestamp/Timestamp.tsx +96 -93
  364. package/src/Timestamp/formatInstant.ts +218 -0
  365. package/src/Timestamp/index.ts +4 -0
  366. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  367. package/src/Timestamp/tooltipEntries.ts +210 -0
  368. package/src/Token/Token.test.tsx +21 -15
  369. package/src/Token/Token.tsx +31 -14
  370. package/src/Token/index.ts +1 -1
  371. package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
  372. package/src/Tokenizer/Tokenizer.tsx +4 -2
  373. package/src/Tooltip/Tooltip.test.tsx +46 -0
  374. package/src/Tooltip/useTooltip.tsx +20 -0
  375. package/src/TopNav/TopNav.test.tsx +93 -2
  376. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  377. package/src/TopNav/TopNavItem.tsx +35 -6
  378. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  379. package/src/TreeList/TreeList.doc.mjs +6 -0
  380. package/src/TreeList/TreeList.test.tsx +50 -10
  381. package/src/TreeList/TreeList.tsx +5 -0
  382. package/src/TreeList/TreeListBranches.tsx +8 -6
  383. package/src/TreeList/TreeListItem.tsx +33 -11
  384. package/src/Typeahead/BaseTypeahead.tsx +29 -14
  385. package/src/Typeahead/Typeahead.doc.mjs +6 -0
  386. package/src/Typeahead/Typeahead.test.tsx +27 -0
  387. package/src/Typeahead/Typeahead.tsx +1 -1
  388. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  389. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  390. package/src/doc-mjs.d.ts +19 -0
  391. package/src/hooks/index.ts +6 -0
  392. package/src/hooks/isRtlElement.ts +27 -0
  393. package/src/hooks/useAnnounce.test.tsx +75 -2
  394. package/src/hooks/useAnnounce.ts +47 -1
  395. package/src/hooks/useFocusTrap.test.tsx +104 -0
  396. package/src/hooks/useFocusTrap.ts +61 -12
  397. package/src/hooks/useGridFocus.doc.mjs +3 -3
  398. package/src/hooks/useGridFocus.test.tsx +41 -0
  399. package/src/hooks/useGridFocus.ts +21 -10
  400. package/src/hooks/useHotkeys.ts +9 -0
  401. package/src/hooks/useInputStatusIcon.test.tsx +162 -0
  402. package/src/hooks/useInputStatusIcon.tsx +224 -0
  403. package/src/hooks/useListFocus.doc.mjs +25 -3
  404. package/src/hooks/useListFocus.test.tsx +153 -1
  405. package/src/hooks/useListFocus.ts +102 -9
  406. package/src/reset.css +0 -26
  407. package/src/reset.test.ts +60 -0
  408. package/src/theme/derivedVarRegistry.test.ts +1 -0
  409. package/src/theme/derivedVarRegistry.ts +6 -0
  410. package/src/theme/generateThemeRules.test.ts +42 -0
  411. package/src/utils/index.ts +2 -0
  412. package/src/utils/plainDate.test.ts +24 -6
  413. package/src/utils/plainDate.ts +18 -4
  414. package/src/utils/rtlStyles.ts +27 -0
@@ -8,10 +8,11 @@
8
8
  */
9
9
 
10
10
  import {describe, it, expect, vi, beforeEach} from 'vitest';
11
- import {render, screen, waitFor, fireEvent} from '@testing-library/react';
11
+ import {render, screen, waitFor, fireEvent, act} from '@testing-library/react';
12
12
  import {CommandPalette} from './CommandPalette';
13
13
  import {createStaticSource} from '@astryxdesign/core/Typeahead';
14
14
  import type {SearchSource, SearchableItem} from '@astryxdesign/core/Typeahead';
15
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
15
16
 
16
17
  const simpleSource = createStaticSource([
17
18
  {id: 'home', label: 'Home'},
@@ -35,6 +36,9 @@ beforeEach(() => {
35
36
  HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
36
37
  this.removeAttribute('open');
37
38
  });
39
+ // The live regions are module-level singletons; reset them so
40
+ // announcements from one test don't leak into the next.
41
+ __resetLiveRegionsForTest();
38
42
  });
39
43
 
40
44
  describe('CommandPalette', () => {
@@ -269,4 +273,94 @@ describe('CommandPalette', () => {
269
273
  expect(screen.getByText('No results')).toBeInTheDocument(),
270
274
  );
271
275
  });
276
+
277
+ describe('screen reader announcements', () => {
278
+ const politeRegion = () =>
279
+ document.querySelector('[data-astryx-live-region="polite"]');
280
+
281
+ it('announces the result count politely after typing a query', async () => {
282
+ render(
283
+ <CommandPalette
284
+ isOpen={true}
285
+ onOpenChange={() => {}}
286
+ searchSource={simpleSource}
287
+ />,
288
+ );
289
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
290
+ // "e" matches both Home and Settings.
291
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'e'}});
292
+ await waitFor(() => {
293
+ expect(politeRegion()).toHaveTextContent('2 results');
294
+ });
295
+ });
296
+
297
+ it('announces the singular form when one item matches', async () => {
298
+ render(
299
+ <CommandPalette
300
+ isOpen={true}
301
+ onOpenChange={() => {}}
302
+ searchSource={simpleSource}
303
+ />,
304
+ );
305
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
306
+ // "set" matches only Settings. Anchored so it cannot pass on "1 results".
307
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'set'}});
308
+ await waitFor(() => {
309
+ expect(politeRegion()).toHaveTextContent(/^1 result$/);
310
+ });
311
+ });
312
+
313
+ it('announces the empty state with the query when nothing matches', async () => {
314
+ render(
315
+ <CommandPalette
316
+ isOpen={true}
317
+ onOpenChange={() => {}}
318
+ searchSource={simpleSource}
319
+ />,
320
+ );
321
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
322
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'zzz'}});
323
+ await waitFor(() => {
324
+ expect(politeRegion()).toHaveTextContent('No results for zzz');
325
+ });
326
+ });
327
+
328
+ it('announces loading politely while a search is in flight', async () => {
329
+ // A source whose searches never resolve, so the palette stays busy and
330
+ // only the loading announcement can reach the live region.
331
+ const source: SearchSource = {
332
+ bootstrap: () => [],
333
+ async search(): Promise<SearchableItem[]> {
334
+ return new Promise<SearchableItem[]>(() => {});
335
+ },
336
+ };
337
+ render(
338
+ <CommandPalette
339
+ isOpen={true}
340
+ onOpenChange={() => {}}
341
+ searchSource={source}
342
+ />,
343
+ );
344
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'z'}});
345
+ await waitFor(() => {
346
+ expect(politeRegion()).toHaveTextContent('Loading');
347
+ });
348
+ });
349
+
350
+ it('does not announce on initial open (bootstrap)', async () => {
351
+ render(
352
+ <CommandPalette
353
+ isOpen={true}
354
+ onOpenChange={() => {}}
355
+ searchSource={simpleSource}
356
+ />,
357
+ );
358
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
359
+ // Flush effects and any pending live-region rAF writes.
360
+ await act(async () => {
361
+ await new Promise(resolve => setTimeout(resolve, 50));
362
+ });
363
+ expect(politeRegion()?.textContent ?? '').toBe('');
364
+ });
365
+ });
272
366
  });
@@ -3,7 +3,7 @@
3
3
  'use client';
4
4
  /**
5
5
  * @file CommandPalette.tsx
6
- * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox
6
+ * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox, useAnnounce
7
7
  * @output Exports CommandPalette root component and props
8
8
  * @position Core root component; dialog shell with searchSource-driven items
9
9
  *
@@ -36,6 +36,7 @@ import {CommandPaletteInput} from './CommandPaletteInput';
36
36
  import {CommandPaletteFooter} from './CommandPaletteFooter';
37
37
  import {CommandPaletteEmpty} from './CommandPaletteEmpty';
38
38
  import type {BaseProps} from '../BaseProps';
39
+ import {useAnnounce} from '../hooks/useAnnounce';
39
40
  import {useTranslator} from '../i18n';
40
41
 
41
42
  export interface CommandPaletteProps<
@@ -291,6 +292,13 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
291
292
  const isBusy = isPending;
292
293
  const searchVersionRef = useRef(0);
293
294
 
295
+ // Announce search status to screen readers through the shared polite live
296
+ // region (comboboxes-7 announce path, mirroring Selector / BaseTypeahead).
297
+ // The busy spinner and the empty states are otherwise purely visual, so a
298
+ // screen-reader user typing a query would hear nothing when loading starts
299
+ // or results disappear.
300
+ const announce = useAnnounce();
301
+
294
302
  const value = controlledValue ?? internalValue;
295
303
 
296
304
  const setValue = useCallback(
@@ -318,8 +326,12 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
318
326
  setInternalValue('');
319
327
  }
320
328
  searchSource.cancel?.();
329
+ // Clear any lingering result / loading announcement when the palette
330
+ // closes so stale status text does not linger in the a11y tree
331
+ // (matching Selector's onHide).
332
+ announce('');
321
333
  onOpenChange(false);
322
- }, [onOpenChange, searchSource, controlledValue]);
334
+ }, [onOpenChange, searchSource, controlledValue, announce]);
323
335
 
324
336
  const selectItem = useCallback(
325
337
  (itemValue: string) => {
@@ -355,6 +367,14 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
355
367
  startTransition(async () => {
356
368
  const isBootstrap = query === '';
357
369
 
370
+ // Loading started for a user query: tell screen-reader users the
371
+ // spinner appeared. The polite region coalesces rapid updates, so for
372
+ // fast sources the result-count announcement below simply replaces
373
+ // this instead of stacking one "Loading" per keystroke.
374
+ if (!isBootstrap) {
375
+ announce(t('@astryx.commandPalette.loading'));
376
+ }
377
+
358
378
  // Client-filter previous results for instant narrowing while fetch is in flight
359
379
  if (!isBootstrap && searchResults.length > 0) {
360
380
  const lower = query.toLowerCase().trim();
@@ -377,6 +397,23 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
377
397
  setOptimisticResults(items);
378
398
  setSearchResults(items);
379
399
 
400
+ // Announce the outcome from the search commit (not a reactive
401
+ // effect), matching Selector / BaseTypeahead: exactly one
402
+ // announcement per committed query, and the version check above
403
+ // already discards stale keystrokes. Bootstrap stays silent — the
404
+ // same role PowerSearch's mount guard plays — so opening the
405
+ // palette announces nothing; clearing the query only clears any
406
+ // lingering status text.
407
+ if (isBootstrap) {
408
+ announce('');
409
+ } else if (items.length === 0) {
410
+ announce(t('@astryx.commandPalette.noResultsFor', {query}));
411
+ } else {
412
+ announce(
413
+ t('@astryx.commandPalette.resultCount', {count: items.length}),
414
+ );
415
+ }
416
+
380
417
  // When opening with a preselected value, highlight it once
381
418
  // bootstrap results arrive. No value → highlight stays at -1
382
419
  // and ArrowDown naturally moves to the first item.
@@ -396,6 +433,8 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
396
433
  value,
397
434
  combobox,
398
435
  setOptimisticResults,
436
+ announce,
437
+ t,
399
438
  ],
400
439
  );
401
440
 
@@ -803,4 +803,46 @@ describe('ContextMenu keyboard access for menuitemradio/menuitemcheckbox (#3829)
803
803
  screen.getByRole('menuitemradio', {name: 'Oldest', hidden: true}),
804
804
  ).toHaveFocus();
805
805
  });
806
+
807
+ it('renders a submenu from a nested items array and keyboard-reaches an item after it', () => {
808
+ // Same inline-flyout scoping the DropdownMenu fix covers: the submenu's
809
+ // items must not pollute the ContextMenu's roving order, and an item after
810
+ // the submenu row must stay keyboard-reachable.
811
+ const onDelete = vi.fn();
812
+ render(
813
+ <ContextMenu
814
+ items={[
815
+ {label: 'Cut', onClick: () => {}},
816
+ {
817
+ label: 'Move to',
818
+ items: [
819
+ {label: 'Folder A', onClick: () => {}},
820
+ {label: 'Folder B', onClick: () => {}},
821
+ ],
822
+ },
823
+ {type: 'divider'},
824
+ {label: 'Delete', onClick: onDelete},
825
+ ]}>
826
+ <div>Right-click me</div>
827
+ </ContextMenu>,
828
+ );
829
+ fireEvent.contextMenu(screen.getByText('Right-click me'));
830
+ // Two role="menu" exist (the context menu + the inline submenu flyout);
831
+ // the context menu is the first in DOM order.
832
+ const menu = screen.getAllByRole('menu', {hidden: true})[0];
833
+ // The submenu trigger renders as a menuitem with aria-haspopup.
834
+ const submenuTrigger = screen.getByRole('menuitem', {
835
+ name: /Move to/,
836
+ hidden: true,
837
+ });
838
+ expect(submenuTrigger).toHaveAttribute('aria-haspopup', 'menu');
839
+ // Cut → Move to → Delete, one step per press (submenu items not swept in).
840
+ screen.getByRole('menuitem', {name: 'Cut', hidden: true}).focus();
841
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
842
+ expect(submenuTrigger).toHaveFocus();
843
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
844
+ expect(
845
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
846
+ ).toHaveFocus();
847
+ });
806
848
  });
@@ -49,6 +49,7 @@ import {
49
49
  import {
50
50
  MENU_ITEM_ROLES,
51
51
  MENU_ITEM_SELECTOR,
52
+ MENU_BOUNDARY_SELECTOR,
52
53
  } from '../DropdownMenu/menuItemRoles';
53
54
  import {useListFocus} from '../hooks/useListFocus';
54
55
  import {useTypeahead} from '../hooks/useTypeahead';
@@ -268,22 +269,18 @@ export function ContextMenu({
268
269
  handleKeyDown: listNavKeyDown,
269
270
  focusFirst,
270
271
  focusItem,
272
+ ownsEvent,
273
+ getItems: getMenuItems,
271
274
  } = useListFocus<HTMLDivElement>({
272
275
  itemSelector: MENU_ITEM_SELECTOR,
276
+ boundarySelector: MENU_BOUNDARY_SELECTOR,
273
277
  wrap: false,
274
278
  onEscape: closeMenu,
275
279
  });
276
280
 
277
- // First-character typeahead over the enabled menu items (menus-11).
278
- const getMenuItems = useCallback(
279
- (): HTMLElement[] =>
280
- listRef.current
281
- ? Array.from(
282
- listRef.current.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR),
283
- )
284
- : [],
285
- [listRef],
286
- );
281
+ // First-character typeahead over the enabled menu items (menus-11). Reuses
282
+ // the hook's scoped item collection so an inline submenu flyout's items
283
+ // aren't swept in.
287
284
  const typeahead = useTypeahead({
288
285
  getItemLabels: () => getMenuItems().map(el => el.textContent),
289
286
  onMatch: focusItem,
@@ -340,6 +337,11 @@ export function ContextMenu({
340
337
 
341
338
  const listKeyDown = useCallback(
342
339
  (e: React.KeyboardEvent) => {
340
+ // A submenu flyout renders inline inside this menu; its key events bubble
341
+ // up here. Let that level own them — only handle events from this level.
342
+ if (!ownsEvent(e)) {
343
+ return;
344
+ }
343
345
  if (e.key === 'Enter' || e.key === ' ') {
344
346
  e.preventDefault();
345
347
  const focused = document.activeElement as HTMLElement | null;
@@ -357,7 +359,7 @@ export function ContextMenu({
357
359
  }
358
360
  listNavKeyDown(e);
359
361
  },
360
- [listNavKeyDown, typeahead],
362
+ [listNavKeyDown, typeahead, ownsEvent],
361
363
  );
362
364
 
363
365
  // Place the zero-size cursor anchor at a point in the trigger's local
@@ -35,3 +35,8 @@ export {
35
35
  DropdownMenuRadioItem as ContextMenuRadioItem,
36
36
  type DropdownMenuRadioItemProps as ContextMenuRadioItemProps,
37
37
  } from '../DropdownMenu/DropdownMenuRadioItem';
38
+ // Submenus work inside a ContextMenu too (nested flyout of items).
39
+ export {
40
+ DropdownMenuSubMenu as ContextMenuSubMenu,
41
+ type DropdownMenuSubMenuProps as ContextMenuSubMenuProps,
42
+ } from '../DropdownMenu/DropdownMenuSubMenu';
@@ -117,9 +117,9 @@ export const docs = {
117
117
  },
118
118
  {
119
119
  name: 'statusVariant',
120
- type: "'attached' | 'detached'",
120
+ type: "'attached' | 'detached' | 'tooltip'",
121
121
  description:
122
- 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing.',
122
+ 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing; tooltip hides the message box and surfaces it in a tooltip on the status icon.',
123
123
  default: "'attached'",
124
124
  },
125
125
  {
@@ -149,6 +149,12 @@ export const docs = {
149
149
  "How the committed date value is displayed. Named values are reused from Timestamp's format vocabulary: 'date' shows 'Mar 21, 2026', 'date_long' shows 'March 21, 2026', 'date_weekday' shows 'Wed, Mar 21, 2026', 'system_date' shows '2026-03-21'. A function receives the ISO value and returns a custom string. Applies only to the committed value, never to text being typed.",
150
150
  default: "'date_long'",
151
151
  },
152
+ {
153
+ name: 'width',
154
+ type: 'SizeValue',
155
+ description:
156
+ 'Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned.',
157
+ },
152
158
  {
153
159
  name: 'xstyle',
154
160
  type: 'StyleXStyles',
@@ -159,6 +165,8 @@ export const docs = {
159
165
  theming: {
160
166
  targets: [
161
167
  {className: 'astryx-date-input', visualProps: ['size', 'status']},
168
+ {className: 'astryx-date-input-toggle-icon', states: ['state']},
169
+ {className: 'astryx-date-input-clear-icon'},
162
170
  ],
163
171
  },
164
172
  usage: {
@@ -393,9 +401,9 @@ export const docsZh = {
393
401
  },
394
402
  {
395
403
  name: 'statusVariant',
396
- type: "'attached' | 'detached'",
404
+ type: "'attached' | 'detached' | 'tooltip'",
397
405
  description:
398
- '状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距。',
406
+ '状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
399
407
  default: "'attached'",
400
408
  },
401
409
  {
@@ -429,6 +437,8 @@ export const docsZh = {
429
437
  className: 'astryx-date-input',
430
438
  visualProps: ['size', 'status'],
431
439
  },
440
+ {className: 'astryx-date-input-toggle-icon', states: ['state']},
441
+ {className: 'astryx-date-input-clear-icon'},
432
442
  ],
433
443
  },
434
444
  };
@@ -506,7 +516,7 @@ export const docsDense = {
506
516
  placeholder: 'placeholder text in input',
507
517
  size: 'input control size',
508
518
  status: 'error/warning/success status w/ message',
509
- statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing.',
519
+ statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
510
520
  labelTooltip: 'tooltip text via info icon at label end',
511
521
  hasClear: 'Shows clear button when date is set. Clears value on click.',
512
522
  numberOfMonths: 'months shown simultaneously in calendar popover',
@@ -20,8 +20,11 @@ import {
20
20
  import userEvent from '@testing-library/user-event';
21
21
  import {getButton, queryButton} from '../__tests__/fastRoleQueries';
22
22
  import {DateInput} from './DateInput';
23
+ import {Icon} from '../Icon';
23
24
  import {InputGroup} from '../InputGroup';
24
25
  import {InputGroupText} from '../InputGroup/InputGroupText';
26
+ import {defineTheme} from '../theme/defineTheme';
27
+ import {generateThemeCSSFlat} from '../theme/generateThemeRules';
25
28
 
26
29
  describe('DateInput', () => {
27
30
  it('renders with label', () => {
@@ -944,11 +947,14 @@ describe('DateInput', () => {
944
947
  });
945
948
  });
946
949
 
947
-
948
950
  describe('DateInput statusVariant forwarding', () => {
949
951
  it('defaults to attached (status renders with data-variant="attached")', () => {
950
952
  const {container} = render(
951
- <DateInput label="Date" onChange={() => {}} status={{type: 'error', message: 'Bad date'}} />,
953
+ <DateInput
954
+ label="Date"
955
+ onChange={() => {}}
956
+ status={{type: 'error', message: 'Bad date'}}
957
+ />,
952
958
  );
953
959
  expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
954
960
  'data-variant',
@@ -958,7 +964,12 @@ describe('DateInput statusVariant forwarding', () => {
958
964
 
959
965
  it('forwards statusVariant="detached" to the underlying Field status', () => {
960
966
  const {container} = render(
961
- <DateInput label="Date" onChange={() => {}} status={{type: 'error', message: 'Bad date'}} statusVariant="detached" />,
967
+ <DateInput
968
+ label="Date"
969
+ onChange={() => {}}
970
+ status={{type: 'error', message: 'Bad date'}}
971
+ statusVariant="detached"
972
+ />,
962
973
  );
963
974
  expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
964
975
  'data-variant',
@@ -966,3 +977,199 @@ describe('DateInput statusVariant forwarding', () => {
966
977
  );
967
978
  });
968
979
  });
980
+
981
+ describe('DateInput clear icon theme target', () => {
982
+ // Resolve the clear glyph span (the astryx-icon element inside the clear
983
+ // button), independent of the theme target class.
984
+ const getClearIcon = (): HTMLElement => {
985
+ const button = getButton('Clear Date');
986
+ const icon = button.querySelector('.astryx-icon');
987
+ if (icon == null) {
988
+ throw new Error('clear icon not found');
989
+ }
990
+ return icon as HTMLElement;
991
+ };
992
+
993
+ it('renders the astryx-date-input-clear-icon target on the clear glyph', () => {
994
+ render(
995
+ <DateInput
996
+ label="Date"
997
+ value="2026-01-15"
998
+ onChange={() => {}}
999
+ hasClear
1000
+ />,
1001
+ );
1002
+ // The stable theme target lands on the icon element itself (not the
1003
+ // button), so a theme can restyle just this glyph (color, size, hover)
1004
+ // via `defineTheme` — a button-level target could not reach the icon's
1005
+ // own color/size.
1006
+ const icon = getClearIcon();
1007
+ expect(icon).toHaveClass('astryx-date-input-clear-icon');
1008
+ expect(icon).toHaveClass('astryx-icon');
1009
+ });
1010
+
1011
+ it('keeps the clear button functional alongside the target', () => {
1012
+ const onChange = vi.fn();
1013
+ render(
1014
+ <DateInput
1015
+ label="Date"
1016
+ value="2026-01-15"
1017
+ onChange={onChange}
1018
+ hasClear
1019
+ />,
1020
+ );
1021
+ const clear = getButton('Clear Date');
1022
+ expect(clear.tagName).toBe('BUTTON');
1023
+ fireEvent.click(clear);
1024
+ expect(onChange).toHaveBeenCalledWith(undefined);
1025
+ });
1026
+
1027
+ it('renders the default icon (secondary color, sm size) byte-identically', () => {
1028
+ // Pixel-identical default guard: the clear glyph must carry the exact same
1029
+ // StyleX color/size classes as a standalone secondary/sm icon. The added
1030
+ // target class is purely additive — it changes nothing until a theme
1031
+ // targets it.
1032
+ render(
1033
+ <DateInput
1034
+ label="Date"
1035
+ value="2026-01-15"
1036
+ onChange={() => {}}
1037
+ hasClear
1038
+ />,
1039
+ );
1040
+ const icon = getClearIcon();
1041
+
1042
+ const {container: refContainer} = render(
1043
+ <Icon icon="close" size="sm" color="secondary" />,
1044
+ );
1045
+ const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
1046
+
1047
+ const styleClasses = (el: HTMLElement) =>
1048
+ el.className
1049
+ .split(' ')
1050
+ .filter(c => c !== 'astryx-date-input-clear-icon')
1051
+ .sort();
1052
+
1053
+ expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
1054
+ });
1055
+
1056
+ it('exposes date-input-clear-icon so a theme reaches the icon color, size, and hover', () => {
1057
+ // jsdom cannot resolve the @layer cascade, so the DOM-class assertion above
1058
+ // (target lands on the icon element) plus this generation assertion (the
1059
+ // theme emits same-element icon rules in @layer astryx-theme) together
1060
+ // prove the seam: a same-element theme rule wins over the icon's own
1061
+ // base-layer color/size.
1062
+ const theme = defineTheme({
1063
+ name: 'date-input-clear-icon-test',
1064
+ components: {
1065
+ 'date-input-clear-icon': {
1066
+ base: {
1067
+ width: '12px',
1068
+ height: '12px',
1069
+ fontSize: '12px',
1070
+ color: 'var(--color-icon-secondary)',
1071
+ ':hover': {color: 'var(--color-icon-primary)'},
1072
+ },
1073
+ },
1074
+ },
1075
+ });
1076
+ const css = generateThemeCSSFlat(theme);
1077
+ expect(css).toContain('.astryx-date-input-clear-icon {');
1078
+ expect(css).toContain('width: 12px');
1079
+ expect(css).toContain('height: 12px');
1080
+ expect(css).toContain('.astryx-date-input-clear-icon:hover {');
1081
+ expect(css).toContain('color: var(--color-icon-primary)');
1082
+ });
1083
+ });
1084
+
1085
+ describe('DateInput calendar-toggle icon theme target', () => {
1086
+ const iconIn = (button: HTMLElement): HTMLElement => {
1087
+ const icon = button.querySelector('.astryx-icon');
1088
+ if (icon == null) {
1089
+ throw new Error('toggle icon not found');
1090
+ }
1091
+ return icon as HTMLElement;
1092
+ };
1093
+
1094
+ it('renders the astryx-date-input-toggle-icon target on the toggle glyph', () => {
1095
+ render(<DateInput label="Date" onChange={() => {}} />);
1096
+ const icon = iconIn(getButton('Open calendar'));
1097
+ // The stable theme target lands on the icon element itself (not the
1098
+ // button), so a theme can restyle just this glyph (color, size, hover) —
1099
+ // and each open/closed state — via `defineTheme`. A button-level target
1100
+ // could not reach the icon's own color/size.
1101
+ expect(icon).toHaveClass('astryx-date-input-toggle-icon');
1102
+ expect(icon).toHaveClass('astryx-icon');
1103
+ // Open/closed state is reflected so a theme can target each state alone.
1104
+ expect(icon).toHaveAttribute('data-state', 'collapsed');
1105
+ });
1106
+
1107
+ it('reflects the expanded state on the toggle icon when the popover is open', async () => {
1108
+ render(<DateInput label="Date" onChange={() => {}} />);
1109
+ // Capture the toggle before opening — its aria-label changes to the close
1110
+ // label once open, but the element reference (and its icon) is stable.
1111
+ const toggle = getButton('Open calendar');
1112
+ fireEvent.click(toggle);
1113
+ await waitFor(() => {
1114
+ expect(iconIn(toggle)).toHaveAttribute('data-state', 'expanded');
1115
+ });
1116
+ });
1117
+
1118
+ it('keeps the calendar-toggle button functional alongside the target', () => {
1119
+ render(<DateInput label="Date" onChange={() => {}} />);
1120
+ const toggle = getButton('Open calendar');
1121
+ expect(toggle.tagName).toBe('BUTTON');
1122
+ });
1123
+
1124
+ it('renders the default icon (secondary color, sm size) byte-identically', () => {
1125
+ // Pixel-identical default guard: the toggle glyph must carry the exact same
1126
+ // StyleX color/size classes as a standalone secondary/sm icon. The added
1127
+ // target class + data-state are purely additive — they change nothing until
1128
+ // a theme targets them.
1129
+ render(<DateInput label="Date" onChange={() => {}} />);
1130
+ const icon = iconIn(getButton('Open calendar'));
1131
+
1132
+ const {container: refContainer} = render(
1133
+ <Icon icon="calendar" size="sm" color="secondary" />,
1134
+ );
1135
+ const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
1136
+
1137
+ // Exclude the additive theme-target classes (the stable target + its
1138
+ // reflected state class) so only the StyleX color/size classes remain.
1139
+ const themeTargetClasses = new Set([
1140
+ 'astryx-date-input-toggle-icon',
1141
+ 'collapsed',
1142
+ 'expanded',
1143
+ ]);
1144
+ const styleClasses = (el: HTMLElement) =>
1145
+ el.className
1146
+ .split(' ')
1147
+ .filter(c => !themeTargetClasses.has(c))
1148
+ .sort();
1149
+
1150
+ expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
1151
+ });
1152
+
1153
+ it('exposes date-input-toggle-icon so a theme reaches the icon size and per-state color', () => {
1154
+ // jsdom cannot resolve the @layer cascade, so the DOM-class assertions
1155
+ // above (target lands on the icon element) plus this generation assertion
1156
+ // (the theme emits same-element icon rules in @layer astryx-theme) together
1157
+ // prove the seam: a same-element theme rule wins over the icon's own
1158
+ // base-layer color/size.
1159
+ const theme = defineTheme({
1160
+ name: 'date-input-toggle-icon-test',
1161
+ components: {
1162
+ 'date-input-toggle-icon': {
1163
+ base: {width: '14px', height: '14px', fontSize: '14px'},
1164
+ 'state:expanded': {color: 'var(--color-icon-primary)'},
1165
+ },
1166
+ },
1167
+ });
1168
+ const css = generateThemeCSSFlat(theme);
1169
+ expect(css).toContain('.astryx-date-input-toggle-icon {');
1170
+ expect(css).toContain('width: 14px');
1171
+ expect(css).toContain('height: 14px');
1172
+ expect(css).toContain('.astryx-date-input-toggle-icon.expanded');
1173
+ expect(css).toContain('color: var(--color-icon-primary)');
1174
+ });
1175
+ });