@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,189 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 7
4
+ kind: component
5
+ id: component:EmptyState
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility, testing]
13
+ verified_by:
14
+ [
15
+ packages/core/src/EmptyState/EmptyState.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ packages/core/src/docPropReferences.test.ts,
18
+ ]
19
+ modules: []
20
+ families: []
21
+ design_specs: []
22
+ architecture:
23
+ [
24
+ architecture:public-component-api,
25
+ architecture:component-theming-surface,
26
+ architecture:component-test-sufficiency,
27
+ ]
28
+ contributing: []
29
+ system_specs: [spec:AST-020/FR1, spec:AST-029/FR3]
30
+ ---
31
+
32
+ # EmptyState component contract
33
+
34
+ ## Contract at a glance
35
+
36
+ | Area | Contract |
37
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
38
+ | Public contract | `EmptyState` renders a required title plus optional description, decorative icon, and caller-owned actions in one status region; callers choose heading level and compact layout. |
39
+ | Behavior | Null, undefined, boolean, and empty-string optional values create no slot wrapper. Rendered content follows icon, text, then actions order; compact layout reduces spacing and stacks actions. |
40
+ | End-user impact | A person reaches a named empty-content message and any next action without blank gaps from shallow-empty optional values; the title fits the surrounding document outline. |
41
+ | Builder impact | Callers supply content and actions, select heading semantics, and may compose supported root props and styling. No additional setup is required. |
42
+ | Compatibility/readiness | The released default is preserved. This draft observes shipped behavior plus the narrow scalar-slot remediation; it adds no API, default, or compatibility promise. |
43
+ | Review checks | Reject a missing heading, an informative icon hidden as decorative, a shallow-empty optional scalar that still changes layout, a dropped root prop/ref/style, or a theme target without anatomy. |
44
+ | Governing rules | `architecture:public-component-api` INV1 and INV5–INV9; `architecture:component-theming-surface` INV2–INV6; `architecture:component-test-sufficiency` INV1–INV7, INV10–INV11; WCAG 2.2. |
45
+
46
+ This table is a review projection; the body below is authoritative.
47
+
48
+ ## Intent
49
+
50
+ `EmptyState` gives an otherwise empty content area a semantic heading, supporting
51
+ context, optional decorative visual, and optional caller-owned next actions.
52
+
53
+ ## Compatibility and migration
54
+
55
+ - Released default preserved: yes
56
+ - Compatibility class: observational and additive documentation; runtime remediation removes wrappers for nullish, boolean, and empty-string optional values
57
+ - Controlled/uncontrolled behavior: not applicable
58
+ - Migration decision: none
59
+
60
+ Consumer migration instructions belong in consumer docs and release notes.
61
+
62
+ ## Ownership boundary
63
+
64
+ **Owns**
65
+
66
+ - the status-region root, content order, heading element, and default/compact layout;
67
+ - hiding the icon slot from assistive technology as decorative; and
68
+ - whether optional slots create component-owned wrappers and spacing.
69
+
70
+ **Does not own / non-goals**
71
+
72
+ - the message copy, icon meaning, action controls, or action behavior — those belong to the product callsite and the components it supplies;
73
+ - page-level heading order — the caller chooses `headingLevel` for its document; or
74
+ - error, loading, or populated-content behavior.
75
+
76
+ ## Public concepts
77
+
78
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
79
+ | -------------- | ----------------------- | ------------------------------------------------------------------------- | ----------------------------------------- | -------- | ---------------------- | --------- | ------------------------------------------------------- |
80
+ | title | string | The empty region's heading content. | all | required | `component:EmptyState` | stable | type rejection |
81
+ | description | string or absent | Optional supporting context below the title. | all | absent | `component:EmptyState` | stable | nullish and empty-string values omit the part |
82
+ | icon | React node or absent | Optional caller visual, exposed as decorative beside the textual message. | all | absent | product callsite | stable | nullish, boolean, and empty-string values omit the slot |
83
+ | actions | React node or absent | Optional caller-owned next actions below the message. | all | absent | product callsite | stable | nullish, boolean, and empty-string values omit the slot |
84
+ | heading level | `1` through `6` | The title's HTML heading level in the surrounding document outline. | all | `3` | product callsite | stable | type rejection |
85
+ | layout variant | default, compact | Compact reduces spacing and stacks the action slot vertically. | all | default | `component:EmptyState` | stable | type rejection |
86
+
87
+ ## Behavioral and layout contract
88
+
89
+ Draft requirements identify their basis so observed code is not mistaken for an
90
+ intentional decision. A `current` contract contains no unresolved rows.
91
+
92
+ | ID | Candidate invariant | Basis | Draft review state |
93
+ | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------ |
94
+ | FR1 | The component MUST render one status-region root containing the icon, text group, and actions in that reading order when present. | Current implementation and consumer docs; WCAG 2.2 SC 1.3.2 and SC 4.1.3 | verify |
95
+ | FR2 | The required title MUST render at the caller-selected heading level without changing the component's visual type role. | Current implementation, tests, and consumer docs; WCAG 2.2 SC 1.3.1 and SC 2.4.6 | verify |
96
+ | FR3 | Null, undefined, boolean, and empty-string optional values MUST NOT create a wrapper, layout gap, or assistive-technology node; containers that are shallowly non-empty remain wrapped. | React's direct-child rendering semantics; established public optional-slot interface | verify |
97
+ | FR4 | The decorative icon wrapper MUST be absent when the slot is absent and hidden from assistive technology when rendered. | Current implementation and tests; WCAG 2.2 SC 1.1.1 | verify |
98
+ | FR5 | Root DOM/data/ARIA/event props, ref, `className`, `style`, and `xstyle` MUST reach and compose on the status-region root. | `architecture:public-component-api` INV5–INV8 | verify |
99
+ | FR6 | Compact layout MUST reduce component spacing and stack the actions slot while preserving content order and root semantics. | Current implementation, tests, and consumer docs | verify |
100
+
101
+ ### Allowed variation
102
+
103
+ - **AV1 — Caller content.** Message wording, decorative artwork, and action
104
+ composition may vary at the product callsite.
105
+ - **AV2 — Theme paint.** Theme-controlled paint and typography may vary through
106
+ the documented targets while anatomy and content order remain stable.
107
+
108
+ ### Representative states
109
+
110
+ | State | Required invariant | Allowed variation |
111
+ | ----------------------------- | --------------------------------------------------------- | -------------------------------------- |
112
+ | title only | status root and selected heading level remain present | message wording |
113
+ | description | supporting text follows the title | text length and wrapping |
114
+ | decorative icon | visual precedes text and is hidden from assistive tech | artwork supplied by the caller |
115
+ | actions | caller controls follow the text group | one or more caller-owned controls |
116
+ | compact | reduced spacing and vertical actions preserve semantics | theme paint and caller content |
117
+ | shallow-empty optional scalar | nullish, boolean, and empty-string values emit no wrapper | which optional slot receives the value |
118
+
119
+ ### Transformation and precedence order
120
+
121
+ - **ORD1 — Root composition.** Component semantics and theme metadata combine
122
+ with component styles, then consumer `xstyle`, `className`, and `style` under
123
+ the shared merge contract; component-owned `role="status"` remains authoritative.
124
+
125
+ ### Performance and resources
126
+
127
+ - **PR1 — Render-only surface.** EmptyState owns no retained state, listener,
128
+ observer, timer, or external resource.
129
+
130
+ ## Accessibility contract
131
+
132
+ - **AR1 — Heading semantics.** The title is a heading at the caller-selected level.
133
+ - **AR2 — Decorative visual.** The icon slot is hidden from assistive technology;
134
+ callers keep all necessary meaning in the title or description.
135
+ - **AR3 — Status exposure.** The root exposes the empty message as a status region
136
+ without moving focus; action controls keep their own names, roles, and behavior.
137
+
138
+ ## Design relationships
139
+
140
+ No current design record governs EmptyState-specific representation. Visual
141
+ judgment remains evidence, not product authority.
142
+
143
+ ### Theming anatomy
144
+
145
+ <!-- anatomy-theming:v1 -->
146
+
147
+ ```json
148
+ {
149
+ "Container": {"target": "empty-state"},
150
+ "Icon": {
151
+ "none": {
152
+ "reason": "intentional: caller-supplied decorative content keeps its own styling and has no component-owned paint target"
153
+ }
154
+ },
155
+ "Title": {"target": "empty-state-title"},
156
+ "Description": {"target": "empty-state-description"},
157
+ "Actions": {
158
+ "none": {
159
+ "reason": "intentional: caller-supplied controls keep their own component targets and behavior"
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ## Family and system relationships
166
+
167
+ No current family record owns EmptyState-specific behavior.
168
+
169
+ ## Verification map
170
+
171
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
172
+ | -------------- | ------------------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------- | ------------------------- |
173
+ | FR1–FR4, AR1–3 | focused DOM tests plus required exact-head accessibility evidence | title only, all slots, absent/shallow-empty slots | order, heading, status, or decorative treatment regresses | `audit:EmptyState/a11y` |
174
+ | FR5, ORD1 | focused root composition tests plus shared API guards | DOM/data/event/style inputs, ref, root target | accepted input is dropped or component semantics/styles are replaced | `audit:EmptyState/api` |
175
+ | FR6 | compact target/DOM tests plus required exact-head rendered evidence | default and compact, wide and narrow | compact state, spacing, or action direction no longer applies | `audit:EmptyState/layout` |
176
+ | PR1 | source review | every render path | a retained resource appears without a named owner | `audit:EmptyState/code` |
177
+
178
+ ## Decision log
179
+
180
+ No component-local decision is approved in this draft.
181
+
182
+ ## Open questions
183
+
184
+ None.
185
+
186
+ ## Content boundary
187
+
188
+ This file does not duplicate consumer prop tables/examples, current audit
189
+ results, implementation steps, or family/system rules. It links to their owners.
@@ -1,7 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
- import {describe, it, expect} from 'vitest';
4
- import {render, screen} from '@testing-library/react';
3
+ import {describe, it, expect, vi} from 'vitest';
4
+ import {fireEvent, render, screen} from '@testing-library/react';
5
+ import * as stylex from '@stylexjs/stylex';
5
6
  import {EmptyState} from './EmptyState';
6
7
  import {defineTheme} from '../theme/defineTheme';
7
8
  import {generateThemeCSS} from '../theme/generateThemeRules';
@@ -11,6 +12,12 @@ function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
11
12
  return [prose, component].filter(Boolean).join('\n\n');
12
13
  }
13
14
 
15
+ const testStyles = stylex.create({
16
+ root: {
17
+ minWidth: '123px',
18
+ },
19
+ });
20
+
14
21
  describe('EmptyState', () => {
15
22
  it('renders with title', () => {
16
23
  render(<EmptyState title="No results found" />);
@@ -52,7 +59,7 @@ describe('EmptyState', () => {
52
59
  />,
53
60
  );
54
61
  expect(screen.getByText('Try adjusting your search.')).toBeInTheDocument();
55
- // Description renders as <div> (never <p>) so block content composes safely.
62
+ // Description keeps its stable target element instead of paragraph semantics.
56
63
  expect(screen.getByText('Try adjusting your search.').tagName).toBe('DIV');
57
64
  });
58
65
 
@@ -132,6 +139,70 @@ describe('EmptyState', () => {
132
139
  expect(screen.getByTestId('empty-state')).toBeInTheDocument();
133
140
  });
134
141
 
142
+ it('does not render empty optional slot wrappers', () => {
143
+ const {container} = render(
144
+ <EmptyState title="No results" description="" icon={false} actions="" />,
145
+ );
146
+ const root = screen.getByRole('status');
147
+ expect(root).toHaveTextContent('No results');
148
+ expect(root).toHaveTextContent(/^No results$/);
149
+ expect(root.children).toHaveLength(1);
150
+ expect(container.querySelector('[aria-hidden="true"]')).toBeNull();
151
+ expect(
152
+ container.querySelector('.astryx-empty-state-description'),
153
+ ).toBeNull();
154
+ });
155
+
156
+ it('composes root props and keeps component semantics authoritative', () => {
157
+ const onClick = vi.fn();
158
+ const expectedXstyleClass = stylex.props(testStyles.root).className;
159
+ if (expectedXstyleClass == null) {
160
+ throw new Error('Expected the test xstyle to produce a class name.');
161
+ }
162
+ render(
163
+ <EmptyState
164
+ title="No results"
165
+ role="region"
166
+ data-owner="consumer"
167
+ className="consumer-class"
168
+ style={{width: '321px'}}
169
+ xstyle={testStyles.root}
170
+ onClick={onClick}
171
+ />,
172
+ );
173
+
174
+ const root = screen.getByRole('status');
175
+ expect(root).toHaveClass('astryx-empty-state', 'consumer-class');
176
+ expect(root).toHaveClass(expectedXstyleClass);
177
+ expect(root).toHaveAttribute('data-owner', 'consumer');
178
+ expect(root).toHaveStyle({width: '321px'});
179
+ fireEvent.click(root);
180
+ expect(onClick).toHaveBeenCalledOnce();
181
+ });
182
+
183
+ it('reflects compact layout on the root and preserves slot order', () => {
184
+ render(
185
+ <EmptyState
186
+ title="No results"
187
+ description="Try again."
188
+ icon={<span data-testid="icon">Icon</span>}
189
+ actions={<button type="button">Retry</button>}
190
+ isCompact
191
+ />,
192
+ );
193
+
194
+ const root = screen.getByRole('status');
195
+ expect(root).toHaveAttribute('data-variant', 'compact');
196
+ expect(root.children).toHaveLength(3);
197
+ expect(root.children[0]).toContainElement(screen.getByTestId('icon'));
198
+ expect(root.children[1]).toContainElement(
199
+ screen.getByRole('heading', {name: 'No results'}),
200
+ );
201
+ expect(root.children[2]).toContainElement(
202
+ screen.getByRole('button', {name: 'Retry'}),
203
+ );
204
+ });
205
+
135
206
  it('renders all slots together', () => {
136
207
  render(
137
208
  <EmptyState
@@ -23,7 +23,7 @@ import {
23
23
  typeScaleVars,
24
24
  } from '../theme/tokens.stylex';
25
25
  import type {BaseProps} from '../BaseProps';
26
- import {mergeProps} from '../utils';
26
+ import {isRenderable, mergeProps} from '../utils';
27
27
  import {themeProps} from '../utils/themeProps';
28
28
 
29
29
  const styles = stylex.create({
@@ -169,7 +169,7 @@ export function EmptyState({
169
169
  className,
170
170
  style,
171
171
  )}>
172
- {icon != null && <div aria-hidden="true">{icon}</div>}
172
+ {isRenderable(icon) && <div aria-hidden="true">{icon}</div>}
173
173
  <div {...stylex.props(styles.textGroup)}>
174
174
  {createElement(
175
175
  HeadingTag,
@@ -181,11 +181,7 @@ export function EmptyState({
181
181
  ),
182
182
  title,
183
183
  )}
184
- {description != null && (
185
- // Rendered as <div> (not <p>): description accepts ReactNode and a
186
- // <p> cannot legally contain block children, which causes hydration
187
- // mismatches. The StyleX style sets margin: 0, so appearance is
188
- // unchanged.
184
+ {isRenderable(description) && (
189
185
  <div
190
186
  {...mergeProps(
191
187
  themeProps('empty-state-description', {
@@ -200,7 +196,7 @@ export function EmptyState({
200
196
  </div>
201
197
  )}
202
198
  </div>
203
- {actions != null && (
199
+ {isRenderable(actions) && (
204
200
  <div
205
201
  {...stylex.props(styles.actions, isCompact && styles.actionsCompact)}>
206
202
  {actions}
@@ -114,7 +114,7 @@ export const docs = {
114
114
  },
115
115
  {
116
116
  name: 'labelIcon',
117
- type: 'IconType',
117
+ type: 'ReactNode | IconType',
118
118
  description: 'Icon to display before the label text. See `astryx docs icons` for valid semantic names.',
119
119
  },
120
120
  {
@@ -129,8 +129,8 @@ export const docs = {
129
129
  },
130
130
  {
131
131
  name: 'statusVariant',
132
- type: "'attached' | 'detached'",
133
- description: 'How the status message renders relative to the input. Attached overlaps the input border; detached floats below.',
132
+ type: "'attached' | 'detached' | 'tooltip'",
133
+ description: 'How status feedback is placed. Attached overlaps an eligible opaque, bordered, fixed-height control; detached renders a message below custom or non-bordered controls; tooltip leaves the visible affordance to the wrapped input and announces its message without a FieldStatus box.',
134
134
  default: "'attached'",
135
135
  },
136
136
  {
@@ -170,17 +170,23 @@ export const docs = {
170
170
  { guidance: true, description: 'Always provide a label for accessibility, even if visually hidden with isLabelHidden.' },
171
171
  { guidance: true, description: 'Use inputID and descriptionID to connect the label and description to the inner control with htmlFor and aria-describedby.' },
172
172
  { guidance: false, description: 'Nest Field around styled inputs such as TextInput, Typeahead, Select, DateInput, or TextArea; those components already render their own Field shell.' },
173
- { guidance: false, description: 'Use the attached status variant on non-bordered controls such as sliders, switches, or checkboxes; use detached so the message does not overlap the control.' },
173
+ { guidance: true, description: 'Use attached status only when the wrapped control is opaque, bordered, and fixed-height; use detached for custom, tall, translucent, or non-bordered controls.' },
174
174
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
175
175
  { guidance: false, description: 'Hide the label without providing an alternative way for the user to understand the field purpose.' },
176
176
  ],
177
177
  anatomy: [
178
+ {name: 'Container', required: true, description: 'Root field box that owns layout, width, and local stacking for the label, control, and status.'},
178
179
  {name: 'Label', required: true, description: 'Text identifying the field. Always rendered for accessibility, optionally hidden visually.'},
179
- {name: 'Description', required: false, description: 'Helper text between the label and input explaining what to enter.'},
180
+ {name: 'Ordinary description', required: false, description: 'Helper text under the label in the ordinary field layout.'},
181
+ {name: 'Horizontal-label description', required: false, description: 'Helper text in the control column when FormLayout owns horizontal labels.'},
180
182
  {name: 'Control slot', required: true, description: 'A custom, native, or third-party control that does not already render a field shell.'},
181
183
  {name: 'Status message', required: false, description: 'Inline validation feedback showing error, warning, or success with a message.'},
184
+ {name: 'Input status icon', required: false, description: 'Shared status affordance used by Field-composed input controls.'},
185
+ {name: 'Input clear button', required: false, description: 'Shared clear action used by Field-composed input controls.'},
186
+ {name: 'Input clear icon', required: false, description: 'Glyph inside the shared input clear action.'},
182
187
  {name: 'Optional/Required indicator', required: false, description: 'Badge next to the label showing whether the field is optional or required.'},
183
- {name: 'Label tooltip', required: false, description: 'Info icon at the end of the label with a tooltip explaining the field.'},
188
+ {name: 'Label tooltip trigger', required: false, description: 'Info icon beside the label that opens explanatory content.'},
189
+ {name: 'Label tooltip surface', required: false, description: 'Layer-hosted explanatory content for the label tooltip.'},
184
190
  ],
185
191
  },
186
192
  examples: [
@@ -279,7 +285,7 @@ export const docsZh = {
279
285
  { guidance: true, description: 'Always provide a label for accessibility, even if visually hidden with isLabelHidden.' },
280
286
  { guidance: true, description: 'Use inputID and descriptionID to connect the label and description to the inner control with htmlFor and aria-describedby.' },
281
287
  { guidance: false, description: 'Nest Field around styled inputs such as TextInput, Typeahead, Select, DateInput, or TextArea; those components already render their own Field shell.' },
282
- { guidance: false, description: 'Use the attached status variant on non-bordered controls such as sliders, switches, or checkboxes; use detached so the message does not overlap the control.' },
288
+ { guidance: true, description: 'Use attached status only when the wrapped control is opaque, bordered, and fixed-height; use detached for custom, tall, translucent, or non-bordered controls.' },
283
289
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
284
290
  { guidance: false, description: 'Hide the label without providing an alternative way for the user to understand the field purpose.' },
285
291
  ],
@@ -297,7 +303,7 @@ export const docsDense = {
297
303
  { guidance: true, description: 'Always provide a label; visually hide it only when context is clear.' },
298
304
  { guidance: true, description: 'Wire inputID/descriptionID to htmlFor and aria-describedby on the inner control.' },
299
305
  { guidance: false, description: 'Nest Field around styled inputs; it double-renders labels and status UI.' },
300
- { guidance: false, description: 'Use attached status on sliders/switches/checkboxes; use detached so messages do not overlap.' },
306
+ { guidance: true, description: 'Use attached status only for opaque, bordered, fixed-height controls; use detached for custom, tall, translucent, or non-bordered controls.' },
301
307
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
302
308
  { guidance: false, description: 'Hide the label without another way to understand field purpose.' },
303
309
  ],
@@ -0,0 +1,217 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 7
4
+ kind: component
5
+ id: component:Field
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Field/Field.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ packages/core/src/docPropLiterals.test.ts,
18
+ packages/core/src/docPropReferences.test.ts,
19
+ ]
20
+ modules: []
21
+ families: [family:input-fields]
22
+ design_specs:
23
+ [design:spatial-hierarchy, design:typography-hierarchy, design:system-states]
24
+ architecture:
25
+ [
26
+ architecture:public-component-api,
27
+ architecture:component-theming-surface,
28
+ architecture:component-test-sufficiency,
29
+ ]
30
+ contributing: []
31
+ system_specs:
32
+ [spec:AST-027/FR1, spec:AST-027/FR2, spec:AST-027/FR3, spec:AST-027/FR11]
33
+ ---
34
+
35
+ # Field component contract
36
+
37
+ ## Contract at a glance
38
+
39
+ | Area | Contract |
40
+ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
+ | Public contract | `Field` wraps one caller-owned control with a label, optional description and status, optionality indicators, an explicit width, and root `BaseProps`; it also consumes FormLayout's horizontal-label mode. |
42
+ | Behavior | Single controls use a native label association; groups use a labelled span. Status messages receive stable IDs and use persistent live regions. Horizontal-label mode exposes one protected `data-layout` theme axis. |
43
+ | End-user impact | People can identify a custom control, hear its description and changing status, and keep label/control/status relationships together across ordinary and horizontal form layouts. |
44
+ | Builder impact | Builders provide the control and its ID, wire descriptions/status IDs to that control, choose group semantics when needed, and use a status placement that is safe for the wrapped surface. |
45
+ | Compatibility/readiness | This draft records released behavior. It adds no API or policy. The released default `statusVariant="attached"` for arbitrary children conflicts with the capability boundary in `family:input-fields/FR8` and remains open. |
46
+ | Review checks | Reject a lost label/group association, dropped root props, caller replacement of `data-layout`, duplicate/missing status announcement, escaped local z-index, undocumented public literals, or a claim that all custom controls safely support attached status. |
47
+ | Governing rules | Established public `Field` interface; `family:input-fields` shared ownership and FR8; `architecture:public-component-api/INV5–INV8`; `spec:AST-027/FR1–FR3`; HTML label and ARIA naming/description semantics. |
48
+
49
+ This table is a review projection; the body below is authoritative.
50
+
51
+ ## Intent
52
+
53
+ `Field` supplies shared label, description, status, width, and layout chrome for a native, custom, or third-party control that does not already own an Astryx field shell. The caller continues to own the control's value, interaction model, and control-specific accessibility semantics.
54
+
55
+ ## Compatibility and migration
56
+
57
+ - Released default preserved: yes; `statusVariant` still defaults to `attached` in the current implementation.
58
+ - Compatibility class: observational only; this draft changes no public prop, default, or behavior.
59
+ - Controlled/uncontrolled behavior: caller-owned and unchanged.
60
+ - Migration decision: none; resolving OQ1 requires a separate compatibility decision.
61
+
62
+ ## Ownership boundary
63
+
64
+ **Owns**
65
+
66
+ - The root field box, explicit width, local stacking boundary, and composition order.
67
+ - Label and description placement around the caller-owned control.
68
+ - Single-control versus grouped-label semantics.
69
+ - Status-message placement and persistent live-region announcement.
70
+ - The FormLayout horizontal-label projection.
71
+
72
+ **Does not own / non-goals**
73
+
74
+ - The wrapped control's value, focus model, keyboard behavior, disabled behavior, or event semantics — owned by the caller or wrapped component.
75
+ - Applying `aria-describedby` to the wrapped control — the caller supplies the control and wires the generated or explicit IDs.
76
+ - The visual and semantic contract of `FieldLabel`, `FieldStatus`, `Tooltip`, or caller-owned children — owned by those components.
77
+ - Input-family member APIs and control-box part props — owned by `family:input-fields` and each member.
78
+
79
+ ## Public concepts
80
+
81
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
82
+ | ---------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ------------------------------ | --------------------- | ------------------------- | ------------------------------------- |
83
+ | label mode | single control; group | Chooses a native `<label htmlFor>` or a `<span>` named through `aria-labelledby`. | All layouts. | single control | `component:Field` | released | Type-constrained |
84
+ | label visibility | visible; visually hidden | Hides label and description visually while preserving them for assistive technology. | All layouts. | visible | `component:Field` | released | Type-constrained |
85
+ | optionality marker | none; optional; required | Shows the localized exception to FormLayout's optionality default. | Label presentation. | none | `component:Field` | released | Both set warns; optional wins visibly |
86
+ | status placement | `attached`; `detached`; `tooltip` | Places a status box behind the control, below it, or leaves visible tooltip affordance to input. | Message required for a rendered box; tooltip still announces messages. | `attached` (see OQ1) | `family:input-fields` | released; conformance gap | Type-constrained |
87
+ | width | absent; number; CSS size string | Sizes the complete ordinary field box. | Ordinary box layout; horizontal-label mode uses `display: contents`. | containing layout | `component:Field` | released | Type-constrained |
88
+ | field layout | ordinary box; `horizontal-labels` from FormLayout | Stacks content or projects label and input group into the parent grid. | Resolved from `FormLayoutContext`. | ordinary box | `component:Field` | released | Context-constrained |
89
+ | description/status IDs | explicit; generated from `inputID`; absent | Names stable description and message elements for caller-owned `aria-describedby` wiring. | Generated only when the corresponding text is present. | generated when text is present | `component:Field` | released | Empty text omits output |
90
+
91
+ ## Behavioral and layout contract
92
+
93
+ Draft requirements identify their basis so observed code is not mistaken for an intentional decision.
94
+
95
+ | ID | Candidate invariant | Basis | Draft review state |
96
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ----------------------------------- |
97
+ | FR1 | A single-control label renders as `<label htmlFor={inputID}>`; a group label renders as a non-label element with `labelID` for caller-owned `aria-labelledby`. | Established public interface; HTML and ARIA semantics | settled observation |
98
+ | FR2 | Visually hidden labels and descriptions remain in the DOM and out of layout flow. | Established public interface and accessibility behavior | settled observation |
99
+ | FR3 | Description and status IDs use explicit values when supplied and otherwise resolve to `${inputID}-desc` and `${inputID}-status` only when text exists. | Established public interface | settled observation |
100
+ | FR4 | Error status messages announce assertively and warning/success messages politely through persistent live regions; tooltip placement renders no `FieldStatus` box but preserves the announcement. | Existing tests; family ownership | settled observation |
101
+ | FR5 | Ordinary layout orders label, control, then optional status. Horizontal-label mode places the label wrapper before one description/control/status group and reflects `data-layout="horizontal-labels"`; caller props cannot replace that owned theme marker. | Established layout interface; `architecture:public-component-api/INV5` | settled observation |
102
+ | FR6 | `width` applies to the ordinary root field box; it does not style the caller-owned control and has no box to target in horizontal-label mode. | Established public interface; `family:input-fields` shared owner | settled observation |
103
+ | FR7 | Root ref, data/ARIA/DOM inputs, events, `xstyle`, class name, and inline style reach or compose on the root without replacing component-owned semantics. | `architecture:public-component-api/INV5–INV8` | settled observation |
104
+ | FR8 | The Field root isolates every local attached-status paint relationship without assigning the root a page-level z-index. | `spec:AST-027/FR1–FR3`, `FR11` | settled observation |
105
+ | FR9 | Attached status is safe only for an opaque, bordered, fixed-height control with reliable size reflection; custom, tall, wrapped, translucent, or non-bordered controls use detached or component-owned placement. | `family:input-fields/FR8`; current default mismatch | human decision for released default |
106
+
107
+ ### Allowed variation
108
+
109
+ - **AV1 — Caller-owned control.** Any control markup may occupy the slot; Field does not inspect, clone, or change it.
110
+ - **AV2 — Form arrangement.** FormLayout may choose ordinary or horizontal-label arrangement without changing label, description, status, or announcement ownership.
111
+ - **AV3 — Status surface.** Eligible controls may use attached placement; other controls use detached or a member-owned tooltip presentation.
112
+
113
+ ### Representative states
114
+
115
+ | State | Required invariant | Allowed variation |
116
+ | ----------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------- |
117
+ | label only | One associated label precedes the caller-owned control. | Visible or visually hidden. |
118
+ | description | One description element receives the resolved ID. | Caller may use an explicit ID or the generated value. |
119
+ | group | Group label is not a literal `<label>` and is referenceable by ID. | Caller owns the group role and `aria-labelledby`. |
120
+ | status | Message receives resolved ID and is announced once on its urgency channel. | Attached/detached box or tooltip-owned visible affordance. |
121
+ | horizontal labels | Label and input group participate in the parent grid and retain `data-layout`. | Description/status may be absent. |
122
+ | explicit width | Ordinary root receives the requested size and remains shrinkable. | Number or CSS size string. |
123
+
124
+ ### Transformation and precedence order
125
+
126
+ - **ORD1 — Root props.** Caller DOM/data/event props are forwarded first; Field-owned visual-state reflection is applied afterward; StyleX, caller class, and caller inline style compose in the documented order.
127
+ - **ORD2 — IDs.** Explicit description/status ID wins; otherwise text presence derives an ID from `inputID`; absent text produces no ID-bearing element.
128
+ - **ORD3 — Optionality.** FormLayout default optionality suppresses the matching visible marker; if both local booleans are true, Field warns and the optional marker wins visibly.
129
+
130
+ ### Performance and resources
131
+
132
+ - **PR1 — No per-field global resource.** Field owns no listener, observer, timer, or external store. Status announcement delegates to the shared persistent live-region runtime.
133
+
134
+ ## Accessibility contract
135
+
136
+ - **AR1 — Label association.** Single controls receive a native label association; grouped controls use caller-wired `aria-labelledby` and never a misleading literal `<label>`.
137
+ - **AR2 — Description association.** Description text remains separate from the accessible name and exposes a stable ID for caller-owned `aria-describedby`.
138
+ - **AR3 — Status announcement.** Changing status text is announced through the persistent live region, including tooltip placement where no message box renders.
139
+ - **AR4 — Hidden labels.** Visually hidden label and description text remain available to assistive technology.
140
+
141
+ ## Design relationships
142
+
143
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
144
+ | ----------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | ------------------------- | ------------------ |
145
+ | Label and description | `design:typography-hierarchy/DR4`, `DR6` | prescribed roles; exact tuning remains token-owned | label and supporting text | FR1–FR3 |
146
+ | Label/control/status grouping | `design:spatial-hierarchy` local and group relationships | prescribed relationship; exact gap remains token-owned | one field group | FR5 |
147
+ | Semantic status | `design:system-states/DR2` | delegated to FieldStatus and wrapped input | supporting feedback | FR4, FR9 |
148
+
149
+ ### Theming anatomy
150
+
151
+ <!-- anatomy-theming:v1 -->
152
+
153
+ ```json
154
+ {
155
+ "Container": {"target": "field"},
156
+ "Label": {"target": "field-label"},
157
+ "Ordinary description": {
158
+ "none": {
159
+ "reason": "reachability-gap: The ordinary-layout description paints supporting text outside the field-label target and has no current public target of its own."
160
+ }
161
+ },
162
+ "Horizontal-label description": {
163
+ "delegatesTo": {"owner": "component:Text", "target": "text"}
164
+ },
165
+ "Control slot": {
166
+ "none": {
167
+ "reason": "intentional: The wrapped control is caller-owned or another Astryx component and retains its own theming surface."
168
+ }
169
+ },
170
+ "Status message": {"target": "field-status"},
171
+ "Input status icon": {"target": "input-status-icon"},
172
+ "Input clear button": {"target": "input-clear-button"},
173
+ "Input clear icon": {"target": "input-clear-icon"},
174
+ "Optional/Required indicator": {"inherits": "field-label"},
175
+ "Label tooltip trigger": {
176
+ "delegatesTo": {"owner": "component:Icon", "target": "icon"}
177
+ },
178
+ "Label tooltip surface": {
179
+ "delegatesTo": {"owner": "component:Tooltip", "target": "tooltip"}
180
+ }
181
+ }
182
+ ```
183
+
184
+ The ordinary description remains stable consumer anatomy without a Field-owned target. A visible horizontal-label description delegates to `Text/text`; when `isLabelHidden` is true, the same content delegates to `VisuallyHidden` and exposes no painted target. The label tooltip splits its visible `Icon/icon` trigger from its layer-hosted `Tooltip/tooltip` surface.
185
+
186
+ ## Family and system relationships
187
+
188
+ - `family:input-fields` owns shared status-placement capability and identifies Field as the standalone label, description, status-placement, and explicit-width owner.
189
+ - `architecture:public-component-api` owns root prop, style, event, and ref composition.
190
+ - `architecture:component-theming-surface` owns anatomy qualification and target disposition.
191
+ - `architecture:component-test-sufficiency` owns mutation-sensitive evidence and bounded partitions.
192
+ - `spec:AST-027` owns the isolated local paint relationship between the control and attached status.
193
+
194
+ ## Verification map
195
+
196
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
197
+ | ------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
198
+ | FR1–FR3, AR1–AR2, AR4 | `Field.test.tsx` DOM/accessibility tests | single control, group, visible/hidden label, explicit/generated IDs | A lost association, wrong element, missing hidden text, or wrong ID fails. | `audit:Field/accessibility` |
199
+ | FR4, AR3 | `Field.test.tsx` announcement tests plus exact-head browser accessibility evidence | first mount, update, repeat, error, warning, attached, detached, tooltip | A missing, duplicate, stale, or wrong-urgency announcement fails. | `audit:Field/accessibility` |
200
+ | FR5–FR7, ORD1 | `Field.test.tsx`, BaseProps lint, theming-target tests, and exact-head visual evidence | ordinary/horizontal layout; class/style/xstyle/data/event/ref; description/status | Dropped caller input, replaceable layout marker, wrong order, or lost target fails. | `audit:Field/api` |
201
+ | FR8 | computed-style tests and exact-head browser rendering | attached, detached, tooltip, no status | Removing isolation or ranking the root outside its parent fails. | `audit:Field/code-health` |
202
+ | FR9 | family contract review and component-owned visual evidence | eligible bordered control; custom/tall/non-bordered control | Attached placement on an ineligible surface remains visible as the current conformance gap. | `audit:Field/behavior` |
203
+ | Theming anatomy | knowledge validation and theming-target tests | Field root, label, description, caller control, status, shared input affordances, indicator, tooltip | Missing, duplicate, or wrongly owned dispositions fail. | `audit:Field/theming` |
204
+ | Documentation projection | docs typecheck and prop literal/reference tests | labelIcon; all status placements; anatomy and usage guidance | Docs omit or misstate a public type/literal/owner and validation fails or review rejects it. | `audit:Field/docs` |
205
+ | RTL applicability | exact-head RTL audit and verified-N/A registry | all checked-in Field stories and source | A directional glyph, physical inline placement, order, scroll/drag, overlay, or directional keyboard behavior makes N/A stale. | `audit:Field/i18n-rtl` |
206
+
207
+ ## Decision log
208
+
209
+ No component-local decision is asserted. This draft records released behavior and leaves the current family-owned status-placement conflict in OQ1.
210
+
211
+ ## Open questions
212
+
213
+ - **OQ1 — How should Field migrate the released `attached` default so arbitrary custom controls cannot receive a placement that `family:input-fields/FR8` says they may not safely support?** (`human-api`)
214
+
215
+ ## Content boundary
216
+
217
+ This draft records verified shipped behavior and the current family-owned conformance gap. It does not approve a new default, migration, theme target, or compatibility promise, and it does not duplicate audit scores or run evidence.