@astryxdesign/core 0.6.1 → 0.6.2-canary.023c3e4

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 (305) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +2 -2
  10. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  11. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  12. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  13. package/dist/Collapsible/Collapsible.js +6 -1
  14. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  15. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  16. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  17. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  18. package/dist/DateInput/DateInput.d.ts.map +1 -1
  19. package/dist/DateInput/DateInput.js +4 -4
  20. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  21. package/dist/DateInput/NativeDateField.js +16 -16
  22. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  23. package/dist/DateInput/TouchDateField.js +2 -2
  24. package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
  25. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  26. package/dist/DateRangeInput/DateRangeInput.js +20 -13
  27. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  28. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  29. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  30. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  31. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  32. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  33. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  34. package/dist/Dialog/DialogHeader.d.ts +1 -1
  35. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  36. package/dist/Dialog/DialogHeader.js +10 -7
  37. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  38. package/dist/Field/PanelSearchInput.js +1 -1
  39. package/dist/FileInput/FileInput.d.ts.map +1 -1
  40. package/dist/FileInput/FileInput.js +16 -10
  41. package/dist/Kbd/Kbd.d.ts +5 -3
  42. package/dist/Kbd/Kbd.d.ts.map +1 -1
  43. package/dist/Kbd/Kbd.js +36 -42
  44. package/dist/Link/Link.d.ts.map +1 -1
  45. package/dist/Link/Link.js +6 -2
  46. package/dist/Markdown/Markdown.d.ts +18 -4
  47. package/dist/Markdown/Markdown.d.ts.map +1 -1
  48. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  49. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  50. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  51. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  52. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  53. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  54. package/dist/Markdown/Markdown.js +311 -114
  55. package/dist/Markdown/ast.d.ts +162 -0
  56. package/dist/Markdown/ast.d.ts.map +1 -0
  57. package/dist/Markdown/ast.js +74 -0
  58. package/dist/Markdown/index.d.ts +5 -3
  59. package/dist/Markdown/index.d.ts.map +1 -1
  60. package/dist/Markdown/index.js +3 -2
  61. package/dist/Markdown/parser/index.d.ts +10 -0
  62. package/dist/Markdown/parser/index.d.ts.map +1 -0
  63. package/dist/Markdown/parser/index.js +10 -0
  64. package/dist/Markdown/parser.d.ts +220 -38
  65. package/dist/Markdown/parser.d.ts.map +1 -1
  66. package/dist/Markdown/parser.js +1072 -188
  67. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  68. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  69. package/dist/Markdown/plugins/frontmatter.js +132 -0
  70. package/dist/Markdown/plugins/index.d.ts +16 -0
  71. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  72. package/dist/Markdown/plugins/index.js +13 -0
  73. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  74. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  75. package/dist/Markdown/plugins/protocol.js +957 -0
  76. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  77. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  78. package/dist/Markdown/plugins/semanticFence.js +213 -0
  79. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  80. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  81. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  82. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  83. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  84. package/dist/Markdown/plugins/textTransform.js +374 -0
  85. package/dist/Markdown/remark.d.ts +196 -0
  86. package/dist/Markdown/remark.d.ts.map +1 -0
  87. package/dist/Markdown/remark.js +1062 -0
  88. package/dist/Markdown/url.d.ts +4 -0
  89. package/dist/Markdown/url.d.ts.map +1 -0
  90. package/dist/Markdown/url.js +25 -0
  91. package/dist/Markdown/utils.d.ts +4 -2
  92. package/dist/Markdown/utils.d.ts.map +1 -1
  93. package/dist/Markdown/utils.js +2 -1
  94. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  95. package/dist/NumberInput/NumberInput.js +4 -4
  96. package/dist/Outline/index.d.ts +1 -0
  97. package/dist/Outline/index.d.ts.map +1 -1
  98. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  99. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  100. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  101. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  102. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  103. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  104. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  105. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  106. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  107. package/dist/ProgressBar/ProgressBar.js +36 -31
  108. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  109. package/dist/RadioList/RadioListItem.js +13 -1
  110. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  111. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  112. package/dist/Selector/Selector.d.ts.map +1 -1
  113. package/dist/Selector/Selector.js +1 -1
  114. package/dist/SideNav/SideNav.d.ts +2 -1
  115. package/dist/SideNav/SideNav.d.ts.map +1 -1
  116. package/dist/SideNav/SideNav.js +9 -3
  117. package/dist/Slider/Slider.d.ts.map +1 -1
  118. package/dist/Slider/Slider.js +19 -10
  119. package/dist/Spinner/Spinner.d.ts +1 -1
  120. package/dist/Spinner/Spinner.d.ts.map +1 -1
  121. package/dist/Spinner/Spinner.js +45 -21
  122. package/dist/Switch/Switch.d.ts.map +1 -1
  123. package/dist/Switch/Switch.js +11 -0
  124. package/dist/TabList/Tab.d.ts +1 -1
  125. package/dist/TabList/Tab.d.ts.map +1 -1
  126. package/dist/TabList/Tab.js +20 -7
  127. package/dist/TextArea/TextArea.d.ts.map +1 -1
  128. package/dist/TextArea/TextArea.js +1 -1
  129. package/dist/TextInput/TextInput.d.ts.map +1 -1
  130. package/dist/TextInput/TextInput.js +2 -2
  131. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  132. package/dist/TimeInput/TimeInput.js +4 -4
  133. package/dist/ToggleButton/ToggleButton.d.ts +2 -1
  134. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  135. package/dist/ToggleButton/ToggleButton.js +7 -1
  136. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  137. package/dist/Typeahead/BaseTypeahead.js +16 -7
  138. package/dist/astryx.css +19 -8
  139. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  140. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  141. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  142. package/dist/hooks/useScrollableArea.d.ts +6 -2
  143. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  144. package/dist/hooks/useScrollableArea.js +17 -5
  145. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  146. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  147. package/dist/utils/interactionOverlay.stylex.js +9 -0
  148. package/locales/en.json +16 -0
  149. package/locales/pseudo.json +12 -0
  150. package/package.json +22 -5
  151. package/scripts/agent-doc-state.mjs +1 -1
  152. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  153. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  154. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  155. package/src/BottomSheet/BottomSheet.tsx +3 -1
  156. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  157. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  158. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  159. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  160. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  161. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  162. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  163. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  164. package/src/Chat/ChatComposerInput.doc.mjs +3 -3
  165. package/src/Chat/ChatComposerInput.tsx +10 -2
  166. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  167. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  168. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  169. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  170. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  171. package/src/Collapsible/Collapsible.test.tsx +21 -0
  172. package/src/Collapsible/Collapsible.tsx +5 -0
  173. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  174. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  175. package/src/DateInput/DateInput.tsx +6 -1
  176. package/src/DateInput/NativeDateField.tsx +24 -8
  177. package/src/DateInput/TouchDateField.tsx +6 -2
  178. package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
  179. package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
  180. package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
  181. package/src/DateRangeInput/DateRangeInput.tsx +35 -21
  182. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  183. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  184. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  185. package/src/Dialog/Dialog.doc.mjs +3 -0
  186. package/src/Dialog/Dialog.spec.md +1 -1
  187. package/src/Dialog/DialogHeader.doc.mjs +38 -0
  188. package/src/Dialog/DialogHeader.test.tsx +49 -0
  189. package/src/Dialog/DialogHeader.tsx +23 -4
  190. package/src/Dialog/modules/DialogHeader.spec.md +152 -0
  191. package/src/Field/PanelSearchInput.tsx +5 -2
  192. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  193. package/src/FileInput/FileInput.doc.mjs +2 -0
  194. package/src/FileInput/FileInput.spec.md +199 -0
  195. package/src/FileInput/FileInput.test.tsx +14 -0
  196. package/src/FileInput/FileInput.tsx +24 -5
  197. package/src/Kbd/Kbd.doc.mjs +3 -3
  198. package/src/Kbd/Kbd.test.tsx +57 -1
  199. package/src/Kbd/Kbd.tsx +57 -37
  200. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  201. package/src/Link/Link.doc.mjs +11 -0
  202. package/src/Link/Link.test.tsx +24 -0
  203. package/src/Link/Link.tsx +5 -0
  204. package/src/Markdown/Markdown.doc.mjs +439 -42
  205. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  206. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  207. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  208. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  209. package/src/Markdown/Markdown.public.test.ts +280 -0
  210. package/src/Markdown/Markdown.spec.md +304 -72
  211. package/src/Markdown/Markdown.test.tsx +118 -3
  212. package/src/Markdown/Markdown.tsx +489 -129
  213. package/src/Markdown/ast.test.ts +307 -0
  214. package/src/Markdown/ast.ts +337 -0
  215. package/src/Markdown/incremental.test.ts +187 -7
  216. package/src/Markdown/index.ts +43 -1
  217. package/src/Markdown/modules/remark.spec.md +269 -0
  218. package/src/Markdown/parser/index.ts +64 -0
  219. package/src/Markdown/parser.perf.test.ts +3 -1
  220. package/src/Markdown/parser.public.test.ts +103 -0
  221. package/src/Markdown/parser.test.ts +122 -0
  222. package/src/Markdown/parser.ts +1721 -248
  223. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  224. package/src/Markdown/plugins/frontmatter.ts +198 -0
  225. package/src/Markdown/plugins/index.ts +53 -0
  226. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  227. package/src/Markdown/plugins/protocol.ts +1618 -0
  228. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  229. package/src/Markdown/plugins/semanticFence.ts +424 -0
  230. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  231. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  232. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  233. package/src/Markdown/plugins/textTransform.ts +554 -0
  234. package/src/Markdown/remark.test.tsx +1877 -0
  235. package/src/Markdown/remark.ts +1574 -0
  236. package/src/Markdown/url.ts +30 -0
  237. package/src/Markdown/utils.ts +19 -1
  238. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  239. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  240. package/src/NumberInput/NumberInput.tsx +6 -1
  241. package/src/Outline/Outline.doc.mjs +7 -5
  242. package/src/Outline/Outline.spec.md +1 -1
  243. package/src/Outline/index.ts +1 -0
  244. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  245. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  246. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  247. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  248. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  249. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  250. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  251. package/src/ProgressBar/ProgressBar.tsx +59 -72
  252. package/src/RadioList/RadioList.doc.mjs +11 -0
  253. package/src/RadioList/RadioList.test.tsx +32 -0
  254. package/src/RadioList/RadioListItem.tsx +25 -1
  255. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  256. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  257. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  258. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  259. package/src/Selector/Selector.spec.md +12 -9
  260. package/src/Selector/Selector.tsx +6 -1
  261. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  262. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  263. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  264. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  265. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  266. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  267. package/src/SideNav/SideNav.doc.mjs +1 -1
  268. package/src/SideNav/SideNav.test.tsx +10 -0
  269. package/src/SideNav/SideNav.tsx +14 -2
  270. package/src/Slider/Slider.doc.mjs +27 -0
  271. package/src/Slider/Slider.spec.md +61 -47
  272. package/src/Slider/Slider.test.tsx +146 -0
  273. package/src/Slider/Slider.tsx +37 -13
  274. package/src/Spinner/Spinner.doc.mjs +6 -3
  275. package/src/Spinner/Spinner.test.tsx +41 -0
  276. package/src/Spinner/Spinner.tsx +62 -32
  277. package/src/Switch/Switch.doc.mjs +11 -0
  278. package/src/Switch/Switch.test.tsx +16 -0
  279. package/src/Switch/Switch.tsx +28 -0
  280. package/src/TabList/Tab.tsx +35 -7
  281. package/src/TabList/TabList.doc.mjs +11 -0
  282. package/src/TabList/TabList.test.tsx +66 -0
  283. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  284. package/src/TextArea/TextArea.tsx +6 -1
  285. package/src/TextInput/TextInput.test.tsx +20 -1
  286. package/src/TextInput/TextInput.tsx +6 -1
  287. package/src/TimeInput/TimeInput.tsx +6 -1
  288. package/src/ToggleButton/ToggleButton.test.tsx +133 -0
  289. package/src/ToggleButton/ToggleButton.tsx +9 -2
  290. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  291. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  292. package/src/TreeList/TreeList.spec.md +123 -75
  293. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  294. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  295. package/src/Typeahead/Typeahead.test.tsx +53 -0
  296. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  297. package/src/__tests__/pressState.ts +93 -0
  298. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  299. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  300. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  301. package/src/hooks/useScrollableArea.test.tsx +59 -1
  302. package/src/hooks/useScrollableArea.ts +34 -10
  303. package/src/inputFontFloor.test.ts +106 -0
  304. package/src/theme/derivedVarRegistry.test.ts +6 -4
  305. package/src/utils/interactionOverlay.stylex.ts +20 -0
