@astryxdesign/core 0.7.1 → 0.7.2-canary.020e1fe

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 (377) hide show
  1. package/dist/AppShell/AppShell.d.ts.map +1 -1
  2. package/dist/AppShell/AppShell.js +40 -1
  3. package/dist/AspectRatio/AspectRatio.d.ts +6 -8
  4. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  5. package/dist/AspectRatio/AspectRatio.js +44 -3
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +38 -14
  10. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  11. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  12. package/dist/CheckboxInput/CheckboxInput.js +3 -1
  13. package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
  14. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  15. package/dist/CheckboxList/CheckboxListItem.js +4 -2
  16. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  17. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteInput.js +37 -5
  19. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  20. package/dist/ContextMenu/ContextMenu.js +5 -2
  21. package/dist/Dialog/Dialog.d.ts.map +1 -1
  22. package/dist/Dialog/Dialog.js +20 -8
  23. package/dist/Drawer/Drawer.d.ts +191 -0
  24. package/dist/Drawer/Drawer.d.ts.map +1 -0
  25. package/dist/Drawer/Drawer.js +382 -0
  26. package/dist/Drawer/DrawerHeader.d.ts +85 -0
  27. package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
  28. package/dist/Drawer/DrawerHeader.js +134 -0
  29. package/dist/Drawer/index.d.ts +11 -0
  30. package/dist/Drawer/index.d.ts.map +1 -0
  31. package/dist/Drawer/index.js +11 -0
  32. package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
  33. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
  34. package/dist/Drawer/useDrawerDialogPresence.js +236 -0
  35. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  36. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  37. package/dist/DropdownMenu/DropdownMenu.js +83 -11
  38. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
  39. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  40. package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
  41. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
  42. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
  43. package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
  44. package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
  45. package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
  46. package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
  47. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  48. package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
  49. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
  50. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
  51. package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
  52. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  53. package/dist/EmptyState/EmptyState.js +4 -10
  54. package/dist/Field/Field.d.ts +1 -1
  55. package/dist/Field/Field.d.ts.map +1 -1
  56. package/dist/Field/Field.js +8 -4
  57. package/dist/Grid/GridSpan.d.ts.map +1 -1
  58. package/dist/Grid/GridSpan.js +1 -0
  59. package/dist/Heading/Heading.d.ts.map +1 -1
  60. package/dist/Heading/Heading.js +4 -2
  61. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  62. package/dist/HoverCard/useHoverCard.js +10 -2
  63. package/dist/Item/Item.d.ts +6 -2
  64. package/dist/Item/Item.d.ts.map +1 -1
  65. package/dist/Item/Item.js +19 -4
  66. package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
  67. package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
  68. package/dist/Layer/layerViewportInset.stylex.js +6 -2
  69. package/dist/Layer/useLayer.d.ts.map +1 -1
  70. package/dist/Layer/useLayer.js +13 -7
  71. package/dist/Layer/useLayerDismissal.d.ts +1 -1
  72. package/dist/Layer/useLayerDismissal.js +1 -1
  73. package/dist/Layout/container.stylex.d.ts +5 -5
  74. package/dist/Layout/container.stylex.js +4 -4
  75. package/dist/List/List.d.ts +46 -19
  76. package/dist/List/List.d.ts.map +1 -1
  77. package/dist/List/List.js +28 -10
  78. package/dist/List/ListContext.d.ts +8 -3
  79. package/dist/List/ListContext.d.ts.map +1 -1
  80. package/dist/List/ListContext.js +7 -2
  81. package/dist/List/ListItem.d.ts +13 -5
  82. package/dist/List/ListItem.d.ts.map +1 -1
  83. package/dist/List/ListItem.js +21 -8
  84. package/dist/List/index.d.ts +1 -1
  85. package/dist/List/index.d.ts.map +1 -1
  86. package/dist/Markdown/Markdown.d.ts.map +1 -1
  87. package/dist/Markdown/Markdown.js +8 -1
  88. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
  89. package/dist/Markdown/plugins/frontmatter.js +7 -3
  90. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  91. package/dist/MetadataList/MetadataList.js +5 -1
  92. package/dist/MetadataList/MetadataListContext.d.ts +1 -1
  93. package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
  94. package/dist/MobileNav/MobileNav.d.ts +9 -0
  95. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  96. package/dist/MobileNav/MobileNav.js +30 -3
  97. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  98. package/dist/MultiSelector/MultiSelector.js +22 -8
  99. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
  100. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  101. package/dist/MultiSelector/hooks.js +3 -1
  102. package/dist/Pagination/Pagination.d.ts +8 -5
  103. package/dist/Pagination/Pagination.d.ts.map +1 -1
  104. package/dist/Pagination/Pagination.js +21 -8
  105. package/dist/Popover/Popover.js +2 -2
  106. package/dist/PowerSearch/PowerSearch.js +1 -1
  107. package/dist/RadioList/RadioList.d.ts +1 -1
  108. package/dist/RadioList/RadioList.d.ts.map +1 -1
  109. package/dist/RadioList/RadioList.js +6 -2
  110. package/dist/RadioList/RadioListItem.d.ts +9 -1
  111. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  112. package/dist/RadioList/RadioListItem.js +2 -0
  113. package/dist/Selector/hooks.d.ts.map +1 -1
  114. package/dist/Selector/hooks.js +6 -2
  115. package/dist/SideNav/SideNavItem.d.ts +8 -1
  116. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  117. package/dist/SideNav/SideNavItem.js +71 -9
  118. package/dist/Switch/Switch.d.ts +1 -1
  119. package/dist/Switch/Switch.d.ts.map +1 -1
  120. package/dist/Switch/Switch.js +13 -1
  121. package/dist/TabList/TabMenu.d.ts +2 -2
  122. package/dist/TabList/TabMenu.d.ts.map +1 -1
  123. package/dist/TabList/TabMenu.js +6 -3
  124. package/dist/Text/Text.d.ts.map +1 -1
  125. package/dist/Text/Text.js +4 -2
  126. package/dist/Text/useTruncation.d.ts +7 -5
  127. package/dist/Text/useTruncation.d.ts.map +1 -1
  128. package/dist/Text/useTruncation.js +16 -5
  129. package/dist/ToggleButton/ToggleButton.d.ts +3 -2
  130. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  131. package/dist/ToggleButton/ToggleButton.js +12 -1
  132. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  133. package/dist/Tooltip/useTooltip.js +11 -0
  134. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  135. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  136. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  137. package/dist/TopNav/TopNavMenu.js +3 -3
  138. package/dist/TreeList/TreeList.d.ts.map +1 -1
  139. package/dist/TreeList/TreeList.js +1 -0
  140. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  141. package/dist/TreeList/TreeListBranches.js +2 -2
  142. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  143. package/dist/TreeList/TreeListItem.js +57 -9
  144. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  145. package/dist/Typeahead/BaseTypeahead.js +26 -0
  146. package/dist/astryx.css +47 -13
  147. package/dist/hooks/useEntryAnimation.d.ts +2 -1
  148. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  149. package/dist/hooks/useEntryAnimation.js +14 -12
  150. package/dist/hooks/useIsHydrating.d.ts +6 -0
  151. package/dist/hooks/useIsHydrating.d.ts.map +1 -0
  152. package/dist/hooks/useIsHydrating.js +30 -0
  153. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  154. package/dist/hooks/useMenuHover.js +18 -13
  155. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  156. package/dist/i18n/generated-locales/en.generated.js +112 -1
  157. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  158. package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
  159. package/dist/index.d.ts +1 -0
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +1 -0
  162. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  163. package/dist/theme/derivedVarRegistry.js +0 -3
  164. package/dist/utils/focusOutline.stylex.d.ts +4 -0
  165. package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
  166. package/dist/utils/focusOutline.stylex.js +4 -0
  167. package/dist/utils/plainDate.d.ts.map +1 -1
  168. package/dist/utils/plainDate.js +15 -9
  169. package/dist/utils/sharedMutationObserver.d.ts +21 -0
  170. package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
  171. package/dist/utils/sharedMutationObserver.js +69 -0
  172. package/locales/en.json +444 -0
  173. package/locales/pseudo.json +333 -0
  174. package/package.json +9 -4
  175. package/scripts/agent-doc-state.mjs +38 -0
  176. package/scripts/postinstall.mjs +1 -1
  177. package/src/AppShell/AppShell.tsx +45 -1
  178. package/src/AspectRatio/AspectRatio.test.tsx +44 -43
  179. package/src/AspectRatio/AspectRatio.tsx +38 -18
  180. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
  181. package/src/Avatar/Avatar.doc.mjs +5 -3
  182. package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
  183. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  184. package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
  185. package/src/Button/Button.doc.mjs +2 -2
  186. package/src/Button/Button.test.tsx +155 -9
  187. package/src/Button/Button.tsx +39 -16
  188. package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
  189. package/src/Button/__tests__/Button.a11y.states.ts +0 -10
  190. package/src/Card/Card.doc.mjs +1 -1
  191. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
  192. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  193. package/src/CheckboxInput/CheckboxInput.tsx +3 -1
  194. package/src/CheckboxList/CheckboxList.test.tsx +22 -0
  195. package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
  196. package/src/CheckboxList/CheckboxListItem.tsx +10 -0
  197. package/src/Code/Code.doc.mjs +1 -0
  198. package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
  199. package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
  200. package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
  201. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
  202. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
  203. package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
  204. package/src/ContextMenu/ContextMenu.tsx +4 -1
  205. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
  206. package/src/Dialog/Dialog.test.tsx +46 -0
  207. package/src/Dialog/Dialog.tsx +20 -7
  208. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
  209. package/src/Drawer/Drawer.doc.mjs +473 -0
  210. package/src/Drawer/Drawer.spec.md +357 -0
  211. package/src/Drawer/Drawer.test.tsx +1300 -0
  212. package/src/Drawer/Drawer.tsx +613 -0
  213. package/src/Drawer/DrawerDirection.test.tsx +127 -0
  214. package/src/Drawer/DrawerHeader.doc.mjs +293 -0
  215. package/src/Drawer/DrawerHeader.test.tsx +102 -0
  216. package/src/Drawer/DrawerHeader.tsx +235 -0
  217. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
  218. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
  219. package/src/Drawer/index.ts +13 -0
  220. package/src/Drawer/useDrawerDialogPresence.ts +284 -0
  221. package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
  222. package/src/DropdownMenu/DropdownMenu.test.tsx +79 -18
  223. package/src/DropdownMenu/DropdownMenu.tsx +83 -12
  224. package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
  225. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
  226. package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
  227. package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
  228. package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
  229. package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
  230. package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
  231. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
  232. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +19 -5
  233. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
  234. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
  235. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
  236. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
  237. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
  238. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
  239. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
  240. package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
  241. package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
  242. package/src/EmptyState/EmptyState.doc.mjs +3 -2
  243. package/src/EmptyState/EmptyState.spec.md +189 -0
  244. package/src/EmptyState/EmptyState.test.tsx +74 -3
  245. package/src/EmptyState/EmptyState.tsx +4 -8
  246. package/src/Field/Field.doc.mjs +14 -8
  247. package/src/Field/Field.spec.md +217 -0
  248. package/src/Field/Field.test.tsx +60 -2
  249. package/src/Field/Field.tsx +17 -8
  250. package/src/Field/FieldLabel.doc.mjs +133 -26
  251. package/src/Field/FieldLabel.spec.md +172 -0
  252. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
  253. package/src/Grid/GridSpan.tsx +3 -0
  254. package/src/Heading/Heading.test.tsx +57 -0
  255. package/src/Heading/Heading.tsx +6 -2
  256. package/src/HoverCard/HoverCard.test.tsx +58 -0
  257. package/src/HoverCard/useHoverCard.tsx +10 -2
  258. package/src/Icon/Icon.doc.mjs +36 -0
  259. package/src/IconButton/IconButton.doc.mjs +2 -2
  260. package/src/IconButton/IconButton.test.tsx +3 -1
  261. package/src/Item/Item.doc.mjs +12 -8
  262. package/src/Item/Item.spec.md +226 -0
  263. package/src/Item/Item.test.tsx +61 -0
  264. package/src/Item/Item.tsx +40 -4
  265. package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
  266. package/src/Layer/layerViewportInset.stylex.ts +6 -2
  267. package/src/Layer/layerViewportInset.test.ts +1 -0
  268. package/src/Layer/useLayer.tsx +24 -13
  269. package/src/Layer/useLayerDismissal.ts +1 -1
  270. package/src/Layout/LayoutHeader.doc.mjs +5 -0
  271. package/src/Layout/container.stylex.ts +6 -6
  272. package/src/List/List.doc.mjs +14 -1
  273. package/src/List/List.spec.md +129 -77
  274. package/src/List/List.test.tsx +163 -94
  275. package/src/List/List.tsx +74 -18
  276. package/src/List/ListContext.tsx +9 -3
  277. package/src/List/ListItem.doc.mjs +24 -5
  278. package/src/List/ListItem.tsx +56 -27
  279. package/src/List/index.ts +1 -1
  280. package/src/Markdown/Markdown.tsx +14 -1
  281. package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
  282. package/src/Markdown/plugins/frontmatter.ts +7 -3
  283. package/src/MetadataList/MetadataList.doc.mjs +3 -3
  284. package/src/MetadataList/MetadataList.test.tsx +38 -0
  285. package/src/MetadataList/MetadataList.tsx +8 -1
  286. package/src/MetadataList/MetadataListContext.tsx +1 -1
  287. package/src/MobileNav/MobileNav.tsx +30 -3
  288. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
  289. package/src/MoreMenu/MoreMenu.test.tsx +5 -1
  290. package/src/MultiSelector/MultiSelector.test.tsx +159 -0
  291. package/src/MultiSelector/MultiSelector.tsx +40 -7
  292. package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
  293. package/src/MultiSelector/hooks.ts +3 -1
  294. package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
  295. package/src/Pagination/Pagination.doc.mjs +7 -7
  296. package/src/Pagination/Pagination.spec.md +9 -9
  297. package/src/Pagination/Pagination.test.tsx +242 -0
  298. package/src/Pagination/Pagination.tsx +27 -11
  299. package/src/Popover/Popover.test.tsx +2 -2
  300. package/src/Popover/Popover.tsx +4 -4
  301. package/src/PowerSearch/PowerSearch.test.tsx +85 -0
  302. package/src/PowerSearch/PowerSearch.tsx +2 -2
  303. package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
  304. package/src/RadioList/RadioList.fr10.test.tsx +45 -2
  305. package/src/RadioList/RadioList.test.tsx +22 -0
  306. package/src/RadioList/RadioList.tsx +4 -1
  307. package/src/RadioList/RadioListItem.doc.mjs +16 -0
  308. package/src/RadioList/RadioListItem.tsx +10 -1
  309. package/src/Resizable/useResizable.doc.mjs +2 -0
  310. package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
  311. package/src/Selector/hooks.ts +6 -2
  312. package/src/Selector/useCombobox.doc.mjs +237 -0
  313. package/src/SideNav/SideNav.spec.md +227 -0
  314. package/src/SideNav/SideNav.test.tsx +179 -0
  315. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  316. package/src/SideNav/SideNavItem.tsx +96 -17
  317. package/src/Slider/Slider.spec.md +39 -18
  318. package/src/Spinner/Spinner.doc.mjs +1 -1
  319. package/src/Switch/Switch.test.tsx +49 -0
  320. package/src/Switch/Switch.tsx +11 -0
  321. package/src/TabList/TabList.spec.md +57 -44
  322. package/src/TabList/TabList.test.tsx +67 -0
  323. package/src/TabList/TabMenu.doc.mjs +4 -3
  324. package/src/TabList/TabMenu.tsx +7 -4
  325. package/src/Text/Text.test.tsx +57 -0
  326. package/src/Text/Text.tsx +6 -2
  327. package/src/Text/useTruncation.test.tsx +69 -0
  328. package/src/Text/useTruncation.ts +16 -5
  329. package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
  330. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  331. package/src/ToggleButton/ToggleButton.test.tsx +95 -0
  332. package/src/ToggleButton/ToggleButton.tsx +12 -1
  333. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
  334. package/src/Tokenizer/Tokenizer.test.tsx +54 -1
  335. package/src/Toolbar/Toolbar.test.tsx +0 -8
  336. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
  337. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
  338. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
  339. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
  340. package/src/Tooltip/Tooltip.test.tsx +31 -0
  341. package/src/Tooltip/useTooltip.test.tsx +13 -0
  342. package/src/Tooltip/useTooltip.tsx +11 -0
  343. package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
  344. package/src/TopNav/TopNavMegaMenu.tsx +7 -3
  345. package/src/TopNav/TopNavMenu.test.tsx +19 -0
  346. package/src/TopNav/TopNavMenu.tsx +11 -3
  347. package/src/TreeList/TreeList.doc.mjs +1 -0
  348. package/src/TreeList/TreeList.spec.md +29 -17
  349. package/src/TreeList/TreeList.test.tsx +327 -6
  350. package/src/TreeList/TreeList.tsx +5 -1
  351. package/src/TreeList/TreeListBranches.tsx +6 -0
  352. package/src/TreeList/TreeListItem.tsx +88 -13
  353. package/src/Typeahead/BaseTypeahead.tsx +26 -0
  354. package/src/Typeahead/Typeahead.test.tsx +260 -2
  355. package/src/docPropLiterals.test.ts +36 -10
  356. package/src/hooks/useEntryAnimation.doc.mjs +4 -4
  357. package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
  358. package/src/hooks/useEntryAnimation.ts +15 -16
  359. package/src/hooks/useIsHydrating.test.tsx +70 -0
  360. package/src/hooks/useIsHydrating.ts +31 -0
  361. package/src/hooks/useMenuHover.ts +18 -13
  362. package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
  363. package/src/i18n/InternationalizationProvider.spec.md +252 -0
  364. package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
  365. package/src/i18n/generated-locales/en.generated.ts +121 -0
  366. package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
  367. package/src/i18n/useTranslator.test.tsx +37 -0
  368. package/src/index.ts +1 -0
  369. package/src/postinstall.test.mjs +46 -0
  370. package/src/reset.css +0 -29
  371. package/src/theme/derivedVarRegistry.test.ts +7 -3
  372. package/src/theme/derivedVarRegistry.ts +0 -3
  373. package/src/utils/focusOutline.stylex.ts +4 -0
  374. package/src/utils/plainDate.test.ts +37 -1
  375. package/src/utils/plainDate.ts +15 -5
  376. package/src/utils/sharedMutationObserver.test.ts +53 -0
  377. package/src/utils/sharedMutationObserver.ts +78 -0
