@astryxdesign/core 0.1.8 → 0.1.9

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 (487) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +6 -2
  4. package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
  5. package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
  6. package/dist/Avatar/Avatar.d.ts +18 -2
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +63 -7
  9. package/dist/Avatar/AvatarStatusDot.d.ts +33 -5
  10. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  11. package/dist/Avatar/AvatarStatusDot.js +144 -19
  12. package/dist/Banner/Banner.d.ts +9 -1
  13. package/dist/Banner/Banner.d.ts.map +1 -1
  14. package/dist/Banner/Banner.js +31 -2
  15. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +31 -2
  16. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  17. package/dist/Breadcrumbs/BreadcrumbItem.js +208 -6
  18. package/dist/Breadcrumbs/index.d.ts +5 -0
  19. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  20. package/dist/Breadcrumbs/index.js +9 -1
  21. package/dist/Button/Button.d.ts +8 -2
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +31 -2
  24. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  25. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  26. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  27. package/dist/Calendar/Calendar.d.ts.map +1 -1
  28. package/dist/Calendar/Calendar.js +30 -2
  29. package/dist/Calendar/dayCellUtils.js +1 -1
  30. package/dist/Card/Card.d.ts +8 -2
  31. package/dist/Card/Card.d.ts.map +1 -1
  32. package/dist/Card/Card.js +28 -2
  33. package/dist/Carousel/Carousel.d.ts +4 -1
  34. package/dist/Carousel/Carousel.d.ts.map +1 -1
  35. package/dist/Carousel/Carousel.js +24 -4
  36. package/dist/Chat/ChatComposer.d.ts +8 -0
  37. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  38. package/dist/Chat/ChatComposer.js +46 -7
  39. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  40. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  41. package/dist/Chat/ChatComposerDrawer.js +11 -1
  42. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  43. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  44. package/dist/Chat/ChatComposerInput.js +35 -3
  45. package/dist/Chat/ChatContext.d.ts +33 -0
  46. package/dist/Chat/ChatContext.d.ts.map +1 -1
  47. package/dist/Chat/ChatContext.js +16 -2
  48. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  49. package/dist/Chat/ChatDictationButton.js +1 -1
  50. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  51. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  52. package/dist/Chat/ChatLayoutScrollButton.js +4 -4
  53. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  54. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  55. package/dist/Chat/ChatSendButton.js +7 -1
  56. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  57. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  58. package/dist/Chat/ChatToolCalls.js +18 -9
  59. package/dist/Chat/index.d.ts +2 -1
  60. package/dist/Chat/index.d.ts.map +1 -1
  61. package/dist/Chat/index.js +1 -1
  62. package/dist/Chat/useChatStreamScroll.d.ts +2 -1
  63. package/dist/Chat/useChatStreamScroll.d.ts.map +1 -1
  64. package/dist/Chat/useChatStreamScroll.js +22 -8
  65. package/dist/Chat/useSpeechRecognition.d.ts +0 -1
  66. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  67. package/dist/Chat/useSpeechRecognition.js +1 -1
  68. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  69. package/dist/CheckboxInput/CheckboxInput.js +1 -1
  70. package/dist/CheckboxList/CheckboxListItem.d.ts +20 -1
  71. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  72. package/dist/CheckboxList/CheckboxListItem.js +11 -1
  73. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  74. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  75. package/dist/ClickableCard/ClickableCard.js +5 -2
  76. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  77. package/dist/CodeBlock/CodeBlock.js +8 -1
  78. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  79. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  80. package/dist/CommandPalette/CommandPalette.js +3 -1
  81. package/dist/CommandPalette/CommandPaletteGroup.d.ts.map +1 -1
  82. package/dist/CommandPalette/CommandPaletteGroup.js +9 -2
  83. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  84. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  85. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  86. package/dist/DateInput/DateInput.d.ts +47 -1
  87. package/dist/DateInput/DateInput.d.ts.map +1 -1
  88. package/dist/DateInput/DateInput.js +26 -2
  89. package/dist/DateInput/index.d.ts +1 -1
  90. package/dist/DateInput/index.d.ts.map +1 -1
  91. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  92. package/dist/DateTimeInput/DateTimeInput.js +10 -1
  93. package/dist/Dialog/Dialog.d.ts +3 -0
  94. package/dist/Dialog/Dialog.d.ts.map +1 -1
  95. package/dist/Dialog/Dialog.js +57 -6
  96. package/dist/Dialog/DialogContext.d.ts +7 -0
  97. package/dist/Dialog/DialogContext.d.ts.map +1 -1
  98. package/dist/Dialog/DialogContext.js +2 -2
  99. package/dist/Dialog/DialogHeader.d.ts +5 -2
  100. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  101. package/dist/Dialog/DialogHeader.js +6 -1
  102. package/dist/Divider/Divider.js +1 -1
  103. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -2
  104. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  105. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +1 -2
  106. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -2
  107. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  108. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -2
  109. package/dist/Field/Field.d.ts.map +1 -1
  110. package/dist/Field/Field.js +2 -3
  111. package/dist/Field/FieldLabel.d.ts +1 -1
  112. package/dist/Field/FieldLabel.d.ts.map +1 -1
  113. package/dist/Field/FieldLabel.js +42 -15
  114. package/dist/FieldStatus/FieldStatus.d.ts +8 -0
  115. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  116. package/dist/FieldStatus/FieldStatus.js +22 -4
  117. package/dist/FileInput/FileInput.d.ts.map +1 -1
  118. package/dist/FileInput/FileInput.js +10 -2
  119. package/dist/HoverCard/HoverCard.d.ts +9 -1
  120. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  121. package/dist/HoverCard/HoverCard.js +2 -0
  122. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  123. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  124. package/dist/HoverCard/useHoverCard.js +7 -2
  125. package/dist/Icon/Icon.d.ts +7 -2
  126. package/dist/Icon/Icon.d.ts.map +1 -1
  127. package/dist/Item/Item.d.ts +6 -1
  128. package/dist/Item/Item.d.ts.map +1 -1
  129. package/dist/Item/Item.js +22 -1
  130. package/dist/Layer/useLayer.d.ts +6 -0
  131. package/dist/Layer/useLayer.d.ts.map +1 -1
  132. package/dist/Layer/useLayer.js +2 -0
  133. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  134. package/dist/MobileNav/MobileNav.js +6 -1
  135. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  136. package/dist/MobileNav/MobileNavToggle.js +4 -0
  137. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  138. package/dist/MultiSelector/MultiSelector.js +39 -12
  139. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  140. package/dist/MultiSelector/hooks.js +16 -0
  141. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  142. package/dist/Outline/Outline.d.ts +0 -1
  143. package/dist/Outline/Outline.d.ts.map +1 -1
  144. package/dist/Outline/Outline.js +0 -1
  145. package/dist/OverflowList/OverflowList.d.ts +20 -3
  146. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  147. package/dist/OverflowList/OverflowList.js +29 -4
  148. package/dist/Popover/Popover.d.ts.map +1 -1
  149. package/dist/Popover/Popover.js +3 -2
  150. package/dist/Popover/usePopover.d.ts.map +1 -1
  151. package/dist/Popover/usePopover.js +3 -9
  152. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  153. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  154. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  155. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  156. package/dist/SelectableCard/SelectableCard.js +16 -13
  157. package/dist/Selector/Selector.d.ts.map +1 -1
  158. package/dist/Selector/Selector.js +4 -2
  159. package/dist/Selector/hooks.d.ts.map +1 -1
  160. package/dist/Selector/hooks.js +16 -0
  161. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  162. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  163. package/dist/SideNav/SideNavHeading.js +21 -8
  164. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  165. package/dist/Slider/Slider.d.ts.map +1 -1
  166. package/dist/Slider/Slider.js +19 -2
  167. package/dist/Stack/stack.stylex.d.ts +9 -9
  168. package/dist/Table/BaseTable.d.ts.map +1 -1
  169. package/dist/Table/BaseTable.js +3 -2
  170. package/dist/Table/index.d.ts +2 -0
  171. package/dist/Table/index.d.ts.map +1 -1
  172. package/dist/Table/index.js +1 -0
  173. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  174. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  175. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  176. package/dist/Table/plugins/rowStatus/index.js +3 -0
  177. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  178. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  179. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  180. package/dist/Table/plugins/selection/useTableSelection.d.ts +13 -0
  181. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  182. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  183. package/dist/Table/plugins/tree/useTableTreeData.d.ts +16 -0
  184. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  185. package/dist/Table/plugins/tree/useTableTreeData.js +87 -0
  186. package/dist/Table/plugins/tree/useTableTreeState.d.ts +6 -0
  187. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -1
  188. package/dist/Table/plugins/tree/useTableTreeState.js +22 -1
  189. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  190. package/dist/Table/useBaseTablePlugins.js +5 -3
  191. package/dist/Thumbnail/Thumbnail.d.ts +26 -5
  192. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  193. package/dist/Thumbnail/Thumbnail.js +97 -54
  194. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +9 -0
  195. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +1 -0
  196. package/dist/Thumbnail/thumbnail.markers.stylex.js +14 -0
  197. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  198. package/dist/TimeInput/TimeInput.js +15 -4
  199. package/dist/Timestamp/Timestamp.d.ts +3 -1
  200. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  201. package/dist/Timestamp/Timestamp.js +8 -6
  202. package/dist/Toast/useToast.d.ts.map +1 -1
  203. package/dist/Toast/useToast.js +2 -5
  204. package/dist/ToggleButton/ToggleButtonGroup.d.ts +0 -1
  205. package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
  206. package/dist/ToggleButton/ToggleButtonGroup.js +1 -1
  207. package/dist/Tokenizer/Tokenizer.d.ts +1 -1
  208. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  209. package/dist/Tokenizer/Tokenizer.js +16 -4
  210. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  211. package/dist/TopNav/TopNavHeading.js +18 -8
  212. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  213. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +9 -1
  214. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
  215. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +8 -1
  216. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  217. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  218. package/dist/TopNav/TopNavMenu.js +68 -6
  219. package/dist/TreeList/TreeList.d.ts +12 -3
  220. package/dist/TreeList/TreeList.d.ts.map +1 -1
  221. package/dist/TreeList/TreeList.js +2 -0
  222. package/dist/TreeList/TreeListBranches.d.ts +5 -0
  223. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  224. package/dist/TreeList/TreeListBranches.js +10 -4
  225. package/dist/TreeList/TreeListItem.d.ts +8 -2
  226. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  227. package/dist/TreeList/TreeListItem.js +27 -7
  228. package/dist/TreeList/TreeListTypes.d.ts +25 -1
  229. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  230. package/dist/TreeList/index.d.ts +2 -2
  231. package/dist/TreeList/index.d.ts.map +1 -1
  232. package/dist/astryx.css +28 -18
  233. package/dist/astryx.umd.js +51 -51
  234. package/dist/astryx.umd.js.map +4 -4
  235. package/dist/authoring/doc.d.ts.map +1 -1
  236. package/dist/docs-types.d.ts +8 -0
  237. package/dist/docs-types.d.ts.map +1 -1
  238. package/dist/hooks/computeOverflow.d.ts +47 -0
  239. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  240. package/dist/hooks/computeOverflow.js +213 -0
  241. package/dist/hooks/index.d.ts +1 -0
  242. package/dist/hooks/index.d.ts.map +1 -1
  243. package/dist/hooks/index.js +2 -1
  244. package/dist/hooks/useDevWarning.d.ts +18 -0
  245. package/dist/hooks/useDevWarning.d.ts.map +1 -0
  246. package/dist/hooks/useDevWarning.js +49 -0
  247. package/dist/hooks/useOverflow.d.ts +19 -0
  248. package/dist/hooks/useOverflow.d.ts.map +1 -1
  249. package/dist/hooks/useOverflow.js +36 -25
  250. package/dist/i18n/resolve.d.ts.map +1 -1
  251. package/dist/i18n/resolve.js +7 -18
  252. package/dist/theme/Theme.d.ts.map +1 -1
  253. package/dist/theme/Theme.js +2 -7
  254. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  255. package/dist/theme/generateThemeRules.js +51 -0
  256. package/dist/theme/hct.d.ts +0 -1
  257. package/dist/theme/hct.d.ts.map +1 -1
  258. package/dist/theme/hct.js +1 -1
  259. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  260. package/dist/theme/syntax/defineSyntaxTheme.js +2 -1
  261. package/dist/utils/devWarning.d.ts +42 -0
  262. package/dist/utils/devWarning.d.ts.map +1 -0
  263. package/dist/utils/devWarning.js +88 -0
  264. package/dist/utils/index.d.ts +3 -1
  265. package/dist/utils/index.d.ts.map +1 -1
  266. package/dist/utils/index.js +3 -2
  267. package/dist/utils/plainDate.d.ts +30 -0
  268. package/dist/utils/plainDate.d.ts.map +1 -1
  269. package/dist/utils/plainDate.js +53 -0
  270. package/dist/utils/types.d.ts +13 -0
  271. package/dist/utils/types.d.ts.map +1 -1
  272. package/locales/en.json +24 -0
  273. package/locales/fr-FR.json +14 -0
  274. package/locales/pseudo.json +18 -0
  275. package/package.json +1 -1
  276. package/src/AppShell/AppShell.tsx +6 -0
  277. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  278. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  279. package/src/Avatar/Avatar.doc.mjs +15 -3
  280. package/src/Avatar/Avatar.test.tsx +156 -1
  281. package/src/Avatar/Avatar.tsx +96 -4
  282. package/src/Avatar/AvatarStatusDot.doc.mjs +23 -12
  283. package/src/Avatar/AvatarStatusDot.test.tsx +284 -0
  284. package/src/Avatar/AvatarStatusDot.tsx +178 -21
  285. package/src/Banner/Banner.doc.mjs +9 -0
  286. package/src/Banner/Banner.test.tsx +30 -0
  287. package/src/Banner/Banner.tsx +47 -3
  288. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +26 -0
  289. package/src/Breadcrumbs/BreadcrumbItem.tsx +333 -14
  290. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -0
  291. package/src/Breadcrumbs/Breadcrumbs.test.tsx +284 -2
  292. package/src/Breadcrumbs/index.ts +27 -0
  293. package/src/Button/Button.doc.mjs +9 -0
  294. package/src/Button/Button.test.tsx +49 -0
  295. package/src/Button/Button.tsx +25 -1
  296. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  297. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  298. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  299. package/src/Calendar/Calendar.doc.mjs +12 -1
  300. package/src/Calendar/Calendar.test.tsx +193 -0
  301. package/src/Calendar/Calendar.tsx +38 -16
  302. package/src/Calendar/dayCellUtils.test.ts +5 -0
  303. package/src/Calendar/dayCellUtils.ts +1 -1
  304. package/src/Card/Card.doc.mjs +9 -0
  305. package/src/Card/Card.test.tsx +21 -3
  306. package/src/Card/Card.tsx +35 -2
  307. package/src/Carousel/Carousel.doc.mjs +1 -0
  308. package/src/Carousel/Carousel.test.tsx +53 -0
  309. package/src/Carousel/Carousel.tsx +34 -4
  310. package/src/Chat/ChatComposer.doc.mjs +12 -3
  311. package/src/Chat/ChatComposer.test.tsx +115 -0
  312. package/src/Chat/ChatComposer.tsx +73 -9
  313. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  314. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  315. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  316. package/src/Chat/ChatComposerInput.test.tsx +103 -0
  317. package/src/Chat/ChatComposerInput.tsx +49 -3
  318. package/src/Chat/ChatContext.tsx +37 -10
  319. package/src/Chat/ChatDictationButton.tsx +4 -1
  320. package/src/Chat/ChatLayout.tsx +3 -1
  321. package/src/Chat/ChatLayoutScrollButton.tsx +7 -2
  322. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  323. package/src/Chat/ChatSendButton.tsx +11 -1
  324. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  325. package/src/Chat/ChatToolCalls.tsx +25 -4
  326. package/src/Chat/index.ts +5 -1
  327. package/src/Chat/useChatStreamScroll.test.tsx +49 -0
  328. package/src/Chat/useChatStreamScroll.ts +27 -10
  329. package/src/Chat/useSpeechRecognition.ts +1 -1
  330. package/src/Chat/useTriggerMenu.tsx +3 -1
  331. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  332. package/src/CheckboxInput/CheckboxInput.tsx +1 -1
  333. package/src/CheckboxList/CheckboxList.test.tsx +81 -3
  334. package/src/CheckboxList/CheckboxListItem.doc.mjs +33 -5
  335. package/src/CheckboxList/CheckboxListItem.tsx +41 -5
  336. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  337. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  338. package/src/ClickableCard/ClickableCard.tsx +13 -3
  339. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  340. package/src/CodeBlock/CodeBlock.tsx +9 -1
  341. package/src/CommandPalette/CommandPalette.doc.mjs +1 -0
  342. package/src/CommandPalette/CommandPalette.tsx +3 -1
  343. package/src/CommandPalette/CommandPaletteGroup.test.tsx +71 -0
  344. package/src/CommandPalette/CommandPaletteGroup.tsx +12 -1
  345. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  346. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  347. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  348. package/src/ContextMenu/ContextMenu.test.tsx +193 -0
  349. package/src/DateInput/DateInput.doc.mjs +16 -0
  350. package/src/DateInput/DateInput.test.tsx +108 -0
  351. package/src/DateInput/DateInput.tsx +68 -3
  352. package/src/DateInput/index.ts +1 -0
  353. package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
  354. package/src/DateTimeInput/DateTimeInput.tsx +14 -2
  355. package/src/Dialog/Dialog.doc.mjs +2 -2
  356. package/src/Dialog/Dialog.test.tsx +77 -4
  357. package/src/Dialog/Dialog.tsx +89 -5
  358. package/src/Dialog/DialogContext.ts +9 -2
  359. package/src/Dialog/DialogHeader.doc.mjs +2 -2
  360. package/src/Dialog/DialogHeader.tsx +9 -2
  361. package/src/Divider/Divider.tsx +2 -2
  362. package/src/DropdownMenu/DropdownMenu.test.tsx +169 -0
  363. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -2
  364. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +1 -2
  365. package/src/Field/Field.test.tsx +57 -12
  366. package/src/Field/Field.tsx +6 -5
  367. package/src/Field/FieldLabel.tsx +8 -0
  368. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  369. package/src/FieldStatus/FieldStatus.test.tsx +78 -40
  370. package/src/FieldStatus/FieldStatus.tsx +22 -4
  371. package/src/FileInput/FileInput.test.tsx +52 -3
  372. package/src/FileInput/FileInput.tsx +13 -2
  373. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  374. package/src/HoverCard/HoverCard.test.tsx +21 -2
  375. package/src/HoverCard/HoverCard.tsx +11 -0
  376. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  377. package/src/HoverCard/useHoverCard.tsx +24 -2
  378. package/src/Icon/Icon.doc.mjs +3 -2
  379. package/src/Icon/Icon.tsx +7 -2
  380. package/src/IconButton/IconButton.doc.mjs +8 -0
  381. package/src/IconButton/IconButton.test.tsx +15 -18
  382. package/src/Item/Item.test.tsx +40 -18
  383. package/src/Item/Item.tsx +38 -2
  384. package/src/Layer/useLayer.tsx +8 -0
  385. package/src/List/List.test.tsx +9 -8
  386. package/src/MobileNav/MobileNav.tsx +12 -1
  387. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  388. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  389. package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
  390. package/src/MultiSelector/MultiSelector.test.tsx +172 -3
  391. package/src/MultiSelector/MultiSelector.tsx +55 -12
  392. package/src/MultiSelector/hooks.ts +17 -0
  393. package/src/NumberInput/NumberInput.test.tsx +11 -7
  394. package/src/NumberInput/NumberInput.tsx +1 -2
  395. package/src/Outline/Outline.tsx +0 -1
  396. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  397. package/src/OverflowList/OverflowList.test.tsx +111 -0
  398. package/src/OverflowList/OverflowList.tsx +48 -8
  399. package/src/Popover/Popover.tsx +7 -4
  400. package/src/Popover/usePopover.tsx +9 -13
  401. package/src/PowerSearch/PowerSearch.doc.mjs +14 -0
  402. package/src/PowerSearch/PowerSearch.test.tsx +20 -0
  403. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  404. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  405. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  406. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  407. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  408. package/src/SelectableCard/SelectableCard.tsx +29 -16
  409. package/src/Selector/Selector.test.tsx +69 -0
  410. package/src/Selector/Selector.tsx +5 -1
  411. package/src/Selector/hooks.ts +17 -0
  412. package/src/SideNav/SideNav.test.tsx +119 -3
  413. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  414. package/src/SideNav/SideNavHeading.tsx +24 -8
  415. package/src/SideNav/SideNavItem.tsx +5 -1
  416. package/src/Slider/Slider.test.tsx +75 -2
  417. package/src/Slider/Slider.tsx +21 -5
  418. package/src/Switch/Switch.test.tsx +32 -1
  419. package/src/Table/BaseTable.tsx +3 -5
  420. package/src/Table/index.ts +5 -0
  421. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  422. package/src/Table/plugins/rowStatus/index.ts +7 -0
  423. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  424. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  425. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  426. package/src/Table/plugins/selection/useTableSelection.tsx +19 -1
  427. package/src/Table/plugins/tree/useTableTreeData.test.tsx +135 -3
  428. package/src/Table/plugins/tree/useTableTreeData.tsx +128 -1
  429. package/src/Table/plugins/tree/useTableTreeState.test.tsx +104 -0
  430. package/src/Table/plugins/tree/useTableTreeState.tsx +46 -2
  431. package/src/Table/useBaseTablePlugins.ts +10 -6
  432. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  433. package/src/Table/useTableSelection.doc.mjs +65 -14
  434. package/src/Table/useTableTreeData.doc.mjs +29 -0
  435. package/src/Table/useTableTreeState.doc.mjs +2 -2
  436. package/src/TextArea/TextArea.test.tsx +9 -1
  437. package/src/Thumbnail/Thumbnail.doc.mjs +19 -10
  438. package/src/Thumbnail/Thumbnail.test.tsx +98 -7
  439. package/src/Thumbnail/Thumbnail.tsx +141 -41
  440. package/src/Thumbnail/thumbnail.markers.stylex.ts +12 -0
  441. package/src/TimeInput/TimeInput.test.tsx +76 -1
  442. package/src/TimeInput/TimeInput.tsx +19 -6
  443. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  444. package/src/Timestamp/Timestamp.test.tsx +32 -0
  445. package/src/Timestamp/Timestamp.tsx +28 -8
  446. package/src/Toast/useToast.tsx +7 -8
  447. package/src/ToggleButton/ToggleButtonGroup.tsx +1 -1
  448. package/src/Tokenizer/Tokenizer.doc.mjs +15 -1
  449. package/src/Tokenizer/Tokenizer.test.tsx +183 -2
  450. package/src/Tokenizer/Tokenizer.tsx +16 -4
  451. package/src/TopNav/TopNav.test.tsx +125 -1
  452. package/src/TopNav/TopNavHeading.tsx +24 -8
  453. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  454. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  455. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +3 -3
  456. package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +19 -2
  457. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  458. package/src/TopNav/TopNavMenu.tsx +83 -5
  459. package/src/TreeList/TreeList.doc.mjs +22 -0
  460. package/src/TreeList/TreeList.test.tsx +232 -0
  461. package/src/TreeList/TreeList.tsx +18 -2
  462. package/src/TreeList/TreeListBranches.tsx +16 -2
  463. package/src/TreeList/TreeListItem.tsx +51 -11
  464. package/src/TreeList/TreeListTypes.ts +27 -1
  465. package/src/TreeList/index.ts +2 -2
  466. package/src/authoring/doc.ts +1 -3
  467. package/src/docs-types.ts +5 -0
  468. package/src/hooks/computeOverflow.test.ts +422 -0
  469. package/src/hooks/computeOverflow.ts +290 -0
  470. package/src/hooks/index.ts +2 -0
  471. package/src/hooks/useDevWarning.test.tsx +45 -0
  472. package/src/hooks/useDevWarning.ts +54 -0
  473. package/src/hooks/useOverflow.test.ts +96 -4
  474. package/src/hooks/useOverflow.ts +74 -33
  475. package/src/i18n/InternationalizationProvider.doc.mjs +99 -2
  476. package/src/i18n/resolve.ts +9 -21
  477. package/src/theme/Theme.tsx +11 -14
  478. package/src/theme/generateThemeRules.test.ts +65 -0
  479. package/src/theme/generateThemeRules.ts +56 -0
  480. package/src/theme/hct.ts +1 -1
  481. package/src/theme/syntax/defineSyntaxTheme.ts +5 -6
  482. package/src/utils/devWarning.test.ts +56 -0
  483. package/src/utils/devWarning.ts +102 -0
  484. package/src/utils/index.ts +6 -0
  485. package/src/utils/plainDate.test.ts +22 -0
  486. package/src/utils/plainDate.ts +61 -0
  487. package/src/utils/types.ts +14 -0
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect, vi} from 'vitest';
4
+ import {render, fireEvent} from '@testing-library/react';
5
+ import {useEffect, useRef} from 'react';
6
+ import {ChatComposer} from './ChatComposer';
7
+ import {useChatComposerContext} from './ChatContext';
8
+
9
+ // The composer body — the elevated surface — is the div that wraps the input
10
+ // area (styles.body). A custom `input` slot renders inside the inputArea div,
11
+ // so the body is two ancestors up from the slot marker. Walking from a known
12
+ // marker keeps this independent of StyleX's generated class names.
13
+ function renderBodyClass(elevation?: 'none' | 'low'): string {
14
+ const {container} = render(
15
+ <ChatComposer
16
+ onSubmit={() => {}}
17
+ elevation={elevation}
18
+ input={<div data-testid="composer-input-marker" />}
19
+ />,
20
+ );
21
+ const marker = container.querySelector(
22
+ '[data-testid="composer-input-marker"]',
23
+ )!;
24
+ const inputArea = marker.parentElement!;
25
+ const body = inputArea.parentElement!;
26
+ return body.className;
27
+ }
28
+
29
+ describe('ChatComposer elevation', () => {
30
+ it('applies a distinct body class for each supported level', () => {
31
+ expect(renderBodyClass('none')).not.toBe(renderBodyClass('low'));
32
+ });
33
+
34
+ it("defaults to 'low' (preserves the raised look)", () => {
35
+ expect(renderBodyClass(undefined)).toBe(renderBodyClass('low'));
36
+ });
37
+ });
38
+
39
+ // A custom input that participates in the composer's composition contract:
40
+ // reads value/submit from the exported context, and registers a focus control
41
+ // so the shell can drive body-click-to-focus without knowing its DOM shape.
42
+ function CustomContextInput({focusSpy}: {focusSpy: () => void}) {
43
+ const ctx = useChatComposerContext();
44
+ const ref = useRef<HTMLInputElement>(null);
45
+ const controlRef = ctx?.inputControlRef;
46
+ useEffect(() => {
47
+ if (!controlRef) {
48
+ return;
49
+ }
50
+ controlRef.current = {
51
+ focus: () => {
52
+ focusSpy();
53
+ ref.current?.focus();
54
+ },
55
+ };
56
+ return () => {
57
+ controlRef.current = null;
58
+ };
59
+ }, [controlRef, focusSpy]);
60
+ return (
61
+ <input
62
+ ref={ref}
63
+ data-testid="custom-input"
64
+ value={ctx?.value ?? ''}
65
+ placeholder={ctx?.placeholder}
66
+ disabled={ctx?.isDisabled}
67
+ onChange={e => ctx?.onChange(e.target.value)}
68
+ />
69
+ );
70
+ }
71
+
72
+ describe('ChatComposer input composition contract', () => {
73
+ it('exposes value/onChange/placeholder/isDisabled to a custom input via context', () => {
74
+ const {getByTestId} = render(
75
+ <ChatComposer
76
+ onSubmit={() => {}}
77
+ value="hello"
78
+ placeholder="Say something"
79
+ isDisabled
80
+ input={<CustomContextInput focusSpy={() => {}} />}
81
+ />,
82
+ );
83
+ const input = getByTestId('custom-input') as HTMLInputElement;
84
+ expect(input.value).toBe('hello');
85
+ expect(input.placeholder).toBe('Say something');
86
+ expect(input.disabled).toBe(true);
87
+ });
88
+
89
+ it('focuses a custom input via its registered control on body click', () => {
90
+ const focusSpy = vi.fn();
91
+ const {getByTestId} = render(
92
+ <ChatComposer
93
+ onSubmit={() => {}}
94
+ input={<CustomContextInput focusSpy={focusSpy} />}
95
+ />,
96
+ );
97
+ const marker = getByTestId('custom-input');
98
+ const body = marker.parentElement!.parentElement!;
99
+ fireEvent.click(body);
100
+ expect(focusSpy).toHaveBeenCalled();
101
+ });
102
+
103
+ it('falls back to focusing a bare textarea when no control is registered', () => {
104
+ const {getByTestId} = render(
105
+ <ChatComposer
106
+ onSubmit={() => {}}
107
+ input={<textarea data-testid="bare-textarea" />}
108
+ />,
109
+ );
110
+ const textarea = getByTestId('bare-textarea') as HTMLTextAreaElement;
111
+ const body = textarea.parentElement!.parentElement!;
112
+ fireEvent.click(body);
113
+ expect(document.activeElement).toBe(textarea);
114
+ });
115
+ });
@@ -40,6 +40,7 @@ import {
40
40
  shadowVars,
41
41
  durationVars,
42
42
  easeVars,
43
+ borderVars,
43
44
  typeScaleVars,
44
45
  typographyVars,
45
46
  } from '../theme/tokens.stylex';
