@astryxdesign/core 0.7.2 → 0.7.3-canary.02f5fe4

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 (237) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  2. package/dist/BottomSheet/BottomSheet.js +8 -4
  3. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheetSwitcher.js +7 -0
  5. package/dist/Calendar/Calendar.js +3 -2
  6. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  7. package/dist/CheckboxInput/CheckboxInput.js +19 -24
  8. package/dist/ClickableCard/ClickableCard.d.ts +2 -2
  9. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  10. package/dist/ClickableCard/ClickableCard.js +11 -9
  11. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  12. package/dist/CodeBlock/CodeBlock.js +11 -1
  13. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  14. package/dist/ComplexSelector/ComplexSelector.js +23 -5
  15. package/dist/DateInput/DateInput.d.ts +4 -4
  16. package/dist/DateInput/DateInput.d.ts.map +1 -1
  17. package/dist/DateInput/DateInput.js +27 -20
  18. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  19. package/dist/DateInput/NativeDateField.js +14 -8
  20. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  21. package/dist/DateInput/TouchDateField.js +14 -10
  22. package/dist/DateRangeInput/DateRangeInput.d.ts +5 -5
  23. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  24. package/dist/DateRangeInput/DateRangeInput.js +27 -18
  25. package/dist/DateTimeInput/DateTimeInput.d.ts +5 -5
  26. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  27. package/dist/DateTimeInput/DateTimeInput.js +25 -27
  28. package/dist/DateTimeInput/NativeDateSegment.d.ts +3 -2
  29. package/dist/DateTimeInput/NativeDateSegment.d.ts.map +1 -1
  30. package/dist/DateTimeInput/NativeDateSegment.js +5 -5
  31. package/dist/DateTimeInput/NativeTimeSegment.d.ts +3 -2
  32. package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
  33. package/dist/DateTimeInput/NativeTimeSegment.js +5 -5
  34. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  35. package/dist/DateTimeInput/TouchDateTimeField.js +12 -9
  36. package/dist/Dialog/Dialog.d.ts.map +1 -1
  37. package/dist/Dialog/Dialog.js +9 -4
  38. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  39. package/dist/DropdownMenu/DropdownMenu.js +52 -10
  40. package/dist/DropdownMenu/DropdownMenuContext.d.ts +34 -1
  41. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  42. package/dist/DropdownMenu/DropdownMenuContext.js +15 -0
  43. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +21 -2
  44. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  45. package/dist/DropdownMenu/DropdownMenuSubMenu.js +375 -42
  46. package/dist/DropdownMenu/menuRegionStops.d.ts +2 -2
  47. package/dist/DropdownMenu/menuRegionStops.js +2 -2
  48. package/dist/FileInput/FileInput.d.ts.map +1 -1
  49. package/dist/FileInput/FileInput.js +14 -12
  50. package/dist/Link/Link.d.ts +2 -3
  51. package/dist/Link/Link.d.ts.map +1 -1
  52. package/dist/Link/Link.js +11 -19
  53. package/dist/Markdown/Markdown.d.ts.map +1 -1
  54. package/dist/Markdown/Markdown.js +48 -15
  55. package/dist/MultiSelector/MultiSelector.d.ts +4 -4
  56. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  57. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts +2 -0
  58. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts.map +1 -0
  59. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.js +48 -0
  60. package/dist/MultiSelector/MultiSelector.js +25 -18
  61. package/dist/Pagination/Pagination.d.ts.map +1 -1
  62. package/dist/Pagination/Pagination.js +5 -2
  63. package/dist/Popover/Popover.d.ts.map +1 -1
  64. package/dist/Popover/Popover.js +8 -4
  65. package/dist/Selector/Selector.d.ts +4 -4
  66. package/dist/Selector/Selector.d.ts.map +1 -1
  67. package/dist/Selector/Selector.js +23 -14
  68. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  69. package/dist/Selector/SelectorOption.js +1 -0
  70. package/dist/Slider/Slider.d.ts.map +1 -1
  71. package/dist/Slider/Slider.js +7 -14
  72. package/dist/Switch/Switch.d.ts.map +1 -1
  73. package/dist/Switch/Switch.js +25 -17
  74. package/dist/Switch/switch.markers.stylex.d.ts +2 -0
  75. package/dist/Switch/switch.markers.stylex.d.ts.map +1 -1
  76. package/dist/Switch/switch.markers.stylex.js +6 -0
  77. package/dist/TimeInput/TimeInput.d.ts +7 -6
  78. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  79. package/dist/TimeInput/TimeInput.js +37 -20
  80. package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
  81. package/dist/TimeInput/TouchTimeField.js +17 -6
  82. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  83. package/dist/Timestamp/Timestamp.js +41 -8
  84. package/dist/Token/Token.d.ts.map +1 -1
  85. package/dist/Token/Token.js +8 -3
  86. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  87. package/dist/Tooltip/Tooltip.js +36 -14
  88. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  89. package/dist/Tooltip/useTooltip.js +11 -2
  90. package/dist/TopNav/TopNav.d.ts.map +1 -1
  91. package/dist/TopNav/TopNav.js +2 -2
  92. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  93. package/dist/TopNav/TopNavHeading.js +1 -0
  94. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  95. package/dist/TreeList/TreeListItem.js +8 -3
  96. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  97. package/dist/Typeahead/Typeahead.js +14 -2
  98. package/dist/astryx.css +8 -2
  99. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  100. package/dist/hooks/useGridFocus.js +10 -1
  101. package/dist/hooks/useMenuPress.d.ts +11 -1
  102. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  103. package/dist/hooks/useMenuPress.js +43 -5
  104. package/dist/utils/disabledInteraction.d.ts +21 -0
  105. package/dist/utils/disabledInteraction.d.ts.map +1 -0
  106. package/dist/utils/disabledInteraction.js +33 -0
  107. package/dist/utils/timeParser.d.ts +4 -0
  108. package/dist/utils/timeParser.d.ts.map +1 -1
  109. package/dist/utils/timeParser.js +9 -2
  110. package/package.json +4 -4
  111. package/scripts/agent-doc-state.mjs +58 -9
  112. package/src/Banner/Banner.spec.md +37 -32
  113. package/src/BottomSheet/BottomSheet.spec.md +34 -54
  114. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  115. package/src/BottomSheet/BottomSheet.tsx +8 -4
  116. package/src/BottomSheet/BottomSheetSwitcher.spec.md +125 -58
  117. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +26 -0
  118. package/src/BottomSheet/BottomSheetSwitcher.tsx +6 -0
  119. package/src/Calendar/Calendar.test.tsx +68 -24
  120. package/src/Calendar/Calendar.tsx +2 -1
  121. package/src/CheckboxInput/CheckboxInput.test.tsx +44 -5
  122. package/src/CheckboxInput/CheckboxInput.tsx +16 -26
  123. package/src/CheckboxInput/__tests__/Checkbox.a11y.states.ts +3 -3
  124. package/src/CheckboxList/CheckboxList.test.tsx +9 -6
  125. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  126. package/src/ClickableCard/ClickableCard.test.tsx +5 -4
  127. package/src/ClickableCard/ClickableCard.tsx +9 -9
  128. package/src/CodeBlock/CodeBlock.highlightStyles.test.tsx +87 -0
  129. package/src/CodeBlock/CodeBlock.tsx +11 -1
  130. package/src/ComplexSelector/ComplexSelector.focusableDisabled.test.tsx +76 -0
  131. package/src/ComplexSelector/ComplexSelector.tsx +21 -4
  132. package/src/ContextMenu/ContextMenu.test.tsx +41 -0
  133. package/src/DateInput/DateInput.doc.mjs +9 -8
  134. package/src/DateInput/DateInput.test.tsx +88 -13
  135. package/src/DateInput/DateInput.tsx +49 -21
  136. package/src/DateInput/DateInputTouch.test.tsx +22 -1
  137. package/src/DateInput/NativeDateField.test.tsx +29 -3
  138. package/src/DateInput/NativeDateField.tsx +20 -7
  139. package/src/DateInput/TouchDateField.tsx +24 -9
  140. package/src/DateRangeInput/DateRangeInput.doc.mjs +6 -5
  141. package/src/DateRangeInput/DateRangeInput.spec.md +13 -13
  142. package/src/DateRangeInput/DateRangeInput.test.tsx +82 -16
  143. package/src/DateRangeInput/DateRangeInput.tsx +40 -20
  144. package/src/DateTimeInput/DateTimeInput.doc.mjs +9 -8
  145. package/src/DateTimeInput/DateTimeInput.test.tsx +126 -11
  146. package/src/DateTimeInput/DateTimeInput.tsx +42 -26
  147. package/src/DateTimeInput/DateTimeInputTouch.test.tsx +24 -0
  148. package/src/DateTimeInput/NativeDateSegment.tsx +9 -6
  149. package/src/DateTimeInput/NativePickerSegments.test.tsx +31 -0
  150. package/src/DateTimeInput/NativeTimeSegment.tsx +9 -6
  151. package/src/DateTimeInput/TouchDateTimeField.tsx +22 -7
  152. package/src/Dialog/Dialog.test.tsx +39 -0
  153. package/src/Dialog/Dialog.tsx +9 -4
  154. package/src/DropdownMenu/DropdownMenu.spec.md +33 -2
  155. package/src/DropdownMenu/DropdownMenu.tsx +79 -8
  156. package/src/DropdownMenu/DropdownMenuContext.public.test.ts +22 -0
  157. package/src/DropdownMenu/DropdownMenuContext.tsx +39 -1
  158. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +66 -7
  159. package/src/DropdownMenu/DropdownMenuItem.spec.md +209 -0
  160. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +16 -1
  161. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +623 -1
  162. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +556 -99
  163. package/src/DropdownMenu/__tests__/DropdownMenuItemAudit.a11y.chromium.spec.ts +366 -0
  164. package/src/DropdownMenu/__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts +267 -0
  165. package/src/DropdownMenu/menuRegionStops.ts +2 -2
  166. package/src/DropdownMenu/modules/useMenuPress.spec.md +9 -0
  167. package/src/FieldStatus/FieldStatus.spec.md +3 -2
  168. package/src/FileInput/FileInput.test.tsx +30 -7
  169. package/src/FileInput/FileInput.tsx +21 -11
  170. package/src/Link/Link.test.tsx +21 -7
  171. package/src/Link/Link.tsx +16 -24
  172. package/src/Link/__tests__/Link.a11y.states.ts +1 -1
  173. package/src/Markdown/Markdown.rerender.test.tsx +143 -0
  174. package/src/Markdown/Markdown.tsx +89 -35
  175. package/src/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.ts +71 -0
  176. package/src/MultiSelector/MultiSelector.doc.mjs +5 -5
  177. package/src/MultiSelector/MultiSelector.focusableDisabled.test.tsx +121 -0
  178. package/src/MultiSelector/MultiSelector.test.tsx +49 -7
  179. package/src/MultiSelector/MultiSelector.tsx +53 -29
  180. package/src/Pagination/Pagination.test.tsx +5 -1
  181. package/src/Pagination/Pagination.tsx +3 -1
  182. package/src/Popover/Popover.test.tsx +15 -0
  183. package/src/Popover/Popover.tsx +19 -8
  184. package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +3 -2
  185. package/src/PowerSearch/PowerSearchToken.test.tsx +7 -5
  186. package/src/Selector/Selector.doc.mjs +4 -4
  187. package/src/Selector/Selector.focusableDisabled.test.tsx +127 -0
  188. package/src/Selector/Selector.spec.md +11 -11
  189. package/src/Selector/Selector.test.tsx +64 -6
  190. package/src/Selector/Selector.tsx +51 -21
  191. package/src/Selector/SelectorOption.tsx +4 -0
  192. package/src/SideNav/SideNav.test.tsx +35 -0
  193. package/src/Slider/Slider.spec.md +11 -11
  194. package/src/Slider/Slider.test.tsx +3 -3
  195. package/src/Slider/Slider.tsx +8 -15
  196. package/src/Switch/Switch.test.tsx +85 -8
  197. package/src/Switch/Switch.tsx +26 -16
  198. package/src/Switch/__tests__/Switch.a11y.states.ts +2 -2
  199. package/src/Switch/switch.markers.stylex.ts +4 -0
  200. package/src/Table/Table.spec.md +2 -0
  201. package/src/Table/plugins/infiniteScroll/useTableInfiniteScroll.spec.md +244 -0
  202. package/src/Table/plugins/selection/useTableSelection.test.tsx +5 -3
  203. package/src/Table/plugins/virtualization/useTableVirtualization.spec.md +266 -0
  204. package/src/TimeInput/NativeTimeInput.test.tsx +25 -1
  205. package/src/TimeInput/Presentation.test.tsx +23 -0
  206. package/src/TimeInput/TimeInput.doc.mjs +13 -10
  207. package/src/TimeInput/TimeInput.test.tsx +139 -7
  208. package/src/TimeInput/TimeInput.tsx +53 -24
  209. package/src/TimeInput/TouchTimeField.tsx +27 -5
  210. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  211. package/src/Timestamp/Timestamp.test.tsx +168 -0
  212. package/src/Timestamp/Timestamp.tsx +62 -11
  213. package/src/Token/Token.test.tsx +26 -4
  214. package/src/Token/Token.tsx +6 -2
  215. package/src/Tokenizer/Tokenizer.spec.md +28 -28
  216. package/src/Tooltip/Tooltip.test.tsx +51 -6
  217. package/src/Tooltip/Tooltip.tsx +41 -18
  218. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +129 -0
  219. package/src/Tooltip/__tests__/Tooltip.a11y.renders.tsx +86 -0
  220. package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +132 -0
  221. package/src/Tooltip/__tests__/Tooltip.a11y.test.tsx +126 -0
  222. package/src/Tooltip/__tests__/TooltipHoverBridge.a11y.chromium.spec.ts +74 -0
  223. package/src/Tooltip/useTooltip.test.tsx +23 -0
  224. package/src/Tooltip/useTooltip.tsx +22 -9
  225. package/src/TopNav/TopNav.tsx +11 -1
  226. package/src/TopNav/TopNavHeading.tsx +1 -0
  227. package/src/TopNav/__tests__/TopNavMobileLongHeading.a11y.chromium.spec.ts +74 -0
  228. package/src/TreeList/TreeList.test.tsx +28 -0
  229. package/src/TreeList/TreeListItem.tsx +6 -2
  230. package/src/Typeahead/Typeahead.test.tsx +89 -0
  231. package/src/Typeahead/Typeahead.tsx +15 -2
  232. package/src/__tests__/DisabledTriggerFocus.a11y.chromium.spec.ts +86 -0
  233. package/src/hooks/useGridFocus.ts +16 -1
  234. package/src/hooks/useMenuPress.ts +45 -7
  235. package/src/utils/disabledInteraction.ts +40 -0
  236. package/src/utils/timeParser.test.ts +11 -0
  237. package/src/utils/timeParser.ts +9 -2