@@ -0,0 +1,357 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 5
4
+ kind: component
5
+ id: component:Drawer
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang, imdreamrunner]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Drawer/Drawer.test.tsx,
16
+ packages/core/src/Drawer/DrawerHeader.test.tsx,
17
+ packages/core/src/Layer/useLayerDismissal.test.tsx,
18
+ packages/core/src/Layer/layerDismissalFamilies.test.tsx,
19
+ .github/scripts/modal-close-visibility.js,
20
+ apps/storybook/rtl-audit/rtl-audit.mjs,
21
+ scripts/check-knowledge.mjs,
22
+ ]
23
+ modules: []
24
+ families: [family:overlay-dismissal]
25
+ design_specs: []
26
+ architecture:
27
+ [
28
+ architecture:component-theming-surface,
29
+ architecture:container-padding,
30
+ architecture:layer-runtime,
31
+ architecture:public-component-api,
32
+ architecture:react-component-runtime,
33
+ ]
34
+ contributing: []
35
+ system_specs: [spec:AST-027/DEC-3]
36
+ ---
37
+
38
+ # Drawer component contract
39
+
40
+ ## Contract at a glance
41
+
42
+ | Area | Contract |
43
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | Current Core surface | `@astryxdesign/core` exports `Drawer`, `DrawerProps`, `DrawerHeader`, and `DrawerHeaderProps` from the package root and the `./Drawer` subpath (DEC-3); `@astryxdesign/lab` does not export them. `DrawerProps` extends `BaseProps<HTMLDialogElement>`, separately declares `ref`, requires `isOpen`, `onOpenChange`, `label`, and `children`, accepts `purpose` (`required`, `form`, or `info`; default `info`), an optional `padding` spacing step (FR16), and an optional `experimental` object (DEC-4), and has no `hasCloseButton`. Stable API admission and compatibility belong to `architecture:public-component-api`. |
45
+ | Behavior | Drawer-owned visibility is controlled by the caller for a viewport-relative, full-height logical-side overlay. `hasScrim` at native open selects `showModal()` or a manual Popover API host; changing it while open is unsupported. The shared layer dismissal stack gates Escape and platform close for the full rendered lifetime, and `purpose` decides, as in Dialog, whether Escape, platform close, and scrim click request close. Drawer renders no close control of its own; `DrawerHeader` renders one only when given `onOpenChange`. The browser top layer owns paint order in both presentations, exit renders current children, and completed close returns focus to the element focused at open. |
46
+ | End-user impact | A drawer paints no close control of its own; a close button appears in a `DrawerHeader` title row where the caller composes one. `form` drawers ignore scrim clicks, and `required` drawers ignore Escape, platform close, and scrim clicks; a modal `required` drawer is announced as an alert dialog. Shared topmost/IME Escape routing, top-layer paint order, one dismissal per request, and focus return apply in both presentations. |
47
+ | Builder impact | Import from `@astryxdesign/core/Drawer` or the Core root. For a visible close action, compose `<DrawerHeader title onOpenChange>` in a `Layout` header slot, as `DialogHeader` is composed in Dialog. The Content area is inset by `--spacing-4` by default; `padding={0}` gives a full-bleed Content area. Set `purpose` where Dialog would. State and content remain caller-owned; Drawer renders the caller's current children during exit. Sibling composition is current consumer guidance, and a consumer `onKeyDown` that prevents default cancels Escape dismissal. |
48
+ | Compatibility/readiness | Drawer is a Core component imported from `@astryxdesign/core/Drawer` (DEC-3). This record remains `draft`, and its candidate statements require approval. FR13–FR14 record shared-family and top-layer conformance; FR7 and FR15 record the Dialog alignment; FR16 records container padding. |
49
+ | Review checks | Reject regional, docked, or block-axis models; claims that modality and scrim are currently independent; reintroduction of a Drawer-local Escape registry, a page-level z-index band, or a Drawer-owned close button; a dismissal policy that diverges from Dialog's `purpose`; claims that mixed-presentation or nested stacking is guaranteed beyond the shared stack's contract; a surviving `@astryxdesign/lab` export; or claims of stable API/theming compatibility that `architecture:public-component-api` has not admitted. |
50
+ | Record context | `component:Drawer` FR1–FR16 and AR1–AR6 are draft candidate statements. Linked records govern only within their own declared authority and scope. |
51
+
52
+ This table summarizes the draft body below; it does not change this record's declared authority.
53
+
54
+ ## Intent
55
+
56
+ Drawer presents contextual details or controls in a full-height side panel that
57
+ floats over the current page without reflowing it. It supports modal inspection
58
+ with a scrim and non-modal master-detail inspection that leaves the page behind
59
+ available.
60
+
61
+ This draft records the viewport-only Core component: its shared layer
62
+ lifecycle, its Dialog-aligned close control and dismissal policy (DEC-1), and its
63
+ container padding (DEC-2). It introduces no scope concept. In particular, it
64
+ does not revive a regional, pane-scoped, or container-targeted Drawer model.
65
+
66
+ ## Compatibility and migration
67
+
68
+ - Released default preserved: `not yet released` in Core.
69
+ - Compatibility class: additive in Core (root and `./Drawer` subpath exports);
70
+ `@astryxdesign/lab`, which publishes only canary builds, does not export
71
+ Drawer.
72
+ - Container padding (FR16, DEC-2): the `padding` prop or a theme's `padding` on
73
+ `drawer` insets the Content area through container tokens. With neither set
74
+ the inset is `--spacing-4`, as in Dialog; `padding={0}` keeps a full-bleed
75
+ Content area. The block-end safe-area inset applies in every mode.
76
+ - Experimental surface (DEC-4): full-viewport mobile coverage is the
77
+ `isFullWidthOnMobile` option inside the optional `experimental` prop object,
78
+ outside the stable compatibility promise (`spec:AST-017/FR21`). Omitting
79
+ `experimental` keeps the 56px page reveal.
80
+ - Controlled/uncontrolled behavior: unchanged for Drawer-owned paths; callers
81
+ provide `isOpen`, while direct native mutation through the public dialog ref is
82
+ outside that guarantee.
83
+ - Migration decision: no codemod, because `@astryxdesign/lab` publishes only
84
+ canary builds. Canary callers import Drawer and DrawerHeader from
85
+ `@astryxdesign/core/Drawer` instead of `@astryxdesign/lab`.
86
+
87
+ Consumer migration instructions belong in consumer docs and release notes.
88
+
89
+ ## Ownership boundary
90
+
91
+ **Owns**
92
+
93
+ - The viewport-relative side-panel surface, logical edge, inline-size budget, and
94
+ entry/exit motion.
95
+ - The current presentation input: its value at native open selects `showModal()`
96
+ or the manual Popover API host. Changing it while open is unsupported.
97
+ - Drawer-local focus entry/return, the `purpose` dismissal policy, uncanceled
98
+ backdrop-click handling, and retention of its host through controlled exit
99
+ while rendering current caller-owned children.
100
+ - The Content area's container padding, published per
101
+ `architecture:container-padding`; the root dialog remains the overlay
102
+ boundary owner.
103
+ - `DrawerHeader`: a title row with optional subtitle and start/end content, and a
104
+ close action rendered only when `onOpenChange` is passed.
105
+ - Registering the layer with the shared dismissal stack for its rendered
106
+ lifetime. Escape and platform-close routing belong to the shared owner; paint
107
+ order in both presentations belongs to the browser top layer.
108
+
109
+ **Does not own / non-goals**
110
+
111
+ - Caller-provided forms, inspectors, footers, or other content. `DrawerHeader`
112
+ is optional composition, not required anatomy.
113
+ - Block-axis sheets — owned by `component:BottomSheet`.
114
+ - Persistent panels that reserve layout space or push page content.
115
+ - Regional or pane-scoped placement, a caller-supplied container, or independent
116
+ modality and scrim axes. Those are not current Drawer concepts.
117
+ - Cross-family Escape and platform-close ordering — owned by
118
+ `family:overlay-dismissal`; Drawer participates through the shared owner.
119
+ - Button painting for the `DrawerHeader` close action — delegated to
120
+ `component:Button`; header row layout is delegated to `LayoutHeader`.
121
+
122
+ ## Public concepts
123
+
124
+ Consumer prop syntax and examples remain in `Drawer.doc.mjs`.
125
+
126
+ | Concept | Closed values or states | Meaning | Availability by state | Default | Owner | Stability | Invalid-value behavior |
127
+ | ------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------- | ------------------ | ----------------------------------------------- | ----------------------------------------------------- |
128
+ | visibility | open, closed | whether caller-controlled Drawer presentation is requested | all presentations | caller-controlled | `component:Drawer` | draft Core contract | required controlled value |
129
+ | logical edge | inline start, inline end | viewport edge from which the panel enters and exits | open and exiting | inline end | `component:Drawer` | draft Core contract | closed type rejects other values |
130
+ | presentation | modal with scrim, non-modal without scrim | native host mode and associated document semantics | chosen when the native host opens; live changes are unsupported | modal with scrim | `component:Drawer` | draft Core contract | one current boolean selects the initial mode |
131
+ | inline-size budget | pixel number or valid CSS length | desktop inline size and reveal-mode mobile cap | desktop and mobile page-reveal mode | `400px` | `component:Drawer` | draft Core contract | invalid CSS lengths are unsupported |
132
+ | mobile coverage | page reveal, full viewport | whether narrow viewports retain a visible page strip | viewports at or below the mobile boundary | 56px page reveal | `component:Drawer` | experimental (DEC-4) | closed boolean |
133
+ | dismissal purpose | info, form, required | which implicit requests close: `info` allows Escape, platform close, and scrim click; `form` allows Escape and platform close; `required` allows none | all presentations; scrim click applies to modal only | info | `component:Drawer` | draft Core contract matching `component:Dialog` | closed type rejects other values |
134
+ | header close action | rendered, absent | whether `DrawerHeader` renders its close button | wherever `DrawerHeader` is composed | absent unless `onOpenChange` is passed | `component:Drawer` | draft Core contract | an omitted callback renders no button |
135
+ | content inset | spacing step, theme `drawer` padding, or the default | container padding of the scrolling Content area, published to descendants per `architecture:container-padding` | all presentations | `--spacing-4`, as in Dialog | `component:Drawer` | draft Core contract matching `component:Dialog` | closed numeric type rejects other values |
136
+ | sibling order | earlier opened, later opened, exiting, closed | shared-stack Escape eligibility and browser top-layer paint order | sibling Drawers in either presentation | later opened is topmost | `component:Drawer` | draft Core contract | nested composition is outside the documented contract |
137
+
138
+ ## Behavioral and layout contract
139
+
140
+ | ID | Candidate invariant | Basis | Draft review state |
141
+ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
142
+ | FR1 | Drawer MUST remain a viewport-relative, full-block-size overlay on the logical inline-start or inline-end edge and MUST NOT reserve layout space or reflow the page beneath it. | Current source, docs, stories, and tests | Verified current behavior; no new behavior decided |
143
+ | FR2 | The logical edge selected for an open Drawer MUST remain latched through its exit even when caller state changes the live edge prop during close. | Current exit-side test and implementation | Verified current behavior |
144
+ | FR3 | The desktop inline-size budget MUST accept pixel numbers or CSS lengths and remain bounded by the viewport. At or below 640px, page-reveal mode MUST preserve 56px and use `width` as a cap; full-mobile mode MUST override `width` with `100dvw`. | Current docs, styles, and width tests | Verified current behavior |
145
+ | FR4 | Visibility through Drawer-owned behavior MUST follow `isOpen`. Uncanceled Escape routed by the shared stack and uncanceled modal-backdrop activation request `false` when `purpose` allows (FR15), and a `DrawerHeader` close action requests `false` through the callback it is given; Drawer prevents its handled native `cancel` event from closing the dialog directly. During controlled exit, Drawer keeps its host and content region mounted while rendering the caller's current `children`. | Current prop contract and lifecycle tests | Verified for Drawer-owned paths; direct native mutation through the public ref is outside this guarantee |
146
+ | FR5 | At open time, `hasScrim={true}` MUST select native `showModal()` with `aria-modal`, a focus trap, body scroll locking, and a visible backdrop; `hasScrim={false}` MUST select a manual Popover API host that leaves the page behind interactive. Changing `hasScrim` while open is not a supported native-mode transition. | Current source, docs, and initial-mode tests | Initial modes verified; live presentation changes unsupported |
147
+ | FR6 | An uncanceled modal root/backdrop click (`target === currentTarget` while live `hasScrim` is true) MUST request close when `purpose` is `info`, and MUST NOT for `form` or `required`. Descendant clicks and dialog-root clicks while live `hasScrim` is false MUST NOT request close, and non-modal presentation MUST NOT install an invisible outside-pointer dismissal plane. | Current click tests | Verified current behavior |
148
+ | FR7 | Drawer MUST NOT render a close control of its own. `DrawerHeader` MUST render a close action with an accessible name only when `onOpenChange` is passed and call it with `false` without taking open-state ownership. It renders its title as an h2 and neither moves focus on mount nor names the Drawer. | DEC-1; close-control and `DrawerHeader.test.tsx` suites | Owner decision (DEC-1); pending approval |
149
+ | FR8 | Opening MUST present the native host, capture the element focused before opening, and attempt to focus the first rendered `[data-autofocus]` descendant, if any. After a completed controlled close releases the native host, Drawer MUST attempt to restore focus to the captured element. The non-modal host's synthetic `close` event MUST dispatch after that restore attempt — matching the task-queued native `dialog.close()` event — so a consumer close listener keeps the last word on final focus. | Current presence hook and focus tests | Verified for completed controlled close |
150
+ | FR9 | Closing MUST retain the panel host, content region, edge, and native presentation while the exit remains visible and continue rendering the caller's current `children`. The transform transition is authoritative, a computed-duration backstop prevents a lost event from stranding the host, and native release and React hiding land together so no frame paints outside the top layer. | Current lifecycle tests and browser close-visibility guard | Observable outcome verified |
151
+ | FR10 | Sibling Drawers MUST be composed as siblings. The last-opened present sibling owns the first Escape request through the shared stack; a closing sibling retains that ownership through its visible exit, and reopening registers a new active cycle so one request never closes two surfaces. Paint order in both presentations follows the browser top layer's chronological order. | Current docs, shared stack, and tests | Verified current behavior |
152
+ | FR11 | At this commit, `DrawerProps` extends `BaseProps<HTMLDialogElement>` and separately declares `ref`. The root filters `open`, merges `xstyle`, `className`, and `style`, forwards remaining unclaimed props, composes consumer `onClick`, forwards consumer `onKeyDown` ahead of the shared document-level Escape owner, and owns `aria-label`, `aria-modal`, `onCancel`, and, for a modal `required` drawer, `role="alertdialog"`. A consumer `onKeyDown` that prevents default cancels shared-stack Escape dismissal for the press. | Current source and focused keyboard tests | Recorded current behavior; not a stable compatibility decision |
153
+ | FR12 | At this commit, the painted root dialog emits the documented `drawer` target and `side` selector axis and applies the container-padding reset. `DrawerHeader` emits `drawer-header`, `drawer-header-start-content`, `drawer-header-title-block`, `drawer-header-end-content`, and `drawer-header-close-icon`, mirroring `DialogHeader`. No separate Drawer target is currently emitted for content or scrim; future target qualification remains owned by `architecture:component-theming-surface`. | Current source, docs, and structural target metadata | Current reachability only; no target-admission decision |
154
+ | FR13 | Drawer MUST join the shared dismissal stack for its full rendered lifetime, provide logical depth to descendant layers, route platform close through the same topmost/IME decision, and preserve consumer `preventDefault()` ownership. | Shared family contract and focused tests | Verified current conformance |
155
+ | FR14 | Non-modal Drawer MUST use a manual Popover API host so cross-surface order comes from the browser top layer rather than a page-level z-index band. The reduced fallback may use `dialog.show()` only below the Popover API support floor. | Current source, browser guard, and `spec:AST-027` | Verified current conformance |
156
+ | FR15 | `purpose` MUST match `component:Dialog`. `info` (default) lets Escape, platform close, and a modal scrim click request close; `form` lets Escape and platform close request close and ignores scrim clicks; `required` lets none request close, registers with the shared stack as `block` so the request does not reach a lower layer, and exposes a modal drawer as `role="alertdialog"`. Without a scrim, `form` and `info` behave the same. | DEC-1; purpose suite; `family:overlay-dismissal` Escape behavior | Owner decision (DEC-1); pending approval |
157
+ | FR16 | The Content area pads caller content by the drawer's container inset, `--spacing-4` by default as in Dialog, and publishes the applied inset to descendants as a container publisher. Its block-end edge applies the container inset plus the home-indicator safe area while publishing the inset alone, so a bleed child subtracts only the inset and the safe area survives below it. The root dialog keeps the container-padding reset (FR12). | `architecture:container-padding`; container padding suite | Candidate (DEC-2); prototype pending owner approval |
158
+
159
+ ### Allowed variation
160
+
161
+ - **AV1 — Caller content.** Any renderable inspector/detail content may occupy the
162
+ scrolling content region without becoming Drawer-owned anatomy. Drawer renders
163
+ current `children`; callers decide whether the underlying data remains available
164
+ during exit.
165
+ - **AV2 — Inline size.** Consumers may choose the desktop budget within valid CSS
166
+ and viewport constraints. It remains the cap in mobile page-reveal mode, while
167
+ full-mobile mode uses `100dvw` instead.
168
+ - **AV3 — Presentation.** The value at native open selects the modal dialog or
169
+ the non-modal popover host. Changing `hasScrim` while open is unsupported.
170
+ - **AV4 — Close control.** Callers choose whether to render a close action,
171
+ normally `DrawerHeader` with `onOpenChange`, or their own control.
172
+ - **AV6 — Dismissal purpose.** Callers choose `info`, `form`, or `required`.
173
+ - **AV5 — Motion duration.** Themes may alter the transform transition duration;
174
+ close timing follows computed CSS and reduced-motion preferences.
175
+
176
+ ### Representative states
177
+
178
+ | State | Required invariant | Allowed variation |
179
+ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
180
+ | Closed | no native presentation or visible panel | caller content may remain mounted in React |
181
+ | Modal inspector | opened with `hasScrim` true via `showModal()` with scrim, modal semantics, focus trap, and body lock | edge, width, caller content, autofocus destination, header close action, and purpose |
182
+ | Non-modal master-detail inspector | opened with `hasScrim` false via the manual popover host; no scrim, `aria-modal`, or body lock; the page behind stays interactive | edge, width, caller content, header close action, and purpose |
183
+ | Required modal drawer | Escape, platform close, and scrim click do not close; exposed as `alertdialog`; a lower layer does not receive the Escape | caller-provided way out |
184
+ | Narrow viewport with page reveal | panel does not exceed its `width` cap and preserves 56px of the page | requested width below the cap |
185
+ | Narrow full-coverage viewport | panel uses `100dvw`, overriding the requested `width` | caller content |
186
+ | Two sibling Drawers | later-opened open Drawer paints above through the browser top layer and owns the first Escape request through the shared stack | edge, width, caller content, and either presentation composed as siblings |
187
+ | Exiting Drawer | panel host, content region, logical edge, native presentation, and stack registration remain while the exit is visible; current children render | caller-controlled child data and motion duration |
188
+
189
+ ### Transformation and precedence order
190
+
191
+ - **ORD0 — Content inset.** The `padding` prop, then the theme's `padding`
192
+ properties on `drawer`, then `--spacing-4`, as in Dialog. The block-end
193
+ safe-area inset is added after that resolution in every mode.
194
+ - **ORD1 — Open.** Resolve the current logical edge and width, retain the rendered
195
+ panel, capture the currently focused element, use current `hasScrim` to select
196
+ the native host, then honor a rendered autofocus destination.
197
+ - **ORD2 — Close.** During controlled close, Drawer keeps its panel and
198
+ caller-provided content visible through the exit animation. Transition
199
+ completion or the computed-duration backstop releases the native host and hides
200
+ React output together, so presentation ends without an intermediate visible
201
+ frame; Drawer then attempts to restore focus to the previously focused element
202
+ when available. The non-modal host's synthetic `close` event dispatches only
203
+ after that restore attempt, so a consumer close listener that retargets focus
204
+ observes the same last-word ordering as the task-queued native event.
205
+ - **ORD3 — Sibling order.** Sibling order comes from the shared dismissal stack
206
+ and the browser top layer: the last-opened present sibling is frontmost and
207
+ handles Escape first in both presentations. Descendant overlays remain governed
208
+ by their own component and family contracts rather than Drawer nesting.
209
+
210
+ ### Performance and resources
211
+
212
+ - **PR1 — Exit-only resources.** Transition listeners and the backstop timer exist
213
+ only while close is waiting for the visible transform exit and are removed on
214
+ completion, interruption, or unmount.
215
+ - **PR2 — Presentation cleanup.** Native presentation and any active body scroll
216
+ lock exist only for their active states and are released on controlled close,
217
+ unmount, or hidden Activity cleanup.
218
+ - **PR3 — Viewport-relative sizing.** Drawer derives sizing from `width`, the
219
+ experimental `experimental.isFullWidthOnMobile` option (DEC-4), and the
220
+ viewport; callers do not supply a regional measurement target.
221
+
222
+ ## Accessibility contract
223
+
224
+ - **AR1 — Name.** `label` is a required string forwarded to `aria-label`; caller
225
+ content is not used as an implicit name. Current code does not reject an empty
226
+ or whitespace-only value.
227
+ - **AR2 — Modal truthfulness.** At open time, `hasScrim={true}` uses native modal
228
+ dialog state and `aria-modal`; `hasScrim={false}` uses the non-modal popover
229
+ host and omits `aria-modal`. A modal `required` drawer uses
230
+ `role="alertdialog"`. Changing `hasScrim` while open is not a supported
231
+ transition.
232
+ - **AR3 — Keyboard dismissal.** Escape requests close only for the topmost
233
+ registered layer through the shared stack and only when `purpose` allows; a
234
+ `required` drawer consumes it. A consumer `onKeyDown` that prevents default
235
+ cancels that dismissal, and unrelated keys do not dismiss.
236
+ - **AR4 — Focus lifecycle.** Focus enters visible Drawer content through the
237
+ documented autofocus/native path and, after a completed controlled close, Drawer
238
+ attempts to restore focus to the element that was active when the Drawer opened.
239
+ - **AR5 — Close affordance.** Drawer provides no close control of its own. When
240
+ rendered, the `DrawerHeader` close action keeps its translated accessible name
241
+ and Button-owned keyboard/focus behavior in both presentations.
242
+ - **AR6 — Direction and motion.** Logical inset placement and slide-direction
243
+ mirroring follow DOM directionality from the nearest effective `dir` attribute,
244
+ including an explicit local override or an inherited direction. As required by
245
+ `design:bidirectionality`, a region whose direction differs from its ancestor
246
+ describes that direction in DOM `dir`; a CSS-only `direction` declaration is
247
+ not a supported substitute. Motion reduces under `prefers-reduced-motion`
248
+ without changing the final state.
249
+
250
+ ## Design relationships
251
+
252
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
253
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | -------------- | ------------------ |
254
+ | Panel | Paints the full-height side surface and owns edge, width, border, shadow, and motion. | Current source and public docs | Prominent | FR1–FR3, FR9, FR12 |
255
+ | Content region | Provides full-height scrolling and the container inset for caller-owned inspector content. | Current source and public docs | Prominent | FR4, FR16, AV1 |
256
+ | Header | `DrawerHeader` supplies the title row and, when given `onOpenChange`, the close action. | `component:Button`, `LayoutHeader` | Supporting | FR7, FR12, AR5 |
257
+ | Modal scrim | At initial modal open, communicates the scrim-backed presentation and provides root-click activation behind the panel. | Current source and public docs | Supporting | FR5, FR6, AR2 |
258
+ | Page reveal | Preserves overlay context on narrow viewports unless full coverage is requested. | Current public docs | Supporting | FR3 |
259
+
260
+ The root Panel carries the current `drawer` target and reflects `side`, and
261
+ `DrawerHeader` carries its five `drawer-header` targets. No separate Drawer target
262
+ is currently reachable for Content region or Modal scrim. This draft does not
263
+ decide future target qualification.
264
+
265
+ ## Family and system relationships
266
+
267
+ - `family:overlay-dismissal` owns cross-component Escape and platform-close
268
+ ordering. Drawer participates through the shared owner for its rendered
269
+ lifetime and supplies logical depth to descendant layers.
270
+ - `architecture:layer-runtime` owns the distinction between native modal hosting,
271
+ non-modal dialog presentation, top-layer behavior, and shared layer plumbing.
272
+ Drawer's content root applies the shared private text baseline and layer
273
+ content boundary, as in Dialog: surface-scoped ancestor membership stops at
274
+ the drawer, while ordinary application contexts keep flowing to its content.
275
+ - `architecture:public-component-api` owns stable API admission and compatibility.
276
+ FR11 records current DOM/ref/event reachability without making a stable
277
+ compatibility decision.
278
+ - `architecture:react-component-runtime` owns effect/resource cleanup, native-host
279
+ synchronization, and node/lifecycle safety. This draft records Drawer-owned
280
+ visible close and focus-handoff outcomes.
281
+ - `architecture:component-theming-surface` owns target qualification and future
282
+ anatomy mapping; this draft records the existing `drawer` target only.
283
+ - `architecture:container-padding` owns the shared container inset protocol.
284
+ Admitting the Content area as a container publisher (FR16) amends that
285
+ `current` record — an owner-approved change outside this draft.
286
+ - `spec:AST-027/DEC-3` requires equivalent floating interactions to use an
287
+ applicable native top-layer host. Modal `showModal()` and non-modal manual
288
+ Popover hosting satisfy that cross-surface route; the documented reduced
289
+ fallback remains below the Popover API support floor.
290
+
291
+ ## Verification map
292
+
293
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
294
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
295
+ | FR1–FR7 | `Drawer.test.tsx` render, initial-mode, click, sizing, side, and control suites plus `DrawerHeader.test.tsx` | closed/open, both edges, initial modal/non-modal modes, mobile widths | Layout reflows; an initial mode gains the wrong semantics; uncanceled root-click or full-mobile sizing becomes nondeterministic. | `audit:Drawer/behavior` |
296
+ | FR11 | Drawer source and focused keyboard composition/cancellation tests | filtered `open`, merged styling/ref, forwarded unclaimed props, owned ARIA/cancel, composed click, forwarded keydown | Current forwarding changes without review or shared-stack Escape ignores documented consumer cancellation. | `audit:Drawer/public-api` |
297
+ | FR8, AR1–AR5 | `Drawer.test.tsx` label forwarding, autofocus, close, and focus-return suites plus the browser guard's final-focus checks | initial modal/non-modal modes, autofocus target, connected previously focused element, close listener retargeting focus | Focus moves before presentation, completed controlled close fails to restore focus, the synthetic close overrides a consumer listener's focus target, or label forwarding/dismissal naming breaks. | `audit:Drawer/accessibility` |
298
+ | FR9, PR1–PR2 | close timing tests, browser close-visibility guard, and presence-hook source inspection | controlled close, transform end, unrelated transition, lost-event backstop | Native presentation ends before visible exit, the host is stranded, cleanup paths retain resources, or an intermediate frame paints outside native presentation. | `audit:Drawer/motion` |
299
+ | FR10, FR13, AR3 | Drawer shared-stack suites plus `useLayerDismissal` and family tests | siblings, closing top, reopen, unmount, nested descendant | One Escape closes two surfaces, a closing host drops ownership, or a reopened sibling keeps stale order. | `audit:Drawer/layers` |
300
+ | FR14 | Drawer source plus `spec:AST-027` impact inventory and native-host browser guard | modal host, manual-popover host, reduced fallback | Non-modal presentation returns to a page-level band or loses native-host ordering. | `audit:Drawer/layers` |
301
+ | FR15 | `Drawer.test.tsx` purpose suite | `info`, `form`, `required`; modal and non-modal; a sibling behind a required drawer | A `form` drawer closes on a scrim click, a `required` drawer closes or lets Escape reach the drawer behind it, or its role semantics drift. | `audit:Drawer/behavior` |
302
+ | FR12 | source, `Drawer.doc.mjs`, `DrawerHeader.doc.mjs`, the `DrawerHeader` target test, and current target discovery | start/end root Panel and inherited container context | Current root target/axis or padding reset changes, or the record claims an unreachable child target or decides future admission. | `audit:Drawer/theming` |
303
+ | FR16 | `Drawer.test.tsx` container padding suites; `defineTheme.test.ts` drawer mapping; browser evidence | default, `padding` prop, theme `padding`, lone padded Section child, Layout composition | The published inset differs from the applied padding, the default drawer changes geometry, or the block-end safe area is lost or double-subtracted. | `audit:Drawer/theming` |
304
+ | AR6 | `DrawerDirection.test.tsx` DOM-direction and closed/entry parity suites plus exact-head Storybook RTL evidence | start/end; self-applied, inherited, and nested opposite `dir`; closed and opening transforms | Any effective DOM direction or local override moves the panel from a different edge than its logical placement, or closed and opening transforms diverge. | `audit:Drawer/accessibility` |
305
+
306
+ ## Decision log
307
+
308
+ - **DEC-1 — The close control and dismissal policy match Dialog** (owner
309
+ decision, 2026-10-01). Drawer renders no built-in close button and has no
310
+ `hasCloseButton`; it takes Dialog's `purpose` values and default; and
311
+ `DrawerHeader` has `DialogHeader`'s API, rendering a close action only when
312
+ `onOpenChange` is passed. A Drawer-owned header, rather than `DialogHeader`,
313
+ keeps Drawer's focus entry (`data-autofocus` after native presentation) and
314
+ `label` naming and avoids `dialog-header` theme targets. Rejected: a built-in
315
+ button hidden only for `required`; reusing `DialogHeader`.
316
+
317
+ - **DEC-2 — Drawer is a container, like Dialog** (owner decision,
318
+ 2026-10-08). The scrolling Content area is a container publisher with
319
+ Dialog's contract: a `padding` prop on Dialog's spacing-step scale, theme
320
+ `padding` on `drawer` expanded to `--astryx-drawer-padding*` container
321
+ tokens, and a `--spacing-4` default shared by the edge and Layout inset
322
+ chains. `DrawerHeader` sits in a `Layout` header slot, and the Layout
323
+ redistributes the inset to its regions, so the header is not inset twice. The
324
+ block-end edge adds the home-indicator safe area on top of the inset in every
325
+ mode. Rejected: padding the panel root (the overlay boundary); a full-bleed
326
+ default (diverges from Dialog).
327
+
328
+ - **DEC-3 — Drawer is a Core component** (owner decision, 2026-10-08).
329
+ `Drawer`, `DrawerHeader`, and their prop types ship from
330
+ `@astryxdesign/core/Drawer` and the Core root, and their docsite demos are
331
+ Core blocks. `@astryxdesign/lab` does not export them.
332
+
333
+ - **DEC-4 — Full-viewport mobile coverage is experimental** (owner decision,
334
+ 2026-10-15). `isFullWidthOnMobile` lives inside the optional `experimental`
335
+ prop object, marked `@experimental` in declarations and
336
+ `stability: 'experimental'` in authored docs, so it may change or be removed in
337
+ a patch release (`spec:AST-017/FR21`). Omitting `experimental` keeps the 56px
338
+ page reveal. Rejected: a top-level stable `isFullWidthOnMobile` prop.
339
+
340
+ ## Open questions
341
+
342
+ - Should `DrawerHeader` supply a default focus target and name the Drawer through
343
+ `aria-labelledby`, as `DialogHeader` does for Dialog? Today `label` is required
344
+ and focus entry uses `data-autofocus`.
345
+ - Should an explicit `padding` prop keep adding the block-end safe area
346
+ (DEC-2), or adopt Dialog's fullscreen rule, where an explicit value replaces
347
+ the safe-area fallback entirely?
348
+
349
+ Regional placement, independent modality/scrim axes, and block-axis sheets
350
+ are outside the current component boundary. Any future proposal for them requires
351
+ fresh public-API and design authority.
352
+
353
+ ## Content boundary
354
+
355
+ This file does not duplicate consumer prop tables or examples, shared
356
+ dismissal-stack internals, transition algorithms, current audit scores,
357
+ implementation steps, or shared family/system rules. It links to their owners.