@@ -10,7 +10,12 @@ approved_by: cixzhang
10
10
  approved_at: 2026-09-11
11
11
  owners: [cixzhang]
12
12
  review_triggers: [public-api, behavior, accessibility]
13
- verified_by: [packages/core/src/hooks/useScrollableArea.test.tsx]
13
+ verified_by:
14
+ [
15
+ packages/core/src/hooks/useScrollableArea.test.tsx,
16
+ packages/core/src/hooks/scrollKeyboardDelegation.test.ts,
17
+ packages/core/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts,
18
+ ]
14
19
  parent_component: component:ScrollableArea
15
20
  references: [architecture:public-component-api, spec:AST-025/DEC-1]
16
21
  ---
@@ -47,24 +52,25 @@ and scroll chaining without inserting structure.
47
52
 
48
53
  ## Public API and concepts
49
54
 
50
- | Concept | Closed values or states | Meaning | Default | Owner | Stability |
51
- | -------------- | ---------------------------------- | ------------------------------------- | ------------------------- | ----------------------------------------- | --------- |
52
- | axis | `inline`, `block`, `both` | requested logical scroll intent | required | `spec:AST-025` | stable |
53
- | keyboard owner | `content`, named `viewport` | where keyboard scrolling is reached | required | `spec:AST-025` | stable |
54
- | overscroll | `allow`, `contain` | edge propagation on effective axes | `allow` | `spec:AST-025` | stable |
55
- | axis state | `isScrollable`, `atStart`, `atEnd` | effective ownership and logical edges | inactive, both edges true | `module:ScrollableArea/useScrollableArea` | stable |
55
+ | Concept | Closed values or states | Meaning | Default | Owner | Stability |
56
+ | -------------- | ------------------------------------------------------ | --------------------------------------- | ------------------------- | ----------------------------------------- | --------- |
57
+ | axis | `inline`, `block`, `both` | requested logical scroll intent | required | `spec:AST-025` | stable |
58
+ | keyboard owner | `content`, named `viewport`, named `contentOrViewport` | fixed or automatic keyboard scroll path | required | `spec:AST-025` | stable |
59
+ | overscroll | `allow`, `contain` | edge propagation on effective axes | `allow` | `spec:AST-025` | stable |
60
+ | axis state | `isScrollable`, `atStart`, `atEnd` | effective ownership and logical edges | inactive, both edges true | `module:ScrollableArea/useScrollableArea` | stable |
56
61
 