@@ -0,0 +1,127 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Selector.focusableDisabled.test.tsx
5
+ * @input Uses vitest, @testing-library/react, StyleX, Selector
6
+ * @output Conformance tests for spec:AST-085 FR1–FR4 and FR6 on Selector
7
+ * @position Testing; validates the disabled trigger of Selector.tsx
8
+ */
9
+
10
+ import {describe, it, expect, vi} from 'vitest';
11
+ import {act, render, screen} from '@testing-library/react';
12
+ import userEvent from '@testing-library/user-event';
13
+ import * as stylex from '@stylexjs/stylex';
14
+ import {colorVars} from '../theme/tokens.stylex';
15
+ import {Selector} from './Selector';
16
+
17
+ type Handler = () => void;
18
+ function Harness(props: {
19
+ isDisabled: boolean;
20
+ variant?: 'input' | 'secondary' | 'ghost';
21
+ onClick?: Handler;
22
+ onKeyDown?: Handler;
23
+ onMouseDown?: Handler;
24
+ onDoubleClick?: Handler;
25
+ }) {
26
+ return (
27
+ <form aria-label="Form">
28
+ <button type="button">Before</button>
29
+ <Selector
30
+ label="Fruit"
31
+ htmlName="fruit"
32
+ options={['Apple', 'Banana']}
33
+ value="Banana"
34
+ onChange={() => {}}
35
+ {...props}
36
+ />
37
+ <button type="button">After</button>
38
+ </form>
39
+ );
40
+ }
41
+
42
+ const trigger = () => screen.getByRole('combobox', {name: 'Fruit'});
43
+
44
+ describe('Selector disabled (spec:AST-085)', () => {
45
+ it('FR1: renders no native disabled and keeps its tab stop', async () => {
46
+ const user = userEvent.setup();
47
+ const {container} = render(<Harness isDisabled />);
48
+ expect(container.querySelector('[disabled]')).toBeNull();
49
+ expect(trigger()).toHaveAttribute('aria-disabled', 'true');
50
+ await user.click(screen.getByRole('button', {name: 'Before'}));
51
+ await user.tab();
52
+ expect(trigger()).toHaveFocus();
53
+ await user.tab();
54
+ expect(screen.getByRole('button', {name: 'After'})).toHaveFocus();
55
+ });
56
+
57
+ it('FR2: no press or key opens it or reaches a caller or ancestor handler', async () => {
58
+ const user = userEvent.setup();
59
+ const handlers = {
60
+ onClick: vi.fn(),
61
+ onKeyDown: vi.fn(),
62
+ onMouseDown: vi.fn(),
63
+ onDoubleClick: vi.fn(),
64
+ };
65
+ const onAncestorClick = vi.fn();
66
+ render(
67
+ <div onClick={onAncestorClick}>
68
+ <Harness isDisabled {...handlers} />
69
+ </div>,
70
+ );
71
+ await user.dblClick(trigger());
72
+ trigger().focus();
73
+ for (const key of ['{Enter}', ' ', '{ArrowDown}', 'b']) {
74
+ await user.keyboard(key);
75
+ }
76
+ expect(trigger()).toHaveAttribute('aria-expanded', 'false');
77
+ expect(screen.queryByRole('listbox')).toBeNull();
78
+ for (const handler of [...Object.values(handlers), onAncestorClick]) {
79
+ expect(handler).not.toHaveBeenCalled();
80
+ }
81
+ });
82
+
83
+ it('FR3: submits no value while disabled', () => {
84
+ const {rerender} = render(<Harness isDisabled={false} />);
85
+ const form = () =>
86
+ screen.getByRole<HTMLFormElement>('form', {name: 'Form'});
87
+ expect(new FormData(form()).getAll('fruit')).toEqual(['Banana']);
88
+ rerender(<Harness isDisabled />);
89
+ expect(new FormData(form()).getAll('fruit')).toEqual([]);
90
+ });
91
+
92
+ it('FR4: keeps focus when it becomes disabled while focused', () => {
93
+ const {rerender} = render(<Harness isDisabled={false} />);
94
+ act(() => trigger().focus());
95
+ rerender(<Harness isDisabled />);
96
+ expect(trigger()).toHaveFocus();
97
+ });
98
+
99
+ // jsdom cannot match `:focus-visible`: compare the ring's StyleX class hash.
100
+ const RING = stylex
101
+ .props(
102
+ stylex.create({
103
+ ring: {
104
+ boxShadow: {
105
+ default: null,
106
+ ':has(> [aria-disabled="true"]:focus-visible)': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
107
+ },
108
+ },
109
+ }).ring,
110
+ )
111
+ .className!.split(' ')
112
+ .pop()!;
113
+
114
+ it.each([
115
+ ['input', true],
116
+ ['secondary', false],
117
+ ['ghost', false],
118
+ ] as const)(
119
+ 'FR6: a disabled %s trigger carries its own ring: %s',
120
+ (variant, ring) => {
121
+ render(<Harness isDisabled variant={variant} />);
122
+ const wrapper = trigger().parentElement!;
123
+ expect(wrapper.classList.contains(RING)).toBe(ring);
124
+ expect(getComputedStyle(wrapper).cursor).toBe('default');
125
+ },
126
+ );
127
+ });
@@ -163,17 +163,17 @@ These requirements describe shipped behavior on current `main`.
163
163
 
