@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1

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 (348) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Button/Button.d.ts +1 -1
  3. package/dist/Button/Button.d.ts.map +1 -1
  4. package/dist/Button/Button.js +5 -2
  5. package/dist/Calendar/Calendar.d.ts.map +1 -1
  6. package/dist/Calendar/Calendar.js +191 -32
  7. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +5 -0
  10. package/dist/Chat/ChatMessage.d.ts +11 -0
  11. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  12. package/dist/Chat/ChatMessage.js +18 -9
  13. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  14. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  15. package/dist/Chat/useTriggerMenu.js +222 -50
  16. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  17. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  18. package/dist/CodeBlock/CodeBlock.js +158 -38
  19. package/dist/CodeBlock/tokenizer.d.ts +39 -0
  20. package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
  21. package/dist/CodeBlock/tokenizer.js +108 -0
  22. package/dist/CommandPalette/CommandPalette.d.ts +9 -5
  23. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  24. package/dist/CommandPalette/CommandPalette.js +38 -20
  25. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  26. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  28. package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
  29. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  30. package/dist/CommandPalette/CommandPaletteInput.js +35 -18
  31. package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
  32. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  33. package/dist/CommandPalette/CommandPaletteItem.js +8 -9
  34. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  35. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  36. package/dist/CommandPalette/CommandPaletteList.js +10 -5
  37. package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
  38. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  39. package/dist/ComplexSelector/ComplexSelector.js +12 -3
  40. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  41. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  42. package/dist/ContextMenu/ContextMenu.js +62 -48
  43. package/dist/DateInput/DateInput.js +1 -1
  44. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  45. package/dist/DateInput/MonthScroller.js +12 -4
  46. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  47. package/dist/DateRangeInput/DateRangeInput.js +12 -2
  48. package/dist/Dialog/Dialog.d.ts +1 -1
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +16 -5
  51. package/dist/Dialog/DialogHeader.d.ts +1 -1
  52. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  53. package/dist/Dialog/DialogHeader.js +7 -7
  54. package/dist/Divider/Divider.d.ts +1 -0
  55. package/dist/Divider/Divider.d.ts.map +1 -1
  56. package/dist/Divider/Divider.js +5 -4
  57. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenu.js +161 -65
  59. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  60. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  61. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  62. package/dist/Field/InputClearButton.d.ts +8 -4
  63. package/dist/Field/InputClearButton.d.ts.map +1 -1
  64. package/dist/Field/InputClearButton.js +23 -7
  65. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  66. package/dist/FormLayout/FormLayout.js +1 -0
  67. package/dist/Grid/Grid.d.ts +13 -4
  68. package/dist/Grid/Grid.d.ts.map +1 -1
  69. package/dist/Grid/Grid.js +7 -1
  70. package/dist/HoverCard/useHoverCard.d.ts +1 -1
  71. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  72. package/dist/HoverCard/useHoverCard.js +67 -49
  73. package/dist/InputGroup/InputGroup.d.ts +2 -4
  74. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  75. package/dist/InputGroup/InputGroup.js +2 -4
  76. package/dist/InputGroup/groupStyles.d.ts +10 -2
  77. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  78. package/dist/InputGroup/groupStyles.js +19 -2
  79. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  80. package/dist/Layer/useLayerDismissal.js +9 -9
  81. package/dist/Markdown/Markdown.d.ts.map +1 -1
  82. package/dist/Markdown/Markdown.js +25 -4
  83. package/dist/MobileNav/MobileNav.d.ts +7 -0
  84. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  85. package/dist/MobileNav/MobileNav.js +45 -2
  86. package/dist/MultiSelector/MultiSelector.d.ts +7 -4
  87. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  88. package/dist/MultiSelector/MultiSelector.js +24 -3
  89. package/dist/NumberInput/NumberInput.js +1 -1
  90. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  91. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  92. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  93. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  94. package/dist/Resizable/ResizeHandle.js +83 -15
  95. package/dist/Resizable/useResizable.d.ts +23 -0
  96. package/dist/Resizable/useResizable.d.ts.map +1 -1
  97. package/dist/Resizable/useResizable.js +78 -0
  98. package/dist/Selector/Selector.d.ts +6 -3
  99. package/dist/Selector/Selector.d.ts.map +1 -1
  100. package/dist/Selector/Selector.js +28 -6
  101. package/dist/SideNav/SideNav.d.ts.map +1 -1
  102. package/dist/SideNav/SideNav.js +10 -2
  103. package/dist/Slider/Slider.d.ts.map +1 -1
  104. package/dist/Slider/Slider.js +18 -7
  105. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  106. package/dist/Tokenizer/Tokenizer.js +16 -4
  107. package/dist/Toolbar/Toolbar.d.ts +2 -1
  108. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  109. package/dist/Toolbar/Toolbar.js +3 -1
  110. package/dist/TreeList/TreeList.d.ts.map +1 -1
  111. package/dist/TreeList/TreeList.js +6 -3
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +7 -1
  114. package/dist/astryx.css +22 -0
  115. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  116. package/dist/hooks/useScrollOverflow.js +55 -12
  117. package/dist/hooks/useTreeFocus.d.ts +21 -0
  118. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  119. package/dist/hooks/useTreeFocus.js +40 -2
  120. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  121. package/dist/i18n/generated-locales/en.generated.js +3 -0
  122. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  123. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  124. package/dist/reset.a11y.chromium.spec.js +6 -1
  125. package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
  126. package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
  127. package/dist/reset.until-found.a11y.browser.spec.js +71 -0
  128. package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
  129. package/dist/theme/syntax/SyntaxTheme.js +43 -2
  130. package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
  131. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  132. package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
  133. package/dist/theme/syntax/index.d.ts +1 -1
  134. package/dist/theme/syntax/index.d.ts.map +1 -1
  135. package/dist/theme/syntax/tokens.d.ts +12 -2
  136. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  137. package/dist/theme/syntax/tokens.js +25 -4
  138. package/dist/utils/plainDate.d.ts +12 -0
  139. package/dist/utils/plainDate.d.ts.map +1 -1
  140. package/dist/utils/plainDate.js +63 -10
  141. package/locales/en.json +13 -1
  142. package/locales/pseudo.json +9 -0
  143. package/package.json +4 -4
  144. package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
  145. package/src/AlertDialog/AlertDialog.spec.md +205 -0
  146. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  147. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  148. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  149. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  150. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  151. package/src/Button/Button.test.tsx +37 -0
  152. package/src/Button/Button.tsx +5 -1
  153. package/src/Calendar/Calendar.doc.mjs +234 -60
  154. package/src/Calendar/Calendar.spec.md +209 -0
  155. package/src/Calendar/Calendar.test.tsx +559 -20
  156. package/src/Calendar/Calendar.tsx +258 -41
  157. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
  158. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
  159. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
  160. package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
  161. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
  162. package/src/Carousel/Carousel.test.tsx +54 -71
  163. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  164. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  165. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  166. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  167. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  168. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  169. package/src/Chat/ChatComposerInput.spec.md +94 -55
  170. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  171. package/src/Chat/ChatComposerInput.tsx +13 -0
  172. package/src/Chat/ChatMessage.doc.mjs +31 -16
  173. package/src/Chat/ChatMessage.spec.md +265 -0
  174. package/src/Chat/ChatMessage.test.tsx +246 -0
  175. package/src/Chat/ChatMessage.tsx +30 -7
  176. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  177. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  178. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  179. package/src/Chat/useTriggerMenu.tsx +278 -46
  180. package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
  181. package/src/CodeBlock/CodeBlock.spec.md +40 -41
  182. package/src/CodeBlock/CodeBlock.test.tsx +89 -9
  183. package/src/CodeBlock/CodeBlock.tsx +205 -25
  184. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  185. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
  186. package/src/CodeBlock/tokenizer.test.ts +112 -0
  187. package/src/CodeBlock/tokenizer.ts +106 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
  189. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
  190. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
  191. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
  192. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
  193. package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
  194. package/src/CommandPalette/CommandPalette.spec.md +100 -49
  195. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  196. package/src/CommandPalette/CommandPalette.tsx +47 -21
  197. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  198. package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
  199. package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
  200. package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
  201. package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
  202. package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
  203. package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
  204. package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
  205. package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
  206. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  207. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  208. package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
  209. package/src/CommandPalette/CommandPaletteList.tsx +10 -5
  210. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  211. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
  212. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  213. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
  214. package/src/ComplexSelector/ComplexSelector.tsx +37 -8
  215. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  216. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  217. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  218. package/src/ContextMenu/ContextMenu.tsx +78 -48
  219. package/src/DateInput/DateInput.doc.mjs +52 -0
  220. package/src/DateInput/DateInput.spec.md +27 -15
  221. package/src/DateInput/DateInput.test.tsx +38 -0
  222. package/src/DateInput/DateInput.tsx +1 -1
  223. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  224. package/src/DateInput/MonthScroller.tsx +12 -4
  225. package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
  226. package/src/DateRangeInput/DateRangeInput.tsx +20 -2
  227. package/src/Dialog/Dialog.test.tsx +27 -1
  228. package/src/Dialog/Dialog.tsx +16 -5
  229. package/src/Dialog/DialogHeader.test.tsx +27 -2
  230. package/src/Dialog/DialogHeader.tsx +7 -8
  231. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  232. package/src/Divider/Divider.doc.mjs +5 -3
  233. package/src/Divider/Divider.spec.md +140 -54
  234. package/src/Divider/Divider.test.tsx +15 -0
  235. package/src/Divider/Divider.tsx +6 -4
  236. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  237. package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
  238. package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
  239. package/src/DropdownMenu/DropdownMenu.tsx +212 -81
  240. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  241. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  242. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  243. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  244. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  245. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  246. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  247. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  248. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
  249. package/src/Field/InputClearButton.test.tsx +29 -4
  250. package/src/Field/InputClearButton.tsx +26 -8
  251. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  252. package/src/FormLayout/FormLayout.tsx +1 -0
  253. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
  254. package/src/Grid/Grid.doc.mjs +14 -11
  255. package/src/Grid/Grid.spec.md +50 -32
  256. package/src/Grid/Grid.tsx +13 -4
  257. package/src/HoverCard/HoverCard.test.tsx +46 -1
  258. package/src/HoverCard/useHoverCard.tsx +68 -48
  259. package/src/InputGroup/InputGroup.doc.mjs +11 -1
  260. package/src/InputGroup/InputGroup.tsx +2 -4
  261. package/src/InputGroup/groupStyles.ts +40 -2
  262. package/src/Layer/useLayerDismissal.ts +9 -9
  263. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
  264. package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
  265. package/src/Link/__tests__/Link.a11y.states.ts +104 -0
  266. package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
  267. package/src/Markdown/Markdown.doc.mjs +1 -1
  268. package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
  269. package/src/Markdown/Markdown.spec.md +45 -23
  270. package/src/Markdown/Markdown.test.tsx +38 -0
  271. package/src/Markdown/Markdown.tsx +24 -2
  272. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  273. package/src/MobileNav/MobileNav.tsx +50 -2
  274. package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
  275. package/src/MoreMenu/MoreMenu.spec.md +18 -7
  276. package/src/MoreMenu/MoreMenu.test.tsx +38 -0
  277. package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
  278. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  279. package/src/MultiSelector/MultiSelector.test.tsx +188 -21
  280. package/src/MultiSelector/MultiSelector.tsx +50 -11
  281. package/src/NumberInput/NumberInput.test.tsx +24 -0
  282. package/src/NumberInput/NumberInput.tsx +1 -1
  283. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  284. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  285. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  286. package/src/Resizable/Resizable.spec.md +41 -9
  287. package/src/Resizable/ResizeHandle.test.tsx +565 -2
  288. package/src/Resizable/ResizeHandle.tsx +126 -21
  289. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
  290. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
  291. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
  292. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
  293. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
  294. package/src/Resizable/useResizable.ts +95 -0
  295. package/src/Selector/Selector.doc.mjs +7 -6
  296. package/src/Selector/Selector.spec.md +37 -24
  297. package/src/Selector/Selector.test.tsx +176 -10
  298. package/src/Selector/Selector.tsx +59 -14
  299. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
  300. package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
  301. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
  302. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
  303. package/src/SideNav/SideNav.test.tsx +35 -0
  304. package/src/SideNav/SideNav.tsx +16 -2
  305. package/src/Slider/Slider.spec.md +43 -17
  306. package/src/Slider/Slider.test.tsx +134 -0
  307. package/src/Slider/Slider.tsx +29 -6
  308. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  309. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  310. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  311. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  312. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  313. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  314. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  315. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  316. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  317. package/src/Tokenizer/Tokenizer.test.tsx +87 -0
  318. package/src/Tokenizer/Tokenizer.tsx +31 -3
  319. package/src/Toolbar/Toolbar.test.tsx +131 -4
  320. package/src/Toolbar/Toolbar.tsx +3 -1
  321. package/src/TreeList/TreeList.spec.md +33 -8
  322. package/src/TreeList/TreeList.test.tsx +72 -0
  323. package/src/TreeList/TreeList.tsx +10 -6
  324. package/src/TreeList/TreeListItem.tsx +5 -0
  325. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  326. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  327. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  328. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  329. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  330. package/src/hooks/useScrollOverflow.test.tsx +222 -0
  331. package/src/hooks/useScrollOverflow.ts +55 -13
  332. package/src/hooks/useTreeFocus.test.tsx +37 -0
  333. package/src/hooks/useTreeFocus.ts +55 -2
  334. package/src/i18n/generated-locales/en.generated.ts +3 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
  336. package/src/reset.a11y.chromium.spec.ts +9 -1
  337. package/src/reset.css +8 -3
  338. package/src/reset.until-found.a11y.browser.spec.ts +75 -0
  339. package/src/reset.until-found.test.ts +32 -0
  340. package/src/stylexValidation.source-build.test.mjs +42 -0
  341. package/src/theme/syntax/SyntaxTheme.tsx +46 -5
  342. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
  343. package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
  344. package/src/theme/syntax/index.ts +5 -1
  345. package/src/theme/syntax/tokens.ts +32 -2
  346. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
  347. package/src/utils/plainDate.test.ts +147 -0
  348. package/src/utils/plainDate.ts +75 -10