57
62
  ## Behavioral contract
58
63
 
59
- | ID | Invariant | Basis | Acceptance and implementation state |
60
- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------- |
61
- | FR1 | An axis MUST be effective only when requested, computed overflow is scroll-capable, and geometry exceeds 1px. | `spec:AST-025` FR1–FR4 | implemented |
62
- | FR2 | Viewport and content geometry MUST be observed; invalidations MUST coalesce and preserve the last valid state while unmeasurable. | `spec:AST-025` FR5–FR9 | implemented |
63
- | FR3 | Logical edges and behavior-owned physical overflow MUST map through direction and horizontal, vertical, or sideways writing modes without requiring adopters to recreate the mapping. | `spec:AST-025` FR1, FR3–FR4 | implemented |
64
- | FR4 | The nearest registered effective owner MUST win independently by axis. | `spec:AST-025` FR10, FR18 | implemented privately for future Sticky |
65
- | FR5 | Viewport keyboard props and overscroll containment MUST appear only while applicable requested axes are effective. | `spec:AST-025` FR12–FR14 | implemented |
66
- | FR6 | Prop getters MUST compose refs and preserve caller handlers/classes/styles/ARIA while behavior-owned accessibility and non-cancellable behavior win conflicts. | `spec:AST-025` IR4 | implemented |
67
- | FR7 | The viewport prop getter MUST consume caller `xstyle` and internally compose geometry-driven `clip`/`auto`/`hidden` overflow plus explicit fitting Sticky containment. | `spec:AST-025` FR21 | implemented |
64
+ | ID | Invariant | Basis | Acceptance and implementation state |
65
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------- |
66
+ | FR1 | An axis MUST be effective only when requested, computed overflow is scroll-capable, and geometry exceeds 1px. | `spec:AST-025` FR1–FR4 | implemented |
67
+ | FR2 | Viewport and content geometry MUST be observed; invalidations MUST coalesce and preserve the last valid state while unmeasurable. | `spec:AST-025` FR5–FR9 | implemented |
68
+ | FR3 | Logical edges and behavior-owned physical overflow MUST map through direction and horizontal, vertical, or sideways writing modes without requiring adopters to recreate the mapping. | `spec:AST-025` FR1, FR3–FR4 | implemented |
69
+ | FR4 | The nearest registered effective owner MUST win independently by axis. | `spec:AST-025` FR10, FR18 | implemented privately for future Sticky |
70
+ | FR5 | Viewport keyboard props and overscroll containment MUST appear only while applicable requested axes are effective. | `spec:AST-025` FR12–FR14 | implemented |
71
+ | FR6 | Prop getters MUST compose refs and preserve caller handlers/classes/styles/ARIA while behavior-owned accessibility and non-cancellable behavior win conflicts. | `spec:AST-025` IR4 | implemented |
72
+ | FR7 | The viewport prop getter MUST consume caller `xstyle` and internally compose geometry-driven `clip`/`auto`/`hidden` overflow plus explicit fitting Sticky containment. | `spec:AST-025` FR21 | implemented |
73
+ | FR8 | `contentOrViewport` MUST retain the named overflowing viewport, inspect the first sequential descendant on forward Tab entry, and delegate only to a native link/button outside navigation-key-owning surfaces. Reverse traversal from the delegated first child skips the viewport. Pointer/programmatic focus never delegates; eligibility is checked again on the next entry. | `spec:AST-025` FR12–FR13 | implemented |
68
74
 
