@astryxdesign/core 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +41 -3
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
  4. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
  9. package/dist/Center/Center.d.ts +23 -16
  10. package/dist/Center/Center.d.ts.map +1 -1
  11. package/dist/Center/Center.js +7 -5
  12. package/dist/CodeBlock/CodeBlock.js +2 -2
  13. package/dist/DateInput/DateInput.d.ts.map +1 -1
  14. package/dist/DateInput/DateInput.js +12 -2
  15. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  16. package/dist/DateTimeInput/DateTimeInput.js +12 -2
  17. package/dist/Field/Field.d.ts.map +1 -1
  18. package/dist/Field/Field.js +1 -0
  19. package/dist/Field/InputClearButton.d.ts +2 -2
  20. package/dist/Field/InputClearButton.d.ts.map +1 -1
  21. package/dist/Field/InputClearButton.js +5 -1
  22. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  23. package/dist/Field/PanelSearchInput.js +16 -4
  24. package/dist/FileInput/FileInput.d.ts.map +1 -1
  25. package/dist/FileInput/FileInput.js +12 -1
  26. package/dist/HoverCard/useHoverCard.js +2 -2
  27. package/dist/Indicator/CheckboxIndicator.js +2 -2
  28. package/dist/Indicator/RadioIndicator.js +2 -2
  29. package/dist/Layer/layerStack.d.ts +10 -0
  30. package/dist/Layer/layerStack.d.ts.map +1 -1
  31. package/dist/Layer/layerStack.js +21 -9
  32. package/dist/Layer/useLayerDismissal.d.ts +2 -3
  33. package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
  34. package/dist/Layer/useLayerDismissal.js +2 -3
  35. package/dist/NavIcon/NavIcon.js +2 -2
  36. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  37. package/dist/NumberInput/NumberInput.js +12 -2
  38. package/dist/Popover/usePopover.d.ts +3 -2
  39. package/dist/Popover/usePopover.d.ts.map +1 -1
  40. package/dist/Popover/usePopover.js +4 -2
  41. package/dist/ProgressBar/ProgressBar.js +2 -2
  42. package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
  43. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
  44. package/dist/ScrollableArea/ScrollableArea.js +144 -0
  45. package/dist/ScrollableArea/index.d.ts +11 -0
  46. package/dist/ScrollableArea/index.d.ts.map +1 -0
  47. package/dist/ScrollableArea/index.js +11 -0
  48. package/dist/StatusDot/StatusDot.js +2 -2
  49. package/dist/TextArea/TextArea.js +2 -2
  50. package/dist/TextInput/TextInput.d.ts.map +1 -1
  51. package/dist/TextInput/TextInput.js +19 -4
  52. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  53. package/dist/TimeInput/TimeInput.js +12 -2
  54. package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
  55. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  56. package/dist/Typeahead/BaseTypeahead.js +56 -20
  57. package/dist/astryx.css +15 -0
  58. package/dist/hooks/index.d.ts +2 -0
  59. package/dist/hooks/index.d.ts.map +1 -1
  60. package/dist/hooks/index.js +1 -0
  61. package/dist/hooks/scrollGeometry.d.ts +24 -0
  62. package/dist/hooks/scrollGeometry.d.ts.map +1 -0
  63. package/dist/hooks/scrollGeometry.js +86 -0
  64. package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
  65. package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
  66. package/dist/hooks/scrollOwnerRegistry.js +24 -0
  67. package/dist/hooks/useFocusTrap.d.ts +8 -0
  68. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  69. package/dist/hooks/useFocusTrap.js +22 -11
  70. package/dist/hooks/useScrollableArea.d.ts +51 -0
  71. package/dist/hooks/useScrollableArea.d.ts.map +1 -0
  72. package/dist/hooks/useScrollableArea.js +287 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -0
  76. package/dist/theme/defineTheme.d.ts +2 -6
  77. package/dist/theme/defineTheme.d.ts.map +1 -1
  78. package/dist/theme/defineTheme.js +1 -1
  79. package/dist/theme/derivedVarRegistry.js +1 -1
  80. package/dist/theme/localTokens.d.ts +8 -11
  81. package/dist/theme/localTokens.d.ts.map +1 -1
  82. package/dist/theme/localTokens.js +17 -71
  83. package/dist/theme/themeAdaptations.d.ts.map +1 -1
  84. package/dist/theme/themeAdaptations.js +4 -4
  85. package/dist/utils/themeProps.d.ts +10 -10
  86. package/dist/utils/themeProps.d.ts.map +1 -1
  87. package/dist/utils/themeProps.js +27 -10
  88. package/locales/en.json +16 -0
  89. package/locales/pseudo.json +12 -0
  90. package/package.json +7 -2
  91. package/src/AppShell/AppShell.test.tsx +36 -0
  92. package/src/AppShell/AppShell.tsx +4 -1
  93. package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
  94. package/src/Banner/Banner.test.tsx +3 -1
  95. package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
  96. package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
  97. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
  98. package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
  99. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
  100. package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
  101. package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
  102. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
  103. package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
  104. package/src/Center/Center.doc.mjs +32 -28
  105. package/src/Center/Center.spec.md +225 -0
  106. package/src/Center/Center.test.tsx +42 -4
  107. package/src/Center/Center.tsx +24 -17
  108. package/src/Chat/ChatSystemMessage.test.tsx +2 -9
  109. package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
  110. package/src/CodeBlock/CodeBlock.tsx +2 -2
  111. package/src/DateInput/DateInput.test.tsx +4 -4
  112. package/src/DateInput/DateInput.tsx +15 -4
  113. package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
  114. package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
  115. package/src/DateTimeInput/DateTimeInput.tsx +18 -7
  116. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
  117. package/src/Field/Field.test.tsx +42 -0
  118. package/src/Field/Field.tsx +6 -0
  119. package/src/Field/InputClearButton.test.tsx +35 -1
  120. package/src/Field/InputClearButton.tsx +7 -3
  121. package/src/Field/PanelSearchInput.tsx +21 -8
  122. package/src/FieldStatus/FieldStatus.spec.md +27 -17
  123. package/src/FieldStatus/FieldStatus.test.tsx +7 -5
  124. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
  125. package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
  126. package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
  127. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
  128. package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
  129. package/src/FileInput/FileInput.tsx +10 -1
  130. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
  131. package/src/HoverCard/HoverCard.doc.mjs +4 -4
  132. package/src/HoverCard/useHoverCard.tsx +2 -2
  133. package/src/Indicator/CheckboxIndicator.tsx +2 -2
  134. package/src/Indicator/Indicator.doc.mjs +2 -2
  135. package/src/Indicator/Indicator.test.tsx +1 -1
  136. package/src/Indicator/RadioIndicator.tsx +2 -2
  137. package/src/Layer/layerStack.ts +20 -9
  138. package/src/Layer/useLayerDismissal.ts +2 -3
  139. package/src/MultiSelector/MultiSelector.test.tsx +4 -4
  140. package/src/NavIcon/NavIcon.doc.mjs +4 -4
  141. package/src/NavIcon/NavIcon.tsx +2 -2
  142. package/src/NumberInput/NumberInput.tsx +18 -7
  143. package/src/Popover/Popover.doc.mjs +10 -10
  144. package/src/Popover/Popover.spec.md +55 -65
  145. package/src/Popover/Popover.test.tsx +29 -0
  146. package/src/Popover/usePopover.doc.mjs +4 -4
  147. package/src/Popover/usePopover.tsx +7 -4
  148. package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
  149. package/src/ProgressBar/ProgressBar.test.tsx +1 -31
  150. package/src/ProgressBar/ProgressBar.tsx +2 -2
  151. package/src/RadioList/RadioList.test.tsx +5 -144
  152. package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
  153. package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
  154. package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
  155. package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
  156. package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
  157. package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
  158. package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
  159. package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
  160. package/src/ScrollableArea/ScrollableArea.tsx +259 -0
  161. package/src/ScrollableArea/index.ts +26 -0
  162. package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
  163. package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
  164. package/src/Selector/Selector.test.tsx +4 -4
  165. package/src/Spinner/Spinner.test.tsx +0 -18
  166. package/src/StatusDot/StatusDot.doc.mjs +4 -4
  167. package/src/StatusDot/StatusDot.tsx +2 -2
  168. package/src/TabList/TabList.test.tsx +5 -9
  169. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
  170. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
  171. package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
  172. package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
  173. package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
  174. package/src/Table/Table.doc.mjs +2 -2
  175. package/src/TextArea/TextArea.doc.mjs +4 -4
  176. package/src/TextArea/TextArea.tsx +2 -2
  177. package/src/TextInput/TextInput.doc.mjs +2 -1
  178. package/src/TextInput/TextInput.test.tsx +94 -0
  179. package/src/TextInput/TextInput.tsx +22 -6
  180. package/src/TimeInput/TimeInput.tsx +18 -7
  181. package/src/Toast/ToastViewport.test.tsx +1 -39
  182. package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
  183. package/src/Typeahead/BaseTypeahead.spec.md +269 -0
  184. package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
  185. package/src/Typeahead/BaseTypeahead.tsx +99 -30
  186. package/src/hooks/index.ts +13 -0
  187. package/src/hooks/scrollGeometry.ts +155 -0
  188. package/src/hooks/scrollOwnerRegistry.ts +47 -0
  189. package/src/hooks/useFocusTrap.ts +22 -11
  190. package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
  191. package/src/hooks/useScrollableArea.doc.mjs +108 -0
  192. package/src/hooks/useScrollableArea.test.tsx +437 -0
  193. package/src/hooks/useScrollableArea.ts +469 -0
  194. package/src/index.ts +1 -0
  195. package/src/theme/defineTheme.test.ts +65 -105
  196. package/src/theme/defineTheme.ts +3 -9
  197. package/src/theme/derivedVarRegistry.ts +1 -1
  198. package/src/theme/localTokens.ts +25 -96
  199. package/src/theme/publicThemeHelperContract.test.ts +2 -2
  200. package/src/theme/themeAdaptations.test.ts +16 -42
  201. package/src/theme/themeAdaptations.ts +6 -9
  202. package/src/utils/themeProps.test.ts +29 -10
  203. package/src/utils/themeProps.ts +36 -17
