@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
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  schema_version: 3
3
- template_version: 3
3
+ template_version: 4
4
4
  kind: component
5
5
  id: component:Slider
6
- authority: draft
6
+ authority: current
7
7
  archive_reason: null
8
8
  superseded_by: null
9
- approved_by: null
10
- approved_at: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-09-14
11
11
  owners: [cixzhang]
12
12
  review_triggers: [theming]
13
13
  verified_by:
@@ -29,14 +29,14 @@ system_specs: []
29
29
  ## Intent
30
30
 
31
31
  Slider presents a labeled control for selecting one numeric value or a bounded
32
- range. This draft records its current consumer anatomy and theming ownership
33
- without changing runtime behavior, styling, targets, or public API.
32
+ range. This contract records its consumer anatomy and theming ownership, including
33
+ an additive target for the interactive control surface.
34
34
 
35
35
  ## Compatibility and migration
36
36
 
37
37
  - Released default preserved: `yes`
38
- - Compatibility class: additive documentation only; runtime, DOM, styling,
39
- targets, and public API remain unchanged
38
+ - Compatibility class: additive target and state reflection only; runtime,
39
+ default styling, DOM semantics, and public props remain unchanged
40
40
  - Controlled/uncontrolled behavior: unchanged; Slider remains controlled
41
41
  - Migration decision: none
42
42
 
@@ -48,7 +48,8 @@ Consumer migration instructions belong in consumer docs and release notes.
48
48
 
49
49
  - The current slider row, background track, filled range, tick marks and labels,
50
50
  thumbs, and adjacent text value presentation.
51
- - The existing `slider`, `slider-track`, and `slider-thumb` public targets.
51
+ - The `slider`, `slider-control`, `slider-track`, and `slider-thumb` public
52
+ targets.
52
53
 
53
54
  **Does not own / non-goals**
54
55
 
@@ -61,24 +62,26 @@ Consumer migration instructions belong in consumer docs and release notes.
61
62
 
62
63
  ## Public concepts
63
64
 
64
- No new public concept is introduced. Consumer props, modes, states, and usage
65
- remain documented in `Slider.doc.mjs`.
65
+ This adds one public theming target without adding or changing a component prop,
66
+ value domain, or behavior. Consumer props, modes, states, and usage remain
67
+ documented in `Slider.doc.mjs`.
66
68
 
67
69
  ## Behavioral and layout contract
68
70
 
69
- | ID | Candidate invariant | Basis | Draft review state |
70
- | --- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
71
- | FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
72
- | FR2 | `Slider`, `Track`, and `Thumb` carry the existing `slider`, `slider-track`, and `slider-thumb` targets respectively. | Current source and public docs | Verified current behavior; no target change |
73
- | FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target. | Current source and public docs | Verified current asymmetry; not ratified as policy |
74
- | FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip. | Current source and focused tests | Verified composition boundary |
71
+ | ID | Candidate invariant | Basis | Review state |
72
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
73
+ | FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
74
+ | FR2 | `Slider`, `Interactive control`, `Track`, and `Thumb` carry the `slider`, `slider-control`, `slider-track`, and `slider-thumb` targets respectively. | Current source, public docs, and #6224 | Approved additive target contract |
75
+ | FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target. | Current source and public docs | Verified current asymmetry; not ratified as policy |
76
+ | FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip. | Current source and focused tests | Verified composition boundary |
75
77
 
76
78
  ### Observed current target asymmetry
77
79
 
78
80
  ProgressBar currently exposes targets for its fill and marks, while Slider
79
- exposes targets for its root row, background track, and thumbs but not its
80
- filled range, tick marks, mark labels, or adjacent text value display. This is
81
- implementation evidence for a joint audit, not approval of either target shape.
81
+ exposes targets for its root row, interactive control, background track, and
82
+ thumbs but not its filled range, tick marks, mark labels, or adjacent text value
83
+ display. This is implementation evidence for a joint audit, not approval of
84
+ either component's remaining target shape.
82
85
 
83
86
  ### Allowed variation
84
87
 
@@ -108,20 +111,21 @@ implementation evidence for a joint audit, not approval of either target shape.
108
111
 
109
112
  ## Accessibility contract
110
113
 