69
75
  ### Transformation and precedence order
70
76
 
@@ -82,9 +88,20 @@ and scroll chaining without inserting structure.
82
88
 
83
89
  ## Accessibility contract
84
90
 
85
- - **AR1 — Named viewport owner.** Viewport-owned access requires a label and adds
86
- the viewport to the tab order only while an applicable axis is effective.
87
- - **AR2 — Focus continuity.** State changes never move or blur focus.
91
+ - **AR1 — Named viewport owner.** Fixed viewport and automatic access require a
92
+ label and retain `tabIndex=0` while effectively scrollable. Automatic access
93
+ delegates only on forward keyboard entry; the descendant retains its own name
94
+ inside the named viewport group/region.
95
+ - **AR2 — Focus continuity.** Content, eligibility, and geometry changes never
96
+ move or blur focus. A viewport losing overflow while focused remains
97
+ programmatically focusable. Reverse traversal from the delegated first child
98
+ skips the viewport, including after visiting later descendants.
99
+ - **AR3 — Conservative eligibility.** Inspect the first sequential target; do not
100
+ search past an excluded one for a link/button. Composite roles, editable
101
+ surfaces, native or registered nested scroll owners, positive tab ordering,
102
+ popup triggers, and unproven interactive roles retain the viewport path.
103
+ - **AR4 — Native scrolling.** Delegation MUST preserve Arrow/Page access to both
104
+ scroll edges without implementing JavaScript scrolling.
88
105
 
89
106
  ## Design relationships
90
107
 
@@ -97,9 +114,9 @@ No visual representation is owned by this hook.
97
114
 
98
115
  ## Verification map
99
116
 
100
- | Contract | Verification | Representative states | Mutation or failure expectation |
101
- | ---------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
102
- | FR1–FR6, AR1–AR2 | `useScrollableArea.test.tsx` plus real-browser Storybook probe | horizontal/vertical/sideways, LTR/RTL, fit/overflow/edge, nested owners, focused loss | style-only or geometry-only ownership, physical-edge leakage, stale state, ref loss, or dead containment fails |
117
+ | Contract | Verification | Representative states | Mutation or failure expectation |
118
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
119
+ | FR1–FR8, AR1–AR4 | `useScrollableArea.test.tsx`, `scrollKeyboardDelegation.test.ts`, and `BottomSheetKeyboard.a11y.browser.spec.ts` | horizontal/vertical/sideways, LTR/RTL, fit/overflow/edge, fixed/automatic keyboard owners, dynamic descendant eligibility, nested owners, focused loss | style-only or geometry-only ownership, physical-edge leakage, duplicate/missing keyboard path, stale state, ref loss, or dead containment fails |
103
120
 
104
121
  ## Decision log
105
122
 
@@ -111,6 +128,17 @@ No visual representation is owned by this hook.
111
128
  One returned prop object per owned element keeps refs, accessibility, handlers,