@@ -0,0 +1,305 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file StatusMessage.a11y.renders.tsx
7
+ * @input Uses current Core status-message components and the binding state ids
8
+ * @output One public-interface rendering for every Core status-message binding state
9
+ * @position Shared jsdom and checked-in Storybook fixture layer
10
+ */
11
+
12
+ import {useEffect, useState, type ReactElement, type ReactNode} from 'react';
13
+ import {ChatSystemMessage} from '../../Chat/ChatSystemMessage';
14
+ import {ProgressBar} from '../../ProgressBar/ProgressBar';
15
+ import {Spinner} from '../../Spinner/Spinner';
16
+ import {Toast} from '../../Toast/Toast';
17
+ import {ToastViewport} from '../../Toast/ToastViewport';
18
+ import {useToast} from '../../Toast/useToast';
19
+ import {useAnnounce, type AnnouncePoliteness} from '../../hooks/useAnnounce';
20
+ import {FieldStatus} from '../FieldStatus';
21
+ import type {CoreStatusMessageStateId} from './StatusMessage.a11y.states';
22
+
23
+ const transitionTestId = (name: string) => `status-transition-${name}`;
24
+
25
+ function TransitionButton({
26
+ name,
27
+ onClick,
28
+ }: {
29
+ name: string;
30
+ onClick: () => void;
31
+ }) {
32
+ return (
33
+ <button
34
+ data-testid={transitionTestId(name)}
35
+ type="button"
36
+ onClick={onClick}>
37
+ {name}
38
+ </button>
39
+ );
40
+ }
41
+
42
+ function Frame({
43
+ children,
44
+ isReady = true,
45
+ }: {
46
+ children: ReactNode;
47
+ isReady?: boolean;
48
+ }) {
49
+ return (
50
+ <div>
51
+ <span data-a11y-ready={String(isReady)} hidden />
52
+ <button data-a11y-relation="focus-anchor" type="button">
53
+ Keep focus
54
+ </button>
55
+ {children}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Establishes the same singleton channel the component will update, then clears
62
+ * the seed before the test starts. This lets a browser binding inspect the
63
+ * pre-existing empty node; the contract's plain-HTML fixtures separately prove
64
+ * the first-use insertion order.
65
+ */
66
+ function PrimeLiveRegion({politeness}: {politeness: AnnouncePoliteness}) {
67
+ const announce = useAnnounce();
68
+ const [isReady, setIsReady] = useState(false);
69
+ useEffect(() => {
70
+ let secondFrame = 0;
71
+ announce('Preparing status channel', politeness);
72
+ const firstFrame = requestAnimationFrame(() => {
73
+ secondFrame = requestAnimationFrame(() => {
74
+ announce('', politeness);
75
+ setIsReady(true);
76
+ });
77
+ });
78
+ return () => {
79
+ cancelAnimationFrame(firstFrame);
80
+ cancelAnimationFrame(secondFrame);
81
+ };
82
+ }, [announce, politeness]);
83
+ return <span data-a11y-ready={String(isReady)} hidden />;
84
+ }
85
+
86
+ function ToastAnnouncementButtons({type}: {type: 'info' | 'error'}) {
87
+ const toast = useToast();
88
+ const message = type === 'error' ? 'Upload failed' : 'Changes saved';
89
+ const replacement =
90
+ type === 'error' ? 'Connection failed' : 'Profile updated';
91
+ const show = (body: string) =>
92
+ toast({
93
+ body,
94
+ type,
95
+ uniqueID: `status-contract-${type}`,
96
+ isAutoHide: false,
97
+ });
98
+ return (
99
+ <>
100
+ <TransitionButton name="show" onClick={() => show(message)} />
101
+ <TransitionButton name="replace" onClick={() => show(replacement)} />
102
+ <TransitionButton name="repeat" onClick={() => show(message)} />
103
+ </>
104
+ );
105
+ }
106
+
107
+ function ToastAnnouncementHarness({type}: {type: 'info' | 'error'}) {
108
+ const politeness = type === 'error' ? 'assertive' : 'polite';
109
+ return (
110
+ <ToastViewport isTopLayer={false}>
111
+ <PrimeLiveRegion politeness={politeness} />
112
+ <button data-a11y-relation="focus-anchor" type="button">
113
+ Keep focus
114
+ </button>
115
+ <ToastAnnouncementButtons type={type} />
116
+ </ToastViewport>
117
+ );
118
+ }
119
+
120
+ function ToastCardHarness({type}: {type: 'info' | 'error'}) {
121
+ const message = type === 'error' ? 'Upload failed' : 'Changes saved';
122
+ const replacement =
123
+ type === 'error' ? 'Connection failed' : 'Profile updated';
124
+ const [body, setBody] = useState(message);
125
+ return (
126
+ <Frame>
127
+ <TransitionButton name="show" onClick={() => setBody(message)} />
128
+ <TransitionButton name="replace" onClick={() => setBody(replacement)} />
129
+ <Toast
130
+ type={type}
131
+ body={body}
132
+ isAutoHide={false}
133
+ autoHideDuration={5000}
134
+ onDismiss={() => {}}
135
+ />
136
+ </Frame>
137
+ );
138
+ }
139
+
140
+ function FieldStatusHarness({
141
+ type,
142
+ variant,
143
+ message,
144
+ replacement,
145
+ }: {
146
+ type: 'error' | 'warning' | 'success';
147
+ variant: 'attached' | 'detached';
148
+ message: string;
149
+ replacement: string;
150
+ }) {
151
+ const politeness = type === 'error' ? 'assertive' : 'polite';
152
+ const [current, setCurrent] = useState('');
153
+ const [version, setVersion] = useState(0);
154
+ return (
155
+ <>
156
+ <PrimeLiveRegion politeness={politeness} />
157
+ <button data-a11y-relation="focus-anchor" type="button">
158
+ Keep focus
159
+ </button>
160
+ <TransitionButton name="show" onClick={() => setCurrent(message)} />
161
+ <TransitionButton
162
+ name="replace"
163
+ onClick={() => setCurrent(replacement)}
164
+ />
165
+ <TransitionButton
166
+ name="repeat"
167
+ onClick={() => {
168
+ setCurrent(message);
169
+ setVersion(value => value + 1);
170
+ }}
171
+ />
172
+ <FieldStatus
173
+ key={version}
174
+ type={type}
175
+ variant={variant}
176
+ message={current}
177
+ />
178
+ </>
179
+ );
180
+ }
181
+
182
+ function SpinnerHarness({visibleLabel}: {visibleLabel: boolean}) {
183
+ const initial = visibleLabel ? 'Fetching data' : undefined;
184
+ const replacement = visibleLabel ? 'Saving data' : 'Saving';
185
+ const [label, setLabel] = useState<string | undefined>(initial);
186
+ return (
187
+ <Frame>
188
+ <TransitionButton name="show" onClick={() => setLabel(initial)} />
189
+ <TransitionButton name="replace" onClick={() => setLabel(replacement)} />
190
+ {visibleLabel ? (
191
+ <Spinner label={label ?? 'Fetching data'} />
192
+ ) : label == null ? (
193
+ <Spinner />
194
+ ) : (
195
+ <Spinner aria-label={label} />
196
+ )}
197
+ </Frame>
198
+ );
199
+ }
200
+
201
+ function ChatSystemMessageHarness() {
202
+ const initial = 'Conversation started';
203
+ const [message, setMessage] = useState(initial);
204
+ return (
205
+ <Frame>
206
+ <TransitionButton name="show" onClick={() => setMessage(initial)} />
207
+ <TransitionButton
208
+ name="replace"
209
+ onClick={() => setMessage('A file was shared')}
210
+ />
211
+ <TransitionButton name="clear" onClick={() => setMessage('')} />
212
+ <ChatSystemMessage>{message}</ChatSystemMessage>
213
+ </Frame>
214
+ );
215
+ }
216
+
217
+ function ProgressHarness({
218
+ initial,
219
+ progressValue = 40,
220
+ completionValue = 100,
221
+ max = 100,
222
+ withMark = false,
223
+ }: {
224
+ initial?: 'loading' | number;
225
+ progressValue?: number;
226
+ completionValue?: number;
227
+ max?: number;
228
+ withMark?: boolean;
229
+ }) {
230
+ const [progress, setProgress] = useState<'loading' | number>(
231
+ initial ?? (withMark ? 20 : 'loading'),
232
+ );
233
+ return (
234
+ <Frame>
235
+ <TransitionButton
236
+ name="progress"
237
+ onClick={() => setProgress(progressValue)}
238
+ />
239
+ <TransitionButton
240
+ name="complete"
241
+ onClick={() => setProgress(completionValue)}
242
+ />
243
+ <ProgressBar
244
+ label="Upload progress"
245
+ isIndeterminate={progress === 'loading'}
246
+ value={typeof progress === 'number' ? progress : 0}
247
+ max={max}
248
+ marks={withMark ? [{value: 75, label: 'Target'}] : undefined}
249
+ />
250
+ </Frame>
251
+ );
252
+ }
253
+
254
+ export type CoreStatusMessageStateRender = () => ReactElement;
255
+
256
+ export const CORE_STATUS_MESSAGE_STATE_RENDERS: Record<
257
+ CoreStatusMessageStateId,
258
+ CoreStatusMessageStateRender
259
+ > = {
260
+ 'toast-info-announcement': () => <ToastAnnouncementHarness type="info" />,
261
+ 'toast-error-announcement': () => <ToastAnnouncementHarness type="error" />,
262
+ 'toast-info-card-mounted': () => <ToastCardHarness type="info" />,
263
+ 'toast-error-card-mounted': () => <ToastCardHarness type="error" />,
264
+ 'field-status-error-attached': () => (
265
+ <FieldStatusHarness
266
+ type="error"
267
+ variant="attached"
268
+ message="This field is required"
269
+ replacement="Enter a valid email address"
270
+ />
271
+ ),
272
+ 'field-status-warning-detached': () => (
273
+ <FieldStatusHarness
274
+ type="warning"
275
+ variant="detached"
276
+ message="Check this value"
277
+ replacement="This value may be visible to others"
278
+ />
279
+ ),
280
+ 'field-status-success-detached': () => (
281
+ <FieldStatusHarness
282
+ type="success"
283
+ variant="detached"
284
+ message="Looks good"
285
+ replacement="Changes saved"
286
+ />
287
+ ),
288
+ 'spinner-default-label-mounted': () => (
289
+ <SpinnerHarness visibleLabel={false} />
290
+ ),
291
+ 'spinner-visible-label-mounted': () => <SpinnerHarness visibleLabel />,
292
+ 'chat-system-status-mounted': () => <ChatSystemMessageHarness />,
293
+ 'progress-loading-to-complete': () => <ProgressHarness />,
294
+ 'progress-custom-range': () => (
295
+ <ProgressHarness
296
+ initial={1}
297
+ progressValue={3}
298
+ completionValue={5}
299
+ max={5}
300
+ />
301
+ ),
302
+ 'progress-mark-focused-update': () => <ProgressHarness withMark />,
303
+ };
304
+
305
+ export {transitionTestId};
@@ -0,0 +1,317 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file StatusMessage.a11y.states.ts
5
+ * @input Uses StatusMessageStateFacts from the shared accessibility contract
6
+ * @output The AST-021 inventory for current Core status-message parts and states, plus explicit exclusions
7
+ * @position Data-only binding inventory shared by jsdom, Storybook, and Chromium
8
+ */
9
+
10
+ import type {StatusMessageStateFacts} from '@astryxdesign/a11y-spec';
11
+
12
+ export type CoreStatusMessageBinding =
13
+ | 'Toast announcement channel'
14
+ | 'Toast card'
15
+ | 'FieldStatus announcement channel'
16
+ | 'Spinner'
17
+ | 'ChatSystemMessage'
18
+ | 'ProgressBar';
19
+
20
+ export interface CoreStatusMessageBindingDefinition {
21
+ readonly id: string;
22
+ readonly binding: CoreStatusMessageBinding;
23
+ readonly summary: string;
24
+ readonly facts: StatusMessageStateFacts;
25
+ readonly focusSelector?: string;
26
+ readonly storyId: string;
27
+ }
28
+
29
+ function liveFacts({
30
+ role,
31
+ politeness,
32
+ message,
33
+ replacement,
34
+ initialMessage = '',
35
+ messageSource = 'text',
36
+ semanticTransitions,
37
+ }: {
38
+ role: 'status' | 'alert' | null;
39
+ politeness: 'polite' | 'assertive';
40
+ message: string;
41
+ replacement: string;
42
+ initialMessage?: string;
43
+ messageSource?: 'text' | 'accessible-name';
44
+ semanticTransitions: ReadonlyArray<'show' | 'replace' | 'clear' | 'repeat'>;
45
+ }): StatusMessageStateFacts {
46
+ return {
47
+ kind: 'live-region',
48
+ role,
49
+ politeness,
50
+ messageSource,
51
+ initialMessage,
52
+ message,
53
+ replacement,
54
+ semanticTransitions,
55
+ };
56
+ }
57
+
58
+ export const CORE_STATUS_MESSAGE_BINDING_STATES = [
59
+ {
60
+ id: 'toast-info-announcement',
61
+ binding: 'Toast announcement channel',
62
+ summary:
63
+ 'an info toast dispatch updates and can repeat through the persistent polite channel',
64
+ facts: liveFacts({
65
+ role: 'status',
66
+ politeness: 'polite',
67
+ message: 'Changes saved',
68
+ replacement: 'Profile updated',
69
+ semanticTransitions: ['show', 'replace', 'repeat'],
70
+ }),
71
+ storyId: 'a11y-status-message-pattern--toast-info-announcement',
72
+ },
73
+ {
74
+ id: 'toast-error-announcement',
75
+ binding: 'Toast announcement channel',
76
+ summary:
77
+ 'an error toast dispatch updates and can repeat through the persistent assertive channel',
78
+ facts: liveFacts({
79
+ role: 'alert',
80
+ politeness: 'assertive',
81
+ message: 'Upload failed',
82
+ replacement: 'Connection failed',
83
+ semanticTransitions: ['show', 'replace', 'repeat'],
84
+ }),
85
+ storyId: 'a11y-status-message-pattern--toast-error-announcement',
86
+ },
87
+ {
88
+ id: 'toast-info-card-mounted',
89
+ binding: 'Toast card',
90
+ summary:
91
+ 'the visible info card is mounted with its polite live-region content',
92
+ facts: liveFacts({
93
+ role: 'status',
94
+ politeness: 'polite',
95
+ initialMessage: 'Changes saved',
96
+ message: 'Changes saved',
97
+ replacement: 'Profile updated',
98
+ semanticTransitions: ['replace'],
99
+ }),
100
+ storyId: 'a11y-status-message-pattern--toast-info-card-mounted',
101
+ },
102
+ {
103
+ id: 'toast-error-card-mounted',
104
+ binding: 'Toast card',
105
+ summary:
106
+ 'the visible error card is mounted with its assertive live-region content',
107
+ facts: liveFacts({
108
+ role: 'alert',
109
+ politeness: 'assertive',
110
+ initialMessage: 'Upload failed',
111
+ message: 'Upload failed',
112
+ replacement: 'Connection failed',
113
+ semanticTransitions: ['replace'],
114
+ }),
115
+ storyId: 'a11y-status-message-pattern--toast-error-card-mounted',
116
+ },
117
+ {
118
+ id: 'field-status-error-attached',
119
+ binding: 'FieldStatus announcement channel',
120
+ summary:
121
+ 'an attached error enters and updates through the persistent assertive channel',
122
+ facts: liveFacts({
123
+ role: 'alert',
124
+ politeness: 'assertive',
125
+ message: 'This field is required',
126
+ replacement: 'Enter a valid email address',
127
+ semanticTransitions: ['show', 'replace', 'repeat'],
128
+ }),
129
+ storyId: 'a11y-status-message-pattern--field-status-error-attached',
130
+ },
131
+ {
132
+ id: 'field-status-warning-detached',
133
+ binding: 'FieldStatus announcement channel',
134
+ summary:
135
+ 'a detached warning enters and updates through the persistent polite channel',
136
+ facts: liveFacts({
137
+ role: 'status',
138
+ politeness: 'polite',
139
+ message: 'Check this value',
140
+ replacement: 'This value may be visible to others',
141
+ semanticTransitions: ['show', 'replace', 'repeat'],
142
+ }),
143
+ storyId: 'a11y-status-message-pattern--field-status-warning-detached',
144
+ },
145
+ {
146
+ id: 'field-status-success-detached',
147
+ binding: 'FieldStatus announcement channel',
148
+ summary:
149
+ 'a detached success enters and updates through the persistent polite channel',
150
+ facts: liveFacts({
151
+ role: 'status',
152
+ politeness: 'polite',
153
+ message: 'Looks good',
154
+ replacement: 'Changes saved',
155
+ semanticTransitions: ['show', 'replace', 'repeat'],
156
+ }),
157
+ storyId: 'a11y-status-message-pattern--field-status-success-detached',
158
+ },
159
+ {
160
+ id: 'spinner-default-label-mounted',
161
+ binding: 'Spinner',
162
+ summary:
163
+ 'a loading spinner is mounted with its default accessible status name',
164
+ facts: liveFacts({
165
+ role: 'status',
166
+ politeness: 'polite',
167
+ initialMessage: 'Loading',
168
+ messageSource: 'accessible-name',
169
+ message: 'Loading',
170
+ replacement: 'Saving',
171
+ semanticTransitions: ['replace'],
172
+ }),
173
+ storyId: 'a11y-status-message-pattern--spinner-default-label-mounted',
174
+ },
175
+ {
176
+ id: 'spinner-visible-label-mounted',
177
+ binding: 'Spinner',
178
+ summary:
179
+ 'a loading spinner is mounted with a visible label that names its status role',
180
+ facts: liveFacts({
181
+ role: 'status',
182
+ politeness: 'polite',
183
+ initialMessage: 'Fetching data',
184
+ messageSource: 'accessible-name',
185
+ message: 'Fetching data',
186
+ replacement: 'Saving data',
187
+ semanticTransitions: ['replace'],
188
+ }),
189
+ storyId: 'a11y-status-message-pattern--spinner-visible-label-mounted',
190
+ },
191
+ {
192
+ id: 'chat-system-status-mounted',
193
+ binding: 'ChatSystemMessage',
194
+ summary: 'a chat status notice is mounted with its live-region content',
195
+ facts: liveFacts({
196
+ role: 'status',
197
+ politeness: 'polite',
198
+ initialMessage: 'Conversation started',
199
+ message: 'Conversation started',
200
+ replacement: 'A file was shared',
201
+ semanticTransitions: ['replace', 'clear'],
202
+ }),
203
+ storyId: 'a11y-status-message-pattern--chat-system-status-mounted',
204
+ },
205
+ {
206
+ id: 'progress-loading-to-complete',
207
+ binding: 'ProgressBar',
208
+ summary:
209
+ 'an indeterminate operation becomes 40 percent complete and then finishes',
210
+ facts: {
211
+ kind: 'progressbar',
212
+ politeness: null,
213
+ name: 'Upload progress',
214
+ initialValue: null,
215
+ initialMin: 0,
216
+ initialMax: 100,
217
+ progressValue: 40,
218
+ completionValue: 100,
219
+ minValue: 0,
220
+ maxValue: 100,
221
+ },
222
+ storyId: 'a11y-status-message-pattern--progress-loading-to-complete',
223
+ },
224
+ {
225
+ id: 'progress-custom-range',
226
+ binding: 'ProgressBar',
227
+ summary: 'a five-step operation exposes and updates its non-default range',
228
+ facts: {
229
+ kind: 'progressbar',
230
+ politeness: null,
231
+ name: 'Upload progress',
232
+ initialValue: 1,
233
+ initialMin: 0,
234
+ initialMax: 5,
235
+ progressValue: 3,
236
+ completionValue: 5,
237
+ minValue: 0,
238
+ maxValue: 5,
239
+ },
240
+ storyId: 'a11y-status-message-pattern--progress-custom-range',
241
+ },
242
+ {
243
+ id: 'progress-mark-focused-update',
244
+ binding: 'ProgressBar',
245
+ summary:
246
+ 'a determinate value update preserves focus on a labeled target mark',
247
+ facts: {
248
+ kind: 'progressbar',
249
+ politeness: null,
250
+ name: 'Upload progress',
251
+ initialValue: 20,
252
+ initialMin: 0,
253
+ initialMax: 100,
254
+ progressValue: 40,
255
+ completionValue: 100,
256
+ minValue: 0,
257
+ maxValue: 100,
258
+ },
259
+ focusSelector: '[role="progressbar"] [tabindex="0"]',
260
+ storyId: 'a11y-status-message-pattern--progress-mark-focused-update',
261
+ },
262
+ ] as const satisfies ReadonlyArray<CoreStatusMessageBindingDefinition>;
263
+
264
+ export type CoreStatusMessageBindingState =
265
+ (typeof CORE_STATUS_MESSAGE_BINDING_STATES)[number];
266
+ export type CoreStatusMessageStateId = CoreStatusMessageBindingState['id'];
267
+
268
+ export const CORE_STATUS_MESSAGE_EXCLUSIONS = [
269
+ {
270
+ owner: 'FieldStatus visible message box',
271
+ part: 'visible validation text',
272
+ reason:
273
+ 'component:FieldStatus AR2 keeps the visible box role-free as the input description target; its separate persistent announcement channel is bound above.',
274
+ },
275
+ {
276
+ owner: 'Spinner compositions',
277
+ part: 'aria-hidden decorative spinner',
278
+ reason:
279
+ 'A composing control that marks Spinner aria-hidden owns the surrounding busy semantics; the decorative ring is not another status message.',
280
+ },
281
+ {
282
+ owner: 'ChatSystemMessage divider',
283
+ part: 'date or section separator',
284
+ reason:
285
+ 'A divider does not report success, results, waiting, progress, or an error. Its current forced status role is tracked as a separate ownership mismatch rather than adopted here.',
286
+ },
287
+ {
288
+ owner: 'ChatMessageList and streaming logs',
289
+ part: 'role=log transcript updates',
290
+ reason:
291
+ 'Sequential log announcements and stream batching are a distinct log pattern, not one atomic status message.',
292
+ },
293
+ {
294
+ owner: 'ChatComposer and ChatToolCalls',
295
+ part: 'composer errors and tool-run states',
296
+ reason:
297
+ 'These adjacent surfaces have their own state ownership and were not named by this bounded first status-message migration.',
298
+ },
299
+ {
300
+ owner: 'Switch',
301
+ part: 'busy announcement',
302
+ reason:
303
+ 'The current switch pattern has no busy announcement expectation, and overlapping work is changing this path; it is not silently absorbed into this migration.',
304
+ },
305
+ {
306
+ owner: 'ProgressBar marks and Tooltip',
307
+ part: 'focusable target-mark triggers and labels',
308
+ reason:
309
+ 'The marked progress state above proves that value updates preserve mark focus. ProgressBar and Tooltip local tests own trigger labeling and interaction; ARIA ownership of focusable descendants is tracked separately.',
310
+ },
311
+ {
312
+ owner: 'ProgressBar component and theme',
313
+ part: 'visual fill, track, animation, and status cadence',
314
+ reason:
315
+ 'The binding covers browser-exposed role, name, values, and focus preservation. Paint stays in visual evidence; spoken cadence and timing stay under AST-009.',
316
+ },
317
+ ] as const;