111
- This draft does not change or extend Slider's existing accessible name, value,
114
+ This contract does not change or extend Slider's existing accessible name, value,
112
115
  range-thumb naming, description/status association, keyboard behavior, disabled
113
116
  behavior, or value-tooltip behavior.
114
117
 
115
118
  ## Design relationships
116
119
 
117
- | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
118
- | ----------------------- | ------------------------------------------------------------- | ------------------------------- | -------------- | ------------------ |
119
- | Label and description | Identify and explain the numeric setting. | Current shared-component source | Supporting | FR4 |
120
- | Slider and track | Arrange the current interactive range control and rail. | Current source and public docs | Prominent | FR1, FR2 |
121
- | Filled range and thumbs | Show the selected value or interval over the available range. | Current source and public docs | Prominent | FR1, FR2, FR3 |
122
- | Tick marks and labels | Show optional supplied positions and their text. | Current source and public docs | Supporting | FR1, FR3 |
123
- | Value presentation | Shows the formatted value as text or a shared Tooltip. | Current source and public docs | Supporting | FR3, FR4 |
124
- | Status message | Presents shared validation feedback below the slider. | Current shared-component source | Supporting | FR4 |
120
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
121
+ | ------------------------------ | ---------------------------------------------------------------------------------------------- | ------------------------------- | -------------- | ------------------ |
122
+ | Label and description | Identify and explain the numeric setting. | Current shared-component source | Supporting | FR4 |
123
+ | Slider and interactive control | Separate the outer row from the pointer/keyboard surface and its composite disabled treatment. | Current source and public docs | Prominent | FR1, FR2 |
124
+ | Track | Shows the available range behind the fill. | Current source and public docs | Prominent | FR1, FR2 |
125
+ | Filled range and thumbs | Show the selected value or interval over the available range. | Current source and public docs | Prominent | FR1, FR2, FR3 |
126
+ | Tick marks and labels | Show optional supplied positions and their text. | Current source and public docs | Supporting | FR1, FR3 |
127
+ | Value presentation | Shows the formatted value as text or a shared Tooltip. | Current source and public docs | Supporting | FR3, FR4 |
128
+ | Status message | Presents shared validation feedback below the slider. | Current shared-component source | Supporting | FR4 |
125
129
 
126
130
  ### Theming anatomy
127
131
 
@@ -138,6 +142,7 @@ behavior, or value-tooltip behavior.
138
142
  }
139
143
  },
140
144
  "Slider": {"target": "slider"},
145
+ "Interactive control": {"target": "slider-control"},
141
146
  "Track": {"target": "slider-track"},