112
129
  and behavior together without making consumer spread order part of correctness.
113
130
 
131
+ ### DEC-2 — Automatic ownership is shared behavior
132
+
133
+ **Reference:** `module:ScrollableArea/useScrollableArea/DEC-2`
134
+ **Decider:** cixzhang, 2026-09-13
135
+
136
+ `contentOrViewport` retains the named overflowing viewport and delegates only on
137
+ forward Tab entry to a safe first sequential native link/button. This replaces
138
+ continuous keyboard-eligibility observation. Geometry observation is unchanged;
139
+ keyboard eligibility adds no subtree or ancestor observers and is inspected anew
140
+ at each entry. BottomSheet is a proving composition of this shared behavior.
141
+
114
142
  ## Open questions
115
143
 
116
144
  None.
@@ -129,9 +129,9 @@ export const docs = {
129
129
  category: 'Color contrast',
130
130
  criterion: '1.4.3 Contrast (Minimum)',
131
131
  requirement: '4.5:1',
132
- states: ['Rest', 'Hover', 'Selected'],
132
+ states: ['Rest', 'Hover', 'Pointer down', 'Selected'],
133
133
  description:
134
- 'Each label must have at least 4.5:1 contrast with its segment background. Check unselected, Hover, and selected colors as they appear on screen.',
134
+ 'Each label must have at least 4.5:1 contrast with its segment background. Check unselected, Hover, Pointer down, and selected colors as they appear on screen. For Hover and Pointer down, measure the final background after the overlay is applied.',
135
135
  },
136
136
  {
137
137
  name: 'Essential icon',
@@ -15,6 +15,7 @@
15
15
  import {describe, it, expect, vi, beforeEach} from 'vitest';
16
16
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
17
17
  import userEvent from '@testing-library/user-event';
18
+ import {hasPressedArm} from '../__tests__/pressState';
18
19
  import {SegmentedControl} from './SegmentedControl';
19
20
  import {SegmentedControlItem} from './SegmentedControlItem';
20
21
  import {
@@ -672,3 +673,33 @@ describe('forced colors (WCAG 1.4.11)', () => {
672
673
  expect(getAllInjectedCss()).toContain('forced-color-adjust: none;');
673
674
  });
674
675
  });
676
+
677
+ describe('pressed state', () => {
678
+ it('paints the pressed overlay on a segment while it is pressed', () => {
679
+ render(
680
+ <SegmentedControl value="grid" onChange={() => {}} label="View mode">
681
+ <SegmentedControlItem value="grid" label="Grid" />
682
+ <SegmentedControlItem value="list" label="List" />
683
+ </SegmentedControl>,
684
+ );
685
+ // The unselected segment is the one a press can change; it carries the
686
+ // system's hover and pressed overlay.
687
+ expect(hasPressedArm(screen.getByRole('radio', {name: 'List'}))).toBe(true);
688
+ // The selected segment keeps its raised surface as it is.
689
+ expect(hasPressedArm(screen.getByRole('radio', {name: 'Grid'}))).toBe(
690
+ false,
691
+ );
692
+ });
693
+
694
+ it('does not press a disabled segment', () => {
695
+ render(
696
+ <SegmentedControl value="grid" onChange={() => {}} label="View mode">
697
+ <SegmentedControlItem value="grid" label="Grid" />
698
+ <SegmentedControlItem value="list" label="List" isDisabled />
699
+ </SegmentedControl>,
700
+ );
701
+ expect(hasPressedArm(screen.getByRole('radio', {name: 'List'}))).toBe(
702
+ false,
703
+ );
704
+ });
705
+ });
@@ -33,6 +33,7 @@ import {mergeProps, composeEventHandlers} from '../utils';
33
33
  import type {BaseProps} from '../BaseProps';
34
34
  import {themeProps} from '../utils/themeProps';
35
35
  import {focusOutlineProps} from '../utils/focusOutline.stylex';
36
+ import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
36
37
 
