@astryxdesign/core 0.7.2-canary.fe2c655 → 0.7.2

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 (297) hide show
  1. package/CHANGELOG.md +156 -2
  2. package/README.md +4 -4
  3. package/dist/AppShell/AppShell.d.ts.map +1 -1
  4. package/dist/AppShell/AppShell.js +40 -1
  5. package/dist/AspectRatio/AspectRatio.d.ts +6 -8
  6. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  7. package/dist/AspectRatio/AspectRatio.js +44 -3
  8. package/dist/Avatar/Avatar.d.ts.map +1 -1
  9. package/dist/Avatar/Avatar.js +8 -1
  10. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  11. package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  13. package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
  14. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  15. package/dist/Chat/ChatLayoutScrollButton.js +1 -0
  16. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  17. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  18. package/dist/CheckboxInput/CheckboxInput.js +3 -1
  19. package/dist/CheckboxList/CheckboxList.d.ts +1 -1
  20. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  21. package/dist/CheckboxList/CheckboxList.js +6 -2
  22. package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
  23. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  24. package/dist/CheckboxList/CheckboxListItem.js +4 -2
  25. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  26. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteInput.js +37 -5
  28. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  29. package/dist/ContextMenu/ContextMenu.js +5 -2
  30. package/dist/Dialog/Dialog.d.ts.map +1 -1
  31. package/dist/Dialog/Dialog.js +20 -8
  32. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  33. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  34. package/dist/DropdownMenu/DropdownMenu.js +84 -113
  35. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -1
  36. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
  37. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  38. package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
  39. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
  40. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
  41. package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
  42. package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
  43. package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
  44. package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
  45. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  46. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  47. package/dist/DropdownMenu/DropdownMenuSubMenu.js +15 -4
  48. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
  49. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
  50. package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
  51. package/dist/DropdownMenu/menuRegionStops.d.ts +38 -0
  52. package/dist/DropdownMenu/menuRegionStops.d.ts.map +1 -0
  53. package/dist/DropdownMenu/menuRegionStops.js +131 -0
  54. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  55. package/dist/EmptyState/EmptyState.js +4 -10
  56. package/dist/Field/Field.d.ts +1 -1
  57. package/dist/Field/Field.d.ts.map +1 -1
  58. package/dist/Field/Field.js +8 -4
  59. package/dist/FormLayout/FormLayout.d.ts +5 -4
  60. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  61. package/dist/FormLayout/FormLayout.js +1 -1
  62. package/dist/FormLayout/FormLayoutContext.d.ts +3 -3
  63. package/dist/FormLayout/FormLayoutContext.js +3 -3
  64. package/dist/Grid/Grid.d.ts +8 -7
  65. package/dist/Grid/Grid.d.ts.map +1 -1
  66. package/dist/Grid/Grid.js +5 -4
  67. package/dist/Grid/GridSpan.d.ts +1 -1
  68. package/dist/Grid/GridSpan.d.ts.map +1 -1
  69. package/dist/Grid/GridSpan.js +1 -0
  70. package/dist/Heading/Heading.d.ts.map +1 -1
  71. package/dist/Heading/Heading.js +4 -2
  72. package/dist/Item/Item.d.ts +6 -2
  73. package/dist/Item/Item.d.ts.map +1 -1
  74. package/dist/Item/Item.js +19 -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/parser.d.ts.map +1 -1
  89. package/dist/Markdown/parser.js +8 -2
  90. package/dist/Markdown/plugins/headingLinks.d.ts.map +1 -1
  91. package/dist/Markdown/plugins/headingLinks.js +9 -1
  92. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  93. package/dist/MetadataList/MetadataList.js +5 -1
  94. package/dist/MetadataList/MetadataListContext.d.ts +1 -1
  95. package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
  96. package/dist/MobileNav/MobileNav.d.ts +9 -0
  97. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  98. package/dist/MobileNav/MobileNav.js +30 -3
  99. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  100. package/dist/PowerSearch/PowerSearch.js +5 -0
  101. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  102. package/dist/PowerSearch/PowerSearchValueEditor.js +45 -11
  103. package/dist/RadioList/RadioList.d.ts +1 -1
  104. package/dist/RadioList/RadioList.d.ts.map +1 -1
  105. package/dist/RadioList/RadioList.js +6 -2
  106. package/dist/RadioList/RadioListItem.d.ts +9 -1
  107. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  108. package/dist/RadioList/RadioListItem.js +2 -0
  109. package/dist/SideNav/SideNavItem.d.ts +8 -1
  110. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  111. package/dist/SideNav/SideNavItem.js +76 -10
  112. package/dist/Text/Text.d.ts.map +1 -1
  113. package/dist/Text/Text.js +4 -2
  114. package/dist/ToggleButton/ToggleButton.d.ts +3 -2
  115. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  116. package/dist/ToggleButton/ToggleButton.js +12 -1
  117. package/dist/Token/Token.js +1 -1
  118. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  119. package/dist/TopNav/TopNavMegaMenu.js +5 -3
  120. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  121. package/dist/TopNav/TopNavMenu.js +5 -3
  122. package/dist/TreeList/TreeList.d.ts.map +1 -1
  123. package/dist/TreeList/TreeList.js +1 -0
  124. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  125. package/dist/TreeList/TreeListBranches.js +2 -2
  126. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  127. package/dist/TreeList/TreeListItem.js +57 -9
  128. package/dist/astryx.css +27 -4
  129. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  130. package/dist/hooks/useMenuHover.js +18 -13
  131. package/dist/reset.a11y.chromium.spec.js +50 -1
  132. package/dist/utils/plainDate.d.ts.map +1 -1
  133. package/dist/utils/plainDate.js +15 -9
  134. package/docs.mjs +4 -4
  135. package/package.json +4 -4
  136. package/scripts/agent-doc-state.mjs +66 -6
  137. package/scripts/postinstall.mjs +44 -3
  138. package/src/AppShell/AppShell.tsx +45 -1
  139. package/src/AspectRatio/AspectRatio.test.tsx +44 -43
  140. package/src/AspectRatio/AspectRatio.tsx +38 -18
  141. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
  142. package/src/Avatar/Avatar.doc.mjs +5 -3
  143. package/src/Avatar/Avatar.tsx +11 -2
  144. package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
  145. package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +24 -1
  146. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -0
  147. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  148. package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
  149. package/src/Card/Card.doc.mjs +1 -1
  150. package/src/Chat/ChatLayoutScrollButton.test.tsx +13 -0
  151. package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
  152. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +64 -19
  153. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
  154. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  155. package/src/CheckboxInput/CheckboxInput.tsx +3 -1
  156. package/src/CheckboxList/CheckboxList.fr10.test.tsx +29 -2
  157. package/src/CheckboxList/CheckboxList.test.tsx +22 -0
  158. package/src/CheckboxList/CheckboxList.tsx +4 -1
  159. package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
  160. package/src/CheckboxList/CheckboxListItem.tsx +10 -0
  161. package/src/Code/Code.doc.mjs +1 -0
  162. package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
  163. package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
  164. package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
  165. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
  166. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
  167. package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
  168. package/src/ContextMenu/ContextMenu.test.tsx +12 -2
  169. package/src/ContextMenu/ContextMenu.tsx +4 -1
  170. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
  171. package/src/Dialog/Dialog.test.tsx +46 -0
  172. package/src/Dialog/Dialog.tsx +20 -7
  173. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
  174. package/src/Drawer/Drawer.doc.mjs +1 -0
  175. package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
  176. package/src/DropdownMenu/DropdownMenu.test.tsx +79 -6
  177. package/src/DropdownMenu/DropdownMenu.tsx +88 -144
  178. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +1 -1
  179. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -1
  180. package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
  181. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
  182. package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
  183. package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
  184. package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
  185. package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
  186. package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
  187. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +1 -1
  188. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
  189. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
  190. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +17 -3
  191. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
  192. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
  193. package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
  194. package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
  195. package/src/DropdownMenu/menuRegionStops.ts +167 -0
  196. package/src/EmptyState/EmptyState.doc.mjs +3 -2
  197. package/src/EmptyState/EmptyState.spec.md +189 -0
  198. package/src/EmptyState/EmptyState.test.tsx +74 -3
  199. package/src/EmptyState/EmptyState.tsx +4 -8
  200. package/src/Field/Field.doc.mjs +14 -8
  201. package/src/Field/Field.spec.md +217 -0
  202. package/src/Field/Field.test.tsx +60 -2
  203. package/src/Field/Field.tsx +17 -8
  204. package/src/Field/FieldLabel.doc.mjs +133 -26
  205. package/src/Field/FieldLabel.spec.md +172 -0
  206. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
  207. package/src/FormLayout/FormLayout.doc.mjs +6 -3
  208. package/src/FormLayout/FormLayout.spec.md +192 -0
  209. package/src/FormLayout/FormLayout.test.tsx +15 -0
  210. package/src/FormLayout/FormLayout.tsx +7 -6
  211. package/src/FormLayout/FormLayoutContext.ts +3 -3
  212. package/src/Grid/Grid.doc.mjs +7 -1
  213. package/src/Grid/Grid.spec.md +93 -113
  214. package/src/Grid/Grid.test.tsx +22 -0
  215. package/src/Grid/Grid.tsx +10 -9
  216. package/src/Grid/GridSpan.doc.mjs +3 -3
  217. package/src/Grid/GridSpan.tsx +4 -1
  218. package/src/Heading/Heading.test.tsx +57 -0
  219. package/src/Heading/Heading.tsx +6 -2
  220. package/src/Icon/Icon.doc.mjs +36 -0
  221. package/src/Indicator/Indicator.doc.mjs +3 -3
  222. package/src/Item/Item.doc.mjs +12 -8
  223. package/src/Item/Item.test.tsx +61 -0
  224. package/src/Item/Item.tsx +40 -4
  225. package/src/Layout/LayoutHeader.doc.mjs +5 -0
  226. package/src/List/List.doc.mjs +14 -1
  227. package/src/List/List.spec.md +129 -77
  228. package/src/List/List.test.tsx +163 -94
  229. package/src/List/List.tsx +74 -18
  230. package/src/List/ListContext.tsx +9 -3
  231. package/src/List/ListItem.doc.mjs +16 -0
  232. package/src/List/ListItem.tsx +56 -27
  233. package/src/List/index.ts +1 -1
  234. package/src/Markdown/Markdown.tsx +14 -1
  235. package/src/Markdown/parser.ts +8 -2
  236. package/src/Markdown/plugins/headingLinks.ts +9 -1
  237. package/src/MetadataList/MetadataList.doc.mjs +3 -3
  238. package/src/MetadataList/MetadataList.test.tsx +38 -0
  239. package/src/MetadataList/MetadataList.tsx +8 -1
  240. package/src/MetadataList/MetadataListContext.tsx +1 -1
  241. package/src/MobileNav/MobileNav.tsx +30 -3
  242. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
  243. package/src/MoreMenu/MoreMenu.test.tsx +5 -1
  244. package/src/PowerSearch/PowerSearch.test.tsx +62 -0
  245. package/src/PowerSearch/PowerSearch.tsx +9 -0
  246. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +71 -0
  247. package/src/PowerSearch/PowerSearchValueEditor.tsx +45 -0
  248. package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
  249. package/src/RadioList/RadioList.fr10.test.tsx +45 -2
  250. package/src/RadioList/RadioList.test.tsx +22 -0
  251. package/src/RadioList/RadioList.tsx +4 -1
  252. package/src/RadioList/RadioListItem.doc.mjs +16 -0
  253. package/src/RadioList/RadioListItem.tsx +10 -1
  254. package/src/Resizable/useResizable.doc.mjs +2 -0
  255. package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
  256. package/src/SideNav/SideNav.spec.md +227 -0
  257. package/src/SideNav/SideNav.test.tsx +220 -0
  258. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  259. package/src/SideNav/SideNavItem.tsx +99 -17
  260. package/src/Slider/Slider.spec.md +39 -18
  261. package/src/Spinner/Spinner.doc.mjs +1 -1
  262. package/src/TabList/TabList.spec.md +57 -44
  263. package/src/Table/Table.spec.md +6 -1
  264. package/src/Table/plugins/activation/useTableActivation.spec.md +254 -0
  265. package/src/Text/Text.test.tsx +57 -0
  266. package/src/Text/Text.tsx +6 -2
  267. package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
  268. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  269. package/src/ToggleButton/ToggleButton.test.tsx +95 -0
  270. package/src/ToggleButton/ToggleButton.tsx +12 -1
  271. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
  272. package/src/Token/Token.tsx +1 -1
  273. package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +86 -0
  274. package/src/Toolbar/Toolbar.test.tsx +0 -8
  275. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
  276. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
  277. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
  278. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
  279. package/src/TopNav/TopNavMegaMenu.test.tsx +47 -0
  280. package/src/TopNav/TopNavMegaMenu.tsx +9 -3
  281. package/src/TopNav/TopNavMenu.test.tsx +46 -1
  282. package/src/TopNav/TopNavMenu.tsx +13 -3
  283. package/src/TreeList/TreeList.doc.mjs +1 -0
  284. package/src/TreeList/TreeList.test.tsx +327 -6
  285. package/src/TreeList/TreeList.tsx +5 -1
  286. package/src/TreeList/TreeListBranches.tsx +6 -0
  287. package/src/TreeList/TreeListItem.tsx +88 -13
  288. package/src/docPropLiterals.test.ts +36 -10
  289. package/src/hooks/useMenuHover.ts +18 -13
  290. package/src/postinstall.test.mjs +79 -0
  291. package/src/reset.a11y.chromium.spec.ts +52 -1
  292. package/src/reset.css +9 -29
  293. package/src/reset.test.ts +11 -0
  294. package/src/theme/derivedVarRegistry.test.ts +7 -0
  295. package/src/unicodePropertyEscapes.source-build.test.mjs +69 -0
  296. package/src/utils/plainDate.test.ts +37 -1
  297. package/src/utils/plainDate.ts +15 -5
