@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
@@ -13,6 +13,8 @@ import {
13
13
  import {render, screen, fireEvent, act} from '@testing-library/react';
14
14
  import React, {useState} from 'react';
15
15
  import {PowerSearch} from './PowerSearch';
16
+ import {PowerSearchEditPopover} from './PowerSearchEditPopover';
17
+ import {useInternalConfig} from './useInternalConfig';
16
18
  import type {PowerSearchConfig, PowerSearchFilter} from './types';
17
19
 
18
20
  // =============================================================================
@@ -216,4 +218,69 @@ describe('PowerSearch', () => {
216
218
  expect(popoverText).toContain('Priority');
217
219
  expect(popoverText).toContain('equals');
218
220
  });
221
+
222
+ it('does not save/close edit popover when Enter is consumed by child listbox option selection', () => {
223
+ const multiConfig: PowerSearchConfig = {
224
+ name: 'test-multi',
225
+ fields: [
226
+ {
227
+ key: 'status',
228
+ label: 'Status',
229
+ operators: [
230
+ {
231
+ key: 'any_of',
232
+ label: 'is any of',
233
+ value: {
234
+ type: 'enum_list',
235
+ values: [
236
+ {value: 'open', label: 'Open'},
237
+ {value: 'closed', label: 'Closed'},
238
+ ],
239
+ },
240
+ },
241
+ ],
242
+ },
243
+ ],
244
+ };
245
+
246
+ const onSave = vi.fn();
247
+ const onCancel = vi.fn();
248
+
249
+ function MultiSelectHarness() {
250
+ const internalConfig = useInternalConfig(multiConfig);
251
+ return (
252
+ <PowerSearchEditPopover
253
+ config={internalConfig}
254
+ filter={{
255
+ field: 'status',
256
+ operator: 'any_of',
257
+ value: {type: 'enum_list', value: ['open']},
258
+ }}
259
+ mode="edit"
260
+ onSave={onSave}
261
+ onCancel={onCancel}
262
+ />
263
+ );
264
+ }
265
+
266
+ const {container} = render(<MultiSelectHarness />);
267
+
268
+ const input = container.querySelector('input');
269
+ expect(input).not.toBeNull();
270
+
271
+ // Fire an Enter event that has been defaultPrevented (e.g. child typeahead option selection)
272
+ const enterEvent = new KeyboardEvent('keydown', {
273
+ key: 'Enter',
274
+ bubbles: true,
275
+ cancelable: true,
276
+ });
277
+ enterEvent.preventDefault();
278
+
279
+ act(() => {
280
+ input?.dispatchEvent(enterEvent);
281
+ });
282
+
283
+ // onSave should NOT be called because the event was already consumed (defaultPrevented)
284
+ expect(onSave).not.toHaveBeenCalled();
285
+ });
219
286
  });