@@ -47,6 +48,7 @@ import {mergeProps} from '../utils';
47
48
  import {Icon} from '../Icon';
48
49
  import {ChatComposerInput} from './ChatComposerInput';
49
50
  import {ChatComposerContext} from './ChatContext';
51
+ import type {ChatComposerInputControl} from './ChatContext';
50
52
  import {ChatSendButton} from './ChatSendButton';
51
53
  import {themeProps} from '../utils/themeProps';
52
54
  import {useTranslator} from '../i18n';
@@ -83,6 +85,14 @@ export interface ChatComposerProps extends Omit<
83
85
  isDisabled?: boolean;
84
86
  /** Density variant */
85
87
  density?: ChatComposerDensity;
88
+ /**
89
+ * Resting elevation of the composer body. `low` (the default) keeps today's
90
+ * raised look — low at rest, bumping to med on hover / focus. `none` flattens
91
+ * it and draws a border with the same rest / hover / focus treatment as a
92
+ * text input (emphasized border → accent on focus, with matching inset rings).
93
+ * @default 'low'
94
+ */
95
+ elevation?: 'none' | 'low';
86
96
 
87
97
  // --- Slot props ---
88
98
 
@@ -143,14 +153,7 @@ const styles = stylex.create({
143
153
  borderRadius: 'var(--_chat-composer-radius)',
144
154
  backgroundColor: colorVars['--color-background-popover'],
145
155
  cursor: 'text',
146
- boxShadow: {
147
- default: shadowVars['--shadow-low'],
148
- ':hover': {'@media (hover: hover)': shadowVars['--shadow-med']},
149
- },
150
- transition: `box-shadow ${durationVars['--duration-fast']} ${easeVars['--ease-standard']}`,
151
- ':focus-within': {
152
- boxShadow: shadowVars['--shadow-med'],
153
- },
156
+ transition: `box-shadow ${durationVars['--duration-fast']} ${easeVars['--ease-standard']}, border-color ${durationVars['--duration-fast']} ${easeVars['--ease-standard']}`,
154
157
  },
155
158
  header: {
156
159
  display: 'flex',
@@ -247,11 +250,56 @@ const styles = stylex.create({
247
250
  color: colorVars['--color-text-yellow'],
248
251
  },
249
252
  compact: {
250
- padding: spacingVars['--spacing-2'],
253
+ // Override the padding var (not the `padding` property) so both the base
254
+ // body padding and the border-inset calc in elevationStyles.none pick up
255
+ // the compact value. Scoped to the body element, so the status bar (which
256
+ // reads the var from root) keeps its own padding.
257
+ '--_chat-composer-padding': spacingVars['--spacing-2'],
251
258
  gap: spacingVars['--spacing-1'],
252
259
  },
253
260
  });
254
261
 
262
+ // Resting elevation for the composer body, narrowed to two steps.
263
+ // - `low` (default) preserves today's look: low at rest, bumping to med on
264
+ // hover / focus-within (a CSS-only interaction state the component owns).
265
+ // - `none` is flat; depth comes from a border + inset rings instead. The
266
+ // border is drawn *inside* the padding — its width is subtracted from every
267
+ // side (like Card) so total inset (border + padding) equals the elevated
268
+ // case and content geometry is unchanged. Border color, hover, and focus
269
+ // states mirror TextInput (shared `inputWrapperStyles.base` treatment):
270
+ // emphasized border at rest → accent on focus-within, with the same inset
271
+ // shadow rings on hover and focus.
272
+ const elevationStyles = stylex.create({
273
+ low: {
274
+ boxShadow: {
275
+ default: shadowVars['--shadow-low'],
276
+ ':hover': {'@media (hover: hover)': shadowVars['--shadow-med']},
277
+ },
278
+ ':focus-within': {
279
+ boxShadow: shadowVars['--shadow-med'],
280
+ },
281
+ },
282
+ none: {
283
+ borderWidth: borderVars['--border-width'],
284
+ borderStyle: 'solid',
285
+ borderColor: {
286
+ default: colorVars['--color-border-emphasized'],
287
+ ':focus-within': colorVars['--color-accent'],
288
+ },
289
+ // Draw the border *inside* the padding — subtract its width from the body
290
+ // padding (like Card's withBorder) so total inset (border + padding) equals
291
+ // the elevated case and content geometry is unchanged.
292
+ padding: `calc(var(--_chat-composer-padding) - ${borderVars['--border-width']})`,
293
+ boxShadow: {
294
+ default: 'none',
295
+ ':hover:not(:focus-within)': {
296
+ '@media (hover: hover)': `inset 0px 0px 0px 2px color-mix(in srgb, ${colorVars['--color-border-emphasized']} 30%, transparent)`,
297
+ },
298
+ ':focus-within': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
299
+ },
300
+ },
301
+ });
302
+
255
303
  // =============================================================================
256
304
  // Component
257
305
  // =============================================================================
@@ -280,6 +328,7 @@ export function ChatComposer(props: ChatComposerProps) {
280
328
  placeholder: placeholderFromProps,
281
329
  isDisabled = false,
282
330
  density = 'balanced',
331
+ elevation = 'low',
283
332
  drawer,
284
333
  headerActions,
285
334
  headerContext,
@@ -325,6 +374,11 @@ export function ChatComposer(props: ChatComposerProps) {
325
374
 
326
375
  const bodyRef = useRef<HTMLDivElement>(null);
327
376
 
377
+ // Input slot registers its focus (and future) control here so the shell can
378
+ // drive it without knowing its DOM shape. Stable ref — safe in the memoized
379
+ // context value.
380
+ const inputControlRef = useRef<ChatComposerInputControl | null>(null);
381
+
328
382
  const handleBodyClick = useCallback((e: MouseEvent<HTMLDivElement>) => {
329
383
  // Focus the input when clicking empty space in the body.
330
384
  // Skip if the click target is a button, link, or interactive element.
@@ -336,6 +390,14 @@ export function ChatComposer(props: ChatComposerProps) {
336
390
  ) {
337
391
  return;
338
392
  }
393
+ // Prefer the input's registered control (works for any input shape,
394
+ // including editors whose focusable node isn't a bare
395
+ // contenteditable/textarea). Fall back to a DOM query so uninstrumented
396
+ // custom inputs still get click-to-focus.
397
+ if (inputControlRef.current) {
398
+ inputControlRef.current.focus();
399
+ return;
400
+ }
339
401
  const editable = bodyRef.current?.querySelector<HTMLElement>(
340
402
  '[contenteditable="true"], textarea',
341
403
  );
@@ -370,6 +432,7 @@ export function ChatComposer(props: ChatComposerProps) {
370
432
  isStopShown,
371
433
  canSend,
372
434
  onStop,
435
+ inputControlRef,
373
436
  }),
374
437
  [
375
438
  currentValue,
@@ -402,6 +465,7 @@ export function ChatComposer(props: ChatComposerProps) {
402
465
  onClick={handleBodyClick}
403
466
  {...stylex.props(
404
467
  styles.body,
468
+ elevationStyles[elevation],
405
469
  density === 'compact' && styles.compact,
406
470
  xstyle,
407
471
  )}>
@@ -0,0 +1,61 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+ import {render, screen, fireEvent} from '@testing-library/react';
5
+ import {ChatComposerDrawer} from './ChatComposerDrawer';
6
+
7
+ describe('ChatComposerDrawer', () => {
8
+ it('links the toggle to the drawer content via aria-controls', () => {
9
+ render(
10
+ <ChatComposerDrawer count={2} label="Attachments">
11
+ <span>Drawer content</span>
12
+ </ChatComposerDrawer>,
13
+ );
14
+
15
+ const toggle = screen.getByRole('button', {name: /Attachments/});
16
+ const controlsId = toggle.getAttribute('aria-controls');
17
+ // aria-controls must be present and point at the real content region.
18
+ expect(controlsId).toBeTruthy();
19
+ const region = document.getElementById(controlsId as string);
20
+ expect(region).not.toBeNull();
21
+ expect(region).toContainElement(screen.getByText('Drawer content'));
22
+ });
23
+
24
+ it('keeps aria-controls resolvable while collapsed (content stays mounted)', () => {
25
+ render(
26
+ <ChatComposerDrawer count={2} label="Attachments" defaultIsCollapsed>
27
+ <span>Drawer content</span>
28
+ </ChatComposerDrawer>,
29
+ );
30
+
31
+ const toggle = screen.getByRole('button', {name: /Attachments/});
32
+ const controlsId = toggle.getAttribute('aria-controls');
33
+ expect(controlsId).toBeTruthy();
34
+ const region = document.getElementById(controlsId as string);
35
+ expect(region).not.toBeNull();
36
+ expect(region).toContainElement(screen.getByText('Drawer content'));
37
+ });
38
+
39
+ it('toggles aria-expanded when the toggle is activated', () => {
40
+ render(
41
+ <ChatComposerDrawer count={2} label="Attachments">
42
+ <span>Drawer content</span>
43
+ </ChatComposerDrawer>,
44
+ );
45
+
46
+ const toggle = screen.getByRole('button', {name: /Attachments/});
47
+ expect(toggle).toHaveAttribute('aria-expanded', 'true');
48
+
49
+ fireEvent.click(toggle);
50
+ expect(screen.getByRole('button', {name: /Attachments/})).toHaveAttribute(
51
+ 'aria-expanded',
52
+ 'false',
53
+ );
54
+
55
+ fireEvent.click(screen.getByRole('button', {name: /Attachments/}));
56
+ expect(screen.getByRole('button', {name: /Attachments/})).toHaveAttribute(
57
+ 'aria-expanded',
58
+ 'true',
59
+ );
60
+ });
61
+ });
@@ -9,13 +9,15 @@
9
9
  * @position Collapsible drawer panel for ChatComposer.
10
10
  * Supports expanded (full content) and collapsed (count + label) states
11
11
  * with fade animation and grid-template-rows height transition.
12
+ * The collapse toggle exposes aria-expanded + aria-controls linking it to
13
+ * the content region (disclosure pattern).
12
14
  *
13
15
  * SYNC: When modified, update:
14
16
  * - /packages/core/src/Chat/index.ts (exports)
15
17
  * - /packages/cli/templates/blocks/components/ChatComposerDrawer/ (block examples)
16
18
  */
17
19
 
18
- import {useState, type ReactNode} from 'react';
20
+ import {useId, useState, type ReactNode} from 'react';
19
21
  import type {StyleXStyles} from '@stylexjs/stylex';
20
22
  import * as stylex from '@stylexjs/stylex';
21
23
  import {
@@ -240,6 +242,12 @@ export function ChatComposerDrawer({
240
242
 
241
243
  const canCollapse = count != null;
242
244
 
245
+ // Links the toggle to the region it shows/hides so assistive tech can move
246
+ // from the button to its controlled content (disclosure pattern). The
247
+ // content stays mounted while collapsed (hidden via the grid-row collapse),
248
+ // so the reference always resolves.
249
+ const contentId = useId();
250
+
243
251
  const toggle = () => {
244
252
  const next = !isCollapsed;
245
253
  if (!isControlled) {
@@ -271,6 +279,7 @@ export function ChatComposerDrawer({
271
279
  role="button"
272
280
  tabIndex={0}
273
281
  aria-expanded={!isCollapsed}
282
+ aria-controls={contentId}
274
283
  aria-label={
275
284
  isCollapsed
276
285
  ? t('@astryx.chatComposerDrawer.expand', {label})
@@ -301,6 +310,7 @@ export function ChatComposerDrawer({
301
310
  )}
302
311
 
303
312
  <div
313
+ id={contentId}
304
314
  {...stylex.props(
305
315
  styles.contentGrid,
306
316
  canCollapse && isCollapsed && styles.contentGridCollapsed,
@@ -80,6 +80,11 @@ export const docs = {
80
80
  type: '(value: string) => void',
81
81
  description: 'Called when the user presses Enter without Shift. The serialized value includes token placeholders.',
82
82
  },
83
+ {
84
+ name: 'onKeyDown',
85
+ type: '(event) => void',
86
+ description: 'Key-down handler invoked before the built-in Enter/history behavior (after any open trigger menu). The seam for platform-specific keys: call event.preventDefault() to suppress the default submit (e.g. newline on a touch keyboard), or act on the event yourself to add behavior (e.g. submit on Cmd/Ctrl+Enter). IME composition is always respected — Enter never submits mid-composition.',
87
+ },
83
88
  ],
84
89
  };
85
90
 
@@ -102,6 +107,7 @@ export const docsZh = {
102
107
  onPaste: '粘贴文本时调用。用于拦截或转换粘贴内容。',
103
108
  onFiles: '粘贴或拖放文件到输入时调用。用于处理附件。',
104
109
  onSubmit: '用户不按 Shift 按 Enter 时调用。序列化值包含标记占位符。',
110
+ onKeyDown: '在内置 Enter/历史处理之前(在打开的触发菜单之后)调用的按键处理器。平台特定按键的接缝:调用 event.preventDefault() 可抑制默认提交(例如触控键盘上插入换行),或自行处理事件以添加行为(例如 Cmd/Ctrl+Enter 提交)。始终尊重输入法组字状态——组字过程中 Enter 永不提交。',
105
111
  },
106
112
  };
107
113
 
@@ -124,5 +130,6 @@ export const docsDense = {
124
130
  onPaste: 'paste handler; intercept/transform pasted content',
125
131
  onFiles: 'file paste/drop handler; use for attachments',
126
132
  onSubmit: 'Enter w/o Shift handler; serialized value includes token placeholders',
133
+ onKeyDown: 'keydown handler before built-in Enter/history (after trigger menu); preventDefault() to suppress default submit (touch newline) or act on event to add behavior (Cmd/Ctrl+Enter). IME composition always respected',
127
134
  },
128
135
  };
@@ -155,6 +155,109 @@ describe('ChatComposerInput', () => {
155
155
  });
156
156
  });
157
157
 
158
+ describe('composer focus control', () => {
159
+ it('registers a focus control so a body click focuses the input', () => {
160
+ render(
161
+ <ChatComposer onSubmit={() => {}} input={<ChatComposerInput />} />,
162
+ );
163
+ const editable = screen.getByRole('textbox');
164
+ // Walk to the composer body: editable → input root → inputArea → body.
165
+ const inputRoot = editable.parentElement!;
166
+ const inputArea = inputRoot.parentElement!;
167
+ const body = inputArea.parentElement!;
168
+ // Click empty space in the body → shell drives the registered control.
169
+ fireEvent.click(body);
170
+ expect(document.activeElement).toBe(editable);
171
+ });
172
+ });
173
+
174
+ describe('Enter submit behavior', () => {
175
+ it('does not submit on Enter while IME composition is in progress', () => {
176
+ const onSubmit = vi.fn();
177
+ render(<ChatComposerInput onSubmit={onSubmit} />);
178
+ const textbox = screen.getByRole('textbox');
179
+ textbox.textContent = 'こんにちは';
180
+ fireEvent.input(textbox);
181
+ // isComposing is surfaced on the native event during IME composition.
182
+ fireEvent.keyDown(textbox, {key: 'Enter', isComposing: true});
183
+ expect(onSubmit).not.toHaveBeenCalled();
184
+ });
185
+
186
+ it('does not submit on Enter for the legacy keyCode 229 composing signal', () => {
187
+ const onSubmit = vi.fn();
188
+ render(<ChatComposerInput onSubmit={onSubmit} />);
189
+ const textbox = screen.getByRole('textbox');
190
+ textbox.textContent = 'ㅎ';
191
+ fireEvent.input(textbox);
192
+ fireEvent.keyDown(textbox, {key: 'Enter', keyCode: 229});
193
+ expect(onSubmit).not.toHaveBeenCalled();
194
+ });
195
+
196
+ it('lets onKeyDown suppress the default submit via preventDefault (touch-newline recipe)', () => {
197
+ // The documented "insert a newline instead of sending" pattern: a
198
+ // consumer preventDefaults Enter (e.g. on a coarse pointer).
199
+ const onSubmit = vi.fn();
200
+ const onKeyDown = vi.fn(e => {
201
+ if (e.key === 'Enter' && !e.shiftKey) {
202
+ e.preventDefault();
203
+ }
204
+ });
205
+ render(<ChatComposerInput onSubmit={onSubmit} onKeyDown={onKeyDown} />);
206
+ const textbox = screen.getByRole('textbox');
207
+ textbox.textContent = 'hello';
208
+ fireEvent.input(textbox);
209
+ fireEvent.keyDown(textbox, {key: 'Enter'});
210
+ expect(onKeyDown).toHaveBeenCalled();
211
+ expect(onSubmit).not.toHaveBeenCalled();
212
+ });
213
+
214
+ it('lets onKeyDown add behavior (Cmd/Ctrl+Enter submit) without preventDefault', () => {
215
+ // Adding a submit shortcut is just handling the event yourself; the
216
+ // built-in Enter handling still runs for the plain-Enter case.
217
+ const onSubmit = vi.fn();
218
+ const handle = vi.fn();
219
+ const onKeyDown = vi.fn(e => {
220
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
221
+ handle();
222
+ }
223
+ });
224
+ render(<ChatComposerInput onSubmit={onSubmit} onKeyDown={onKeyDown} />);
225
+ const textbox = screen.getByRole('textbox');
226
+ textbox.textContent = 'hello';
227
+ fireEvent.input(textbox);
228
+ fireEvent.keyDown(textbox, {key: 'Enter', metaKey: true});
229
+ expect(handle).toHaveBeenCalled();
230
+ // Consumer did not preventDefault, so the built-in submit also fires.
231
+ expect(onSubmit).toHaveBeenCalledWith('hello');
232
+ });
233
+
234
+ it('calls onKeyDown before submit and lets preventDefault take over', () => {
235
+ const onSubmit = vi.fn();
236
+ const onKeyDown = vi.fn(e => {
237
+ e.preventDefault();
238
+ });
239
+ render(<ChatComposerInput onSubmit={onSubmit} onKeyDown={onKeyDown} />);
240
+ const textbox = screen.getByRole('textbox');
241
+ textbox.textContent = 'hello';
242
+ fireEvent.input(textbox);
243
+ fireEvent.keyDown(textbox, {key: 'Enter'});
244
+ expect(onKeyDown).toHaveBeenCalled();
245
+ expect(onSubmit).not.toHaveBeenCalled();
246
+ });
247
+
248
+ it('calls onKeyDown but still submits when the consumer does not preventDefault', () => {
249
+ const onSubmit = vi.fn();
250
+ const onKeyDown = vi.fn();
251
+ render(<ChatComposerInput onSubmit={onSubmit} onKeyDown={onKeyDown} />);
252
+ const textbox = screen.getByRole('textbox');
253
+ textbox.textContent = 'hello';
254
+ fireEvent.input(textbox);
255
+ fireEvent.keyDown(textbox, {key: 'Enter'});
256
+ expect(onKeyDown).toHaveBeenCalled();
257
+ expect(onSubmit).toHaveBeenCalledWith('hello');
258
+ });
259
+ });
260
+
158
261
  // Controlled-value sync used to overwrite `textContent` on every
159
262
  // distinct render, which (a) collapsed the caret to offset 0
160
263
  // (visible after a slash-command pick like
@@ -11,8 +11,10 @@
11
11
  *
12
12
  * ContentEditable-based rich input for the chat composer.
13
13
  * Supports trigger menus (@ mentions, / commands) via SearchSource,
14
- * inline token rendering, serialization, Enter/Shift+Enter, message
15
- * history, paste/drop file handling, and mobile-safe touch typography.
14
+ * inline token rendering, serialization, Enter-to-submit with
15
+ * IME-composition guarding and an onKeyDown seam for platform-specific
16
+ * key handling, message history, paste/drop file handling, and
17
+ * mobile-safe touch typography.
16
18
  *
17
19
  *
18
20
  * SYNC: When modified, update:
@@ -188,6 +190,21 @@ export interface ChatComposerInputProps extends Omit<
188
190
  onFiles?: (files: File[]) => void;
189
191
  /** Submit handler (Enter without Shift) */
190
192
  onSubmit?: (value: string) => void;
193
+ /**
194
+ * Key-down handler invoked before the built-in Enter/history behavior
195
+ * (but after an open trigger menu consumes the event).
196
+ *
197
+ * This is the seam for platform- or app-specific key handling:
198
+ * - Call `event.preventDefault()` to suppress the default submit (e.g.
199
+ * let Enter insert a newline on a touch keyboard).
200
+ * - Add behavior by acting on the event yourself (e.g. submit on
201
+ * Cmd/Ctrl+Enter) without calling `preventDefault()`, so the default
202
+ * handling still runs for other keys.
203
+ *
204
+ * IME composition is always respected regardless of this handler: Enter
205
+ * never submits while a composition is in progress.
206
+ */
207
+ onKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void;
191
208
  }
192
209
 
193
210
  // =============================================================================
@@ -299,6 +316,7 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
299
316
  pasteAsToken: pasteAsTokenProp,
300
317
  onFiles,
301
318
  onSubmit = composerCtx?.onSubmit,
319
+ onKeyDown: onKeyDownProp,
302
320
  xstyle,
303
321
  className,
304
322
  style,
@@ -366,6 +384,20 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
366
384
  selfRef.current = handle;
367
385
  useImperativeHandle(handleRef, () => handle);
368
386
 
387
+ // Register a focus control with the composer shell so body-click-to-focus
388
+ // works without the shell sniffing the input's DOM shape. Cleared on
389
+ // unmount so the shell falls back cleanly if the input goes away.
390
+ const inputControlRef = composerCtx?.inputControlRef;
391
+ useEffect(() => {
392
+ if (!inputControlRef) {
393
+ return;
394
+ }
395
+ inputControlRef.current = {focus: () => editableRef.current?.focus()};
396
+ return () => {
397
+ inputControlRef.current = null;
398
+ };
399
+ }, [inputControlRef]);
400
+
369
401
  useEffect(() => {
370
402
  if (controlledValue === undefined || !editableRef.current) {
371
403
  return;
@@ -477,6 +509,13 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
477
509
  return;
478
510
  }
479
511
 
512
+ // Consumer passthrough — runs before built-in Enter/history handling.
513
+ // A consumer can preventDefault() to fully own the keystroke.
514
+ onKeyDownProp?.(e);
515
+ if (e.defaultPrevented) {
516
+ return;
517
+ }
518
+
480
519
  // Handle Backspace near tokens — prevent browser from creating
481
520
  // stray <br> elements or moving the cursor unexpectedly.
482
521
  if (e.key === 'Backspace') {
@@ -519,6 +558,13 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
519
558
  }
520
559
 
521
560
  if (e.key === 'Enter' && !e.shiftKey) {
561
+ // Never submit mid-composition — an IME uses Enter to commit a
562
+ // candidate (e.g. Japanese/Chinese/Korean input), and browsers may
563
+ // also surface the legacy keyCode 229 for composing keystrokes.
564
+ if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) {
565
+ return;
566
+ }
567
+
522
568
  e.preventDefault();
523
569
  if (!editableRef.current) {
524
570
  return;
@@ -583,7 +629,7 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
583
629
  }
584
630
  }
585
631
  },
586
- [hasHistory, onSubmit, onChange, emitChange, triggerMenu],
632
+ [hasHistory, onSubmit, onChange, emitChange, triggerMenu, onKeyDownProp],
587
633
  );
588
634
 
589
635
  const handlePaste = useCallback(