@@ -7,6 +7,7 @@
7
7
  * @position Testing; validates CommandPaletteInput.tsx implementation
8
8
  */
9
9
 
10
+ import {createRef} from 'react';
10
11
  import {describe, it, expect, vi} from 'vitest';
11
12
  import {render, screen, fireEvent} from '@testing-library/react';
12
13
  import {CommandPaletteInput} from './CommandPaletteInput';
@@ -14,12 +15,47 @@ import {CommandPaletteContext} from './CommandPaletteContext';
14
15
  import {Dialog} from '../Dialog';
15
16
  import type {CommandPaletteContextValue} from './CommandPaletteContext';
16
17
 
18
+ function makeContext(
19
+ overrides: Partial<CommandPaletteContextValue> = {},
20
+ ): CommandPaletteContextValue {
21
+ return {
22
+ search: '',
23
+ setSearch: vi.fn(),
24
+ value: '',
25
+ setValue: vi.fn(),
26
+ listId: 'list-1',
27
+ highlightedIndex: -1,
28
+ setHighlightedIndex: vi.fn(),
29
+ getItemId: (i: number) => `item-${i}`,
30
+ selectableItems: [{value: 'open', label: 'Open'}],
31
+ searchResults: [],
32
+ selectItem: vi.fn(),
33
+ onKeyDown: vi.fn(),
34
+ onClose: vi.fn(),
35
+ isOpen: true,
36
+ isBusy: false,
37
+ ...overrides,
38
+ };
39
+ }
40
+
17
41
  describe('CommandPaletteInput', () => {
18
42
  it('renders with default placeholder', () => {
19
43
  render(<CommandPaletteInput />);
20
44
  expect(screen.getByPlaceholderText('Search…')).toBeInTheDocument();
21
45
  });
22
46
 
47
+ it('renders the local input target and delegated search icon', () => {
48
+ const {container} = render(<CommandPaletteInput />);
49
+
50
+ expect(container.firstElementChild).toHaveClass(
51
+ 'astryx-command-palette-input',
52
+ );
53
+ expect(container.querySelector('.astryx-icon')).toHaveAttribute(
54
+ 'aria-hidden',
55
+ 'true',
56
+ );
57
+ });
58
+
23
59
  it('renders with custom placeholder', () => {
24
60
  render(<CommandPaletteInput placeholder="Type a command..." />);
25
61
  expect(
@@ -27,40 +63,166 @@ describe('CommandPaletteInput', () => {
27
63
  ).toBeInTheDocument();
28
64
  });
29
65
 
30
- it('has combobox role', () => {
66
+ it('uses searchbox semantics when no popup context exists', () => {
31
67
  render(<CommandPaletteInput />);
32
- expect(screen.getByRole('combobox')).toBeInTheDocument();
68
+ const input = screen.getByRole('searchbox');
69
+ expect(input).not.toHaveAttribute('aria-expanded');
70
+ expect(input).not.toHaveAttribute('aria-controls');
71
+ expect(input).not.toHaveAttribute('aria-autocomplete');
33
72
  });
34
73
 
35
- it('has an accessible name by default', () => {
36
- render(<CommandPaletteInput />);
37
- expect(screen.getByRole('combobox')).toHaveAttribute(
38
- 'aria-label',
39
- 'Search…',
74
+ it('preserves standalone caller-owned popup semantics', () => {
75
+ render(
76
+ <CommandPaletteInput
77
+ aria-expanded="false"
78
+ aria-autocomplete="both"
79
+ aria-controls="caller-list"
80
+ aria-activedescendant="caller-item"
81
+ />,
40
82
  );
83
+
84
+ const input = screen.getByRole('combobox');
85
+ expect(input).toHaveAttribute('aria-expanded', 'false');
86
+ expect(input).toHaveAttribute('aria-autocomplete', 'both');
87
+ expect(input).toHaveAttribute('aria-controls', 'caller-list');
88
+ expect(input).toHaveAttribute('aria-activedescendant', 'caller-item');
41
89
  });
42
90
 
43
- it('uses the label prop as the accessible name', () => {
44
- render(<CommandPaletteInput label="Search commands" />);
91
+ it('infers caller-owned popup defaults from aria-controls alone', () => {
92
+ render(<CommandPaletteInput aria-controls="caller-list" />);
93
+
94
+ const input = screen.getByRole('combobox');
95
+ expect(input).toHaveAttribute('aria-expanded', 'true');
96
+ expect(input).toHaveAttribute('aria-autocomplete', 'list');
97
+ expect(input).toHaveAttribute('aria-controls', 'caller-list');
98
+ });
99
+
100
+ it('preserves a caller-owned combobox role and supplies popup defaults', () => {
101
+ render(<CommandPaletteInput role="combobox" />);
102
+
45
103
  const input = screen.getByRole('combobox');
46
- expect(input).toHaveAttribute('aria-label', 'Search commands');
47
- // The label prop does not affect the visible placeholder
104
+ expect(input).toHaveAttribute('aria-expanded', 'true');
105
+ expect(input).toHaveAttribute('aria-autocomplete', 'list');
106
+ });
107
+
108
+ it('keeps a toggling caller popup a combobox with its own values', () => {
109
+ function CallerPopup({open}: {open: boolean}) {
110
+ return (
111
+ <CommandPaletteInput
112
+ aria-expanded={open || undefined}
113
+ aria-controls={open ? 'caller-list' : undefined}
114
+ />
115
+ );
116
+ }
117
+ const {rerender} = render(<CallerPopup open={false} />);
118
+
119
+ const closed = screen.getByRole('combobox');
120
+ expect(closed).not.toHaveAttribute('aria-expanded');
121
+ expect(closed).not.toHaveAttribute('aria-controls');
122
+
123
+ rerender(<CallerPopup open />);
124
+ const open = screen.getByRole('combobox');
125
+ expect(open).toHaveAttribute('aria-expanded', 'true');
126
+ expect(open).toHaveAttribute('aria-controls', 'caller-list');
127
+
128
+ rerender(<CallerPopup open={false} />);
129
+ expect(screen.getByRole('combobox')).not.toHaveAttribute('aria-expanded');
130
+ });
131
+
132
+ it('omits an explicitly undefined aria-expanded beside a caller list', () => {
133
+ render(
134
+ <CommandPaletteInput
135
+ aria-expanded={undefined}
136
+ aria-controls="caller-list"
137
+ />,
138
+ );
139
+
140
+ const input = screen.getByRole('combobox');
141
+ expect(input).not.toHaveAttribute('aria-expanded');
142
+ expect(input).toHaveAttribute('aria-controls', 'caller-list');
143
+ });
144
+
145
+ it.each([
146
+ ['aria-owns', {'aria-owns': 'caller-list'}],
147
+ ['aria-haspopup="listbox"', {'aria-haspopup': 'listbox' as const}],
148
+ ['aria-haspopup={true}', {'aria-haspopup': true}],
149
+ ])(
150
+ 'keeps a field wired to its popup only through %s a combobox',
151
+ (_label, popupProps) => {
152
+ render(<CommandPaletteInput {...popupProps} />);
153
+
154
+ const input = screen.getByRole('combobox');
155
+ expect(input).toHaveAttribute('aria-expanded', 'true');
156
+ expect(input).toHaveAttribute('aria-autocomplete', 'list');
157
+ },
158
+ );
159
+
160
+ it.each([
161
+ ['false', false],
162
+ ['"false"', 'false' as const],
163
+ ])('stays a searchbox when aria-haspopup is %s', (_label, value) => {
164
+ render(<CommandPaletteInput aria-haspopup={value} />);
165
+
166
+ const input = screen.getByRole('searchbox');
167
+ expect(input).not.toHaveAttribute('aria-expanded');
168
+ expect(input).not.toHaveAttribute('aria-autocomplete');
169
+ });
170
+
171
+ it('is named "Search commands" by default, not by its placeholder', () => {
172
+ render(<CommandPaletteInput />);
173
+ const input = screen.getByRole('searchbox', {name: 'Search commands'});
48
174
  expect(input).toHaveAttribute('placeholder', 'Search…');
49
175
  });
50
176
 
51
- it('falls back to a custom placeholder for the accessible name', () => {
177
+ it('uses the label prop as the accessible name', () => {
178
+ render(<CommandPaletteInput label="Find files" />);
179
+ const input = screen.getByRole('searchbox', {name: 'Find files'});
180
+ expect(input).toHaveAttribute('placeholder', 'Search…');
181
+ });
182
+
183
+ it('never takes its name from a custom placeholder', () => {
52
184
  render(<CommandPaletteInput placeholder="Type a command..." />);
53
- expect(screen.getByRole('combobox')).toHaveAttribute(
54
- 'aria-label',
55
- 'Type a command...',
185
+ expect(
186
+ screen.getByRole('searchbox', {name: 'Search commands'}),
187
+ ).toHaveAttribute('placeholder', 'Type a command...');
188
+ });
189
+
190
+ it('owns role and popup state inside CommandPalette context, passing other ARIA through', () => {
191
+ render(
192
+ <CommandPaletteContext value={makeContext()}>
193
+ <CommandPaletteInput
194
+ role="searchbox"
195
+ aria-expanded={false}
196
+ aria-controls="caller-list"
197
+ aria-haspopup="dialog"
198
+ aria-owns="caller-owned"
199
+ />
200
+ </CommandPaletteContext>,
201
+ );
202
+ const input = screen.getByRole('combobox');
203
+ expect(input).toHaveAttribute('aria-expanded', 'true');
204
+ expect(input).toHaveAttribute('aria-controls', 'list-1');
205
+ expect(input).toHaveAttribute('aria-autocomplete', 'list');
206
+ expect(input).toHaveAttribute('aria-haspopup', 'dialog');
207
+ expect(input).toHaveAttribute('aria-owns', 'caller-owned');
208
+ });
209
+
210
+ it('keeps the default name inside CommandPalette context', () => {
211
+ render(
212
+ <CommandPaletteContext value={makeContext()}>
213
+ <CommandPaletteInput placeholder="Jump to…" />
214
+ </CommandPaletteContext>,
56
215
  );
216
+ expect(
217
+ screen.getByRole('combobox', {name: 'Search commands'}),
218
+ ).toBeInTheDocument();
57
219
  });
58
220
 
59
221
  it('lets a consumer-passed aria-label override the default', () => {
60
222
  render(
61
223
  <CommandPaletteInput label="Search commands" aria-label="Custom name" />,
62
224
  );
63
- expect(screen.getByRole('combobox')).toHaveAttribute(
225
+ expect(screen.getByRole('searchbox')).toHaveAttribute(
64
226
  'aria-label',
65
227
  'Custom name',
66
228
  );
@@ -70,15 +232,16 @@ describe('CommandPaletteInput', () => {
70
232
  const handleChange = vi.fn();
71
233
  render(<CommandPaletteInput onValueChange={handleChange} />);
72
234
 
73
- const input = screen.getByRole('combobox');
74
- fireEvent.change(input, {target: {value: 'test'}});
235
+ fireEvent.change(screen.getByRole('searchbox'), {
236
+ target: {value: 'test'},
237
+ });
75
238
 
76
239
  expect(handleChange).toHaveBeenCalledWith('test');
77
240
  });
78
241
 
79
242
  it('displays controlled value', () => {
80
243
  render(<CommandPaletteInput value="hello" onValueChange={() => {}} />);
81
- expect(screen.getByRole('combobox')).toHaveValue('hello');
244
+ expect(screen.getByRole('searchbox')).toHaveValue('hello');
82
245
  });
83
246
 
84
247
  it('forwards native onChange alongside onValueChange', () => {
@@ -92,46 +255,207 @@ describe('CommandPaletteInput', () => {
92
255
  />,
93
256
  );
94
257
 
95
- const input = screen.getByRole('combobox');
96
- fireEvent.change(input, {target: {value: 'x'}});
258
+ fireEvent.change(screen.getByRole('searchbox'), {target: {value: 'x'}});
97
259
 
98
260
  expect(handleChange).toHaveBeenCalledWith('x');
99
261
  expect(handleNativeChange).toHaveBeenCalled();
100
262
  });
101
263
 
102
- it('has aria-expanded and aria-autocomplete', () => {
103
- render(<CommandPaletteInput />);
264
+ it('keeps visible numeric end content in the trailing group', () => {
265
+ render(<CommandPaletteInput endContent={0} />);
266
+ const input = screen.getByRole('searchbox');
267
+ const trailingGroup = screen.getByText('0');
268
+ expect(trailingGroup.tagName).toBe('SPAN');
269
+ expect(input.parentElement?.lastElementChild).toBe(trailingGroup);
270
+ });
271
+
272
+ it.each([false, true, '', null, undefined])(
273
+ 'omits trailing chrome for empty end content %#',
274
+ endContent => {
275
+ render(<CommandPaletteInput endContent={endContent} />);
276
+ expect(
277
+ screen.getByRole('searchbox').parentElement?.children,
278
+ ).toHaveLength(2);
279
+ },
280
+ );
281
+
282
+ it('does not schedule focus when hasAutoFocus is false', async () => {
283
+ const focusSpy = vi.spyOn(HTMLElement.prototype, 'focus');
284
+ render(<CommandPaletteInput hasAutoFocus={false} />);
285
+
286
+ const input = screen.getByRole('searchbox');
287
+ await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
288
+ const inputFocusCalls = focusSpy.mock.calls.filter((_, i) => {
289
+ return focusSpy.mock.contexts[i] === input;
290
+ });
291
+ expect(input).not.toHaveAttribute('data-autofocus');
292
+ expect(inputFocusCalls).toHaveLength(0);
293
+ focusSpy.mockRestore();
294
+ });
295
+
296
+ it('forwards the ref and native data attributes to the query field', () => {
297
+ const ref = createRef<HTMLInputElement>();
298
+ render(<CommandPaletteInput ref={ref} data-testid="query-field" />);
299
+
300
+ expect(ref.current).toBe(screen.getByTestId('query-field'));
301
+ });
302
+ });
303
+
304
+ describe('CommandPaletteInput popup context', () => {
305
+ it('preserves component-owned combobox semantics against rest props', () => {
306
+ render(
307
+ <CommandPaletteContext value={makeContext({isOpen: false})}>
308
+ <CommandPaletteInput
309
+ role="button"
310
+ aria-expanded="true"
311
+ aria-autocomplete="none"
312
+ aria-controls="wrong-list"
313
+ aria-activedescendant="wrong-item"
314
+ />
315
+ </CommandPaletteContext>,
316
+ );
317
+
104
318
  const input = screen.getByRole('combobox');
105
- expect(input).toHaveAttribute('aria-expanded', 'true');
319
+ expect(input).toHaveAttribute('aria-expanded', 'false');
106
320
  expect(input).toHaveAttribute('aria-autocomplete', 'list');
321
+ expect(input).toHaveAttribute('aria-controls', 'list-1');
322
+ expect(input).not.toHaveAttribute('aria-activedescendant');
323
+ });
324
+
325
+ it('renders the busy spinner before trailing content', () => {
326
+ render(
327
+ <CommandPaletteContext value={makeContext({isBusy: true})}>
328
+ <CommandPaletteInput
329
+ endContent={<span data-testid="trailing-content">⌘K</span>}
330
+ />
331
+ </CommandPaletteContext>,
332
+ );
333
+
334
+ const spinner = screen.getByRole('status');
335
+ const trailingContent = screen.getByTestId('trailing-content');
336
+ expect(trailingContent.parentElement?.firstElementChild).toContainElement(
337
+ spinner,
338
+ );
339
+ });
340
+
341
+ it('reports the contextual open state and controlled list', () => {
342
+ render(
343
+ <CommandPaletteContext value={makeContext()}>
344
+ <CommandPaletteInput />
345
+ </CommandPaletteContext>,
346
+ );
347
+
348
+ const input = screen.getByRole('combobox');
349
+ expect(input).toHaveAttribute('aria-expanded', 'true');
350
+ expect(input).toHaveAttribute('aria-controls', 'list-1');
351
+ });
352
+
353
+ it('exposes the highlighted option as the active descendant', () => {
354
+ render(
355
+ <CommandPaletteContext
356
+ value={makeContext({
357
+ highlightedIndex: 2,
358
+ selectableItems: [{value: 'a'}, {value: 'b'}, {value: 'c'}],
359
+ })}>
360
+ <CommandPaletteInput />
361
+ </CommandPaletteContext>,
362
+ );
363
+
364
+ expect(screen.getByRole('combobox')).toHaveAttribute(
365
+ 'aria-activedescendant',
366
+ 'item-2',
367
+ );
368
+ });
369
+
370
+ it('reads and writes search state through context', () => {
371
+ const setSearch = vi.fn();
372
+ render(
373
+ <CommandPaletteContext value={makeContext({search: 'hel', setSearch})}>
374
+ <CommandPaletteInput />
375
+ </CommandPaletteContext>,
376
+ );
377
+
378
+ const input = screen.getByRole('combobox');
379
+ expect(input).toHaveValue('hel');
380
+ fireEvent.change(input, {target: {value: 'help'}});
381
+ expect(setSearch).toHaveBeenCalledWith('help');
382
+ });
383
+
384
+ it('gives explicit search state precedence over context', () => {
385
+ const setSearch = vi.fn();
386
+ const onValueChange = vi.fn();
387
+ render(
388
+ <CommandPaletteContext
389
+ value={makeContext({search: 'context', setSearch})}>
390
+ <CommandPaletteInput value="explicit" onValueChange={onValueChange} />
391
+ </CommandPaletteContext>,
392
+ );
393
+
394
+ const input = screen.getByRole('combobox');
395
+ expect(input).toHaveValue('explicit');
396
+ fireEvent.change(input, {target: {value: 'next'}});
397
+ expect(onValueChange).toHaveBeenCalledWith('next');
398
+ expect(setSearch).not.toHaveBeenCalled();
399
+ });
400
+
401
+ it('does not delegate a consumer-prevented keyboard event', () => {
402
+ const onKeyDown = vi.fn();
403
+ render(
404
+ <CommandPaletteContext value={makeContext({onKeyDown})}>
405
+ <CommandPaletteInput onKeyDown={event => event.preventDefault()} />
406
+ </CommandPaletteContext>,
407
+ );
408
+
409
+ fireEvent.keyDown(screen.getByRole('combobox'), {key: 'ArrowDown'});
410
+ expect(onKeyDown).not.toHaveBeenCalled();
411
+ });
412
+
413
+ it('omits popup ARIA while the result surface has no listbox', () => {
414
+ render(
415
+ <CommandPaletteContext value={makeContext({selectableItems: []})}>
416
+ <CommandPaletteInput />
417
+ </CommandPaletteContext>,
418
+ );
419
+
420
+ const input = screen.getByRole('combobox');
421
+ expect(input).toHaveAttribute('aria-expanded', 'false');
422
+ expect(input).not.toHaveAttribute('aria-controls');
423
+ expect(input).not.toHaveAttribute('aria-activedescendant');
424
+ });
425
+
426
+ it('omits an active descendant whose highlighted index has no option', () => {
427
+ render(
428
+ <CommandPaletteContext
429
+ value={makeContext({
430
+ highlightedIndex: 1,
431
+ selectableItems: [{value: 'home'}],
432
+ })}>
433
+ <CommandPaletteInput />
434
+ </CommandPaletteContext>,
435
+ );
436
+
437
+ expect(screen.getByRole('combobox')).not.toHaveAttribute(
438
+ 'aria-activedescendant',
439
+ );
440
+ });
441
+
442
+ it('does not route IME composition keys to palette commands', () => {
443
+ const onKeyDown = vi.fn();
444
+ render(
445
+ <CommandPaletteContext value={makeContext({onKeyDown})}>
446
+ <CommandPaletteInput />
447
+ </CommandPaletteContext>,
448
+ );
449
+
450
+ fireEvent.keyDown(screen.getByRole('combobox'), {
451
+ key: 'Enter',
452
+ isComposing: true,
453
+ });
454
+
455
+ expect(onKeyDown).not.toHaveBeenCalled();
107
456
  });
108
- });
109
457
 
110
- describe('CommandPaletteInput dialog context', () => {
111
- function makeContext(
112
- overrides: Partial<CommandPaletteContextValue> = {},
113
- ): CommandPaletteContextValue {
114
- return {
115
- search: '',
116
- setSearch: vi.fn(),
117
- value: '',
118
- setValue: vi.fn(),
119
- listId: 'list-1',
120
- highlightedIndex: -1,
121
- setHighlightedIndex: vi.fn(),
122
- getItemId: (i: number) => `item-${i}`,
123
- selectableItems: [],
124
- searchResults: [],
125
- selectItem: vi.fn(),
126
- onKeyDown: vi.fn(),
127
- onClose: vi.fn(),
128
- isOpen: true,
129
- isBusy: false,
130
- ...overrides,
131
- };
132
- }
133
-
134
- it('does not auto-focus inside an inline dialog', () => {
458
+ it('does not auto-focus inside an inline dialog', async () => {
135
459
  const focusSpy = vi.spyOn(HTMLElement.prototype, 'focus');
136
460
  render(
137
461
  <Dialog isOpen isInline onOpenChange={() => {}}>
@@ -142,6 +466,7 @@ describe('CommandPaletteInput dialog context', () => {
142
466
  );
143
467
 
144
468
  const input = screen.getByRole('combobox');
469
+ await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
145
470
  const inputFocusCalls = focusSpy.mock.calls.filter((_, i) => {
146
471
  return focusSpy.mock.contexts[i] === input;
147
472
  });
@@ -156,7 +481,6 @@ describe('CommandPaletteInput dialog context', () => {
156
481
  </CommandPaletteContext>,
157
482
  );
158
483
 
159
- // Auto-focus uses requestAnimationFrame — flush it
160
484
  await vi.waitFor(() => {
161
485
  expect(screen.getByRole('combobox')).toHaveFocus();
162
486
  });
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * SYNC: When modified, update these files to stay in sync:
11
11
  * - /apps/storybook/stories/CommandPalette.stories.tsx
12
+ * - /apps/storybook/stories/CommandPaletteInput.stories.tsx
12
13
  * - /packages/cli/assets/templates/blocks/components/CommandPalette/ (showcase blocks)
13
14
  */
14
15
 
@@ -16,7 +17,7 @@ import {useCallback, useEffect, useRef, type ReactNode} from 'react';
16
17
  import * as stylex from '@stylexjs/stylex';
17
18
  import {Icon} from '../Icon';
18
19
  import {Spinner} from '../Spinner';
19
- import {mergeProps} from '../utils';
20
+ import {isImeKeyEvent, isRenderable, mergeProps} from '../utils';
20
21
  import {
21
22
  colorVars,
22
23
  typeScaleVars,
@@ -112,14 +113,14 @@ export interface CommandPaletteInputProps extends Omit<
112
113
 
113
114
  /**
114
115
  * Placeholder text for the input.
115
- * @default 'Search...'
116
+ * @default 'Search…'
116
117
  */
117
118
  placeholder?: string;
118
119
 
119
120
  /**
120
- * Accessible label for the combobox input, announced by screen readers.
121
- * Falls back to the placeholder text (`'Search…'` by default), since a
122
- * placeholder alone is not a reliable accessible name.
121
+ * Accessible label for the query field, announced by screen readers. The
122
+ * placeholder is never the field's name.
123
+ * @default 'Search commands'
123
124
  */
124
125
  label?: string;
125
126
 
@@ -178,12 +179,42 @@ export function CommandPaletteInput({
178
179
  const placeholder =
179
180
  placeholderFromProps ?? t('@astryx.commandPalette.input.placeholder');
180
181
  const ctx = useCommandPaletteContext();
182
+ // Inside CommandPalette the results surface is a listbox only while it holds
183
+ // selectable results; empty and pending states expose no popup.
184
+ const hasListbox = ctx ? ctx.selectableItems.length > 0 : true;
181
185
  const dialogContext = useDialogContext();
182
186
  const inputRef = useRef<HTMLInputElement>(null);
183
187
 
184
188
  // Use context values as fallback
185
189
  const value = controlledValue ?? ctx?.search;
186
190
  const handleValueChange = onValueChange ?? ctx?.setSearch;
191
+ const accessibleLabel =
192
+ props['aria-label'] ?? label ?? t('@astryx.commandPalette.input.label');
193
+ const hasEndContent = isRenderable(endContent);
194
+ // A popup attribute the caller passes is caller-owned even while its value
195
+ // is undefined (`aria-expanded={open || undefined}`), so the role stays
196
+ // stable as the caller's popup opens and closes, and a present value is
197
+ // passed through unchanged.
198
+ // `aria-haspopup` counts unless it says there is no popup.
199
+ const callerSets = (key: keyof typeof props) => Object.hasOwn(props, key);
200
+ const callerDeclaresPopup =
201
+ callerSets('aria-haspopup') &&
202
+ props['aria-haspopup'] !== false &&
203
+ props['aria-haspopup'] !== 'false';
204
+ const callerOwnsPopup =
205
+ !ctx &&
206
+ (props.role === 'combobox' ||
207
+ callerSets('aria-expanded') ||
208
+ callerSets('aria-autocomplete') ||
209
+ callerSets('aria-controls') ||
210
+ callerSets('aria-owns') ||
211
+ callerSets('aria-activedescendant') ||
212
+ callerDeclaresPopup);
213
+ const standaloneRole = callerSets('role')
214
+ ? props.role
215
+ : callerOwnsPopup
216
+ ? 'combobox'
217
+ : 'searchbox';
187
218
 
188
219
  // When rendered inside an inline dialog, disable auto-focus by default
189
220
  // to avoid stealing focus from the surrounding page.
@@ -202,7 +233,10 @@ export function CommandPaletteInput({
202
233
  const handleKeyDown = useCallback(
203
234
  (e: React.KeyboardEvent<HTMLInputElement>) => {
204
235
  onKeyDown?.(e);
205
- if (e.defaultPrevented) {
236
+ if (e.defaultPrevented || isImeKeyEvent(e.nativeEvent)) {
237
+ // An in-progress IME composition owns Enter, Escape, and arrows. Let
238
+ // the input method finish or cancel composition without selecting or
239
+ // dismissing palette content.
206
240
  return;
207
241
  }
208
242
  // Delegate to useCombobox's keyboard handler from context
@@ -223,20 +257,41 @@ export function CommandPaletteInput({
223
257
  <Icon icon="search" size="sm" color="inherit" />
224
258
  </span>
225
259
  <input
260
+ {...props}
226
261
  ref={useMergedRefs(ref, inputRef)}
262
+ {...stylex.props(styles.input)}
227
263
  type="text"
228
- role="combobox"
229
- aria-expanded={ctx?.isOpen ?? true}
230
- aria-autocomplete="list"
231
- aria-controls={ctx?.listId}
264
+ role={ctx ? 'combobox' : standaloneRole}
265
+ aria-expanded={
266
+ ctx
267
+ ? ctx.isOpen && hasListbox
268
+ : callerSets('aria-expanded')
269
+ ? props['aria-expanded']
270
+ : callerOwnsPopup || undefined
271
+ }
272
+ aria-autocomplete={
273
+ ctx
274
+ ? 'list'
275
+ : callerSets('aria-autocomplete')
276
+ ? props['aria-autocomplete']
277
+ : callerOwnsPopup
278
+ ? 'list'
279
+ : undefined
280
+ }
281
+ aria-controls={
282
+ ctx ? (hasListbox ? ctx.listId : undefined) : props['aria-controls']
283
+ }
232
284
  aria-activedescendant={
233
- ctx && ctx.highlightedIndex >= 0
234
- ? ctx.getItemId(ctx.highlightedIndex)
235
- : undefined
285
+ ctx
286
+ ? hasListbox &&
287
+ ctx.highlightedIndex >= 0 &&
288
+ ctx.highlightedIndex < ctx.selectableItems.length
289
+ ? ctx.getItemId(ctx.highlightedIndex)
290
+ : undefined
291
+ : props['aria-activedescendant']
236
292
  }
237
- // A placeholder alone is not a reliable accessible name; give the
238
- // combobox an explicit one (consumer aria-label via rest props wins).
239
- aria-label={label ?? placeholder}
293
+ // Placeholder text is never the field's name (AST-056 AR2).
294
+ aria-label={accessibleLabel}
240
295
  placeholder={placeholder}
241
296
  value={value}
242
297
  data-autofocus={effectiveAutoFocus || undefined}
@@ -245,19 +300,17 @@ export function CommandPaletteInput({
245
300
  onChange?.(e);
246
301
  }}
247
302
  onKeyDown={handleKeyDown}
248
- {...stylex.props(styles.input)}
249
- {...props}
250
303
  />
251
- {(ctx?.isBusy || endContent) && (
304
+ {(ctx?.isBusy || hasEndContent) && (
252
305
  <span {...stylex.props(styles.end)}>
253
306
  {ctx?.isBusy && (
254
307
  <span {...stylex.props(styles.icon, styles.spinner)}>
255
308
  <Spinner size="sm" />
256
309
  </span>
257
310
  )}
258
- {endContent}
311
+ {hasEndContent && endContent}
259
312
  </span>
260
- )}{' '}
313
+ )}
261
314
  </div>
262
315
  );
263
316
  }