@@ -702,7 +702,7 @@ export function PowerSearchEditPopover({
702
702
  // Handle Enter to save, Escape to cancel
703
703
  const handleKeyDown = useCallback(
704
704
  (e: React.KeyboardEvent) => {
705
- if (e.key === 'Enter' && !isSaveDisabled) {
705
+ if (e.key === 'Enter' && !isSaveDisabled && !e.defaultPrevented) {
706
706
  e.preventDefault();
707
707
  handleSave();
708
708
  } else if (e.key === 'Escape' && !e.defaultPrevented) {
@@ -76,8 +76,7 @@ export function usePowerSearchSource(
76
76
 
77
77
  // Check each field+operator combo against the query
78
78
  for (const op of field.operators) {
79
- const combinedLabel =
80
- `${field.label} ${opLabel(op)}`.toLowerCase();
79
+ const combinedLabel = `${field.label} ${opLabel(op)}`.toLowerCase();
81
80
  if (combinedLabel.includes(lower)) {
82
81
  const id = `${field.key}:${op.key}`;
83
82
  if (!seen.has(id)) {
@@ -27,6 +27,7 @@ export const docs = {
27
27
  {name: 'children', type: 'ReactNode', description: 'Card content.'},
28
28
  {name: 'padding', type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10', description: 'Inner padding.', default: '4'},
29
29
  {name: 'variant', type: "'default' | 'transparent' | 'muted' | 'blue' | 'cyan' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'teal' | 'yellow'", description: 'Background color variant.', default: "'default'"},
30
+ {name: 'elevation', type: "'none' | 'low' | 'med' | 'high'", description: 'Resting shadow depth. The selection ring composes on top, so a selected card keeps its shadow.', default: "'none'"},
30
31
  {name: 'width', type: 'SizeValue', description: 'Card width.'},
31
32
  {name: 'height', type: 'SizeValue', description: 'Card height.'},
32
33
  {name: 'maxWidth', type: 'SizeValue', description: 'Maximum card width.'},
@@ -71,6 +72,7 @@ export const docsDense = {
71
72
  isDisabled: 'disables card',
72
73
  padding: 'inner padding',
73
74
  variant: 'background color variant',
75
+ elevation: 'resting shadow depth: none|low|med|high; selection ring composes on top',
74
76
  width: 'card width',
75
77
  height: 'card height',
76
78
  maxWidth: 'max card width',
@@ -85,8 +85,7 @@ describe('SelectableCard', () => {
85
85
  label="Disabled"
86
86
  isSelected={false}
87
87
  onChange={handleChange}
88
- isDisabled
89
- >
88
+ isDisabled>
90
89
  Content
91
90
  </SelectableCard>,
92
91
  );
@@ -101,12 +100,55 @@ describe('SelectableCard', () => {
101
100
  label="Disabled"
102
101
  isSelected={false}
103
102
  onChange={handleChange}
104
- isDisabled
105
- >
103
+ isDisabled>
106
104
  <span>Content</span>
107
105
  </SelectableCard>,
108
106
  );
109
107
  fireEvent.click(screen.getByText('Content'));
110
108
  expect(handleChange).not.toHaveBeenCalled();
111
109
  });
110
+
111
+ describe('elevation', () => {
112
+ const noop = () => {};
113
+
114
+ it('forwards a distinct elevation class to the card for each level', () => {
115
+ const classFor = (elevation: 'none' | 'low' | 'med' | 'high') => {
116
+ const {container} = render(
117
+ <SelectableCard
118
+ label="Card"
119
+ isSelected={false}
120
+ onChange={noop}
121
+ elevation={elevation}>
122
+ Content
123
+ </SelectableCard>,
124
+ );
125
+ return container.firstElementChild!.className;
126
+ };
127
+ const classes = new Set([
128
+ classFor('none'),
129
+ classFor('low'),
130
+ classFor('med'),
131
+ classFor('high'),
132
+ ]);
133
+ expect(classes.size).toBe(4);
134
+ });
135
+
136
+ it('still varies elevation while selected (ring composes with elevation)', () => {
137
+ const selectedClassFor = (elevation: 'none' | 'med') => {
138
+ const {container} = render(
139
+ <SelectableCard
140
+ label="Card"
141
+ isSelected
142
+ onChange={noop}
143
+ elevation={elevation}>
144
+ Content
145
+ </SelectableCard>,
146
+ );
147
+ return container.firstElementChild!.className;
148
+ };
149
+ // A selected card at 'med' must differ from a selected card at 'none' —
150
+ // proving the selection ring does not clobber the elevation shadow.
151
+ expect(selectedClassFor('med')).not.toBe(selectedClassFor('none'));
152
+ });
153
+ });
112
154
  });
@@ -14,6 +14,7 @@
14
14
  * - /apps/storybook/stories/SelectableCard.stories.tsx (storybook stories)
15
15
  * - /packages/cli/templates/blocks/components/Card/SelectableCardShowcase.tsx (showcase block)
16
16
  * - /packages/cli/templates/blocks/components/Card/SelectableCardMulti.tsx (block)
17
+ * - /packages/cli/templates/blocks/components/Card/SelectableCardElevated.tsx (block)
17
18
  *
18
19
  * Composes Card for all visual styling. Adds selection state with
19
20
  * an inset box-shadow (zero layout jitter) and useClickableContainer
@@ -36,7 +37,7 @@ import {
36
37
  import * as stylex from '@stylexjs/stylex';
37
38
  import type {StyleXStyles} from '@stylexjs/stylex';
38
39
  import {colorVars, durationVars, easeVars} from '../theme/tokens.stylex';
39
- import type {SizeValue, SpacingStep} from '../utils/types';
40
+ import type {Elevation, SizeValue, SpacingStep} from '../utils/types';
40
41
  import {mergeProps, mergeRefs} from '../utils';
41
42
  import {Card} from '../Card/Card';
42
43
  import type {CardVariant} from '../Card/Card';
@@ -78,13 +79,13 @@ const styles = stylex.create({
78
79
  backgroundColor: 'transparent',
79
80
  },
80
81
  ':active::after': {
81
- backgroundColor: 'color-mix(in srgb, currentColor 10%, transparent)',
82
+ backgroundColor: colorVars['--color-overlay-pressed'],
82
83
  },
83
84
  },
84
85
  hoverOnPointer: {
85
86
  '@media (hover: hover)': {
86
87
  ':hover::after': {
87
- backgroundColor: 'color-mix(in srgb, currentColor 5%, transparent)',
88
+ backgroundColor: colorVars['--color-overlay-hover'],
88
89
  },
89
90
  },
90
91
  },
@@ -103,51 +104,54 @@ const styles = stylex.create({
103
104
  whiteSpace: 'nowrap',
104
105
  borderWidth: 0,
105
106
  },
106
- // Selection indicator — inset box-shadow for the thick inner ring (zero layout
107
- // jitter) plus borderColor change on the card's own border for a cohesive look.
107
+ // Selection indicator — an inset ring drawn via the card's --_card-ring
108
+ // shadow slot (zero layout jitter) plus a borderColor change on the card's
109
+ // own border for a cohesive look. Routing the ring through --_card-ring
110
+ // (rather than box-shadow directly) lets it compose with the card's
111
+ // elevation instead of clobbering it.
108
112
  selected: {
109
113
  borderColor: colorVars['--color-accent'],
110
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-accent']}`,
114
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-accent']}`,
111
115
  },
112
116
  selectedBlue: {
113
117
  borderColor: colorVars['--color-border-blue'],
114
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-blue']}`,
118
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-blue']}`,
115
119
  },
116
120
  selectedCyan: {
117
121
  borderColor: colorVars['--color-border-cyan'],
118
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-cyan']}`,
122
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-cyan']}`,
119
123
  },
120
124
  selectedGray: {
121
125
  borderColor: colorVars['--color-border-gray'],
122
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-gray']}`,
126
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-gray']}`,
123
127
  },
124
128
  selectedGreen: {
125
129
  borderColor: colorVars['--color-border-green'],
126
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-green']}`,
130
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-green']}`,
127
131
  },
128
132
  selectedOrange: {
129
133
  borderColor: colorVars['--color-border-orange'],
130
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-orange']}`,
134
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-orange']}`,
131
135
  },
132
136
  selectedPink: {
133
137
  borderColor: colorVars['--color-border-pink'],
134
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-pink']}`,
138
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-pink']}`,
135
139
  },
136
140
  selectedPurple: {
137
141
  borderColor: colorVars['--color-border-purple'],
138
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-purple']}`,
142
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-purple']}`,
139
143
  },
140
144
  selectedRed: {
141
145
  borderColor: colorVars['--color-border-red'],
142
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-red']}`,
146
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-red']}`,
143
147
  },
144
148
  selectedTeal: {
145
149
  borderColor: colorVars['--color-border-teal'],
146
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-teal']}`,
150
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-teal']}`,
147
151
  },
148
152
  selectedYellow: {
149
153
  borderColor: colorVars['--color-border-yellow'],
150
- boxShadow: `inset 0 0 0 2px ${colorVars['--color-border-yellow']}`,
154
+ '--_card-ring': `inset 0 0 0 2px ${colorVars['--color-border-yellow']}`,
151
155
  },
152
156
  });
153
157
 
@@ -228,6 +232,13 @@ export interface SelectableCardProps extends Omit<BaseProps, 'onChange'> {
228
232
  */
229
233
  variant?: CardVariant;
230
234
 
235
+ /**
236
+ * Resting elevation — the shadow depth the card sits at.
237
+ * The selection ring composes on top, so a selected card keeps its shadow.
238
+ * @default 'none'
239
+ */
240
+ elevation?: Elevation;
241
+
231
242
  /** Width of the card. */
232
243
  width?: SizeValue;
233
244
 
@@ -278,6 +289,7 @@ export function SelectableCard({
278
289
  children,
279
290
  padding,
280
291
  variant = 'default',
292
+ elevation = 'none',
281
293
  width,
282
294
  height,
283
295
  maxWidth,
@@ -328,6 +340,7 @@ export function SelectableCard({
328
340
  maxWidth={maxWidth}
329
341
  padding={padding}
330
342
  variant={variant}
343
+ elevation={elevation}
331
344
  {...mergeProps(
332
345
  themeProps('selectable-card', {
333
346
  variant,
@@ -503,6 +503,58 @@ describe('Selector', () => {
503
503
  expect(search).toHaveAttribute('aria-activedescendant');
504
504
  });
505
505
 
506
+ it('PageDown/PageUp jump the highlight to the last/first filtered option', async () => {
507
+ const user = userEvent.setup();
508
+ render(
509
+ <Selector
510
+ label="Fruit"
511
+ options={OPTIONS}
512
+ value="Apple"
513
+ onChange={() => {}}
514
+ hasSearch
515
+ />,
516
+ );
517
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
518
+ const search = screen.getByRole('combobox', h);
519
+ // Filter to Apple and Banana so "last" means last *visible* option.
520
+ await user.type(search, 'a');
521
+ const options = screen.getAllByRole('option', h);
522
+ expect(options).toHaveLength(2);
523
+ await user.keyboard('{PageDown}');
524
+ expect(search).toHaveAttribute(
525
+ 'aria-activedescendant',
526
+ options[options.length - 1].id,
527
+ );
528
+ await user.keyboard('{PageUp}');
529
+ expect(search).toHaveAttribute('aria-activedescendant', options[0].id);
530
+ });
531
+
532
+ it('Home/End move the search caret, not the option highlight', async () => {
533
+ const user = userEvent.setup();
534
+ render(
535
+ <Selector
536
+ label="Fruit"
537
+ options={OPTIONS}
538
+ value="Apple"
539
+ onChange={() => {}}
540
+ hasSearch
541
+ />,
542
+ );
543
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
544
+ const search = screen.getByRole<HTMLInputElement>('combobox', h);
545
+ await user.type(search, 'an');
546
+ expect(search.selectionStart).toBe(2);
547
+ const activeBefore = search.getAttribute('aria-activedescendant');
548
+ // Home/End stay on the input for caret movement (APG editable
549
+ // combobox); the option highlight must not move.
550
+ await user.keyboard('{Home}');
551
+ expect(search.selectionStart).toBe(0);
552
+ expect(search.getAttribute('aria-activedescendant')).toBe(activeBefore);
553
+ await user.keyboard('{End}');
554
+ expect(search.selectionStart).toBe(2);
555
+ expect(search.getAttribute('aria-activedescendant')).toBe(activeBefore);
556
+ });
557
+
506
558
  it('does not render search input when hasSearch is false', async () => {
507
559
  const user = userEvent.setup();
508
560
  render(
@@ -722,6 +774,23 @@ describe('Selector', () => {
722
774
  expect(trigger).toHaveAttribute('aria-expanded', 'true');
723
775
  });
724
776
 
777
+ it('End/Home jump the highlight to the last/first option (non-search)', async () => {
778
+ const user = userEvent.setup();
779
+ render(<Selector label="Fruit" options={OPTIONS} />);
780
+
781
+ const trigger = screen.getByRole('combobox');
782
+ await user.click(trigger);
783
+ const options = screen.getAllByRole('option', h);
784
+
785
+ await user.keyboard('{End}');
786
+ expect(trigger).toHaveAttribute(
787
+ 'aria-activedescendant',
788
+ options[options.length - 1].id,
789
+ );
790
+ await user.keyboard('{Home}');
791
+ expect(trigger).toHaveAttribute('aria-activedescendant', options[0].id);
792
+ });
793
+
725
794
  it('opens and selects an option with Enter (no mouse)', async () => {
726
795
  const user = userEvent.setup();
727
796
  const onChange = vi.fn();
@@ -858,10 +858,14 @@ export function Selector<T extends SelectorOptionType>(
858
858
  onChange={handleSearchChange}
859
859
  onKeyDown={e => {
860
860
  // Arrow keys navigate options; Enter selects; Escape/Tab close.
861
- // Home/End are left to the input for caret movement.
861
+ // Home/End are left to the input for caret movement (APG editable
862
+ // combobox); PageUp/PageDown are the sanctioned substitute for
863
+ // jumping to the first/last option.
862
864
  if (
863
865
  e.key === 'ArrowDown' ||
864
866
  e.key === 'ArrowUp' ||
867
+ e.key === 'PageUp' ||
868
+ e.key === 'PageDown' ||
865
869
  e.key === 'Enter' ||
866
870
  e.key === 'Escape' ||
867
871
  e.key === 'Tab'
@@ -318,6 +318,23 @@ export function useCombobox({
318
318
  }
319
319
  break;
320
320
 
321
+ // PageUp/PageDown mirror Home/End. In search mode Home/End stay on
322
+ // the input for caret movement (APG editable combobox), so these are
323
+ // the sanctioned substitute for jumping to the first/last option.
324
+ case 'PageUp':
325
+ e.preventDefault();
326
+ if (isOpen && enabledIndices.length > 0) {
327
+ setHighlightedIndex(enabledIndices[0]);
328
+ }
329
+ break;
330
+
331
+ case 'PageDown':
332
+ e.preventDefault();
333
+ if (isOpen && enabledIndices.length > 0) {
334
+ setHighlightedIndex(enabledIndices[enabledIndices.length - 1]);
335
+ }
336
+ break;
337
+
321
338
  case 'Delete':
322
339
  case 'Backspace':
323
340
  // Keyboard equivalent of the clear button (comboboxes-2): clear the
@@ -337,7 +337,9 @@ describe('SideNavHeading', () => {
337
337
  it('whole heading is popover trigger when menu provided without hrefs', () => {
338
338
  render(<SideNavHeading heading="My App" menu={<div>Menu</div>} />);
339
339
  const button = screen.getByRole('button');
340
- expect(button).toHaveAttribute('aria-haspopup', 'dialog');
340
+ // The popup is no longer a dialog (role: 'none' on usePopover), so the
341
+ // trigger advertises a generic popup rather than aria-haspopup="dialog".
342
+ expect(button).toHaveAttribute('aria-haspopup', 'true');
341
343
  expect(button).toHaveAttribute('aria-expanded', 'false');
342
344
  });
343
345
 
@@ -351,7 +353,7 @@ describe('SideNavHeading', () => {
351
353
  const button = screen.getByRole('button');
352
354
  // The trigger button uses aria attributes from usePopover and
353
355
  // an onClick handler from useMenuHover for click-to-lock toggle.
354
- expect(button).toHaveAttribute('aria-haspopup', 'dialog');
356
+ expect(button).toHaveAttribute('aria-haspopup', 'true');
355
357
  expect(button).toHaveAttribute('aria-expanded');
356
358
  });
357
359
 
@@ -364,13 +366,92 @@ describe('SideNavHeading', () => {
364
366
  />,
365
367
  );
366
368
  const button = screen.getByRole('button', {name: 'Open menu'});
367
- expect(button).toHaveAttribute('aria-haspopup', 'dialog');
369
+ expect(button).toHaveAttribute('aria-haspopup', 'true');
368
370
  });
369
371
 
370
372
  it('passes data-testid', () => {
371
373
  render(<SideNavHeading heading="My App" data-testid="nav-header" />);
372
374
  expect(screen.getByTestId('nav-header')).toBeInTheDocument();
373
375
  });
376
+
377
+ // ===========================================================================
378
+ // Menu popover semantics — the popup must not be announced as a modal
379
+ // dialog, and role="menu" must be scoped to the actual menu items so the
380
+ // heading button is not an invalid child of the menu.
381
+ // ===========================================================================
382
+
383
+ // The popover layer keeps `popover` content display:none in jsdom even
384
+ // when open, hiding it from role queries — so these tests assert the
385
+ // popup's ARIA semantics at the DOM level instead.
386
+ describe('menu popover semantics', () => {
387
+ const menuItems = (
388
+ <>
389
+ <div role="menuitem">Alpha</div>
390
+ <div role="menuitem">Beta</div>
391
+ </>
392
+ );
393
+
394
+ it('does not wrap the heading menu popup in a modal dialog', async () => {
395
+ const user = userEvent.setup();
396
+ render(<SideNavHeading heading="My App" menu={menuItems} />);
397
+ await user.click(screen.getByRole('button', {name: 'Open menu'}));
398
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
399
+ expect(document.querySelector('[aria-modal="true"]')).toBeNull();
400
+ });
401
+
402
+ it('scopes role="menu" to only menuitem children with an accessible name', async () => {
403
+ const user = userEvent.setup();
404
+ render(<SideNavHeading heading="My App" menu={menuItems} />);
405
+ await user.click(screen.getByRole('button', {name: 'Open menu'}));
406
+ const menu = document.querySelector('[role="menu"]');
407
+ expect(menu).not.toBeNull();
408
+ expect(menu).toHaveAttribute('aria-label', 'My App');
409
+ const children = Array.from(menu!.children);
410
+ expect(children.length).toBeGreaterThan(0);
411
+ for (const child of children) {
412
+ expect(child).toHaveAttribute('role', 'menuitem');
413
+ }
414
+ });
415
+
416
+ it('keeps the popup heading button outside the menu and closes on click', async () => {
417
+ const user = userEvent.setup();
418
+ render(<SideNavHeading heading="My App" menu={menuItems} />);
419
+ const trigger = screen.getByRole('button', {name: 'Open menu'});
420
+ await user.click(trigger);
421
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
422
+ const menu = document.querySelector('[role="menu"]');
423
+ expect(menu).not.toBeNull();
424
+ // The heading replica button in the popup is a sibling of the menu,
425
+ // not an invalid menu child.
426
+ const headingButton = Array.from(
427
+ document.querySelectorAll('button'),
428
+ ).find(b => b !== trigger && b.textContent?.includes('My App'));
429
+ expect(headingButton).toBeDefined();
430
+ expect(menu!.contains(headingButton!)).toBe(false);
431
+ // Clicking it still closes the popup.
432
+ fireEvent.click(headingButton!);
433
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
434
+ });
435
+
436
+ it('applies the same semantics in mixed mode (menu + hrefs)', async () => {
437
+ const user = userEvent.setup();
438
+ render(
439
+ <SideNavHeading
440
+ heading="Product"
441
+ headingHref="/product"
442
+ menu={menuItems}
443
+ />,
444
+ );
445
+ await user.click(screen.getByRole('button', {name: 'Open menu'}));
446
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
447
+ const menu = document.querySelector('[role="menu"]');
448
+ expect(menu).not.toBeNull();
449
+ expect(menu).toHaveAttribute('aria-label', 'Product');
450
+ for (const child of Array.from(menu!.children)) {
451
+ expect(child).toHaveAttribute('role', 'menuitem');
452
+ }
453
+ });
454
+ });
374
455
  });
375
456
 
376
457
  // =============================================================================
@@ -476,6 +557,41 @@ describe('SideNavHeading collapsed', () => {
476
557
  );
477
558
  expect(screen.getByTestId('nav-header')).toBeInTheDocument();
478
559
  });
560
+
561
+ it('collapsed menu popup is not a dialog and scopes role="menu" to menu items', async () => {
562
+ const user = userEvent.setup();
563
+ render(
564
+ <CollapsedWrapper>
565
+ <SideNavHeading
566
+ heading="My App"
567
+ icon={<span data-testid="app-icon">🏠</span>}
568
+ menu={
569
+ <>
570
+ <div role="menuitem">Alpha</div>
571
+ <div role="menuitem">Beta</div>
572
+ </>
573
+ }
574
+ />
575
+ </CollapsedWrapper>,
576
+ );
577
+ await user.click(screen.getByRole('button', {name: 'My App'}));
578
+ // No modal dialog wrapper around the menu popup.
579
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
580
+ expect(document.querySelector('[aria-modal="true"]')).toBeNull();
581
+ // role="menu" is scoped to the menu items and has a direct name.
582
+ const menu = document.querySelector('[role="menu"]');
583
+ expect(menu).not.toBeNull();
584
+ expect(menu).toHaveAttribute('aria-label', 'My App');
585
+ const children = Array.from(menu!.children);
586
+ expect(children.length).toBeGreaterThan(0);
587
+ for (const child of children) {
588
+ expect(child).toHaveAttribute('role', 'menuitem');
589
+ }
590
+ // No button (trigger or heading replica) lives inside the menu.
591
+ for (const button of Array.from(document.querySelectorAll('button'))) {
592
+ expect(menu!.contains(button)).toBe(false);
593
+ }
594
+ });
479
595
  });
480
596
 
481
597
  // =============================================================================
@@ -120,7 +120,12 @@ export function SideNavCollapseButton({
120
120
  return (
121
121
  <Button
122
122
  ref={ref}
123
- label={label ?? (isCollapsed ? t('@astryx.sideNavCollapseButton.expandSidebar') : t('@astryx.sideNavCollapseButton.collapseSidebar'))}
123
+ label={
124
+ label ??
125
+ (isCollapsed
126
+ ? t('@astryx.sideNavCollapseButton.expandSidebar')
127
+ : t('@astryx.sideNavCollapseButton.collapseSidebar'))
128
+ }
124
129
  variant="ghost"
125
130
  {...props}
126
131
  onClick={composeEventHandlers(onClickProp, toggle)}