164
164
  ### Representative states
165
165
 
166
- | State | Required invariant | Allowed variation |
167
- | ------------------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
168
- | closed with no value | Label and placeholder identify the field | Consumer placeholder text |
169
- | closed with a value | Selected option is represented in the trigger | Custom `renderValue` content |
170
- | pointer / popover | Anchored surface exposes the listbox without modal-dialog semantics | Default or explicit placement |
171
- | compact coarse pointer | BottomSheet exposes a modal dialog containing the listbox | Default or explicit `adaptive`, or explicit `bottom-sheet` |
172
- | searching | Visible options, keyboard navigation, and announced result count use one filter | Consumer search and empty text |
173
- | loading | Trigger is busy; empty and no-results output is suppressed | Consumer loading duration |
174
- | disabled with reason | Trigger remains focusable enough to expose the reason while activation stays blocked | Consumer reason text |
175
- | read-only | Value stays focusable and submittable; menu, clear, and disclosure affordances are absent | Value rendering, status, and busy presentation |
176
- | selected/unselected rows | Empty marks consume no width; visible marks retain space at the logical edge | State-dependent label position/width and themed indicators |
166
+ | State | Required invariant | Allowed variation |
167
+ | ------------------------ | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
168
+ | closed with no value | Label and placeholder identify the field | Consumer placeholder text |
169
+ | closed with a value | Selected option is represented in the trigger | Custom `renderValue` content |
170
+ | pointer / popover | Anchored surface exposes the listbox without modal-dialog semantics | Default or explicit placement |
171
+ | compact coarse pointer | BottomSheet exposes a modal dialog containing the listbox | Default or explicit `adaptive`, or explicit `bottom-sheet` |
172
+ | searching | Visible options, keyboard navigation, and announced result count use one filter | Consumer search and empty text |
173
+ | loading | Trigger is busy; empty and no-results output is suppressed | Consumer loading duration |
174
+ | disabled with reason | Trigger remains focusable (`spec:AST-085`) and exposes the reason; activation stays blocked | Consumer reason text |
175
+ | read-only | Value stays focusable and submittable; menu, clear, and disclosure affordances are absent | Value rendering, status, and busy presentation |
176
+ | selected/unselected rows | Empty marks consume no width; visible marks retain space at the logical edge | State-dependent label position/width and themed indicators |
177
177
 