142
147
  "Filled range": {
143
148
  "none": {
@@ -172,10 +177,12 @@ behavior, or value-tooltip behavior.
172
177
  }
173
178
  ```
174
179
 
175
- `Filled range`, `Tick mark`, `Mark label`, and `Value display` remain stable
176
- consumer anatomy, but no current Slider target reaches them. The map records
177
- those gaps without making their absence intentional. `Value display` names the
178
- adjacent text mode; the separately listed value tooltip retains Tooltip's target.
180
+ `Interactive control` names the pointer/keyboard hit surface and the composite
181
+ opacity boundary around the rail, fill, marks, and thumbs. `Filled range`, `Tick
182
+ mark`, `Mark label`, and `Value display` remain stable consumer anatomy, but no
183
+ current Slider target reaches them. The map records those gaps without making
184
+ their absence intentional. `Value display` names the adjacent text mode; the
185
+ separately listed value tooltip retains Tooltip's target.
179
186
 
180
187
  ## Family and system relationships
181
188
 
@@ -191,24 +198,31 @@ adjacent text mode; the separately listed value tooltip retains Tooltip's target
191
198
 
192
199
  ## Verification map
193
200
 
194
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
195
- | ------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------- |
196
- | FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
197
- | FR2 | `themingTargets.test.ts` | Root, track, and thumb target call sites | Source and public target metadata drift fails the target guard. | `audit:Slider/theming` |
198
- | FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
199
- | FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
200
- | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and three current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
201
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
202
+ | ------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------- |
203
+ | FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
204
+ | FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
205
+ | FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
206
+ | FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
207
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
201
208
 
202
- The focused Slider suite does not separately assert the exact `slider`,
203
- `slider-track`, or `slider-thumb` class placement. The source/metadata target
204
- guard covers those declarations. It also covers only the disabled-reason
205
- Tooltip path, not the `valueDisplay="tooltip"` composition; that anatomy is
206
- source-inspected and remains missing focused test coverage.
209
+ The focused Slider suite asserts the `slider-control` target's orientation and
210
+ disabled state. Source/metadata guards cover all four target declarations. The
211
+ suite covers only the disabled-reason Tooltip path, not the
212
+ `valueDisplay="tooltip"` composition; that anatomy is source-inspected and
213
+ remains missing focused test coverage.
207
214
 
208
215
  ## Decision log
209
216
 
210
- None. This draft records current facts and introduces no component-local design,
211
- family, theming, or API decision.
217
+ ### DEC-1 — Interactive control is public Slider anatomy
218
+
219
+ **Reference:** `component:Slider/DEC-1`
220
+ **Decider:** cixzhang, 2026-09-14
221
+
222
+ The stable pointer/keyboard interaction and compositing surface is a
223
+ consumer-recognizable part, so it carries `slider-control`. `Thumb` remains
224
+ separate anatomy and continues to carry `slider-thumb`. This adds no default
225
+ behavior or styling change.
212
226
 
213
227
  ## Open questions
214
228
 
@@ -13,6 +13,7 @@ import {useState} from 'react';
13
13
  import {describe, it, expect, vi, beforeEach} from 'vitest';
14
14
  import {render, screen, act, fireEvent, waitFor} from '@testing-library/react';
15
15
  import userEvent from '@testing-library/user-event';
16
+ import {declaresPressedOverlay} from '../__tests__/pressState';
16
17
  import * as stylex from '@stylexjs/stylex';
17
18
  import {Slider} from './Slider';
18
19
  import {focusOutlineStyles} from '../utils/focusOutline.stylex';
@@ -137,6 +138,24 @@ describe('Slider', () => {
137
138
  },
138
139
  );
139
140
 
141
+ it('reflects orientation and disabled state on the interactive control target', () => {
142
+ render(
143
+ <Slider
144
+ label="Volume"
145
+ value={50}
146
+ orientation="vertical"
147
+ valueDisplay="text"
148
+ isDisabled
149
+ />,
150
+ );
151
+ const control = screen.getByRole('slider').parentElement;
152
+ expect(control).not.toBeNull();
153
+ expect(control).toHaveClass('astryx-slider-control');
154
+ expect(control).toHaveAttribute('data-orientation', 'vertical');
155
+ expect(control).toHaveAttribute('data-disabled', 'disabled');
156
+ expect(control).not.toContainElement(screen.getByText('50'));
157
+ });
158
+
140
159
  it('range mode sets correct aria values on both thumbs', () => {
141
160
  render(
142
161
  <Slider
@@ -1083,3 +1102,130 @@ describe('Slider', () => {
1083
1102
  });
1084
1103
  });
1085
1104
  });
1105
+
1106
+ describe('pressed state', () => {
1107
+ it('paints the pressed overlay on the thumb for as long as it is dragged, from wherever the press lands', () => {
1108
+ render(
1109
+ <Slider label="Volume" value={50} min={0} max={100} onChange={vi.fn()} />,
1110
+ );
1111
+ const thumb = screen.getByRole('slider');
1112
+ const trackContainer = thumb.parentElement!;
1113
+ trackContainer.getBoundingClientRect = () => ({
1114
+ left: 0,
1115
+ top: 0,
1116
+ right: 200,
1117
+ bottom: 20,
1118
+ width: 200,
1119
+ height: 20,
1120
+ x: 0,
1121
+ y: 0,
1122
+ toJSON: () => {},
1123
+ });
1124
+
1125
+ expect(declaresPressedOverlay(thumb)).toBe(false);
1126
+ // A slider is a drag: the press lands on the track, not the thumb, and
1127
+ // the thumb follows it. The pressed paint follows the drag.
1128
+ fireEvent.pointerDown(trackContainer, {
1129
+ clientX: 20,
1130
+ clientY: 10,
1131
+ pointerId: 1,
1132
+ });
1133
+ expect(declaresPressedOverlay(thumb)).toBe(true);
1134
+ fireEvent.pointerMove(trackContainer, {
1135
+ clientX: 120,
1136
+ clientY: 10,
1137
+ pointerId: 1,
1138
+ });
1139
+ expect(declaresPressedOverlay(thumb)).toBe(true);
1140
+ fireEvent.pointerUp(trackContainer, {
1141
+ clientX: 120,
1142
+ clientY: 10,
1143
+ pointerId: 1,
1144
+ });
1145
+ expect(declaresPressedOverlay(thumb)).toBe(false);
1146
+ });
1147
+
1148
+ it('paints only the thumb being dragged in a range slider', () => {
1149
+ render(
1150
+ <Slider
1151
+ label="Price"
1152
+ value={[20, 80] as [number, number]}
1153
+ min={0}
1154
+ max={100}
1155
+ onChange={vi.fn()}
1156
+ />,
1157
+ );
1158
+ const [start, end] = screen.getAllByRole('slider');
1159
+ // In range mode the control is the group; each thumb sits in its own
1160
+ // value-tooltip wrapper.
1161
+ const trackContainer = screen.getByRole('group');
1162
+ trackContainer.getBoundingClientRect = () => ({
1163
+ left: 0,
1164
+ top: 0,
1165
+ right: 200,
1166
+ bottom: 20,
1167
+ width: 200,
1168
+ height: 20,
1169
+ x: 0,
1170
+ y: 0,
1171
+ toJSON: () => {},
1172
+ });
1173
+ fireEvent.pointerDown(trackContainer, {
1174
+ clientX: 170,
1175
+ clientY: 10,
1176
+ pointerId: 1,
1177
+ });
1178
+ expect(declaresPressedOverlay(end)).toBe(true);
1179
+ expect(declaresPressedOverlay(start)).toBe(false);
1180
+ fireEvent.pointerUp(trackContainer, {
1181
+ clientX: 170,
1182
+ clientY: 10,
1183
+ pointerId: 1,
1184
+ });
1185
+ expect(declaresPressedOverlay(end)).toBe(false);
1186
+ });
1187
+
1188
+ it('keeps a direct press on the visible upper thumb when range values coincide', () => {
1189
+ render(
1190
+ <Slider
1191
+ label="Price"
1192
+ value={[50, 50] as [number, number]}
1193
+ min={0}
1194
+ max={100}
1195
+ onChange={vi.fn()}
1196
+ />,
1197
+ );
1198
+ const [start, end] = screen.getAllByRole('slider');
1199
+ const trackContainer = screen.getByRole('group');
1200
+ trackContainer.getBoundingClientRect = () => ({
1201
+ left: 0,
1202
+ top: 0,
1203
+ right: 200,
1204
+ bottom: 20,
1205
+ width: 200,
1206
+ height: 20,
1207
+ x: 0,
1208
+ y: 0,
1209
+ toJSON: () => {},
1210
+ });
1211
+
1212
+ fireEvent.pointerDown(end, {
1213
+ clientX: 100,
1214
+ clientY: 10,
1215
+ pointerId: 1,
1216
+ });
1217
+ expect(declaresPressedOverlay(end)).toBe(true);
1218
+ expect(declaresPressedOverlay(start)).toBe(false);
1219
+ });
1220
+
1221
+ it('does not press a disabled slider', () => {
1222
+ render(<Slider label="Volume" value={50} isDisabled onChange={vi.fn()} />);
1223
+ const thumb = screen.getByRole('slider');
1224
+ fireEvent.pointerDown(thumb.parentElement!, {
1225
+ clientX: 20,
1226
+ clientY: 10,
1227
+ pointerId: 1,
1228
+ });
1229
+ expect(declaresPressedOverlay(thumb)).toBe(false);
1230
+ });
1231
+ });
@@ -288,6 +288,15 @@ const styles = stylex.create({
288
288
  },
289
289
  },
290
290
  },
291
+ // Pressed: the system's pressed overlay over the thumb's fill for as long
292
+ // as the thumb is being dragged. A slider is a drag, not a tap — the finger
293
+ // lands anywhere on the track and the thumb follows it — so the pressed
294
+ // paint follows the drag state the pointer handlers already keep, on a
295
+ // mouse and on a finger alike, rather than `:active` on the thumb itself
296
+ // (which a press on the track never activates).
297
+ thumbPressed: {
298
+ backgroundImage: `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`,
299
+ },
291
300
  thumbDisabled: {
292
301
  backgroundColor: colorVars['--color-background-muted'],
293
302
  cursor: 'default',
@@ -701,17 +710,25 @@ export function Slider({ref, ...props}: SliderProps) {
701
710
  const newVal = markEl
702
711
  ? Number(markEl.dataset.markValue)
703
712
  : getValueFromPosition(e.clientX, e.clientY);
704
- const thumbIndex = getClosestThumb(newVal);
713
+ const track = trackRef.current;
714
+ const thumbs = track?.querySelectorAll<HTMLElement>('[role="slider"]');
715
+ const pressedThumb = (e.target as Element).closest<HTMLElement>(
716
+ '[role="slider"]',
717
+ );
718
+ const pressedThumbIndex =
719
+ pressedThumb == null || thumbs == null
720
+ ? -1
721
+ : Array.from(thumbs).indexOf(pressedThumb);
722
+ // A direct thumb press owns that thumb even when range values coincide.
723
+ // Track and mark presses still choose the nearest value.
724
+ const thumbIndex =
725
+ pressedThumbIndex >= 0 ? pressedThumbIndex : getClosestThumb(newVal);
705
726
  draggingThumbRef.current = thumbIndex;
706
727
  setDraggingThumb(thumbIndex);
707
728
  updateValue(thumbIndex, newVal);
708
729
 
709
- // Focus the closest thumb
710
- const track = trackRef.current;
711
- if (track) {
712
- const thumbs = track.querySelectorAll<HTMLElement>('[role="slider"]');
713
- thumbs[thumbIndex]?.focus();
714
- }
730
+ // Focus the thumb that owns this drag.
731
+ thumbs?.[thumbIndex]?.focus();
715
732
  // Also clear it explicitly: focusing an already-focused thumb fires no
716
733
  // focus event, so a thumb the user had tabbed to would keep its ring
717
734
  // through the drag.
@@ -908,6 +925,7 @@ export function Slider({ref, ...props}: SliderProps) {
908
925
  ? styles.thumbHorizontal
909
926
  : rtlStyles.centerInline('50%'),
910
927
  !isDisabled && styles.thumbHover,
928
+ !isDisabled && draggingThumb === thumbIndex && styles.thumbPressed,
911
929
  !isDisabled &&
912
930
  keyboardFocusThumb === thumbIndex &&
913
931
  focusOutlineStyles.focusVisible,
@@ -1029,12 +1047,18 @@ export function Slider({ref, ...props}: SliderProps) {
1029
1047
  onPointerMove={handlePointerMove}
1030
1048
  onPointerUp={handlePointerUp}
1031
1049
  onPointerCancel={handlePointerUp}
1032
- {...stylex.props(
1033
- styles.trackContainer,
1034
- isHorizontal
1035
- ? styles.trackContainerHorizontal
1036
- : styles.trackContainerVertical,
1037
- isDisabled && styles.trackContainerDisabled,
1050
+ {...mergeProps(
1051
+ themeProps('slider-control', {
1052
+ orientation,
1053
+ disabled: isDisabled ? 'disabled' : null,
1054
+ }),
1055
+ stylex.props(
1056
+ styles.trackContainer,
1057
+ isHorizontal
1058
+ ? styles.trackContainerHorizontal
1059
+ : styles.trackContainerVertical,
1060
+ isDisabled && styles.trackContainerDisabled,
1061
+ ),
1038
1062
  )}>
1039
1063
  {/* Background track */}
1040
1064
  <div
@@ -30,7 +30,7 @@ export const docs = {
30
30
  name: 'aria-label',
31
31
  type: 'string',
32
32
  description:
33
- 'Accessible name for screen readers. Defaults to label (if string) or "Loading".',
33
+ 'Accessible name for screen readers. Defaults to label (if string) or a translated "Loading" from the i18n catalog.',
34
34
  default: "'Loading'",
35
35
  },
36
36
  {
@@ -48,6 +48,7 @@ export const docs = {
48
48
  {name: '--spinner-stroke-width', description: 'Stroke width of both circles the ring is drawn from: the moving arc and the track behind it. Set it per size alongside the diameter. One stroke width drives both, so 0 is honoured as a zero-width stroke and paints nothing at all rather than falling back to the default; for an arc with no track behind it, set --spinner-track-color to transparent instead.', default: '2px (sm), 3px (md), 3px (lg), 4px (xl)'},
49
49
  {name: '--spinner-color', description: "Color of the moving arc. Defaults to the shade's token, so set it on a shade-variant target to retheme one shade (spinner: { 'shade:subtle': { '--spinner-color': 'var(--color-text-tertiary)' } }), or on the base target to retheme all four. Accepts any color notation, including var(), color-mix() and currentColor.", default: 'var(--color-accent) (default), var(--color-text-secondary) (subtle), var(--color-on-dark) (onMedia), currentColor (inherit)'},
50
50
  {name: '--spinner-track-color', description: 'Color of the track the arc travels on. Set it to `transparent` for an arc with no track. The onMedia and inherit shades draw the track at reduced alpha (30%) so it reads against an arbitrary backdrop; that fade applies to a themed color too.', default: 'var(--color-track) (default, subtle), var(--color-on-dark) (onMedia), currentColor (inherit)'},
51
+ {name: '--spinner-arc-fraction', description: "Fraction of the ring the moving arc covers, as a plain number (not a percentage or angle). Set it on a size-variant target the same way as --spinner-diameter, e.g. spinner: { 'size:xl': { '--spinner-arc-fraction': '0.75' } } for a 270deg sweep. Only takes effect once the stylesheet loads; a render with no CSS (SSR, no-JS) always draws the default 135deg arc.", default: '0.375 (135deg), same for every size'},
51
52
  ],
52
53
  },
53
54
  usage: {
@@ -87,7 +88,7 @@ export const docsZh = {
87
88
  {
88
89
  name: 'aria-label',
89
90
  type: 'string',
90
- description: '屏幕阅读器的无障碍名称。默认为 label(如果是字符串)或 "Loading"。',
91
+ description: '屏幕阅读器的无障碍名称。默认为 label(如果是字符串)或来自 i18n 词条的已翻译 "Loading"。',
91
92
  default: "'Loading'",
92
93
  },
93
94
  {
@@ -106,6 +107,7 @@ export const docsZh = {
106
107
  {name: '--spinner-stroke-width', description: '绘制环的两个圆——移动圆弧与其后的轨道——的描边宽度。与直径一起按尺寸设置。同一个描边宽度同时驱动两者,因此 0 会被采纳为零宽描边——什么都不绘制,而不会回退到默认值;若想要没有轨道的圆弧,请改将 --spinner-track-color 设为 transparent。', default: '2px (sm), 3px (md), 3px (lg), 4px (xl)'},
107
108
  {name: '--spinner-color', description: "运动圆弧的颜色。默认取所在 shade 的令牌,因此可在 shade 变体目标上设置以重新定义单个 shade——spinner: { 'shade:subtle': { '--spinner-color': 'var(--color-text-tertiary)' } }——或在 base 目标上设置以覆盖全部四种。接受任意颜色写法,包括 var()、color-mix() 与 currentColor。", default: 'var(--color-accent)(default)、var(--color-text-secondary)(subtle)、var(--color-on-dark)(onMedia)、currentColor(inherit)'},
108
109
  {name: '--spinner-track-color', description: '圆弧所在轨道的颜色。设为 `transparent` 可得到无轨道的圆弧。onMedia 与 inherit 两种 shade 会以降低的透明度(30%)绘制轨道,以便在任意背景上可辨;该淡化同样作用于主题化的颜色。', default: 'var(--color-track)(default、subtle)、var(--color-on-dark)(onMedia)、currentColor(inherit)'},
110
+ {name: '--spinner-arc-fraction', description: "运动圆弧覆盖圆环的比例,为纯数字(非百分比或角度)。在尺寸变体目标上设置,方式与 --spinner-diameter 相同,例如 spinner: { 'size:xl': { '--spinner-arc-fraction': '0.75' } } 可得到 270 度的圆弧。仅在样式表加载后生效;无 CSS 的渲染(SSR、无 JS)始终绘制默认的 135 度圆弧。", default: '0.375(135 度),各尺寸相同'},
109
111
  ],
110
112
  },
111
113
  usage: {
@@ -136,7 +138,8 @@ export const docsDense = {
136
138
  size: 'Spinner size: ring diameter (10px, 14px, 18px, 28px).',
137
139
  shade: 'Color shade for light or dark backgrounds.',
138
140
  label: 'Visible content below spinner. String auto-sets aria-label.',
139
- 'aria-label': 'A11y name for screen readers. Defaults to label or "Loading".',
141
+ 'aria-label':
142
+ 'A11y name for screen readers. Defaults to label or a translated "Loading".',
140
143
  xstyle: 'StyleX styles for layout customization. Must be stylex.create() value, not inline style.',
141
144
  },
142
145
  };
@@ -12,6 +12,7 @@
12
12
  import {describe, it, expect, vi, afterEach} from 'vitest';
13
13
  import {render, screen} from '@testing-library/react';
14
14
  import {Spinner} from './Spinner';
15
+ import {InternationalizationProvider} from '../i18n';
15
16
  import {defineTheme} from '../theme/defineTheme';
16
17
  import {generateThemeCSS} from '../theme/generateThemeRules';
17
18
 
@@ -72,6 +73,34 @@ describe('Spinner', () => {
72
73
  expect(spinner).toHaveAttribute('data-shade', 'inherit');
73
74
  });
74
75
 
76
+ it('localizes the default assistive label through the i18n catalog', () => {
77
+ render(
78
+ <InternationalizationProvider
79
+ locale="fr"
80
+ overrides={{fr: {'@astryx.spinner.loading': 'Chargement'}}}>
81
+ <Spinner data-testid="spinner" />
82
+ </InternationalizationProvider>,
83
+ );
84
+ expect(screen.getByTestId('spinner')).toHaveAttribute(
85
+ 'aria-label',
86
+ 'Chargement',
87
+ );
88
+ });
89
+
90
+ it('keeps an explicit aria-label over the localized default', () => {
91
+ render(
92
+ <InternationalizationProvider
93
+ locale="fr"
94
+ overrides={{fr: {'@astryx.spinner.loading': 'Chargement'}}}>
95
+ <Spinner aria-label="Veuillez patienter" data-testid="spinner" />
96
+ </InternationalizationProvider>,
97
+ );
98
+ expect(screen.getByTestId('spinner')).toHaveAttribute(
99
+ 'aria-label',
100
+ 'Veuillez patienter',
101
+ );
102
+ });
103
+
75
104
  it('does not duplicate a visible string label as aria-label', () => {
76
105
  render(<Spinner label="Fetching data" data-testid="spinner" />);
77
106
  const status = screen.getByRole('status');
@@ -204,6 +233,14 @@ describe('Spinner', () => {
204
233
  '.astryx-spinner {\n --spinner-color: var(--color-brand);',
205
234
  );
206
235
  });
236
+
237
+ it('scopes a themed arc fraction to that size variant (#5819)', () => {
238
+ expect(
239
+ cssFor({spinner: {'size:xl': {'--spinner-arc-fraction': '0.75'}}}),
240
+ ).toContain(
241
+ '.astryx-spinner[data-size="xl"] {\n --spinner-arc-fraction: 0.75;',
242
+ );
243
+ });
207
244
  });
208
245
  });
209
246
 
@@ -367,6 +404,10 @@ describe('Spinner ring', () => {
367
404
  frames.forEach(cb => cb(0));
368
405
  expect(animations).toHaveLength(5);
369
406
  expect(animations.every(a => a.startTime === 0)).toBe(true);
407
+ // The dash animation lives on the arc <circle>, a descendant of the
408
+ // <svg> this ref sits on, not the <svg> itself (#6253) — subtree:true
409
+ // is what lets getAnimations() find it from here.
410
+ expect(getAnimations).toHaveBeenCalledWith({subtree: true});
370
411
  // The pin runs on the branch the other shade cases cannot reach, so the
371
412
  // no-read assertion is made here too.
372
413
  expect(