37
38
  export interface SegmentedControlItemProps extends BaseProps<HTMLButtonElement> {
38
39
  ref?: React.Ref<HTMLButtonElement>;
@@ -91,14 +92,6 @@ const styles = stylex.create({
91
92
  transitionDuration: durationVars['--duration-fast'],
92
93
  transitionTimingFunction: easeVars['--ease-standard'],
93
94
  },
94
- hover: {
95
- backgroundColor: {
96
- default: null,
97
- ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
98
- '@media (hover: hover)': colorVars['--color-overlay-hover'],
99
- },
100
- },
101
- },
102
95
  selected: {
103
96
  // Forced colors (Windows High Contrast) strips the painted surface fill
104
97
  // and box shadow, which would leave the selected segment with no state
@@ -248,7 +241,11 @@ export function SegmentedControlItem({
248
241
  sizeStyles[size],
249
242
  isFill && styles.fill,
250
243
  isSelected && styles.selected,
251
- !isSelected && !isItemDisabled && styles.hover,
244
+ // The shared hover and pressed overlay, on the segments a press can
245
+ // change: the selected segment keeps its raised surface as it is.
246
+ !isSelected &&
247
+ !isItemDisabled &&
248
+ interactionOverlayStyles.backgroundColor,
252
249
  isItemDisabled && styles.disabled,
253
250
  xstyle,
254
251
  ),
@@ -14,6 +14,8 @@ verified_by:
14
14
  [
15
15
  packages/core/src/Selector/Selector.test.tsx,
16
16
  packages/core/src/Selector/Selector.source-build.test.mjs,
17
+ packages/core/src/Selector/__tests__/Selector.listbox.a11y.test.tsx,
18
+ packages/core/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts,
17
19
  ]
18
20
  modules: []
19
21
  families: [family:input-fields]
@@ -274,15 +276,16 @@ in `spec:AST-004` as current runtime behavior.
274
276
 
275
277
  ## Verification map
276
278
 
277
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
278
- | --------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------- |
279
- | FR1, AR2, AR3 | `Selector.test.tsx` selection, focus, and keyboard suites | closed/open, search/non-search, disabled option | Removing selection wiring, focus movement, or keyboard behavior fails the named interaction tests | `audit:Selector/behavior` |
280
- | FR2 | placement and selected-item geometry tests | default, explicit, RTL, transformed entry | Using the wrong positioning model fails the expected position-area or margin | `audit:Selector/behavior` |
281
- | FR3, AV1 | `itemMarkColumn` source inspection plus indicator-position and replacement-indicator tests | start/end, selected/unselected, check/radio | Removing the wrapper fails row-structure tests; changing its reserved width requires source/layout review | `audit:Selector/design-rendered` |
282
- | FR4, AR1, AR2 | presentation tests plus `aria-haspopup` source review | pointer, compact coarse pointer, search/non-search | Wrong Popover/dialog roles or focus destinations fail tests; `aria-haspopup` values require source/a11y review | `audit:Selector/accessibility` |
283
- | FR5 | loading, empty-state, and live-region tests | empty options, unmatched search, loading | Empty/no-results output appears or is announced while loading | `audit:Selector/behavior` |
284
- | FR6, AR4 | read-only interaction, form, ARIA, and theme-state tests | search/non-search, clearable, open→read-only, disabled precedence | A value changes, popup or edit affordance remains, form value disappears, or read-only semantics are absent | `audit:Selector/accessibility` |
285
- | source-build contract | `Selector.source-build.test.mjs` | package source compiled by consumer Babel | Moving evaluated StyleX values outside the supported source form fails compilation | `audit:Selector/code-health` |
279
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
280
+ | --------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
281
+ | FR1, AR2, AR3 | `Selector.test.tsx` selection, focus, and keyboard suites | closed/open, search/non-search, disabled option | Removing selection wiring, focus movement, or keyboard behavior fails the named interaction tests | `audit:Selector/behavior` |
282
+ | FR2 | placement and selected-item geometry tests | default, explicit, RTL, transformed entry | Using the wrong positioning model fails the expected position-area or margin | `audit:Selector/behavior` |
283
+ | FR3, AV1 | `itemMarkColumn` source inspection plus indicator-position and replacement-indicator tests | start/end, selected/unselected, check/radio | Removing the wrapper fails row-structure tests; changing its reserved width requires source/layout review | `audit:Selector/design-rendered` |
284
+ | FR4, AR1, AR2 | presentation tests plus `aria-haspopup` source review | pointer, compact coarse pointer, search/non-search | Wrong Popover/dialog roles or focus destinations fail tests; `aria-haspopup` values require source/a11y review | `audit:Selector/accessibility` |
285
+ | AR1, AV2 | `Selector.listbox.a11y.test.tsx` and `Listbox.a11y.chromium.spec.ts` against the shared Listbox contract | popover/sheet, flat/grouped, selected/unselected, disabled, search, loading, custom RTL | A new or wider loss of role, listbox/option name, state, or ownership gates; the exact unnamed no-search sheet state remains known debt | `audit:Selector/accessibility` |
286
+ | FR5 | loading, empty-state, and live-region tests | empty options, unmatched search, loading | Empty/no-results output appears or is announced while loading | `audit:Selector/behavior` |
287
+ | FR6, AR4 | read-only interaction, form, ARIA, and theme-state tests | search/non-search, clearable, open→read-only, disabled precedence | A value changes, popup or edit affordance remains, form value disappears, or read-only semantics are absent | `audit:Selector/accessibility` |
288
+ | source-build contract | `Selector.source-build.test.mjs` | package source compiled by consumer Babel | Moving evaluated StyleX values outside the supported source form fails compilation | `audit:Selector/code-health` |
286
289
 
287
290
  ## Decision log
288
291
 
@@ -102,9 +102,14 @@ const styles = stylex.create({
102
102
  paddingBlock: spacingVars['--spacing-2'],
103
103
  paddingInline: spacingVars['--spacing-3'],
104
104
  fontFamily: typographyVars['--font-family-body'],
105
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
106
+ // control sits under 16px, and only iOS WebKit implements
107
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
105
108
  fontSize: {
106
109
  default: typeScaleVars['--text-label-size'],
107
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
110
+ '@media (pointer: coarse)': {
111
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
112
+ },
108
113
  },
109
114
  // A FIXED line box, not the ratio: the trigger's padding is derived from
110
115
  // one line being `--spacing-5` tall, and a ratio makes the line box track
@@ -0,0 +1,130 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Listbox.a11y.chromium.spec.ts
5
+ * @input Uses the Listbox contract, browser harness, state inventory, and checked-in stories
6
+ * @output DOM/accessibility-tree evidence for each real Listbox binding part
7
+ * @position Browser binding; keyboard policy, pixels, and real AT remain separate owners.
8
+ */
9
+
10
+ import {expect, test} from '@playwright/test';
11
+ import {
12
+ LISTBOX_PATTERN,
13
+ checkAccessibilitySpec,
14
+ blockingResults,
15
+ formatFailures,
16
+ formatReport,
17
+ summarize,
18
+ neverExercised,
19
+ unmatchedKnownFailures,
20
+ type BindingResult,
21
+ } from '@astryxdesign/a11y-spec';
22
+ import {
23
+ createChromiumHarness,
24
+ holdMotionStill,
25
+ } from '@astryxdesign/a11y-spec/chromium';
26
+ import {
27
+ serveStorybook,
28
+ DEFAULT_STORYBOOK_DIR,
29
+ type StaticServer,
30
+ } from '@astryxdesign/a11y-spec/storybook';
31
+ import {LISTBOX_KNOWN_FAILURES} from './Listbox.a11y.known-failures';
32
+ import {LISTBOX_SCENARIOS, listboxParts} from './Listbox.a11y.states';
33
+
34
+ let storybook: StaticServer;
35
+ const observedResults: BindingResult[] = [];
36
+ test.beforeAll(async () => {
37
+ storybook = await serveStorybook(
38
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
39
+ );
40
+ });
41
+ test.afterAll(async () => {
42
+ await storybook?.close();
43
+ expect(
44
+ unmatchedKnownFailures(LISTBOX_KNOWN_FAILURES, observedResults),
45
+ ).toEqual([]);
46
+ });
47
+
48
+ for (const scenario of LISTBOX_SCENARIOS) {
49
+ test(`${scenario.component} — ${scenario.id}`, async ({page}) => {
50
+ test.setTimeout(2 * 60 * 1000);
51
+ const cdp = await page.context().newCDPSession(page);
52
+ const results: BindingResult[] = [];
53
+ const mount = async () => {
54
+ await page.goto(
55
+ `${storybook.origin}/iframe.html?id=a11y-listbox-pattern--${scenario.id}&viewMode=story&globals=direction:${scenario.direction ?? 'ltr'}`,
56
+ {waitUntil: 'load'},
57
+ );
58
+ await holdMotionStill(page);
59
+ await expect(
60
+ page.locator(`[data-listbox-scenario="${scenario.id}"]`),
61
+ ).toBeAttached();
62
+ await page.getByRole('listbox').waitFor({state: 'visible'});
63
+ if (scenario.query != null) {
64
+ await page.getByRole('combobox').fill(scenario.query);
65
+ }
66
+ await expect(page.getByRole('listbox').getByRole('option')).toHaveCount(
67
+ scenario.expectedOptions.length,
68
+ );
69
+ };
70
+
71
+ for (const part of listboxParts(scenario)) {
72
+ results.push(
73
+ await checkAccessibilitySpec({
74
+ spec: LISTBOX_PATTERN,
75
+ binding: `${scenario.component}.${part.role}`,
76
+ state: part.state,
77
+ facts: part.facts,
78
+ knownFailures: LISTBOX_KNOWN_FAILURES,
79
+ mount: async () => {
80
+ await mount();
81
+ const listbox = page.getByRole('listbox');
82
+ const subject =
83
+ part.role === 'listbox'
84
+ ? listbox
85
+ : part.role === 'group'
86
+ ? listbox
87
+ .getByRole('group')
88
+ .nth(scenario.groups.indexOf(part.name ?? ''))
89
+ : listbox.getByRole('option', {
90
+ name: part.name,
91
+ exact: true,
92
+ });
93
+ return createChromiumHarness({
94
+ page,
95
+ cdp,
96
+ subject,
97
+ related: {
98
+ listbox,
99
+ ...Object.fromEntries(
100
+ scenario.expectedOptions.map(option => [
101
+ `option:${option.value}`,
102
+ listbox.getByRole('option', {
103
+ name: option.name,
104
+ exact: true,
105
+ }),
106
+ ]),
107
+ ),
108
+ ...Object.fromEntries(
109
+ scenario.groups.map((name, index) => [
110
+ `group:${name}`,
111
+ listbox.getByRole('group').nth(index),
112
+ ]),
113
+ ),
114
+ },
115
+ });
116
+ },
117
+ }),
118
+ );
119
+ }
120
+
121
+ observedResults.push(...results);
122
+ const report = summarize(LISTBOX_PATTERN, results);
123
+ console.log(formatReport(report));
124
+ expect(formatFailures(blockingResults(results))).toBe('');
125
+ expect(report.counts.unrun).toBe(0);
126
+ if (scenario.component === 'Selector' && scenario.groups.length > 0) {
127
+ expect(neverExercised(results)).toEqual([]);
128
+ }
129
+ });
130
+ }
@@ -0,0 +1,38 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Listbox.a11y.dom.ts
5
+ * @input Uses Vitest lifecycle hooks and the DOM Dialog prototype
6
+ * @output Scoped stubs for Dialog methods missing from jsdom
7
+ * @position DOM-only binding setup; these stubs prove no native modal behavior.
8
+ */
9
+
10
+ import {afterAll, beforeAll} from 'vitest';
11
+
12
+ export function installListboxDialogStubs(): void {
13
+ const originals = new Map<string, PropertyDescriptor | undefined>();
14
+ beforeAll(() => {
15
+ for (const name of ['show', 'showModal', 'close']) {
16
+ originals.set(
17
+ name,
18
+ Object.getOwnPropertyDescriptor(HTMLDialogElement.prototype, name),
19
+ );
20
+ Object.defineProperty(HTMLDialogElement.prototype, name, {
21
+ configurable: true,
22
+ writable: true,
23
+ value: function (this: HTMLDialogElement) {
24
+ this.open = name !== 'close';
25
+ },
26
+ });
27
+ }
28
+ });
29
+ afterAll(() => {
30
+ for (const [name, descriptor] of originals) {
31
+ if (descriptor == null) {
32
+ Reflect.deleteProperty(HTMLDialogElement.prototype, name);
33
+ } else {
34
+ Object.defineProperty(HTMLDialogElement.prototype, name, descriptor);
35
+ }
36
+ }
37
+ });
38
+ }
@@ -0,0 +1,63 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Listbox.a11y.inventory.test.ts
5
+ * @input Uses the locked Listbox migration matrix and checklist
6
+ * @output Coverage-boundary and part-inventory regression checks
7
+ * @position Binding metadata proof, separate from runtime conformance results.
8
+ */
9
+
10
+ import {describe, expect, it} from 'vitest';
11
+ import {LISTBOX_PATTERN, unansweredDimensions} from '@astryxdesign/a11y-spec';
12
+ import {LISTBOX_KNOWN_FAILURES} from './Listbox.a11y.known-failures';
13
+ import {
14
+ LISTBOX_SCENARIOS,
15
+ LISTBOX_EXCLUSIONS,
16
+ listboxParts,
17
+ } from './Listbox.a11y.states';
18
+
19
+ describe('Listbox migration inventory', () => {
20
+ it('accounts for every checklist dimension without claiming other evidence layers', () => {
21
+ expect(unansweredDimensions(LISTBOX_PATTERN)).toEqual([]);
22
+ expect(LISTBOX_EXCLUSIONS).toHaveLength(5);
23
+ });
24
+
25
+ it('binds 21 scenarios and all six real semantic parts', () => {
26
+ expect(LISTBOX_SCENARIOS).toHaveLength(21);
27
+ const parts = LISTBOX_SCENARIOS.flatMap(scenario =>
28
+ listboxParts(scenario).map(part => ({
29
+ binding: `${scenario.component}.${part.role}`,
30
+ state: part.state,
31
+ })),
32
+ );
33
+ expect(parts).toHaveLength(89);
34
+ expect(new Set(parts.map(part => part.state)).size).toBe(89);
35
+ expect([...new Set(parts.map(part => part.binding))].sort()).toEqual([
36
+ 'MultiSelector.group',
37
+ 'MultiSelector.listbox',
38
+ 'MultiSelector.option',
39
+ 'Selector.group',
40
+ 'Selector.listbox',
41
+ 'Selector.option',
42
+ ]);
43
+ });
44
+
45
+ it('pins each known failure to one exact inventory part', () => {
46
+ const parts = LISTBOX_SCENARIOS.flatMap(scenario =>
47
+ listboxParts(scenario).map(part => ({
48
+ binding: `${scenario.component}.${part.role}`,
49
+ state: part.state,
50
+ })),
51
+ );
52
+
53
+ expect(LISTBOX_KNOWN_FAILURES).toHaveLength(2);
54
+ for (const record of LISTBOX_KNOWN_FAILURES) {
55
+ expect(
56
+ parts.filter(
57
+ part =>
58
+ part.binding === record.binding && part.state === record.state,
59
+ ),
60
+ ).toHaveLength(1);
61
+ }
62
+ });
63
+ });
@@ -0,0 +1,37 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Listbox.a11y.known-failures.ts
5
+ * @input Uses KnownFailure from @astryxdesign/a11y-spec
6
+ * @output Exact public-safe Selector and MultiSelector listbox migration failures
7
+ * @position AST-021 debt records; operational ownership remains outside public source.
8
+ */
9
+
10
+ import type {KnownFailure} from '@astryxdesign/a11y-spec';
11
+
12
+ export const LISTBOX_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
13
+ {
14
+ expectation: 'listbox.exposure.identity',
15
+ binding: 'Selector.listbox',
16
+ state: 'single-sheet:listbox',
17
+ evidenceLayer: 'accessibility-tree',
18
+ failureEquals: 'the browser exposes the listbox without an accessible name',
19
+ standardsReference: 'WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
20
+ userImpact:
21
+ 'An accessibility consumer encounters an unnamed listbox in the bottom-sheet presentation and cannot identify what set of choices it contains.',
22
+ reason:
23
+ 'The no-search bottom-sheet presentation renders a visible sheet heading without assigning that heading, or another label, as the listbox accessible name. This migration records the existing behavior without changing the component.',
24
+ },
25
+ {
26
+ expectation: 'listbox.exposure.identity',
27
+ binding: 'MultiSelector.listbox',
28
+ state: 'multiple-sheet:listbox',
29
+ evidenceLayer: 'accessibility-tree',
30
+ failureEquals: 'the browser exposes the listbox without an accessible name',
31
+ standardsReference: 'WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
32
+ userImpact:
33
+ 'An accessibility consumer encounters an unnamed multi-select listbox in the bottom-sheet presentation and cannot identify what set of choices it contains.',
34
+ reason:
35
+ 'The no-search bottom-sheet presentation renders a visible sheet heading without assigning that heading, or another label, as the listbox accessible name. This migration records the existing behavior without changing the component.',
36
+ },
37
+ ];