@@ -17,6 +17,11 @@
17
17
  * `TopNavMenu` use). Hover is a progressive enhancement over the popover's
18
18
  * click behavior and is inert on coarse pointers.
19
19
  *
20
+ * Nested rows span the full width of the nav list, like top-level rows, so
21
+ * their hover and selected fill lines up with the rows above them. The
22
+ * nesting indent sits inside the row as extra inline-start padding, one step
23
+ * per level, tracked through a private depth context.
24
+ *
20
25
  * SYNC: When modified, update these files to stay in sync:
21
26
  * - /packages/core/src/SideNav/SideNav.doc.mjs
22
27
  * - /packages/core/src/SideNav/SideNav.test.tsx
@@ -26,6 +31,8 @@
26
31
  */
27
32
 
28
33
  import {
34
+ createContext,
35
+ use,
29
36
  useCallback,
30
37
  useId,
31
38
  useMemo,
@@ -108,26 +115,45 @@ const styles = stylex.create({
108
115
  gap: spacingVars['--spacing-1'],
109
116
  pointerEvents: 'auto',
110
117
  },
111
- children: {
112
- paddingInlineStart: spacingVars['--spacing-6'],
113
- },
118
+ // The group clips its rows only for the 0fr collapse animation. Nested rows
119
+ // sit flush with the group, so a permanent clip would cut off their focus
120
+ // ring, which paints outside the row. The expanded group therefore stops
121
+ // clipping once it has finished opening: `overflow` is discrete, so it rides
122
+ // the same transition with `allow-discrete` and a delay equal to the open
123
+ // duration, holding `hidden` until the rows' height has settled. Collapsing
124
+ // flips back to `hidden` at once (no delay on the collapsed state), so
125
+ // nothing spills while the group closes.
126
+ //
127
+ // Reduced fallback: a browser without `transition-behavior: allow-discrete`
128
+ // (the tier below current Baseline) cannot delay the switch, so the group
129
+ // stops clipping as soon as it starts opening and the opening rows briefly
130
+ // overlap the row below. The group still opens and the ring still shows.
114
131
  childrenCollapsible: {
115
132
  display: 'grid',
116
133
  gridTemplateRows: '1fr',
117
- transitionProperty: 'grid-template-rows',
134
+ overflow: 'visible',
135
+ transitionProperty: 'grid-template-rows, overflow',
136
+ transitionBehavior: 'allow-discrete',
118
137
  transitionDuration: {
119
- default: durationVars['--duration-medium'],
120
- '@media (prefers-reduced-motion: reduce)': '0s',
138
+ default: `${durationVars['--duration-medium']}, 0s`,
139
+ '@media (prefers-reduced-motion: reduce)': '0s, 0s',
140
+ },
141
+ transitionDelay: {
142
+ default: `0s, ${durationVars['--duration-medium']}`,
143
+ '@media (prefers-reduced-motion: reduce)': '0s, 0s',
121
144
  },
122
145
  transitionTimingFunction: easeVars['--ease-standard'],
123
146
  },
124
147
  childrenCollapsed: {
125
148
  gridTemplateRows: '0fr',
149
+ overflow: 'hidden',
150
+ transitionDelay: '0s, 0s',
126
151
  },
152
+ // No inline padding: nested rows fill the full width of the nav list, and
153
+ // each row carries its own indent (`indentStyles` below). `minHeight: 0`
154
+ // lets the 0fr track shrink this item to nothing; the group clips it.
127
155
  childrenInner: {
128
- overflow: 'hidden',
129
156
  minHeight: 0,
130
- paddingInlineStart: spacingVars['--spacing-6'],
131
157
  },
132
158
  expandChevron: {
133
159
  display: 'inline-flex',
@@ -232,6 +258,35 @@ const styles = stylex.create({
232
258
  },
233
259
  });
234
260
 
261
+ // Nesting indent, drawn inside the row: one `--spacing-6` step per nesting
262
+ // level. The row itself stays full width, so its hover and selected fill
263
+ // matches a top-level row's.
264
+ // - `sm` / `md` — the ordinary row, where the row element is the link or
265
+ // button: its own inline-start padding plus the steps.
266
+ // SYNC: the base padding matches navItemStyles.sm / .md / .lg.
267
+ // - `primary` — the row-wrapper path: the wrapper keeps its base padding and
268
+ // the steps go on the primary link or button, so the whole indent is part
269
+ // of the click target rather than dead space that still shows feedback.
270
+ const indentStyles = stylex.create({
271
+ sm: (depth: number) => ({
272
+ paddingInlineStart: `calc(${spacingVars['--spacing-1']} + ${depth} * ${spacingVars['--spacing-6']})`,
273
+ }),
274
+ md: (depth: number) => ({
275
+ paddingInlineStart: `calc(${spacingVars['--spacing-2']} + ${depth} * ${spacingVars['--spacing-6']})`,
276
+ }),
277
+ primary: (depth: number) => ({
278
+ paddingInlineStart: `calc(${depth} * ${spacingVars['--spacing-6']})`,
279
+ }),
280
+ });
281
+
282
+ /**
283
+ * How many SideNavItems deep the current item sits: 0 at the top level,
284
+ * 1 inside one parent, and so on. A collapsed rail's flyout resets it to 0,
285
+ * since the flyout lists a parent's children as its own top level.
286
+ */
287
+ const SideNavItemDepthContext = createContext(0);
288
+ SideNavItemDepthContext.displayName = 'SideNavItemDepthContext';
289
+
235
290
  /**
236
291
  * Cascaded to the `actions` slot through `SizeContext` so a consumer's row
237
292
  * controls come out the same height as the built-in expand/collapse toggle,
@@ -363,7 +418,9 @@ export interface SideNavItemProps extends BaseProps<HTMLElement> {
363
418
  */
364
419
  actions?: ReactNode;
365
420
  /**
366
- * Sub-items for nesting.
421
+ * Sub-items for nesting. Nested items span the full width of the nav
422
+ * list, like top-level items; their content is indented one step per
423
+ * nesting level.
367
424
  */
368
425
  children?: ReactNode;
369
426
  /**
@@ -455,6 +512,7 @@ export function SideNavItem({
455
512
  const id = useId();
456
513
  const hasChildren = !!children;
457
514
  const itemRef = useRef<HTMLDivElement>(null);
515
+ const depth = use(SideNavItemDepthContext);
458
516
 
459
517
  // Popover for collapsed items with children
460
518
  const popover = usePopover({
@@ -576,6 +634,8 @@ export function SideNavItem({
576
634
  isSelected && navItemStyles.selected,
577
635
  isDisabled && navItemStyles.disabled,
578
636
  ),
637
+ // Spread after `rest`, so keep the caller's class beside these.
638
+ rest.className,
579
639
  ),
580
640
  ...pressable,
581
641
  };
@@ -608,7 +668,9 @@ export function SideNavItem({
608
668
  onClick={() => popover.hide()}>
609
669
  <div {...stylex.props(styles.popoverHeader)}>{label}</div>
610
670
  <SideNavCollapseContext value={EXPANDED_COLLAPSE_STATE}>
611
- {children}
671
+ <SideNavItemDepthContext value={0}>
672
+ {children}
673
+ </SideNavItemDepthContext>
612
674
  </SideNavCollapseContext>
613
675
  </div>,
614
676
  {placement: 'end', alignment: 'start', xstyle: styles.popoverGap},
@@ -686,6 +748,15 @@ export function SideNavItem({
686
748
  isSelected && navItemStyles.selected,
687
749
  isDisabled && navItemStyles.disabled,
688
750
  ] as const;
751
+ const rowIndent =
752
+ depth > 0 && indentStyles[size === 'sm' ? 'sm' : 'md'](depth);
753
+ const primaryIndent = depth > 0 && indentStyles.primary(depth);
754
+
755
+ // The indent is a runtime value, so it reaches the element as an inline
756
+ // style. These props are spread after `rest`, so fold the caller's `style`
757
+ // back in (caller wins) rather than letting the indent replace it, and the
758
+ // caller's `className` beside the row's own classes.
759
+ const callerStyle = {className: rest.className, style: rest.style};
689
760
 
690
761
  // Three shapes of the same row appearance:
691
762
  // - `rowProps` — split-action path (toggle, no actions): presentational
@@ -702,7 +773,10 @@ export function SideNavItem({
702
773
  const focusableRowProps = {
703
774
  ...mergeProps(
704
775
  itemThemeProps,
705
- focusOutlineProps.focusVisible(...itemStyleArgs),
776
+ mergeProps(
777
+ focusOutlineProps.focusVisible(...itemStyleArgs, rowIndent),
778
+ callerStyle,
779
+ ),
706
780
  ),
707
781
  ...pressable,
708
782
  };
@@ -748,11 +822,17 @@ export function SideNavItem({
748
822
  onClick={handleClick}
749
823
  {...rest}
750
824
  {...rowPrimaryAriaProps}
751
- {...(hasActions
752
- ? // The wrapper rings for this element; suppressing here is what
753
- // keeps the UA's own ring from painting inside that one.
754
- focusOutlineProps.suppressed(styles.splitAction)
755
- : focusOutlineProps.focusVisible(styles.splitAction))}>
825
+ {...mergeProps(
826
+ hasActions
827
+ ? // The wrapper rings for this element; suppressing here is what
828
+ // keeps the UA's own ring from painting inside that one.
829
+ focusOutlineProps.suppressed(styles.splitAction, primaryIndent)
830
+ : focusOutlineProps.focusVisible(
831
+ styles.splitAction,
832
+ primaryIndent,
833
+ ),
834
+ callerStyle,
835
+ )}>
756
836
  {itemContent}
757
837
  </NavItemElement>
758
838
  {hasIndependentToggle && (
@@ -831,7 +911,9 @@ export function SideNavItem({
831
911
  <span id={`${id}-label`} hidden>
832
912
  {label}
833
913
  </span>
834
- {children}
914
+ <SideNavItemDepthContext value={depth + 1}>
915
+ {children}
916
+ </SideNavItemDepthContext>
835
917
  </div>
836
918
  </div>
837
919
  )}
@@ -34,13 +34,20 @@ an additive target for the interactive control surface.
34
34
 
35
35
  ## Compatibility and migration
36
36
 
37
- - Released default preserved: `yes`
37
+ - Released default preserved: `no` — FR5 changes the released `onChangeEnd`
38
+ payload; every other released default is unchanged
38
39
  - Compatibility class: additive target and state reflection; default styling,
39
40
  DOM semantics, and public props remain unchanged, and the one keyboard change
40
41
  AR1 names — the horizontal arrows follow the track's direction under RTL —
41
42
  ships as a behavior fix
43
+ - `onChangeEnd` payload (FR5): a new contract decision (`spec:AST-017/FR33`),
44
+ not a correction. Released victim: an owner that moves `value` only from
45
+ `onChangeEnd`, with no `onChange`, moves by keyboard today and does not under
46
+ FR5, so the change is breaking (`spec:AST-017/FR1`) and ships as
47
+ `[breaking]` in a declared minor (`spec:AST-017/FR31`).
42
48
  - Controlled/uncontrolled behavior: unchanged; Slider remains controlled
43
- - Migration decision: none
49
+ - Migration decision: an owner that moves `value` only from `onChangeEnd`
50
+ moves it from `onChange` instead (FR5)
44
51
 
45
52
  Consumer migration instructions belong in consumer docs and release notes.
46
53
 
@@ -64,18 +71,20 @@ Consumer migration instructions belong in consumer docs and release notes.
64
71
 
65
72
  ## Public concepts
66
73
 
67
- The record adds one public theming target and names the keyboard model (AR1)
68
- without adding or changing a component prop or value domain. Consumer props,
74
+ The record adds one public theming target, names the keyboard model (AR1), and
75
+ states which value `onChangeEnd` carries (FR5) without adding or changing a
76
+ component prop or value domain. Consumer props,
69
77
  modes, states, and usage remain documented in `Slider.doc.mjs`.
70
78
 
71
79
  ## Behavioral and layout contract
72
80
 
73
- | ID | Candidate invariant | Basis | Review state |
74
- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
75
- | FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
76
- | FR2 | `Slider`, `Interactive control`, `Track`, and `Thumb` carry the `slider`, `slider-control`, `slider-track`, and `slider-thumb` targets respectively. | Current source, public docs, and #6224 | Approved additive target contract |
77
- | FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target. | Current source and public docs | Verified current asymmetry; not ratified as policy |
78
- | FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip. | Current source and focused tests | Verified composition boundary |
81
+ | ID | Candidate invariant | Basis | Review state |
82
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
83
+ | FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
84
+ | FR2 | `Slider`, `Interactive control`, `Track`, and `Thumb` carry the `slider`, `slider-control`, `slider-track`, and `slider-thumb` targets respectively. | Current source, public docs, and DEC-1 | Approved additive target contract |
85
+ | FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target. | Current source and public docs | Verified current asymmetry; not ratified as policy |
86
+ | FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip. | Current source and focused tests | Verified composition boundary |
87
+ | FR5 | `onChangeEnd` fires once when an interaction ends — a pointer drag or track press is released or cancelled, or a keyboard step (arrow, Page, Home, End) completes — and carries the value the owner renders after its synchronous response to that interaction's `onChange` calls, including any clamp, snap, or rejection; it never carries a value Slider emitted that the owner did not render. A range reports both values the same way. | Maintainer decision (DEC-3) | Approved |
79
88
 
80
89
  ### Observed current target asymmetry
81
90
 
@@ -106,6 +115,8 @@ either component's remaining target shape.
106
115
 
107
116
  - ORD1: an arrow or Page key result snaps to the nearest step, then clamps to
108
117
  the bounds; Home and End go to `min` and `max` (AR1).
118
+ - ORD2: within one interaction, Slider calls `onChange`, the owner responds and
119
+ renders, then `onChangeEnd` reports the rendered value (FR5).
109
120
  - The anatomy decision (DEC-1) introduces no value, snapping, geometry,
110
121
  interaction, or styling precedence rule of its own.
111
122
 
@@ -214,14 +225,15 @@ separately listed value tooltip retains Tooltip's target.
214
225
 
215
226
  ## Verification map
216
227
 
217
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
218
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
219
- | FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
220
- | AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider, in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value | A thumb loses its role, name or value properties; an arrow, Page, Home or End key stops stepping or steps against the mirrored track; a range thumb crosses the other | `audit:Slider/accessibility` |
221
- | FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
222
- | FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
223
- | FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
224
- | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
228
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
229
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
230
+ | FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
231
+ | AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider, in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value | A thumb loses its role, name or value properties; an arrow, Page, Home or End key stops stepping or steps against the mirrored track; a range thumb crosses the other | `audit:Slider/accessibility` |
232
+ | FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
233
+ | FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
234
+ | FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
235
+ | FR5 | Pending: `Slider.test.tsx` change-end cases driven by an owner that renders, clamps, or rejects the emitted value (not yet in the suite) | Single and range; pointer release before and after the owner re-renders; each keyboard step | `onChangeEnd` reports a value the owner did not render, fires more than once per interaction, or skips a completed keyboard step | `audit:Slider/accessibility` |
236
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
225
237
 
226
238
  The focused Slider suite asserts the `slider-control` target's orientation and
227
239
  disabled state. Source/metadata guards cover all four target declarations. The
@@ -250,6 +262,15 @@ A thumb already plays the slider role as one composite control, so it carries th
250
262
 
251
263
  Rejected: keeping ArrowRight as "increase" under RTL; a Tab stop or arrow model per range thumb that differs from the single thumb.
252
264
 
265
+ ### DEC-3 — `onChangeEnd` reports the value the owner renders
266
+
267
+ **Reference:** `component:Slider/DEC-3`
268
+ **Decider:** `cixzhang`, `2026-10-16`
269
+
270
+ `onChangeEnd` marks the end of an interaction, so it reports what the slider shows when the interaction ends: the controlled value after the owner has answered `onChange`. An owner that clamps, snaps, or rejects a value therefore receives the value it kept, and the commit agrees with the thumb (FR5).
271
+
272
+ Rejected: reporting the last value Slider emitted through `onChange`.
273
+
253
274
  ## Open questions
254
275
 
255
276
  - **OQ1 — After a joint ProgressBar and Slider family audit, should any shared
@@ -6,7 +6,7 @@ export const docs = {
6
6
  name: 'Spinner',
7
7
  displayName: 'Spinner',
8
8
  category: 'Feedback & Status',
9
- keywords: ["spinner","loader","loading","circular","progress","spin","activity","busy","indeterminate"],
9
+ keywords: ["spinner","loader","loading","circular","progress","spin","activity","busy","indeterminate","circularprogress"],
10
10
  props: [
11
11
  {
12
12
  name: 'size',
@@ -31,10 +31,10 @@ system_specs: [spec:AST-002/DEC-1, spec:AST-002/DEC-2, spec:AST-017/DEC-1]
31
31
 
32
32
  | Area | Contract |
33
33
  | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
- | Public contract | Canonicalize the existing alignment mode as `edgeCompensation?: 'inline'`; deprecate `isFullBleed` and remove it in the next minor release. |
34
+ | Public contract | Canonicalize the existing mode as `edge?: 'padded' \| 'bleed'` (default `'padded'`) from the shared `edge` vocabulary; deprecate `isFullBleed` as an alias of `edge="bleed"` and remove it in the next minor release. |
35
35
  | Behavior | Move first and last visible tab content toward the host content line, reaching it when the host inset is at least the edge-tab padding, while preserving tab interaction padding, focus-ring room, overflow controls, selection-indicator geometry, and the optional divider rail. |
36
36
  | End-user impact | Edge tabs lose unnecessary extra inset where host geometry permits and remain fully interactive without clipped focus, selection, overflow, or divider treatment. |
37
- | Builder impact | Existing `isFullBleed` callsites keep working during the deprecation window and migrate mechanically to `edgeCompensation="inline"`. |
37
+ | Builder impact | Existing `isFullBleed` callsites keep working during the deprecation window and migrate mechanically to `edge="bleed"`. |
38
38
  | Compatibility/readiness | Current behavior is shipped; the canonical alias is additive, while removing the released legacy prop is a separately implemented breaking change in the next minor release. |
39
39
  | Review checks | Reject a behavior change disguised as a rename, directional values without overflow/indicator proof, or early removal of the legacy prop. |
40
40
  | Governing rules | `architecture:container-padding/INV9–INV11`; `spec:AST-002/DEC-1–DEC-2`; `spec:AST-017/DEC-1`. |
@@ -43,12 +43,13 @@ This table is a review projection; the body below is authoritative.
43
43
 
44
44
  ## Intent
45
45
 
46
- TabList's current opt-in is edge compensation: it consumes the full inherited
47
- host inset, moves visible edge-tab content toward the surrounding content line,
46
+ TabList's current opt-in is the shared `bleed` edge treatment: it consumes the
47
+ full inherited host inset, moves visible edge-tab content toward the surrounding content line,
48
48
  and retains Tab-owned interaction padding plus TabList-owned divider, overflow,
49
49
  and indicator geometry. Content reaches the host line only when that inset is at
50
50
  least the edge tab's own padding. The public `isFullBleed` name is misleading
51
- because the strip restores uncovered content inset after the outer box expands.
51
+ because the strip keeps a content line: it restores uncovered content inset
52
+ after the outer box expands, which `architecture:container-padding` names `bleed`.
52
53
 
53
54
  ## Compatibility and migration
54
55
 
@@ -61,21 +62,21 @@ because the strip restores uncovered content inset after the outer box expands.
61
62
 
62
63
  During the compatibility window:
63
64
 
64
- - `edgeCompensation="inline"` is the canonical spelling;
65
- - omitted `edgeCompensation` plus `isFullBleed={true}` selects the same mode;
65
+ - `edge="bleed"` is the canonical spelling, and omitted `edge` means `padded`;
66
+ - omitted `edge` plus `isFullBleed={true}` selects `bleed`;
66
67
  - `isFullBleed` is deprecated and maintained examples use the canonical prop;
67
- - if both props are supplied, explicit `edgeCompensation` is canonical and the
68
- legacy boolean cannot disable it.
68
+ - if both props are supplied, explicit `edge` is canonical and the legacy
69
+ boolean cannot override it.
69
70
 
70
71
  The removal release MUST include a `[breaking]` Changeset for
71
72
  `@astryxdesign/core`, consumer release notes, and an `astryx upgrade` codemod that rewrites
72
- `isFullBleed` to `edgeCompensation="inline"`.
73
+ `isFullBleed` to `edge="bleed"`.
73
74
 
74
75
  ## Ownership boundary
75
76
 
76
77
  **Owns**
77
78
 
78
- - The TabList-wide edge-compensation mode and its compatibility alias.
79
+ - The TabList-wide `bleed` edge treatment and its compatibility alias.
79
80
  - The relationship between the outer TabList box, inner strip padding, edge tabs,
80
81
  focus-ring room, overflow controls, and selection indicator.
81
82
  - Which logical edges are supported by the public mode.
@@ -84,33 +85,34 @@ The removal release MUST include a `[breaking]` Changeset for
84
85
 
85
86
  - Host padding publication, nesting, or overlay resets — owned by
86
87
  `architecture:container-padding`.
87
- - Button or List edge-compensation projections.
88
- - New `start` or `end` values without separate proof and owner approval.
88
+ - Button or List edge projections.
89
+ - The `flush` or `outset` values, or per-edge application, without separate
90
+ proof and owner approval.
89
91
  - A change to navigation/tab semantics, selection state, keyboard behavior, or
90
92
  overflow ownership.
91
93
 
92
94
  ## Public concepts
93
95
 
94
- | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
95
- | ----------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- | ------------------- | ------------------------------- | ------------------------------------------------------ |
96
- | Edge compensation | omitted, `inline` | Consume both inherited inline host insets and move edge-tab content toward the host lines while retaining local interaction and strip-owned geometry | Navigation and tabs patterns across current layout/overflow states | omitted | `component:TabList` | accepted; canonical API pending | Other values are rejected by the public type |
97
- | Legacy alias | omitted, `false`, `true` | Compatibility spelling for omitted or inline edge compensation | Until the next minor release | omitted/`false` | `component:TabList` | deprecated | `true` maps to `inline`; explicit canonical input wins |
96
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
97
+ | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------ | ------------------- | ------------------------------- | -------------------------------------------- |
98
+ | Edge treatment | omitted, `padded`, `bleed` | `bleed`: consume both inherited inline host insets and move edge-tab content toward the host lines while retaining local interaction and strip-owned geometry; `padded`: current default geometry | Navigation and tabs patterns across current layout/overflow states | omitted (`padded`) | `component:TabList` | accepted; canonical API pending | Other values are rejected by the public type |
99
+ | Legacy alias | omitted, `false`, `true` | Compatibility spelling for `padded` or `bleed` | Until the next minor release | omitted/`false` | `component:TabList` | deprecated | `true` maps to `bleed`; explicit `edge` wins |
98
100
 
99
101
  ## Behavioral and layout contract
100
102
 
101
- | ID | Invariant | Basis | Status |
102
- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------- |
103
- | FR1 | The current opt-in MUST remain edge compensation: the outer TabList box consumes the full inherited inline host inset and any `hasDivider` rail spans that expanded width; the inner strip restores `max(host inset - edge-tab padding, 0)` at each logical edge. Visible edge-tab content reaches the host line only when the host inset is at least the tab padding; otherwise it moves toward that line without crossing the host boundary. | Current source and `component:TabList/DEC-1` | Shipped behavior |
104
- | FR2 | Compensation MUST preserve Tab interaction padding, focus-ring room, overflow detection and controls, scroll access, selection-indicator geometry, and the optional divider rail's expanded inline span. | Current source plus owner direction | Shipped behavior; expanded evidence required with rename |
105
- | FR3 | `edgeCompensation?: 'inline'` MUST become the canonical API without changing FR1–FR2 geometry or the omitted default. | `architecture:container-padding/INV10`; `component:TabList/DEC-1` | Accepted contract; implementation pending |
106
- | FR4 | During the deprecation window, omitted `edgeCompensation` plus `isFullBleed={true}` MUST produce the canonical `inline` behavior. Explicit `edgeCompensation` MUST remain authoritative when both props are supplied. | Compatibility decision | Accepted contract; implementation pending |
107
- | FR5 | `isFullBleed` MUST be removed in the next minor release only after maintained docs/examples use `edgeCompensation`, both paths have compatibility evidence, and the removal includes a `[breaking]` Changeset plus an `astryx upgrade` codemod for mechanical consumer migration. | `component:TabList/DEC-1`; `spec:AST-017/DEC-1` | Accepted contract; implementation pending |
108
- | FR6 | `start` and `end` MUST remain rejected until per-edge overflow, clipping, scroll-control, focus-ring, indicator, direction, and theme-padding evidence receives owner approval. | Shared logical vocabulary and TabList boundary | Accepted boundary |
103
+ | ID | Invariant | Basis | Status |
104
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------- |
105
+ | FR1 | The current opt-in MUST remain the `bleed` treatment: the outer TabList box consumes the full inherited inline host inset and any `hasDivider` rail spans that expanded width; the inner strip restores `max(host inset - edge-tab padding, 0)` at each logical edge. Visible edge-tab content reaches the host line only when the host inset is at least the tab padding; otherwise it moves toward that line without crossing the host boundary. | Current source and `component:TabList/DEC-1` | Shipped behavior |
106
+ | FR2 | Compensation MUST preserve Tab interaction padding, focus-ring room, overflow detection and controls, scroll access, selection-indicator geometry, and the optional divider rail's expanded inline span. | Current source plus owner direction | Shipped behavior; expanded evidence required with rename |
107
+ | FR3 | `edge?: 'padded' \| 'bleed'` MUST become the canonical API, with `bleed` selecting FR1–FR2 geometry and `padded` the omitted default, without changing either. | `architecture:container-padding/INV10`; `component:TabList/DEC-1` | Accepted contract; implementation pending |
108
+ | FR4 | During the deprecation window, omitted `edge` plus `isFullBleed={true}` MUST produce the canonical `bleed` behavior. Explicit `edge` MUST remain authoritative when both props are supplied. | Compatibility decision | Accepted contract; implementation pending |
109
+ | FR5 | `isFullBleed` MUST be removed in the next minor release only after maintained docs/examples use `edge`, both paths have compatibility evidence, and the removal includes a `[breaking]` Changeset plus an `astryx upgrade` codemod for mechanical consumer migration. | `component:TabList/DEC-1`; `spec:AST-017/DEC-1` | Accepted contract; implementation pending |
110
+ | FR6 | `flush`, `outset`, and per-edge application MUST remain rejected until overflow, clipping, scroll-control, focus-ring, indicator, direction, and theme-padding evidence receives owner approval. | Shared edge vocabulary and TabList boundary | Accepted boundary |
109
111
 
110
112
  ### Allowed variation
111
113
 
112
114
  - Layout, size, role, selected value, labels, tab content, and overflow mode may
113
- vary while the same edge-compensation meaning is preserved.
115
+ vary while the same `bleed` meaning is preserved.
114
116
  - The implementation may retain the current outer-box/inner-strip technique or
115
117
  replace it with equivalent geometry that satisfies FR1–FR2.
116
118
  - A host may be painted or bounded only where TabList's component-specific
@@ -118,20 +120,20 @@ The removal release MUST include a `[breaking]` Changeset for
118
120
 
119
121
  ### Representative states
120
122
 
121
- | State | Required invariant | Allowed variation |
122
- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
123
- | Default/omitted | Current TabList box, strip, and tab geometry are unchanged. | Any current layout, role, or overflow mode. |
124
- | Canonical inline mode | Outer box consumes each host inset; edge content reaches the host line only when that inset is at least the tab padding. | Host inset smaller than, equal to, or larger than edge-tab padding; asymmetric insets; LTR/RTL. |
125
- | Divider | `hasDivider` rail spans the expanded outer inline width. | Fitting or overflowing strips. |
126
- | Legacy alias | `isFullBleed={true}` matches canonical inline geometry during the compatibility window. | The canonical prop may also be present. |
127
- | Overflowing strip | Scroll access, edge fades, controls, and selected-tab reveal remain correct. | Start/end overflow independently. |
128
- | Focused edge tab | The focus ring remains visible and local to the tab. | Keyboard or pointer focus. |
123
+ | State | Required invariant | Allowed variation |
124
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
125
+ | Default/omitted | Current TabList box, strip, and tab geometry are unchanged. | Any current layout, role, or overflow mode. |
126
+ | Canonical `bleed` | Outer box consumes each host inset; edge content reaches the host line only when that inset is at least the tab padding. | Host inset smaller than, equal to, or larger than edge-tab padding; asymmetric insets; LTR/RTL. |
127
+ | Divider | `hasDivider` rail spans the expanded outer inline width. | Fitting or overflowing strips. |
128
+ | Legacy alias | `isFullBleed={true}` matches canonical `bleed` geometry during the compatibility window. | The canonical prop may also be present. |
129
+ | Overflowing strip | Scroll access, edge fades, controls, and selected-tab reveal remain correct. | Start/end overflow independently. |
130
+ | Focused edge tab | The focus ring remains visible and local to the tab. | Keyboard or pointer focus. |
129
131
 
130
132
  ### Transformation and precedence order
131
133
 
132
- - **ORD1 — Public input.** Resolve explicit `edgeCompensation`; otherwise map
133
- `isFullBleed={true}` to `inline` during the compatibility window; otherwise use
134
- no compensation.
134
+ - **ORD1 — Public input.** Resolve explicit `edge`; otherwise map
135
+ `isFullBleed={true}` to `bleed` during the compatibility window; otherwise use
136
+ `padded`.
135
137
  - **ORD2 — Host geometry.** Read the matching inherited logical host insets, with
136
138
  missing values falling back to zero.
137
139
  - **ORD3 — Component geometry.** Resolve edge-tab padding and focus/indicator
@@ -145,10 +147,10 @@ The removal release MUST include a `[breaking]` Changeset for
145
147
 
146
148
  ## Accessibility contract
147
149
 
148
- - **AR1 — Interaction remains stable.** Edge compensation MUST NOT change the
150
+ - **AR1 — Interaction remains stable.** The `bleed` treatment MUST NOT change the
149
151
  tab/nav pattern, accessible names, selected state, keyboard movement, tab stop,
150
152
  focus target, or controlled value behavior.
151
- - **AR2 — Overflow remains reachable.** Compensated edge geometry MUST NOT clip or
153
+ - **AR2 — Overflow remains reachable.** `bleed` geometry MUST NOT clip or
152
154
  hide a focus ring, selected indicator, visible tab content, or an overflow
153
155
  control required to reach offscreen tabs.
154
156
 
@@ -162,8 +164,8 @@ The removal release MUST include a `[breaking]` Changeset for
162
164
 
163
165
  ## Family and system relationships
164
166
 
165
- - `architecture:container-padding` owns host publication, the shared
166
- `edgeCompensation` vocabulary, logical-edge semantics, and overlay reset.
167
+ - `architecture:container-padding` owns host publication, the shared `edge`
168
+ vocabulary, logical-edge semantics, and overlay reset.
167
169
  - `architecture:public-component-api` and `spec:AST-002` own public API
168
170
  admission; `spec:AST-017/DEC-1` owns the breaking classification and migration
169
171
  evidence required when the released alias is removed.
@@ -189,9 +191,20 @@ TabList's current mode consumes the full inherited inline host inset, keeps an
189
191
  optional divider across that expanded width, and restores only the portion of
190
192
  content inset not already supplied by edge-tab padding. The user-facing intent is
191
193
  therefore edge compensation, not a promise that tab content itself reaches an
192
- outer edge. `edgeCompensation="inline"` becomes the canonical spelling;
193
- `isFullBleed` remains a compatibility alias until its `[breaking]` next-minor
194
- removal, with the required codemod and release evidence.
194
+ outer edge. DEC-2 sets the canonical spelling; `isFullBleed` remains a
195
+ compatibility alias until its `[breaking]` next-minor removal, with the required
196
+ codemod and release evidence.
197
+
198
+ ### DEC-2 — TabList spells its mode with the shared edge vocabulary
199
+
200
+ **Reference:** `component:TabList/DEC-2`
201
+
202
+ **Decider:** cixzhang, 2026-10-16
203
+
204
+ `architecture:container-padding` names TabList's geometry `bleed`: paint reaches
205
+ the host's padding edge and content stays on the host content line, or at the
206
+ edge tab's own padding when the host inset is smaller. `edge="bleed"` is the
207
+ canonical spelling, so TabList and List share one prop name and one value set.
195
208
 
196
209
  ## Open questions
197
210
 
@@ -24,7 +24,12 @@ verified_by:
24
24
  packages/core/src/theme/themingTargets.test.ts,
25
25
  scripts/check-knowledge.mjs,
26
26
  ]
27
- modules: [module:Table/useTableRowStatus, module:Table/useTableColumnResize]
27
+ modules:
28
+ [
29
+ module:Table/useTableActivation,
30
+ module:Table/useTableRowStatus,
31
+ module:Table/useTableColumnResize,
32
+ ]
28
33
  families: []
29
34
  design_specs: []
30
35
  architecture: