@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1

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 (348) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Button/Button.d.ts +1 -1
  3. package/dist/Button/Button.d.ts.map +1 -1
  4. package/dist/Button/Button.js +5 -2
  5. package/dist/Calendar/Calendar.d.ts.map +1 -1
  6. package/dist/Calendar/Calendar.js +191 -32
  7. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +5 -0
  10. package/dist/Chat/ChatMessage.d.ts +11 -0
  11. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  12. package/dist/Chat/ChatMessage.js +18 -9
  13. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  14. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  15. package/dist/Chat/useTriggerMenu.js +222 -50
  16. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  17. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  18. package/dist/CodeBlock/CodeBlock.js +158 -38
  19. package/dist/CodeBlock/tokenizer.d.ts +39 -0
  20. package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
  21. package/dist/CodeBlock/tokenizer.js +108 -0
  22. package/dist/CommandPalette/CommandPalette.d.ts +9 -5
  23. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  24. package/dist/CommandPalette/CommandPalette.js +38 -20
  25. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  26. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  28. package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
  29. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  30. package/dist/CommandPalette/CommandPaletteInput.js +35 -18
  31. package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
  32. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  33. package/dist/CommandPalette/CommandPaletteItem.js +8 -9
  34. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  35. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  36. package/dist/CommandPalette/CommandPaletteList.js +10 -5
  37. package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
  38. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  39. package/dist/ComplexSelector/ComplexSelector.js +12 -3
  40. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  41. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  42. package/dist/ContextMenu/ContextMenu.js +62 -48
  43. package/dist/DateInput/DateInput.js +1 -1
  44. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  45. package/dist/DateInput/MonthScroller.js +12 -4
  46. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  47. package/dist/DateRangeInput/DateRangeInput.js +12 -2
  48. package/dist/Dialog/Dialog.d.ts +1 -1
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +16 -5
  51. package/dist/Dialog/DialogHeader.d.ts +1 -1
  52. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  53. package/dist/Dialog/DialogHeader.js +7 -7
  54. package/dist/Divider/Divider.d.ts +1 -0
  55. package/dist/Divider/Divider.d.ts.map +1 -1
  56. package/dist/Divider/Divider.js +5 -4
  57. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenu.js +161 -65
  59. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  60. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  61. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  62. package/dist/Field/InputClearButton.d.ts +8 -4
  63. package/dist/Field/InputClearButton.d.ts.map +1 -1
  64. package/dist/Field/InputClearButton.js +23 -7
  65. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  66. package/dist/FormLayout/FormLayout.js +1 -0
  67. package/dist/Grid/Grid.d.ts +13 -4
  68. package/dist/Grid/Grid.d.ts.map +1 -1
  69. package/dist/Grid/Grid.js +7 -1
  70. package/dist/HoverCard/useHoverCard.d.ts +1 -1
  71. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  72. package/dist/HoverCard/useHoverCard.js +67 -49
  73. package/dist/InputGroup/InputGroup.d.ts +2 -4
  74. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  75. package/dist/InputGroup/InputGroup.js +2 -4
  76. package/dist/InputGroup/groupStyles.d.ts +10 -2
  77. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  78. package/dist/InputGroup/groupStyles.js +19 -2
  79. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  80. package/dist/Layer/useLayerDismissal.js +9 -9
  81. package/dist/Markdown/Markdown.d.ts.map +1 -1
  82. package/dist/Markdown/Markdown.js +25 -4
  83. package/dist/MobileNav/MobileNav.d.ts +7 -0
  84. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  85. package/dist/MobileNav/MobileNav.js +45 -2
  86. package/dist/MultiSelector/MultiSelector.d.ts +7 -4
  87. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  88. package/dist/MultiSelector/MultiSelector.js +24 -3
  89. package/dist/NumberInput/NumberInput.js +1 -1
  90. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  91. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  92. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  93. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  94. package/dist/Resizable/ResizeHandle.js +83 -15
  95. package/dist/Resizable/useResizable.d.ts +23 -0
  96. package/dist/Resizable/useResizable.d.ts.map +1 -1
  97. package/dist/Resizable/useResizable.js +78 -0
  98. package/dist/Selector/Selector.d.ts +6 -3
  99. package/dist/Selector/Selector.d.ts.map +1 -1
  100. package/dist/Selector/Selector.js +28 -6
  101. package/dist/SideNav/SideNav.d.ts.map +1 -1
  102. package/dist/SideNav/SideNav.js +10 -2
  103. package/dist/Slider/Slider.d.ts.map +1 -1
  104. package/dist/Slider/Slider.js +18 -7
  105. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  106. package/dist/Tokenizer/Tokenizer.js +16 -4
  107. package/dist/Toolbar/Toolbar.d.ts +2 -1
  108. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  109. package/dist/Toolbar/Toolbar.js +3 -1
  110. package/dist/TreeList/TreeList.d.ts.map +1 -1
  111. package/dist/TreeList/TreeList.js +6 -3
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +7 -1
  114. package/dist/astryx.css +22 -0
  115. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  116. package/dist/hooks/useScrollOverflow.js +55 -12
  117. package/dist/hooks/useTreeFocus.d.ts +21 -0
  118. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  119. package/dist/hooks/useTreeFocus.js +40 -2
  120. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  121. package/dist/i18n/generated-locales/en.generated.js +3 -0
  122. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  123. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  124. package/dist/reset.a11y.chromium.spec.js +6 -1
  125. package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
  126. package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
  127. package/dist/reset.until-found.a11y.browser.spec.js +71 -0
  128. package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
  129. package/dist/theme/syntax/SyntaxTheme.js +43 -2
  130. package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
  131. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  132. package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
  133. package/dist/theme/syntax/index.d.ts +1 -1
  134. package/dist/theme/syntax/index.d.ts.map +1 -1
  135. package/dist/theme/syntax/tokens.d.ts +12 -2
  136. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  137. package/dist/theme/syntax/tokens.js +25 -4
  138. package/dist/utils/plainDate.d.ts +12 -0
  139. package/dist/utils/plainDate.d.ts.map +1 -1
  140. package/dist/utils/plainDate.js +63 -10
  141. package/locales/en.json +13 -1
  142. package/locales/pseudo.json +9 -0
  143. package/package.json +4 -4
  144. package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
  145. package/src/AlertDialog/AlertDialog.spec.md +205 -0
  146. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  147. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  148. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  149. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  150. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  151. package/src/Button/Button.test.tsx +37 -0
  152. package/src/Button/Button.tsx +5 -1
  153. package/src/Calendar/Calendar.doc.mjs +234 -60
  154. package/src/Calendar/Calendar.spec.md +209 -0
  155. package/src/Calendar/Calendar.test.tsx +559 -20
  156. package/src/Calendar/Calendar.tsx +258 -41
  157. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
  158. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
  159. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
  160. package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
  161. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
  162. package/src/Carousel/Carousel.test.tsx +54 -71
  163. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  164. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  165. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  166. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  167. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  168. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  169. package/src/Chat/ChatComposerInput.spec.md +94 -55
  170. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  171. package/src/Chat/ChatComposerInput.tsx +13 -0
  172. package/src/Chat/ChatMessage.doc.mjs +31 -16
  173. package/src/Chat/ChatMessage.spec.md +265 -0
  174. package/src/Chat/ChatMessage.test.tsx +246 -0
  175. package/src/Chat/ChatMessage.tsx +30 -7
  176. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  177. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  178. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  179. package/src/Chat/useTriggerMenu.tsx +278 -46
  180. package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
  181. package/src/CodeBlock/CodeBlock.spec.md +40 -41
  182. package/src/CodeBlock/CodeBlock.test.tsx +89 -9
  183. package/src/CodeBlock/CodeBlock.tsx +205 -25
  184. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  185. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
  186. package/src/CodeBlock/tokenizer.test.ts +112 -0
  187. package/src/CodeBlock/tokenizer.ts +106 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
  189. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
  190. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
  191. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
  192. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
  193. package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
  194. package/src/CommandPalette/CommandPalette.spec.md +100 -49
  195. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  196. package/src/CommandPalette/CommandPalette.tsx +47 -21
  197. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  198. package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
  199. package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
  200. package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
  201. package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
  202. package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
  203. package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
  204. package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
  205. package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
  206. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  207. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  208. package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
  209. package/src/CommandPalette/CommandPaletteList.tsx +10 -5
  210. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  211. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
  212. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  213. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
  214. package/src/ComplexSelector/ComplexSelector.tsx +37 -8
  215. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  216. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  217. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  218. package/src/ContextMenu/ContextMenu.tsx +78 -48
  219. package/src/DateInput/DateInput.doc.mjs +52 -0
  220. package/src/DateInput/DateInput.spec.md +27 -15
  221. package/src/DateInput/DateInput.test.tsx +38 -0
  222. package/src/DateInput/DateInput.tsx +1 -1
  223. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  224. package/src/DateInput/MonthScroller.tsx +12 -4
  225. package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
  226. package/src/DateRangeInput/DateRangeInput.tsx +20 -2
  227. package/src/Dialog/Dialog.test.tsx +27 -1
  228. package/src/Dialog/Dialog.tsx +16 -5
  229. package/src/Dialog/DialogHeader.test.tsx +27 -2
  230. package/src/Dialog/DialogHeader.tsx +7 -8
  231. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  232. package/src/Divider/Divider.doc.mjs +5 -3
  233. package/src/Divider/Divider.spec.md +140 -54
  234. package/src/Divider/Divider.test.tsx +15 -0
  235. package/src/Divider/Divider.tsx +6 -4
  236. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  237. package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
  238. package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
  239. package/src/DropdownMenu/DropdownMenu.tsx +212 -81
  240. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  241. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  242. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  243. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  244. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  245. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  246. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  247. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  248. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
  249. package/src/Field/InputClearButton.test.tsx +29 -4
  250. package/src/Field/InputClearButton.tsx +26 -8
  251. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  252. package/src/FormLayout/FormLayout.tsx +1 -0
  253. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
  254. package/src/Grid/Grid.doc.mjs +14 -11
  255. package/src/Grid/Grid.spec.md +50 -32
  256. package/src/Grid/Grid.tsx +13 -4
  257. package/src/HoverCard/HoverCard.test.tsx +46 -1
  258. package/src/HoverCard/useHoverCard.tsx +68 -48
  259. package/src/InputGroup/InputGroup.doc.mjs +11 -1
  260. package/src/InputGroup/InputGroup.tsx +2 -4
  261. package/src/InputGroup/groupStyles.ts +40 -2
  262. package/src/Layer/useLayerDismissal.ts +9 -9
  263. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
  264. package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
  265. package/src/Link/__tests__/Link.a11y.states.ts +104 -0
  266. package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
  267. package/src/Markdown/Markdown.doc.mjs +1 -1
  268. package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
  269. package/src/Markdown/Markdown.spec.md +45 -23
  270. package/src/Markdown/Markdown.test.tsx +38 -0
  271. package/src/Markdown/Markdown.tsx +24 -2
  272. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  273. package/src/MobileNav/MobileNav.tsx +50 -2
  274. package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
  275. package/src/MoreMenu/MoreMenu.spec.md +18 -7
  276. package/src/MoreMenu/MoreMenu.test.tsx +38 -0
  277. package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
  278. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  279. package/src/MultiSelector/MultiSelector.test.tsx +188 -21
  280. package/src/MultiSelector/MultiSelector.tsx +50 -11
  281. package/src/NumberInput/NumberInput.test.tsx +24 -0
  282. package/src/NumberInput/NumberInput.tsx +1 -1
  283. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  284. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  285. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  286. package/src/Resizable/Resizable.spec.md +41 -9
  287. package/src/Resizable/ResizeHandle.test.tsx +565 -2
  288. package/src/Resizable/ResizeHandle.tsx +126 -21
  289. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
  290. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
  291. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
  292. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
  293. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
  294. package/src/Resizable/useResizable.ts +95 -0
  295. package/src/Selector/Selector.doc.mjs +7 -6
  296. package/src/Selector/Selector.spec.md +37 -24
  297. package/src/Selector/Selector.test.tsx +176 -10
  298. package/src/Selector/Selector.tsx +59 -14
  299. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
  300. package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
  301. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
  302. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
  303. package/src/SideNav/SideNav.test.tsx +35 -0
  304. package/src/SideNav/SideNav.tsx +16 -2
  305. package/src/Slider/Slider.spec.md +43 -17
  306. package/src/Slider/Slider.test.tsx +134 -0
  307. package/src/Slider/Slider.tsx +29 -6
  308. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  309. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  310. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  311. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  312. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  313. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  314. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  315. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  316. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  317. package/src/Tokenizer/Tokenizer.test.tsx +87 -0
  318. package/src/Tokenizer/Tokenizer.tsx +31 -3
  319. package/src/Toolbar/Toolbar.test.tsx +131 -4
  320. package/src/Toolbar/Toolbar.tsx +3 -1
  321. package/src/TreeList/TreeList.spec.md +33 -8
  322. package/src/TreeList/TreeList.test.tsx +72 -0
  323. package/src/TreeList/TreeList.tsx +10 -6
  324. package/src/TreeList/TreeListItem.tsx +5 -0
  325. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  326. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  327. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  328. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  329. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  330. package/src/hooks/useScrollOverflow.test.tsx +222 -0
  331. package/src/hooks/useScrollOverflow.ts +55 -13
  332. package/src/hooks/useTreeFocus.test.tsx +37 -0
  333. package/src/hooks/useTreeFocus.ts +55 -2
  334. package/src/i18n/generated-locales/en.generated.ts +3 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
  336. package/src/reset.a11y.chromium.spec.ts +9 -1
  337. package/src/reset.css +8 -3
  338. package/src/reset.until-found.a11y.browser.spec.ts +75 -0
  339. package/src/reset.until-found.test.ts +32 -0
  340. package/src/stylexValidation.source-build.test.mjs +42 -0
  341. package/src/theme/syntax/SyntaxTheme.tsx +46 -5
  342. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
  343. package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
  344. package/src/theme/syntax/index.ts +5 -1
  345. package/src/theme/syntax/tokens.ts +32 -2
  346. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
  347. package/src/utils/plainDate.test.ts +147 -0
  348. package/src/utils/plainDate.ts +75 -10
@@ -9,7 +9,7 @@
9
9
  * SYNC: When Dialog.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {useState} from 'react';
12
+ import {useState, type SyntheticEvent} from 'react';
13
13
  import {readFileSync} from 'node:fs';
14
14
  import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
15
15
  import type {MockInstance} from 'vitest';
@@ -1030,6 +1030,32 @@ describe('Dialog', () => {
1030
1030
  expect(screen.getByTestId('custom-dialog')).toBeInTheDocument();
1031
1031
  });
1032
1032
 
1033
+ it('runs backdrop dismissal before a consumer onClick', () => {
1034
+ const callOrder: string[] = [];
1035
+ const onClick = vi.fn((event: SyntheticEvent) => {
1036
+ event.preventDefault();
1037
+ callOrder.push('consumer');
1038
+ });
1039
+ const onOpenChange = vi.fn(() => {
1040
+ callOrder.push('dialog');
1041
+ });
1042
+ render(
1043
+ <Dialog
1044
+ isOpen={true}
1045
+ onOpenChange={onOpenChange}
1046
+ onClick={onClick}
1047
+ data-testid="click-dialog">
1048
+ Content
1049
+ </Dialog>,
1050
+ );
1051
+
1052
+ fireEvent.click(screen.getByTestId('click-dialog'));
1053
+
1054
+ expect(onClick).toHaveBeenCalledTimes(1);
1055
+ expect(onOpenChange).toHaveBeenCalledWith(false);
1056
+ expect(callOrder).toEqual(['dialog', 'consumer']);
1057
+ });
1058
+
1033
1059
  it('does not forward native open prop to dialog element', () => {
1034
1060
  render(
1035
1061
  <Dialog
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @file Dialog.tsx
7
7
  * @input Uses React, DialogHTMLAttributes, ReactNode, container (Layout), DialogContext, layerTextReset,
8
- * layerStructureReset, modalOutlet, interactionModality
8
+ * layerStructureReset, modalOutlet, interactionModality, chainEventHandlers
9
9
  * @output Exports Dialog component, DialogProps, DialogVariant, DialogPurpose types
10
10
  * @position Core implementation; consumed by index.ts, tested by Dialog.test.tsx
11
11
  *
@@ -65,6 +65,7 @@ import {
65
65
  } from '../Layout/padding.stylex';
66
66
  import type {SpacingStep} from '../utils/types';
67
67
  import {mergeProps} from '../utils';
68
+ import {chainEventHandlers} from '../utils/chainEventHandlers';
68
69
  import {devWarn} from '../utils/devWarning';
69
70
  import {DialogContext} from './DialogContext';
70
71
  import {themeProps} from '../utils/themeProps';
@@ -679,9 +680,19 @@ export function Dialog({
679
680
  </div>
680
681
  );
681
682
 
682
- // Filter out native open to prevent InvalidStateError when accidentally passed
683
+ // Filter out native open to prevent InvalidStateError when accidentally passed.
684
+ // Keep the consumer click handler separate so the modal path can compose it
685
+ // with Dialog-owned behavior without changing the inline preview path.
683
686
  const hasPosition = position != null && !isFullscreen;
684
- const {open: _open, ...safeProps} = props as Record<string, unknown>;
687
+ const {onClick: onClickProp, ...propsWithoutClick} = props;
688
+ const {open: _open, ...safeProps} = propsWithoutClick as Record<
689
+ string,
690
+ unknown
691
+ >;
692
+ const inlineSafeProps: Record<string, unknown> = {
693
+ ...safeProps,
694
+ onClick: onClickProp,
695
+ };
685
696
 
686
697
  // --- Inline rendering path (for documentation previews) ---
687
698
  if (isInline) {
@@ -691,7 +702,7 @@ export function Dialog({
691
702
 
692
703
  return (
693
704
  <div
694
- {...safeProps}
705
+ {...inlineSafeProps}
695
706
  {...mergeProps(
696
707
  themeProps('dialog', {variant}),
697
708
  stylex.props(
@@ -760,7 +771,7 @@ export function Dialog({
760
771
  className,
761
772
  style,
762
773
  )}
763
- onClick={handleClick}
774
+ onClick={chainEventHandlers(handleClick, onClickProp)}
764
775
  onCancel={handleCancel}
765
776
  aria-modal="true"
766
777
  // The default aria-labelledby (from a DialogHeader title) is set
@@ -260,6 +260,19 @@ describe('DialogHeader', () => {
260
260
  expect(closeIcon).toHaveClass('astryx-icon');
261
261
  });
262
262
 
263
+ it('renders numeric zero in both content slots', () => {
264
+ const {container} = render(
265
+ <DialogHeader title="Title" startContent={0} endContent={0} />,
266
+ );
267
+
268
+ expect(
269
+ container.querySelector('.astryx-dialog-header-start-content'),
270
+ ).toHaveTextContent(/^0$/);
271
+ expect(
272
+ container.querySelector('.astryx-dialog-header-end-content'),
273
+ ).toHaveTextContent(/^0$/);
274
+ });
275
+
263
276
  it('exposes the start-content target when startContent renders', () => {
264
277
  const {container} = render(
265
278
  <DialogHeader
@@ -292,8 +305,20 @@ describe('DialogHeader', () => {
292
305
  ).toHaveLength(1);
293
306
  });
294
307
 
295
- it('omits the content-slot targets when their content does not render', () => {
296
- const {container} = render(<DialogHeader title="Title" />);
308
+ it.each([
309
+ ['empty string', ''],
310
+ ['false', false],
311
+ ['true', true],
312
+ ['null', null],
313
+ ['undefined', undefined],
314
+ ])('omits both content-slot targets for %s', (_, content) => {
315
+ const {container} = render(
316
+ <DialogHeader
317
+ title="Title"
318
+ startContent={content}
319
+ endContent={content}
320
+ />,
321
+ );
297
322
 
298
323
  expect(
299
324
  container.querySelector('.astryx-dialog-header-start-content'),
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DialogHeader.tsx
7
- * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, DialogContext, mergeProps, themeProps
7
+ * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, DialogContext, isRenderable, mergeProps, themeProps
8
8
  * @output Exports DialogHeader component and DialogHeaderProps
9
9
  * @position Dialog header component; used with Dialog and Layout
10
10
  *
@@ -25,7 +25,7 @@ import {Icon} from '../Icon';
25
25
  import {Heading} from '../Heading/Heading';
26
26
  import {Text} from '../Text/Text';
27
27
  import type {BaseProps} from '../BaseProps';
28
- import {mergeProps} from '../utils';
28
+ import {isRenderable, mergeProps} from '../utils';
29
29
  import {themeProps} from '../utils/themeProps';
30
30
  import {useDialogContext} from './DialogContext';
31
31
  import {useTranslator} from '../i18n';
@@ -164,10 +164,9 @@ export function DialogHeader({
164
164
  const dialogContext = useDialogContext();
165
165
  const shouldAutoFocus = dialogContext?.isInline !== true;
166
166
  const titleId = dialogContext?.titleId;
167
- // A node subtitle may be `0`: render it inside Text instead of letting a
168
- // truthiness check leak a bare text node, and skip only empty values.
169
- const hasSubtitle =
170
- subtitle != null && typeof subtitle !== 'boolean' && subtitle !== '';
167
+ const hasSubtitle = isRenderable(subtitle);
168
+ const hasStartContent = isRenderable(startContent);
169
+ const hasEndContent = isRenderable(endContent);
171
170
  const shouldCompensateEndBlock =
172
171
  endContentEdgeCompensation == null
173
172
  ? onOpenChange != null
@@ -203,7 +202,7 @@ export function DialogHeader({
203
202
  themeProps('dialog-header'),
204
203
  stylex.props(styles.container),
205
204
  )}>
206
- {startContent && (
205
+ {hasStartContent && (
207
206
  <div
208
207
  {...mergeProps(
209
208
  themeProps('dialog-header-start-content'),
@@ -231,7 +230,7 @@ export function DialogHeader({
231
230
  </Text>
232
231
  )}
233
232
  </div>
234
- {(endContent || onOpenChange) && (
233
+ {(hasEndContent || onOpenChange != null) && (
235
234
  <div
236
235
  {...mergeProps(
237
236
  themeProps('dialog-header-end-content'),
@@ -180,25 +180,17 @@ async function runState(
180
180
  });
181
181
  }
182
182
 
183
- test('every expectation is exercised and every known failure matches once', async ({
184
- page,
185
- }) => {
186
- test.setTimeout(5 * 60 * 1000);
187
- const cdp = await page.context().newCDPSession(page);
188
- const results: BindingResult[] = [];
189
- for (const state of DIALOG_MODAL_BINDING_STATES) {
190
- results.push(await runState(page, cdp, state));
191
- }
192
- expect(neverExercised(results)).toEqual([]);
193
- expect(unmatchedKnownFailures(DIALOG_MODAL_KNOWN_FAILURES, results)).toEqual(
194
- [],
195
- );
196
- });
183
+ // Each state's result, recorded by its own test below so the whole-suite check
184
+ // at the end reads it instead of measuring every state a second time. A state
185
+ // whose test did not run in this worker (a filtered run, or a worker restarted
186
+ // after a failure) is measured by the whole-suite check itself.
187
+ const recorded = new Map<string, BindingResult>();
197
188
 
198
189
  for (const state of DIALOG_MODAL_BINDING_STATES) {
199
190
  test(`Dialog [${state.id}] — ${state.summary}`, async ({page}) => {
200
191
  const cdp = await page.context().newCDPSession(page);
201
192
  const result = await runState(page, cdp, state);
193
+ recorded.set(state.id, result);
202
194
  const report = summarize(MODAL_DIALOG_PATTERN, [result]);
203
195
  // eslint-disable-next-line no-console -- the report is this run's artifact
204
196
  console.log(formatReport(report));
@@ -208,3 +200,18 @@ for (const state of DIALOG_MODAL_BINDING_STATES) {
208
200
  expect(report.counts.unexpectedPass).toBe(0);
209
201
  });
210
202
  }
203
+
204
+ test('every expectation is exercised and every known failure matches once', async ({
205
+ page,
206
+ }) => {
207
+ test.setTimeout(5 * 60 * 1000);
208
+ const cdp = await page.context().newCDPSession(page);
209
+ const results: BindingResult[] = [];
210
+ for (const state of DIALOG_MODAL_BINDING_STATES) {
211
+ results.push(recorded.get(state.id) ?? (await runState(page, cdp, state)));
212
+ }
213
+ expect(neverExercised(results)).toEqual([]);
214
+ expect(unmatchedKnownFailures(DIALOG_MODAL_KNOWN_FAILURES, results)).toEqual(
215
+ [],
216
+ );
217
+ });
@@ -40,7 +40,8 @@ export const docs = {
40
40
  {
41
41
  name: 'label',
42
42
  type: 'ReactNode',
43
- description: 'Optional label centered on the divider.',
43
+ description:
44
+ 'Optional label centered on the divider. Numeric zero is preserved; null, booleans, and empty strings render no label.',
44
45
  },
45
46
  {
46
47
  name: 'variant',
@@ -92,7 +93,8 @@ export const docsZh = {
92
93
  {
93
94
  name: 'label',
94
95
  type: 'ReactNode',
95
- description: '居中显示在分隔线上的可选标签。',
96
+ description:
97
+ '居中显示在分隔线上的可选标签。数字 0 会保留;null、布尔值和空字符串不渲染标签。',
96
98
  },
97
99
  {
98
100
  name: 'variant',
@@ -143,7 +145,7 @@ export const docsDense = {
143
145
  },
144
146
  propDescriptions: {
145
147
  orientation: 'divider orientation',
146
- label: 'optional centered label on divider',
148
+ label: 'optional centered label; preserves 0; null/boolean/"" omit it',
147
149
  variant: 'visual weight of divider line',
148
150
  isFullBleed: 'extend to container edges w/ negative margins',
149
151
  xstyle: 'StyleX styles for layout; must be stylex.create() value',
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  schema_version: 3
3
- template_version: 3
3
+ template_version: 7
4
4
  kind: component
5
5
  id: component:Divider
6
6
  authority: draft
@@ -9,33 +9,61 @@ superseded_by: null
9
9
  approved_by: null
10
10
  approved_at: null
11
11
  owners: [cixzhang]
12
- review_triggers: [theming]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility, testing]
13
13
  verified_by:
14
- [packages/core/src/Divider/Divider.test.tsx, scripts/check-knowledge.mjs]
14
+ [
15
+ packages/core/src/Divider/Divider.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ apps/storybook/stories/Divider.stories.tsx,
18
+ ]
15
19
  modules: []
16
20
  families: []
17
21
  design_specs: []
18
22
  architecture:
19
- [architecture:component-theming-surface, architecture:container-padding]
23
+ [
24
+ architecture:public-component-api,
25
+ architecture:component-theming-surface,
26
+ architecture:container-padding,
27
+ architecture:component-test-sufficiency,
28
+ ]
20
29
  contributing: []
21
- system_specs: []
30
+ system_specs: [spec:AST-020, spec:AST-029]
22
31
  ---
23
32
 
24
33
  # Divider component contract
25
34
 
35
+ ## Contract at a glance
36
+
37
+ | Area | Contract |
38
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | Public contract | `orientation`, `label`, theme-extensible `variant`, `isFullBleed`, `BaseProps<HTMLDivElement>`, and a root `ref`. |
40
+ | Behavior | The separator renders one rule without a label and two rules around a renderable label; full bleed consumes inherited logical container inset. The theme-extensible variant axis has no settled no-match line-paint fallback. |
41
+ | End-user impact | People can perceive a named separator across horizontal, vertical, labelled, full-bleed, narrow, and RTL compositions. A custom theme variant can still lose its line paint; OQ2 records that retained gap. |
42
+ | Builder impact | No new caller choice. Numeric zero remains a valid label; booleans and empty strings leave one continuous unnamed rule. |
43
+ | Compatibility/readiness | Patch-compatible observational contract. Public props, defaults, target names, and built-in appearances remain unchanged; authority stays `draft` until exact-head owner approval. |
44
+ | Review checks | Reject missing numeric labels, physical-edge bleed, dropped root inputs, or a target/axis that disagrees with the rendered separator. Keep the custom-variant paint gap unresolved until OQ2 is settled. |
45
+ | Governing rules | `architecture:public-component-api/INV1,INV5–INV9`; `architecture:component-theming-surface/INV3–INV7,INV14`; `architecture:container-padding/INV1,INV3,INV7–INV10`; WCAG 2.2 SC 1.3.1 and 4.1.2 through `spec:AST-020/FR1`. |
46
+
47
+ This table is a review projection; the body below is authoritative.
48
+
26
49
  ## Intent
27
50
 
28
- Divider presents a separator group as one rule, or as two rules around an
29
- optional label. This draft records current consumer anatomy and theming
30
- reachability without changing runtime behavior, styling, targets, or public API.
51
+ Divider provides a programmatic and visual separator between caller-owned content
52
+ regions. It owns orientation, line weight, optional label placement and naming,
53
+ logical full-bleed geometry, and the aggregate `divider` theme target. The caller
54
+ owns the meaning of the surrounding regions.
55
+
56
+ Consumer syntax and examples belong in `Divider.doc.mjs`.
31
57
 
32
58
  ## Compatibility and migration
33
59
 
34
- - Released default preserved: `yes`
35
- - Compatibility class: additive documentation only; runtime, DOM, styling,
36
- targets, aliases, and public API remain unchanged
37
- - Controlled/uncontrolled behavior: not applicable
38
- - Migration decision: none
60
+ - Released default preserved: yes — horizontal, subtle, unlabelled, and not full
61
+ bleed.
62
+ - Compatibility class: patch fix. ReactNode values that React renders, including
63
+ numeric zero, stay inside the label wrapper; skipped scalar labels leave one
64
+ continuous unnamed rule.
65
+ - Controlled/uncontrolled behavior: not applicable.
66
+ - Migration decision: none.
39
67
 
40
68
  Consumer migration instructions belong in consumer docs and release notes.
41
69
 
@@ -43,61 +71,104 @@ Consumer migration instructions belong in consumer docs and release notes.
43
71
 
44
72
  **Owns**
45
73
 
46
- - The separator group, its current `divider` target, the painted rule segments,
47
- and the optional label placement.
74
+ - One semantic separator root and its public ref, DOM, data, ARIA, class, style,
75
+ event, and `xstyle` composition.
76
+ - Horizontal and vertical rule geometry.
77
+ - Optional label placement and the separator naming relationship.
78
+ - Built-in subtle and strong line weights.
79
+ - The current reflected custom-variant state, including the missing deterministic
80
+ line-paint fallback recorded by OQ2.
81
+ - Logical full-bleed consumption of inherited container inset.
82
+ - The aggregate `divider` target with reflected orientation and variant axes.
48
83
 
49
84
  **Does not own / non-goals**
50
85
 
51
- - The meaning of the content regions separated by the divider, which is owned by
52
- the product callsite.
53
- - Container inset publication or structural page regions. Divider only reads
54
- inherited container geometry when `isFullBleed` is enabled.
55
- - Whether Rule or Label should gain public targets, which is unresolved by this
56
- factual backfill.
86
+ - The meaning or heading structure of surrounding content regions — owned by the
87
+ product callsite.
88
+ - Container inset publication — owned by `architecture:container-padding`.
89
+ - A separate public target for Rule or Label — unresolved under OQ1.
90
+ - Theme-authored custom variant paint and the public part that should own it —
91
+ unresolved under OQ2.
57
92
 
58
93
  ## Public concepts
59
94
 
60
- No new public concept is introduced. Consumer props and usage remain documented
61
- in `Divider.doc.mjs`.
95
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
96
+ | ------------ | --------------------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------ | ---------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------- |
97
+ | Orientation | `horizontal`; `vertical` | Selects the separator axis, rule geometry, and `aria-orientation`. | Every divider | `horizontal` | component | stable | Closed union; rejected by types |
98
+ | Label | renderable ReactNode; omitted/skipped scalar | Inserts one label between two rules and names the separator unless explicit ARIA naming wins. | Every orientation and variant | omitted | caller/component | stable | `null`, booleans, and empty strings render no label; numeric zero remains visible |
99
+ | Variant | `subtle`; `strong`; theme-added visual values | Selects line paint and reflects the requested value on the target. | Every orientation and label state | `subtle` | component/theme | stable built-ins; theme-extensible axis | An unmatched custom value has no deterministic line-paint baseline; see OQ2 |
100
+ | Full bleed | false; true | Consumes inherited logical container inset on the divider axis. | Every orientation | false | component/container protocol | stable | Missing variables resolve to zero |
101
+ | Root surface | ref; DOM/data/ARIA; class/style/xstyle/events | Extends the semantic separator root. | Every divider | omitted | public component API | stable | Accepted inputs compose; component-owned role and orientation remain authoritative |
62
102
 
63
103
  ## Behavioral and layout contract
64
104
 
65
- | ID | Candidate invariant | Basis | Draft review state |
66
- | --- | -------------------------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------- |
67
- | FR1 | The current render contains one rule without a label and two rules with the optional label between them. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
68
- | FR2 | The divider group carries the current `divider` target; Rule and Label currently carry no public target. | Current source, docs, and tests | Verified current behavior; theming intent unsettled |
105
+ Draft requirements identify their basis so observed code is not mistaken for an
106
+ intentional decision.
107
+
108
+ | ID | Candidate invariant | Basis | Draft review state |
109
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------- |
110
+ | FR1 | The divider MUST render one rule without a renderable label and two rules with the label between them. Numeric zero MUST remain a renderable label. | Released ReactNode API; current source/docs; `architecture:public-component-api/INV9` | verify |
111
+ | FR2 | The root MUST expose `role="separator"` and the selected `aria-orientation`. A renderable label MUST name it through `aria-labelledby` unless an explicit caller `aria-label` or `aria-labelledby` wins. | WCAG 2.2 SC 1.3.1 and 4.1.2; current source/tests | verify |
112
+ | FR3 | Built-in variants MUST paint subtle or strong lines. The exported map accepts theme-added values, but an unmatched value currently leaves the inner rule without line paint; the correct fallback/target ownership requires OQ2. | `architecture:component-theming-surface/INV4,INV6,INV14`; exported `DividerVariantMap` | human decision; open BLOCK |
113
+ | FR4 | Full bleed MUST consume inherited logical inline inset for horizontal dividers and logical block inset for vertical dividers; absent inset MUST resolve to zero. | `architecture:container-padding/INV1,INV3,INV7–INV10` | verify |
114
+ | FR5 | The root MUST carry the `divider` target and reflect orientation and requested variant. Rule and Label currently carry no public target. | `architecture:component-theming-surface/INV3–INV6`; current metadata | verify; part exposure unsettled |
115
+ | FR6 | Accepted ref, DOM/data/ARIA, class, style, events, and `xstyle` MUST reach or compose on the separator root without replacing component-owned role or orientation. | `architecture:public-component-api/INV5–INV8` | verify |
69
116
 
70
117
  ### Allowed variation
71
118
 
72
- - Orientation, visual weight, full-bleed layout, and label content may vary
73
- without changing the three-part anatomy recorded here.
119
+ - **AV1 — Label content.** Any caller-owned ReactNode that React renders may fill
120
+ the label wrapper.
121
+ - **AV2 — Theme paint.** An active theme may restyle the aggregate target and its
122
+ variant axis. OQ2 owns the unresolved inner-rule paint and fallback boundary.
123
+ - **AV3 — Container geometry.** Full bleed follows the inherited per-edge inset,
124
+ including asymmetric and zero values.
74
125
 
75
126
  ### Representative states
76
127
 
77
- - Unlabelled horizontal and vertical dividers render one Rule.
78
- - Labelled horizontal and vertical dividers render Label between two instances
79
- of Rule.
128
+ | State | Required invariant | Allowed variation |
129
+ | ----------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------ |
130
+ | Unlabelled horizontal | One subtle or strong inline rule; horizontal separator semantics | Container width and theme paint |
131
+ | Labelled horizontal | Two rules around one visible, naming label | Any renderable label content |
132
+ | Numeric zero label | One visible `0` inside the label wrapper; separator name `0` | Any orientation or variant |
133
+ | Vertical labelled/unlabelled | Block-axis rules and vertical separator semantics | Container height and label content |
134
+ | Theme-added variant without a match | Requested variant reflected; inner rule has no component-owned paint (open BLOCK) | Custom variant name |
135
+ | Full bleed | Logical-axis inset consumed without physical-direction assumptions | Uniform, asymmetric, zero, or absent inset |
136
+ | RTL | Horizontal geometry remains logical; full bleed consumes inline start/end independently | Surrounding content and label language |
80
137
 
81
138
  ### Transformation and precedence order
82
139
 
83
- - No new layout or styling precedence rule is introduced.
140
+ - **ORD1 — Label presence.** Apply the shared renderable-value predicate; when a
141
+ label remains, render and reference one wrapper between two rules.
142
+ - **ORD2 — Accessible name.** Explicit `aria-labelledby` wins, then explicit
143
+ `aria-label`, then the rendered label wrapper; an unlabelled separator stays
144
+ unnamed.
145
+ - **ORD3 — Variant paint.** Reflect the requested value on the root target. Built-in
146
+ values select subtle or strong inner-rule paint; unmatched custom values select no
147
+ inner-rule paint pending OQ2.
84
148
 
85
149
  ### Performance and resources
86
150
 
87
- - No new performance or resource rule is introduced.
151
+ - **PR1 — No external lifecycle.** Divider installs no listener, observer, timer,
152
+ request, state machine, or Effect.
88
153
 
89
154
  ## Accessibility contract
90
155
 
91
- This draft does not change or extend Divider's existing separator naming or
92
- orientation behavior.
156
+ - **AR1 — Programmatic separator.** The root exposes separator role and the exact
157
+ visual orientation.
158
+ - **AR2 — Label relationship.** A rendered label names the separator unless the
159
+ caller supplies an explicit accessible name relationship.
160
+ - **AR3 — Decorative rule segments.** Internal rule elements add no separate role,
161
+ name, focus target, or interaction.
93
162
 
94
163
  ## Design relationships
95
164
 
96
- | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
97
- | ---------------- | ----------------------------------------------------------------- | ------------------------------ | -------------- | ------------------ |
98
- | Divider group | Arranges the separator's rule segments and optional label. | Current source and public docs | Supporting | FR1, FR2 |
99
- | Rule | Paints the separator line in the selected orientation and weight. | Current source and public docs | Supporting | FR1, FR2 |
100
- | Label | Presents optional content between two rule segments. | Current source and public docs | Prominent | FR1, FR2 |
165
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
166
+ | ---------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------- | ------------------ |
167
+ | Divider group | Arrange rule segments and the optional label on one logical axis. | observed; container geometry settled by current architecture | supporting | FR1, FR4 |
168
+ | Rule | Paint a visible subtle or strong separation line; theme-added values lack a settled fallback. | built-ins observed; custom fallback unsettled | supporting | FR3 |
169
+ | Label | Present optional content between two rules using supporting text tokens. | observed; separate target exposure unsettled | prominent | FR1–FR2 |
170
+
171
+ No component-local visual decision is settled by this draft.
101
172
 
102
173
  ### Theming anatomy
103
174
 
@@ -125,20 +196,27 @@ public targets.
125
196
 
126
197
  ## Family and system relationships
127
198
 
128
- `family:layout-regions` does not own Divider: separating content is not a
129
- structural page-region contract. `architecture:container-padding` owns the
130
- inherited inset geometry that Divider reads only when `isFullBleed` is enabled.
131
- `architecture:component-theming-surface` owns anatomy qualification, target
132
- mapping, and the difference between factual reachability and intended public
133
- theming API.
199
+ - `architecture:container-padding` owns the inherited internal inset protocol;
200
+ Divider consumes it only through `isFullBleed`.
201
+ - `architecture:component-theming-surface` owns the aggregate target, reflected
202
+ axes, anatomy qualification, and the requirement that an open visual axis have
203
+ a deterministic fallback. Divider's current custom-value path does not satisfy
204
+ that requirement; OQ2 keeps remediation out of this objective audit patch.
205
+ - `architecture:public-component-api` owns root composition, ref behavior, and
206
+ compatibility of the released ReactNode label surface.
207
+ - There is no current Divider family or design record.
208
+ - `spec:AST-029` keeps this observational draft separate from the audit receipt.
134
209
 
135
210
  ## Verification map
136
211
 
137
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
138
- | ------------------- | ------------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------- | ----------------------- |
139
- | FR1 | `Divider.test.tsx` structure and label suites | Labelled and unlabelled; both axes | Removing or reordering Rule or Label instances fails existing child-count and content assertions. | `audit:Divider/anatomy` |
140
- | FR2 | Source inspection and current target inventories | Group, Rule, and Label | Adding or documenting a current target without updating the anatomy map fails repository checks. | `audit:Divider/theming` |
141
- | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current target | Missing, extra, prefixed, stale, or alias-backed mappings fail repository validation. | `audit:Divider/theming` |
212
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
213
+ | ---------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------- |
214
+ | FR1–FR2, AR1–AR3 | `Divider.test.tsx`; component-owned Storybook states; scoped axe | labelled/unlabelled, numeric zero, explicit names, both orientations | Truthiness renders zero outside wrappers or leaves the separator unnamed; role/orientation/name drift fails | `audit:Divider/§1,§4` |
215
+ | FR3 | Source review; global extensible-axis structural guard | subtle, strong, unmatched theme-added value | The current unmatched path leaves the rule unpainted; no Divider-specific browser fallback proof exists | `audit:Divider/§2` |
216
+ | FR4 | Source review; horizontal and vertical full-bleed stories | inline/block, LTR/RTL logical properties, absent inset | A physical property or wrong axis consumes unrelated container geometry | `audit:Divider/§5,§9,§10` |
217
+ | FR5 | `themingTargets.test.ts`; component metadata; spec anatomy map | both orientations and variants | Target/axis/anatomy metadata disagrees with runtime reflection | `audit:Divider/§2` |
218
+ | FR6 | `Divider.test.tsx`; lint, typecheck, and source review | ref, DOM/data/ARIA, class/style/xstyle/events | An accepted input is dropped or component-owned semantics are overwritten | `audit:Divider/§3` |
219
+ | PR1 | Source review | every state | A lifecycle mechanism appears without a resource contract | `audit:Divider/§7` |
142
220
 
143
221
  ## Decision log
144
222
 
@@ -150,8 +228,16 @@ or theming decision.
150
228
  - **OQ1 — Should Rule or Label gain stable public theming targets?** (`human-api`)
151
229
  Their target exposure is unsettled; the current lack of reachability does not
152
230
  decide the answer.
231
+ - **OQ2 — Should the open `variant` axis close, or which painting part owns its
232
+ deterministic custom-value fallback?** (`human-api`) The root target can carry
233
+ theme paint while the inner rule carries built-in paint. Applying a subtle inner
234
+ fallback can cover an existing root background; keeping the current path can make
235
+ the rule disappear. Current authority requires a safe fallback but does not choose
236
+ between closing the axis, moving target ownership, or routing paint through a
237
+ reviewed public property.
153
238
 
154
239
  ## Content boundary
155
240
 
156
- This file does not duplicate consumer prop tables/examples, current audit
157
- results, implementation steps, or system rules. It links to their owners.
241
+ This file does not duplicate consumer prop tables/examples, current audit scores,
242
+ screenshot matrices, implementation steps, or system rules. The audit pull request
243
+ owns the closed run evidence required by `spec:AST-029/FR5`.
@@ -128,6 +128,21 @@ describe('Divider', () => {
128
128
  expect(divider).not.toHaveAttribute('aria-labelledby');
129
129
  });
130
130
 
131
+ it.each([false, true, ''])('omits a label wrapper for %j', label => {
132
+ render(<Divider label={label} data-testid="divider" />);
133
+ const divider = screen.getByTestId('divider');
134
+ expect(divider).not.toHaveAttribute('aria-labelledby');
135
+ expect(divider.children).toHaveLength(1);
136
+ });
137
+
138
+ it('preserves a numeric zero label and uses it as the accessible name', () => {
139
+ render(<Divider label={0} data-testid="divider" />);
140
+ const divider = screen.getByTestId('divider');
141
+ expect(screen.getByText('0')).toBeInTheDocument();
142
+ expect(divider).toHaveAccessibleName('0');
143
+ expect(divider.children).toHaveLength(3);
144
+ });
145
+
131
146
  it('renders astryx-* class names for theme targeting', () => {
132
147
  render(
133
148
  <Divider variant="strong" orientation="vertical" data-testid="divider" />,
@@ -25,7 +25,7 @@ import {
25
25
  typeScaleVars,
26
26
  borderVars,
27
27
  } from '../theme/tokens.stylex';
28
- import {mergeProps} from '../utils';
28
+ import {isRenderable, mergeProps} from '../utils';
29
29
  import {themeProps} from '../utils/themeProps';
30
30
  import type {DividerVariantMap} from './index';
31
31
 
@@ -45,6 +45,7 @@ export interface DividerProps extends BaseProps<HTMLDivElement> {
45
45
 
46
46
  /**
47
47
  * Optional label to display centered on the divider.
48
+ * Numeric zero is preserved; null, booleans, and empty strings render no label.
48
49
  * Rendered with small, secondary text styling.
49
50
  */
50
51
  label?: ReactNode;
@@ -153,6 +154,7 @@ export function Divider({
153
154
  ...props
154
155
  }: DividerProps) {
155
156
  const isHorizontal = orientation === 'horizontal';
157
+ const hasLabel = isRenderable(label);
156
158
  const labelId = useId();
157
159
 
158
160
  // A separator does not derive its accessible name from its content, so the
@@ -160,7 +162,7 @@ export function Divider({
160
162
  // exposed as the separator's name (WCAG 1.3.1). An explicit aria-label or
161
163
  // aria-labelledby from the consumer takes precedence.
162
164
  const resolvedLabelledBy =
163
- ariaLabelledBy ?? (label && ariaLabel == null ? labelId : undefined);
165
+ ariaLabelledBy ?? (hasLabel && ariaLabel == null ? labelId : undefined);
164
166
 
165
167
  return (
166
168
  <div
@@ -189,7 +191,7 @@ export function Divider({
189
191
  lineStyles[variant],
190
192
  )}
191
193
  />
192
- {label && (
194
+ {hasLabel && (
193
195
  <div
194
196
  id={labelId}
195
197
  {...stylex.props(
@@ -199,7 +201,7 @@ export function Divider({
199
201
  {label}
200
202
  </div>
201
203
  )}
202
- {label && (
204
+ {hasLabel && (
203
205
  <div
204
206
  {...stylex.props(
205
207
  isHorizontal ? lineStyles.horizontalLine : lineStyles.verticalLine,