178
178
  ### Transformation and precedence order
179
179
 
@@ -36,7 +36,7 @@ import {InternationalizationProvider} from '../i18n';
36
36
  import {defineTheme} from '../theme/defineTheme';
37
37
  import {Theme} from '../theme/Theme';
38
38
  import {generateThemeCSS} from '../theme/generateThemeRules';
39
- import {spacingVars} from '../theme/tokens.stylex';
39
+ import {colorVars, spacingVars} from '../theme/tokens.stylex';
40
40
  import {selectorPresentationStyles} from './selectorPresentation.stylex';
41
41
  import {groupStyles} from '../InputGroup/groupStyles';
42
42
 
@@ -303,6 +303,45 @@ describe('Selector', () => {
303
303
  );
304
304
  });
305
305
 
306
+ it('shows no disabled focus ring after a pointer pick (spec:AST-085 FR6)', async () => {
307
+ const field = (isDisabled: boolean) => (
308
+ <Selector
309
+ label="Fruit"
310
+ options={OPTIONS}
311
+ onChange={() => {}}
312
+ presentation="bottom-sheet"
313
+ isDisabled={isDisabled}
314
+ />
315
+ );
316
+ const {rerender} = render(field(false));
317
+ const trigger = screen.getByRole('combobox');
318
+ fireEvent.pointerDown(trigger, {pointerType: 'touch'});
319
+ fireEvent.click(trigger, {detail: 1});
320
+ const option = await screen.findByRole('option', {name: /Banana/});
321
+ fireEvent.pointerDown(option, {pointerType: 'touch'});
322
+ fireEvent.click(option, {detail: 1});
323
+ rerender(field(true));
324
+
325
+ const ring = stylex
326
+ .props(
327
+ stylex.create({
328
+ ring: {
329
+ boxShadow: {
330
+ default: null,
331
+ ':has(> [aria-disabled="true"]:focus-visible)': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
332
+ },
333
+ },
334
+ }).ring,
335
+ )
336
+ .className!.split(' ')
337
+ .pop()!;
338
+ const wrapper = screen.getByRole('combobox').parentElement!;
339
+ expect(wrapper).toHaveClass(
340
+ stylex.props(selectorPresentationStyles.pointerRestoredFocus).className!,
341
+ );
342
+ expect(wrapper).not.toHaveClass(ring);
343
+ });
344
+
306
345
  it('uses content-hugging height without extra bottom content padding', () => {
307
346
  const source = readFileSync(
308
347
  'packages/core/src/Selector/SelectorBottomSheet.tsx',
@@ -1821,9 +1860,11 @@ describe('Selector', () => {
1821
1860
  expect(screen.getByRole('combobox')).toHaveFocus();
1822
1861
  });
1823
1862
 
1824
- it('trigger is not focusable when disabled', () => {
1863
+ it('trigger keeps its tab stop when disabled (spec:AST-085 FR1)', () => {
1825
1864
  render(<Selector label="Fruit" options={OPTIONS} isDisabled />);
1826
- expect(screen.getByRole('combobox')).toHaveAttribute('tabIndex', '-1');
1865
+ const trigger = screen.getByRole('combobox');
1866
+ expect(trigger).toHaveAttribute('tabIndex', '0');
1867
+ expect(trigger).toHaveAttribute('aria-disabled', 'true');
1827
1868
  });
1828
1869
 
1829
1870
  it('opens the listbox with ArrowDown from a focused trigger', async () => {
@@ -2715,11 +2756,12 @@ describe('Selector', () => {
2715
2756
  expect(onChange).not.toHaveBeenCalled();
2716
2757
  });
2717
2758
 
2718
- it('remains non-focusable when disabled without a reason', () => {
2759
+ it('stays focusable when disabled without a reason (spec:AST-085 FR1)', () => {
2719
2760
  render(<Selector label="Fruit" options={OPTIONS} isDisabled />);
2720
2761
  const trigger = screen.getByRole('combobox');
2721
- expect(trigger).toBeDisabled();
2722
- expect(trigger).toHaveAttribute('tabIndex', '-1');
2762
+ expect(trigger).not.toHaveAttribute('disabled');
2763
+ expect(trigger).toHaveAttribute('aria-disabled', 'true');
2764
+ expect(trigger).toHaveAttribute('tabIndex', '0');
2723
2765
  });
2724
2766
  });
2725
2767
  describe('isReadOnly', () => {
@@ -4809,6 +4851,22 @@ describe('Selector in a narrow row', () => {
4809
4851
  expect(root.getAttribute('style')).toContain('80');
4810
4852
  expect(getComputedStyle(root).minWidth).toBe('0');
4811
4853
  });
4854
+
4855
+ it('lets a narrow list shrink an option so its label can ellipsize', () => {
4856
+ render(
4857
+ <Selector
4858
+ label="Project"
4859
+ options={['An option with a deliberately long readable label']}
4860
+ value={undefined}
4861
+ onChange={() => {}}
4862
+ isDefaultOpen
4863
+ />,
4864
+ );
4865
+ const option = screen
4866
+ .getByRole('option', {hidden: true})
4867
+ .querySelector('.astryx-selector-option')!;
4868
+ expect(getComputedStyle(option).minWidth).toBe('0');
4869
+ });
4812
4870
  });
4813
4871
 
4814
4872
  describe('Selector and option actions (not adopted)', () => {
@@ -291,6 +291,21 @@ const styles = stylex.create({
291
291
  '[data-astryx-press="on"]': 'scale(0.98)',
292
292
  },
293
293
  },
294
+ // A disabled input-variant trigger keeps its tab stop (`spec:AST-085` FR1),
295
+ // and the shared disabled wrapper style drops the field focus ring, so the
296
+ // trigger shows keyboard focus here instead (FR6). It keys on the trigger
297
+ // itself, a direct child, so a focused status button keeps its own outline.
298
+ triggerDisabledFocus: {
299
+ borderColor: {
300
+ default: colorVars['--color-border-emphasized'],
301
+ ':has(> [aria-disabled="true"]:focus-visible)':
302
+ colorVars['--color-accent'],
303
+ },
304
+ boxShadow: {
305
+ default: 'none',
306
+ ':has(> [aria-disabled="true"]:focus-visible)': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
307
+ },
308
+ },
294
309
  triggerGhostDisabled: {
295
310
  backgroundImage: 'none',
296
311
  transform: {
@@ -627,8 +642,8 @@ interface SelectorPropsBase<
627
642
  * The selected value stays visible, focusable, and included in form
628
643
  * submission, and retains its combobox identity with `aria-readonly`. The
629
644
  * selection surface and editing affordances are removed.
630
- * Unlike `isDisabled`, a read-only selector is not dimmed and stays in the
631
- * tab order. `isDisabled` takes precedence when both are set.
645
+ * Unlike `isDisabled`, a read-only selector is not dimmed and keeps its
646
+ * value in form submission. `isDisabled` takes precedence when both are set.
632
647
  * @default false
633
648
  */
634
649
  isReadOnly?: boolean;
@@ -640,8 +655,8 @@ interface SelectorPropsBase<
640
655
  * so the reason is discoverable by keyboard and assistive technology.
641
656
  * Activation stays blocked.
642
657
  *
643
- * Use this instead of wrapping a disabled selector in `Tooltip` — disabled
644
- * controls don't emit the pointer events an external tooltip needs.
658
+ * Use this instead of wrapping a disabled selector in `Tooltip`, so the
659
+ * reason is attached to the trigger itself.
645
660
  *
646
661
  * @example
647
662
  * ```
@@ -1872,8 +1887,24 @@ export function Selector<T extends SelectorOptionType>(
1872
1887
  });
1873
1888
  }
1874
1889
 
1890
+ // A disabled trigger stays focusable but, like a natively disabled button,
1891
+ // runs no caller event handler other than focus and blur, and a press on it
1892
+ // reaches no handler at all (`spec:AST-085` FR2).
1893
+ const callerProps = isDisabled
1894
+ ? Object.fromEntries(
1895
+ Object.entries(rest).filter(
1896
+ ([key]) => !/^on[A-Z]/.test(key) || /^on(Focus|Blur)/.test(key),
1897
+ ),
1898
+ )
1899
+ : rest;
1875
1900
  const triggerSharedProps: React.HTMLAttributes<HTMLElement> = {
1876
- ...rest,
1901
+ ...callerProps,
1902
+ onClick: isDisabled
1903
+ ? event => {
1904
+ event.preventDefault();
1905
+ event.stopPropagation();
1906
+ }
1907
+ : rest.onClick,
1877
1908
  id: triggerId,
1878
1909
  // A caller's description is kept alongside the field's own.
1879
1910
  'aria-describedby':
@@ -1887,12 +1918,14 @@ export function Selector<T extends SelectorOptionType>(
1887
1918
  // it leaves uncancelled. A caller `onClick` arrives through `rest` and
1888
1919
  // runs on the trigger before the press bubbles to the control box's
1889
1920
  // toggle, as it always has.
1890
- onKeyDown: composeEventHandlers(handleTriggerKeyDown, onKeyDownProp),
1921
+ onKeyDown: isDisabled
1922
+ ? handleTriggerKeyDown
1923
+ : composeEventHandlers(handleTriggerKeyDown, onKeyDownProp),
1891
1924
  onFocus: event => {
1892
1925
  onFocus?.(event);
1893
1926
  surface.onTriggerFocus(event);
1894
1927
  },
1895
- tabIndex: isDisabled && !showsDisabledMessage ? -1 : 0,
1928
+ tabIndex: 0,
1896
1929
  ...stylex.props(
1897
1930
  styles.trigger,
1898
1931
  isEffectivelyReadOnly && styles.triggerReadOnly,
@@ -1932,6 +1965,10 @@ export function Selector<T extends SelectorOptionType>(
1932
1965
  surface.isTriggerFocusRingSuppressed &&
1933
1966
  selectorPresentationStyles.pointerRestoredFocus,
1934
1967
  isDisabled && inputWrapperStyles.disabled,
1968
+ variant === 'input' &&
1969
+ isDisabled &&
1970
+ !surface.isTriggerFocusRingSuppressed &&
1971
+ styles.triggerDisabledFocus,
1935
1972
  isEffectivelyReadOnly && styles.triggerReadOnly,
1936
1973
  variant !== 'input' && isDisabled && styles.triggerGhostDisabled,
1937
1974
  variant !== 'input' &&
@@ -1991,22 +2028,15 @@ export function Selector<T extends SelectorOptionType>(
1991
2028
  ? getItemId(highlightedIndex)
1992
2029
  : undefined
1993
2030
  }
1994
- // With a disabledMessage the trigger keeps focusability via
1995
- // aria-disabled so the reason is focus-discoverable; activation is
1996
- // still blocked by the isDisabled guards in useCombobox.
1997
- disabled={isDisabled && !showsDisabledMessage}
1998
- aria-disabled={showsDisabledMessage ? 'true' : undefined}>
2031
+ // A disabled trigger keeps its tab stop and says so through
2032
+ // aria-disabled (`spec:AST-085` FR1); the isDisabled guards in
2033
+ // useCombobox block every way of opening it.
2034
+ aria-disabled={isDisabled ? 'true' : undefined}>
1999
2035
  {valueContent}
2000
2036
  </button>
2001
- {htmlName != null && (
2002
- <input
2003
- type="hidden"
2004
- name={htmlName}
2005
- value={value ?? ''}
2006
- // Disabled native controls are excluded from form submission;
2007
- // mirror that for the hidden carrier.
2008
- disabled={isDisabled}
2009
- />
2037
+ {/* A disabled selector submits no value (`spec:AST-085` FR3). */}
2038
+ {htmlName != null && !isDisabled && (
2039
+ <input type="hidden" name={htmlName} value={value ?? ''} />
2010
2040
  )}
2011
2041
  {isBusy && <Spinner size="sm" />}
2012
2042
  {hasClear && value != null && !isDisabled && !isEffectivelyReadOnly && (
@@ -22,6 +22,10 @@ const embeddedStyles = stylex.create({
22
22
  paddingBlock: 0,
23
23
  paddingInline: 0,
24
24
  borderRadius: 0,
25
+ // The host row lays this out as a flex item, whose automatic minimum
26
+ // size is the full label width. Without the reset a long label runs
27
+ // past a narrow popover and is clipped instead of ellipsizing.
28
+ minWidth: 0,
25
29
  },
26
30
  });
27
31
 
@@ -3691,3 +3691,38 @@ describe('SideNav collapse conflict warning', () => {
3691
3691
  expect(warn).not.toHaveBeenCalled();
3692
3692
  });
3693
3693
  });
3694
+
3695
+ // =============================================================================
3696
+ // SideNavItem — collapsed-from-mount rail tooltip
3697
+ // =============================================================================
3698
+
3699
+ describe('SideNavItem collapsed-from-mount rail tooltip', () => {
3700
+ // #7692: a childless item that mounts inside an already-collapsed SideNav
3701
+ // renders its Tooltip inside the very element anchorRef points at. React
3702
+ // attaches that ref only after the tooltip's layout effect has run, so the
3703
+ // tooltip never attached and a persisted collapsed rail lost all tooltips
3704
+ // on page load.
3705
+ it('anchors the rail tooltip when the nav is collapsed at mount', async () => {
3706
+ render(
3707
+ <SideNav
3708
+ aria-label="Main"
3709
+ collapsible={{isCollapsed: true, hasButton: false}}>
3710
+ <SideNavItem label="Dashboard" icon={StubIcon} href="/dashboard" />
3711
+ </SideNav>,
3712
+ );
3713
+ const link = screen.getByRole('link', {name: 'Dashboard'});
3714
+ const anchor = link.parentElement as HTMLElement;
3715
+
3716
+ // The tooltip attaches despite mounting inside its own anchor: the item
3717
+ // row is described by the tooltip layer.
3718
+ await waitFor(() => {
3719
+ expect(anchor.getAttribute('aria-describedby')).not.toBe('');
3720
+ });
3721
+ const layer = document.getElementById(
3722
+ anchor.getAttribute('aria-describedby') as string,
3723
+ );
3724
+ expect(layer).not.toBeNull();
3725
+ expect(layer).toHaveAttribute('role', 'tooltip');
3726
+ expect(layer).toHaveTextContent('Dashboard');
3727
+ });
3728
+ });
@@ -134,8 +134,8 @@ value-tooltip behavior; AR1 records the keyboard model and its one correction.
134
134
  thumb as one composite control: every thumb is `role="slider"` with an
135
135
  accessible name (a range names each thumb), `aria-valuemin`, `aria-valuemax`,
136
136
  `aria-valuenow`, `aria-valuetext` when a formatter changes the spoken value,
137
- `aria-orientation`, and `aria-disabled` when disabled; an enabled thumb, and a
138
- disabled thumb that carries a disabled message, is a Tab stop.
137
+ `aria-orientation`, and `aria-disabled` when disabled; every thumb is a Tab
138
+ stop, enabled or disabled (`spec:AST-085`).
139
139
  ArrowUp and ArrowDown step by `step`; on a horizontal track the horizontal
140
140
  arrows follow the track's direction — ArrowRight increases and ArrowLeft
141
141
  decreases under LTR, and under RTL ArrowLeft increases and ArrowRight
@@ -225,15 +225,15 @@ separately listed value tooltip retains Tooltip's target.
225
225
 
226
226
  ## Verification map
227
227
 
228
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
229
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
230
- | FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
231
- | AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider, in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value | A thumb loses its role, name or value properties; an arrow, Page, Home or End key stops stepping or steps against the mirrored track; a range thumb crosses the other | `audit:Slider/accessibility` |
232
- | FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
233
- | FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
234
- | FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
235
- | FR5 | Pending: `Slider.test.tsx` change-end cases driven by an owner that renders, clamps, or rejects the emitted value (not yet in the suite) | Single and range; pointer release before and after the owner re-renders; each keyboard step | `onChangeEnd` reports a value the owner did not render, fires more than once per interaction, or skips a completed keyboard step | `audit:Slider/accessibility` |
236
- | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
228
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
229
+ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
230
+ | FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
231
+ | AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider; disabled-thumb Tab stop pending conformance; in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value | A thumb loses its role, name or value properties; an arrow, Page, Home or End key stops stepping or steps against the mirrored track; a range thumb crosses the other | `audit:Slider/accessibility` |
232
+ | FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
233
+ | FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
234
+ | FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
235
+ | FR5 | Pending: `Slider.test.tsx` change-end cases driven by an owner that renders, clamps, or rejects the emitted value (not yet in the suite) | Single and range; pointer release before and after the owner re-renders; each keyboard step | `onChangeEnd` reports a value the owner did not render, fires more than once per interaction, or skips a completed keyboard step | `audit:Slider/accessibility` |
236
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
237
237
 
238
238
  The focused Slider suite asserts the `slider-control` target's orientation and
239
239
  disabled state. Source/metadata guards cover all four target declarations. The
@@ -385,7 +385,7 @@ describe('Slider', () => {
385
385
  render(<Slider label="Volume" value={50} isDisabled />);
386
386
  const slider = screen.getByRole('slider');
387
387
  expect(slider).toHaveAttribute('aria-disabled', 'true');
388
- expect(slider).toHaveAttribute('tabIndex', '-1');
388
+ expect(slider).toHaveAttribute('tabIndex', '0');
389
389
  });
390
390
 
391
391
  it('does not fire onChange on pointer down when disabled', () => {
@@ -1111,11 +1111,11 @@ describe('Slider', () => {
1111
1111
  expect(onChange).not.toHaveBeenCalled();
1112
1112
  });
1113
1113
 
1114
- it('remains non-focusable when disabled without a reason', () => {
1114
+ it('stays focusable when disabled without a reason', () => {
1115
1115
  render(
1116
1116
  <Slider label="Volume" value={50} valueDisplay="none" isDisabled />,
1117
1117
  );
1118
- expect(screen.getByRole('slider')).toHaveAttribute('tabindex', '-1');
1118
+ expect(screen.getByRole('slider')).toHaveAttribute('tabindex', '0');
1119
1119
  });
1120
1120
  });
1121
1121
  describe('form participation', () => {
@@ -558,15 +558,12 @@ export function Slider({ref, ...props}: SliderProps) {
558
558
 
559
559
  const handleThumbFocus = useCallback(
560
560
  (thumbIndex: number, _e: FocusEvent<HTMLDivElement>) => {
561
- // A disabled thumb draws no ring even when it stays focusable for its
562
- // reason tooltip, so there is nothing to track for one.
561
+ // A focused disabled thumb still shows the ring (spec:AST-085 FR6).
563
562
  setKeyboardFocusThumb(
564
- !isDisabled && getInteractionModality() === 'keyboard'
565
- ? thumbIndex
566
- : null,
563
+ getInteractionModality() === 'keyboard' ? thumbIndex : null,
567
564
  );
568
565
  },
569
- [isDisabled],
566
+ [],
570
567
  );
571
568
 
572
569
  const handleThumbBlur = useCallback((_e: FocusEvent<HTMLDivElement>) => {
@@ -960,10 +957,8 @@ export function Slider({ref, ...props}: SliderProps) {
960
957
  ref={!isRange ? ref : undefined}
961
958
  id={!isRange ? id : undefined}
962
959
  role="slider"
963
- // With a disabledMessage the thumb keeps focusability so the reason is
964
- // focus-discoverable; value changes stay blocked by the isDisabled
965
- // guards in the pointer/keyboard handlers.
966
- tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
960
+ // A disabled thumb keeps its Tab stop (spec:AST-085 FR1).
961
+ tabIndex={0}
967
962
  aria-valuemin={ariaValueMin}
968
963
  aria-valuemax={ariaValueMax}
969
964
  aria-valuenow={val}
@@ -999,8 +994,7 @@ export function Slider({ref, ...props}: SliderProps) {
999
994
  : rtlStyles.centerInline('50%'),
1000
995
  !isDisabled && styles.thumbHover,
1001
996
  !isDisabled && draggingThumb === thumbIndex && styles.thumbPressed,
1002
- !isDisabled &&
1003
- keyboardFocusThumb === thumbIndex &&
997
+ keyboardFocusThumb === thumbIndex &&
1004
998
  focusOutlineStyles.focusVisible,
1005
999
  isDisabled && styles.thumbDisabled,
1006
1000
  ),
@@ -1095,7 +1089,9 @@ export function Slider({ref, ...props}: SliderProps) {
1095
1089
  }),
1096
1090
  stylex.props(styles.sliderRow),
1097
1091
  )}>
1092
+ {/* A disabled slider submits no value (spec:AST-085 FR3). */}
1098
1093
  {htmlName != null &&
1094
+ !isDisabled &&
1099
1095
  values.map((v, i) => (
1100
1096
  <input
1101
1097
  // Positional identity: index 0 is the start thumb, 1 the end.
@@ -1103,9 +1099,6 @@ export function Slider({ref, ...props}: SliderProps) {
1103
1099
  type="hidden"
1104
1100
  name={htmlName}
1105
1101
  value={String(v)}
1106
- // Disabled native controls are excluded from form submission;
1107
- // mirror that for the hidden carrier.
1108
- disabled={isDisabled}
1109
1102
  />
1110
1103
  ))}
1111
1104
  <div