@astryxdesign/lab 0.7.3-canary.f94e962 → 0.7.4-canary.3f6ea7d

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 (28) hide show
  1. package/dist/Chat/ChatTypingIndicator.d.ts +3 -2
  2. package/dist/Chat/ChatTypingIndicator.d.ts.map +1 -1
  3. package/dist/Chat/ChatTypingIndicator.js +39 -9
  4. package/dist/Comments/CommentComposer.d.ts.map +1 -1
  5. package/dist/Comments/CommentComposer.js +14 -2
  6. package/dist/Comments/CommentParts.d.ts.map +1 -1
  7. package/dist/Comments/CommentParts.js +4 -3
  8. package/dist/MobileTokenizer/MobileTokenizer.d.ts.map +1 -1
  9. package/dist/MobileTokenizer/MobileTokenizer.js +49 -11
  10. package/dist/lab.css +2 -0
  11. package/package.json +4 -4
  12. package/src/Chat/ChatTypingIndicator.doc.mjs +3 -3
  13. package/src/Chat/ChatTypingIndicator.test.tsx +66 -1
  14. package/src/Chat/ChatTypingIndicator.tsx +43 -9
  15. package/src/Chat/__tests__/ChatTypingIndicator.a11y.renders.tsx +30 -7
  16. package/src/Chat/__tests__/ChatTypingIndicator.a11y.states.ts +19 -19
  17. package/src/Chat/__tests__/ChatTypingIndicator.a11y.test.tsx +2 -0
  18. package/src/CircularProgress/__tests__/CircularProgress.a11y.chromium.spec.ts +114 -0
  19. package/src/CircularProgress/__tests__/CircularProgress.a11y.known-failures.ts +12 -0
  20. package/src/CircularProgress/__tests__/CircularProgress.a11y.renders.tsx +41 -0
  21. package/src/CircularProgress/__tests__/CircularProgress.a11y.states.ts +106 -0
  22. package/src/CircularProgress/__tests__/CircularProgress.a11y.test.tsx +108 -0
  23. package/src/Comments/CommentComposer.test.tsx +83 -1
  24. package/src/Comments/CommentComposer.tsx +14 -2
  25. package/src/Comments/CommentParts.tsx +3 -6
  26. package/src/Comments/Comments.test.tsx +5 -14
  27. package/src/MobileTokenizer/MobileTokenizer.test.tsx +138 -0
  28. package/src/MobileTokenizer/MobileTokenizer.tsx +81 -10
@@ -14,8 +14,9 @@ export interface ChatTypingIndicatorProps extends BaseProps<HTMLDivElement> {
14
14
  * Animated three-dot typing hint with a name-aware label.
15
15
  *
16
16
  * The dots bounce with staggered delays (disabled under
17
- * prefers-reduced-motion) and the label is announced politely to
18
- * screen readers via role="status".
17
+ * prefers-reduced-motion). Each new label is announced politely to screen
18
+ * readers through the shared announcement channel, unless an enclosing live
19
+ * container such as a chat log delivers it.
19
20
  *
20
21
  * @example
21
22
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"ChatTypingIndicator.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatTypingIndicator.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAMlD,MAAM,WAAW,wBAAyB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACzE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AA0FD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GACJ,EAAE,wBAAwB,+BA4B1B;yBAnCe,mBAAmB"}
1
+ {"version":3,"file":"ChatTypingIndicator.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatTypingIndicator.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAOlD,MAAM,WAAW,wBAAyB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACzE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAkGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GACJ,EAAE,wBAAwB,+BAkD1B;yBAzDe,mBAAmB"}
@@ -10,19 +10,22 @@
10
10
  *
11
11
  * Three staggered bouncing dots (stylex.keyframes, reduced-motion safe)
12
12
  * with a grammar-aware label: "Ana is typing…", "Ana and Ben are typing…",
13
- * or "Ana and 2 others are typing…". Announced politely via role="status".
14
- * Renders dots only when no names are provided.
13
+ * or "Ana and 2 others are typing…". The label is announced once per change
14
+ * through the shared polite channel (`useAnnounce`), or left to an enclosing
15
+ * live container such as a chat log; the element itself carries no live role
16
+ * (spec:AST-086 FR2, FR4). Renders dots only when no names are provided.
15
17
  *
16
18
  * SYNC: When modified, update these files to stay in sync:
17
19
  * - /packages/lab/src/Chat/index.ts (exports)
18
20
  * - /packages/lab/src/Chat/ChatTypingIndicator.doc.mjs
19
21
  * - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
20
22
  */
21
- import { useMemo } from 'react';
23
+ import { useEffect, useMemo, useRef } from 'react';
22
24
  import * as stylex from '@stylexjs/stylex';
23
25
  import '@astryxdesign/core/theme/tokens.stylex';
24
26
  import { colorVars, spacingVars, radiusVars, durationVars, typographyVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
25
- import { mergeProps } from '@astryxdesign/core/utils';
27
+ import { mergeProps, mergeRefs } from '@astryxdesign/core/utils';
28
+ import { useAnnounce } from '@astryxdesign/core/hooks';
26
29
  import { themeProps } from '@astryxdesign/core/utils';
27
30
  import { useLocale, useTranslator } from '@astryxdesign/core/i18n';
28
31
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -67,6 +70,13 @@ function typingLabel(names, t, listFormat) {
67
70
  });
68
71
  }
69
72
 
73
+ /**
74
+ * An enclosing live container (a chat transcript's role="log", or any element
75
+ * the page made live) already delivers content added inside it, so the
76
+ * indicator must not announce the same words again (spec:AST-086 FR4).
77
+ */
78
+ const LIVE_CONTAINER_SELECTOR = '[role="log"], [role="status"], [role="alert"], [aria-live="polite"], [aria-live="assertive"]';
79
+
70
80
  // =============================================================================
71
81
  // Component
72
82
  // =============================================================================
@@ -75,8 +85,9 @@ function typingLabel(names, t, listFormat) {
75
85
  * Animated three-dot typing hint with a name-aware label.
76
86
  *
77
87
  * The dots bounce with staggered delays (disabled under
78
- * prefers-reduced-motion) and the label is announced politely to
79
- * screen readers via role="status".
88
+ * prefers-reduced-motion). Each new label is announced politely to screen
89
+ * readers through the shared announcement channel, unless an enclosing live
90
+ * container such as a chat log delivers it.
80
91
  *
81
92
  * @example
82
93
  * ```
@@ -99,10 +110,29 @@ export function ChatTypingIndicator({
99
110
  type: 'conjunction'
100
111
  }), [locale]);
101
112
  const label = typingLabel(names, t, listFormat);
113
+ const rootRef = useRef(null);
114
+ const mergedRef = useMemo(() => mergeRefs(rootRef, ref), [ref]);
115
+ const announce = useAnnounce();
116
+ // The label last announced, so a rerender or a StrictMode effect replay does
117
+ // not repeat it; cleared when nobody is typing, so the same people starting
118
+ // again is a new occasion (spec:AST-086 FR6).
119
+ const announcedLabelRef = useRef(null);
120
+ useEffect(() => {
121
+ if (label == null) {
122
+ announcedLabelRef.current = null;
123
+ return;
124
+ }
125
+ if (announcedLabelRef.current === label) {
126
+ return;
127
+ }
128
+ announcedLabelRef.current = label;
129
+ const isInsideLiveContainer = rootRef.current?.parentElement?.closest(LIVE_CONTAINER_SELECTOR) != null;
130
+ if (!isInsideLiveContainer) {
131
+ announce(label, 'polite');
132
+ }
133
+ }, [announce, label]);
102
134
  return /*#__PURE__*/_jsxs("div", {
103
- ref: ref,
104
- role: "status",
105
- "aria-live": "polite",
135
+ ref: mergedRef,
106
136
  "data-testid": testId,
107
137
  ...mergeProps(themeProps('chat-typing-indicator'), stylex.props(styles.root, xstyle), className, styleProp),
108
138
  children: [/*#__PURE__*/_jsxs("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"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"}
@@ -152,6 +152,8 @@ export function CommentComposerBase({
152
152
  latestValueRef.current = value;
153
153
  const isMountedRef = useRef(true);
154
154
  const actionPendingRef = useRef(false);
155
+ const isSubmittingRef = useRef(isSubmitting);
156
+ isSubmittingRef.current = isSubmitting;
155
157
  const imageInputRef = useRef(null);
156
158
  const inputRef = useRef(null);
157
159
  // Focus lands once per `focusKey`, when the input attaches: the handle ref
@@ -224,7 +226,15 @@ export function CommentComposerBase({
224
226
  setActionError({
225
227
  title: t('@astryx.commentComposer.imagesOnly'),
226
228
  actionLabel: t('@astryx.commentComposer.chooseImages'),
227
- onAction: () => imageInputRef.current?.click()
229
+ onAction: () => {
230
+ // The picker input is never natively disabled, so its click() would
231
+ // open the file chooser while busy and the busy guard would drop
232
+ // the pick.
233
+ if (isSubmittingRef.current || actionPendingRef.current) {
234
+ return;
235
+ }
236
+ imageInputRef.current?.click();
237
+ }
228
238
  });
229
239
  return;
230
240
  }
@@ -329,10 +339,12 @@ export function CommentComposerBase({
329
339
  accept: "image/*",
330
340
  multiple: true,
331
341
  hidden: true,
332
- disabled: isBusy,
333
342
  onChange: event => {
334
343
  const files = Array.from(event.currentTarget.files ?? []);
335
344
  event.currentTarget.value = '';
345
+ if (isBusy) {
346
+ return;
347
+ }
336
348
  beginImageUpload(files);
337
349
  }
338
350
  }), /*#__PURE__*/_jsx(IconButton, {
@@ -1 +1 @@
1
- {"version":3,"file":"CommentParts.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentParts.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,EAAW,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAC,MAAM,OAAO,CAAC;AAC7E,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAqC3C,OAAO,EAAgB,KAAK,UAAU,EAAC,MAAM,SAAS,CAAC;AACvD,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,cAAc,EACd,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAEjB,4EAA4E;AAC5E,eAAO,MAAM,WAAW,QAAwE,CAAC;AAwBjG;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,GAC7C,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,MAAM,CAE7B,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyIrB,CAAC;AAiBH,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,EACP,cAAc,EACd,GAAG,SAAS,EACb,EAAE;IACD,SAAS,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,CAAC,aAAa,EAAE,QAAQ,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;CACvD,+BA4BA;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,EAAE,GACH,EAAE;IACD,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IACvB,EAAE,EAAE,QAAQ,GAAG,KAAK,CAAC;CACtB,+BAaA;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,gCASlC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,IAAa,GACd,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;CAC9B,+BAMA;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,EACjC,eAAe,EACf,UAAU,EACV,UAAU,EACV,KAAK,EACL,OAAO,EACP,QAAuB,GACxB,EAAE;IACD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;CAClC,+BAmBA;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC,+BAiBA;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,cAAc,EACd,UAAU,GACX,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,EAAE,qBAAqB,CAAC;IACtC,gEAAgE;IAChE,UAAU,EAAE,OAAO,CAAC;CACrB,+BA2BA;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,EAAC,QAAQ,EAAC,EAAE;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAC,+BAsB5E;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,UAAU,EACV,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB,+BAUA;AAED,wBAAgB,WAAW,uCAmB1B;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,EAAC,UAAU,EAAC,EAAE;IAAC,UAAU,CAAC,EAAE,WAAW,CAAA;CAAC,+BAmBrE;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,EAAE,EACF,MAAM,GACP,EAAE;IACD,MAAM,EAAE,WAAW,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC;CAC9B,+BAmBA;AA2BD;kBACkB;AAClB,wBAAgB,kBAAkB,CAAC,EAAC,IAAI,EAAC,EAAE;IAAC,IAAI,EAAE,WAAW,CAAA;CAAC,+BAY7D;AAED,gEAAgE;AAChE,wBAAgB,YAAY,gCAM3B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,oBAAoB,CAAC;IAC5B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,SAAS,CAAC;CAC1D,+BAgDA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,OAAO,EACP,iBAAiB,GAClB,EAAE;IACD,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,iBAAiB,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,SAAS,CAAC;CAC3D,sCA+EA;AAwFD,+EAA+E;AAC/E,wBAAgB,4BAA4B,CAAC,EAC3C,SAAS,GACV,EAAE;IACD,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;CAC/B,+BAaA;AAED,0EAA0E;AAC1E,wBAAgB,8BAA8B,CAAC,EAC7C,WAAW,GACZ,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;CACrB,+BAuCA"}
1
+ {"version":3,"file":"CommentParts.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentParts.tsx"],"names":[],"mappings":"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"}
@@ -334,9 +334,10 @@ export function MetadataActionLink({
334
334
  type: textType,
335
335
  color: "secondary",
336
336
  xstyle: partStyles.noWrap,
337
- "data-metadata-action": actionRole,
338
- "aria-disabled": isDisabled || undefined,
339
- tabIndex: isDisabled ? -1 : undefined,
337
+ "data-metadata-action": actionRole
338
+ // Link keeps a disabled link in the tab order (spec:AST-085 FR1).
339
+ ,
340
+ isDisabled: isDisabled,
340
341
  onClick: event => {
341
342
  event.preventDefault();
342
343
  if (isDisabled !== true) {
@@ -1 +1 @@
1
- {"version":3,"file":"MobileTokenizer.d.ts","sourceRoot":"","sources":["../../src/MobileTokenizer/MobileTokenizer.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAYlD,OAAO,KAAK,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAQ/E,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,cAAc,IACtD;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAC,GACtB;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GACzB;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GACzB;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC;AAEtB,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,IAAI,CAC1E,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAoID,wBAAgB,eAAe,CAAC,CAAC,SAAS,cAAc,EAAE,EACxD,KAAK,EACL,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAkB,EAClB,SAAiB,EACjB,UAAU,EACV,YAAiB,EACjB,cAAkB,EAClB,UAAgB,EAChB,sBAA2C,EAC3C,aAAa,EACb,MAAM,GACP,EAAE,oBAAoB,CAAC,CAAC,CAAC,+BA8UzB;yBA/Ve,eAAe"}
1
+ {"version":3,"file":"MobileTokenizer.d.ts","sourceRoot":"","sources":["../../src/MobileTokenizer/MobileTokenizer.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAYlD,OAAO,KAAK,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAQ/E,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,cAAc,IACtD;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAC,GACtB;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GACzB;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GACzB;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC;AAEtB,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,IAAI,CAC1E,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AA8KD,wBAAgB,eAAe,CAAC,CAAC,SAAS,cAAc,EAAE,EACxD,KAAK,EACL,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAkB,EAClB,SAAiB,EACjB,UAAU,EACV,YAAiB,EACjB,cAAkB,EAClB,UAAgB,EAChB,sBAA2C,EAC3C,aAAa,EACb,MAAM,GACP,EAAE,oBAAoB,CAAC,CAAC,CAAC,+BA2WzB;yBA5Xe,eAAe"}
@@ -67,6 +67,14 @@ function ListBulletIcon(props) {
67
67
  });
68
68
  }
69
69
  const styles = {
70
+ tokenDisabled: {
71
+ kMwMTN: "x1tgivj0 x1bhcspo",
72
+ $$css: true
73
+ },
74
+ indicatorDisabled: {
75
+ kVAM5u: "x14i3s5s xmnqt3w",
76
+ $$css: true
77
+ },
70
78
  footerAction: {
71
79
  kzQI83: "x1iyjqo2",
72
80
  kCS8Yb: "x1r8uery",
@@ -229,23 +237,37 @@ export function MobileTokenizer({
229
237
  "aria-haspopup": "dialog",
230
238
  "aria-expanded": activeSheet != null,
231
239
  "aria-label": label,
232
- disabled: isDisabled,
240
+ "aria-disabled": isDisabled || undefined,
233
241
  onClick: () => {
242
+ if (isDisabled) {
243
+ return;
244
+ }
234
245
  setSelectedItemsAtOpen(value);
235
246
  lastLoadScrollHeightRef.current = null;
236
247
  setRenderedItemCount(LIST_RENDER_BATCH_SIZE);
237
248
  setActiveSheet('manage');
238
249
  },
239
250
  ...{
240
- className: "x78zum5 x1a02dak x6s0dn4 xzye2dw xh8yej3 x1gg8mnh xu0wf1k xf314gf xmkeg23 x1y0btm7 x14i3s5s xh6dtrn xjbqb8w x1yc453h x1ypdohk x16khyan"
241
- },
251
+ 0: {
252
+ className: "x78zum5 x1a02dak x6s0dn4 xzye2dw xh8yej3 x1gg8mnh xu0wf1k xf314gf xmkeg23 x1y0btm7 x14i3s5s xh6dtrn xjbqb8w x1yc453h x1ypdohk x16khyan"
253
+ },
254
+ 1: {
255
+ className: "x78zum5 x1a02dak x6s0dn4 xzye2dw xh8yej3 x1gg8mnh xu0wf1k xf314gf xmkeg23 x1y0btm7 xh6dtrn xjbqb8w x1yc453h x1ypdohk x16khyan x14i3s5s xmnqt3w x1bhcspo"
256
+ }
257
+ }[!!isDisabled << 0],
242
258
  children: value.length > 0 ? value.map(item => /*#__PURE__*/_jsx(Token, {
243
259
  label: item.label,
244
- isDisabled: isDisabled
260
+ isDisabled: isDisabled,
261
+ xstyle: isDisabled ? styles.tokenDisabled : undefined
245
262
  }, item.id)) : /*#__PURE__*/_jsx("span", {
246
263
  ...{
247
- className: "xv1l7n4"
248
- },
264
+ 0: {
265
+ className: "xv1l7n4"
266
+ },
267
+ 1: {
268
+ className: "xv1l7n4 x1bhcspo"
269
+ }
270
+ }[!!isDisabled << 0],
249
271
  children: placeholder ?? ''
250
272
  })
251
273
  }), !isDisabled && /*#__PURE__*/_jsx(BottomSheetSwitcher, {
@@ -341,23 +363,39 @@ export function MobileTokenizer({
341
363
  role: "checkbox",
342
364
  "aria-checked": isSelected,
343
365
  "aria-label": item.label,
344
- disabled: rowDisabled,
345
- onClick: () => isSelected ? handleRemove(item) : handleAdd(item),
346
- ...{
347
- className: "x78zum5 x6s0dn4 x1qughib x1txdalj xh8yej3 x1gg8mnh xce4md1 x7a5moj xc342km xh6dtrn xjbqb8w x1tgivj0 x1yc453h x1ypdohk x16khyan"
366
+ "aria-disabled": rowDisabled || undefined,
367
+ onClick: () => {
368
+ if (rowDisabled) {
369
+ return;
370
+ }
371
+ if (isSelected) {
372
+ handleRemove(item);
373
+ } else {
374
+ handleAdd(item);
375
+ }
348
376
  },
377
+ ...{
378
+ 0: {
379
+ className: "x78zum5 x6s0dn4 x1qughib x1txdalj xh8yej3 x1gg8mnh xce4md1 x7a5moj xc342km xh6dtrn xjbqb8w x1tgivj0 x1yc453h x1ypdohk x16khyan"
380
+ },
381
+ 1: {
382
+ className: "x78zum5 x6s0dn4 x1qughib x1txdalj xh8yej3 x1gg8mnh xce4md1 x7a5moj xc342km xh6dtrn xjbqb8w x1yc453h x1ypdohk x16khyan x1tgivj0 x1bhcspo"
383
+ }
384
+ }[!!rowDisabled << 0],
349
385
  children: [/*#__PURE__*/_jsx("span", {
350
386
  ...{
351
387
  className: "x1iyjqo2 xeuugli"
352
388
  },
353
389
  children: /*#__PURE__*/_jsx(Text, {
354
390
  type: "body",
391
+ color: rowDisabled ? 'inherit' : undefined,
355
392
  children: item.label
356
393
  })
357
394
  }), /*#__PURE__*/_jsx(CheckboxIndicator, {
358
395
  state: isSelected ? 'checked' : 'unchecked',
359
396
  size: "md",
360
- isDisabled: rowDisabled
397
+ isDisabled: rowDisabled,
398
+ xstyle: rowDisabled ? styles.indicatorDisabled : undefined
361
399
  })]
362
400
  }, item.id);
363
401
  })]
package/dist/lab.css CHANGED
@@ -157,6 +157,7 @@
157
157
  .xz7zccy.xz7zccy:hover:where(:not(:disabled,[aria-disabled="true"])):not(#\#):not(#\#){border-color:var(--color-accent)}
158
158
  .xg9gykk.xg9gykk:hover:where(:not(:disabled,[aria-disabled="true"])):not(#\#):not(#\#){text-decoration:underline}
159
159
  @media (forced-colors: active){.x1w1tqly.x1w1tqly:not(#\#):not(#\#){border-color:CanvasText}}
160
+ @media (forced-colors: active){.xmnqt3w.xmnqt3w:not(#\#):not(#\#){border-color:GrayText}}
160
161
  @media (forced-colors: active){.x1n2ykn.x1n2ykn:not(#\#):not(#\#){border-color:Highlight}}
161
162
  @media (hover: hover){.xmhmdd9.xmhmdd9.xmhmdd9:hover:where(:not(:disabled,[aria-disabled="true"])):not(#\#):not(#\#){border-color:var(--color-accent)}}
162
163
  @media (hover: hover){.x1xwh4np.x1xwh4np.x1xwh4np:hover:where(:not(:disabled,[aria-disabled="true"])):not(#\#):not(#\#){border-color:var(--color-border-emphasized)}}
@@ -596,6 +597,7 @@
596
597
  @media (prefers-reduced-motion: reduce) and (not (forced-colors: active)){.x1peun2k.x1peun2k:not(#\#):not(#\#):not(#\#){background-image:none}}
597
598
  @media (forced-colors: active){.xwaqzdf.xwaqzdf:not(#\#):not(#\#):not(#\#){box-shadow:none}}
598
599
  @media (forced-colors: active){.xs5hli.xs5hli:not(#\#):not(#\#):not(#\#){color:CanvasText}}
600
+ @media (forced-colors: active){.x1bhcspo.x1bhcspo:not(#\#):not(#\#):not(#\#){color:GrayText}}
599
601
  @media (forced-colors: active){.x1k5gbb1.x1k5gbb1:not(#\#):not(#\#):not(#\#){color:HighlightText}}
600
602
  @media (prefers-reduced-motion: reduce) and (not (forced-colors: active)){.x1wi2gft.x1wi2gft:not(#\#):not(#\#):not(#\#){color:var(--color-text-secondary)}}
601
603
  @media (forced-colors: active){.x1tbh5bv.x1tbh5bv:not(#\#):not(#\#):not(#\#){opacity:1}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/lab",
3
- "version": "0.7.3-canary.f94e962",
3
+ "version": "0.7.4-canary.3f6ea7d",
4
4
  "description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
5
5
  "author": "Meta Open Source",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@stylexjs/stylex": ">=0.10.0",
52
- "@astryxdesign/core": "0.7.3-canary.f94e962",
52
+ "@astryxdesign/core": "0.7.4-canary.3f6ea7d",
53
53
  "react": ">=19.0.0",
54
54
  "react-dom": ">=19.0.0"
55
55
  },
@@ -59,8 +59,8 @@
59
59
  "d3-shape": "^3.2.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@astryxdesign/a11y-spec": "0.7.3-canary.f94e962",
63
- "@astryxdesign/cli": "0.7.3-canary.f94e962",
62
+ "@astryxdesign/a11y-spec": "0.7.4-canary.3f6ea7d",
63
+ "@astryxdesign/cli": "0.7.4-canary.3f6ea7d",
64
64
  "@babel/cli": "^7.29.7",
65
65
  "@babel/core": "^7.29.7",
66
66
  "@babel/preset-react": "^7.29.7",
@@ -8,7 +8,7 @@ export const docs = {
8
8
  group: 'Chat',
9
9
  category: 'Chat',
10
10
  displayName: 'Chat Typing Indicator',
11
- description: 'Animated three-dot typing hint with a localized, grammar-aware label: "Ana is typing...", "Ana and Ben are typing...", or "Ana and 2 others are typing...". Dots bounce with staggered stylex.keyframes delays, disabled under prefers-reduced-motion; the label is announced politely via role="status".',
11
+ description: 'Animated three-dot typing hint with a localized, grammar-aware label: "Ana is typing...", "Ana and Ben are typing...", or "Ana and 2 others are typing...". Dots bounce with staggered stylex.keyframes delays, disabled under prefers-reduced-motion. Each new label is announced once, politely, through the shared announcement channel (useAnnounce); inside a chat log the log announces it instead. The indicator itself carries no live role, so it can mount conditionally without being skipped or heard twice.',
12
12
  props: [
13
13
  {
14
14
  name: 'names',
@@ -21,7 +21,7 @@ export const docs = {
21
21
  export const docsZh = {
22
22
  name: 'ChatTypingIndicator',
23
23
  displayName: 'Chat Typing Indicator',
24
- description: '带语法感知标签的三点跳动输入提示:"Ana is typing..."、"Ana and Ben are typing..." 或 "Ana and 2 others are typing..."。圆点用 stylex.keyframes 交错跳动,prefers-reduced-motion 下禁用;标签通过 role="status" 礼貌播报。',
24
+ description: '带语法感知标签的三点跳动输入提示:"Ana is typing..."、"Ana and Ben are typing..." 或 "Ana and 2 others are typing..."。圆点用 stylex.keyframes 交错跳动,prefers-reduced-motion 下禁用。每个新标签通过共享播报通道(useAnnounce)礼貌播报一次;位于聊天记录(role="log")内时由记录负责播报。指示器本身不带 live 角色,因此可按条件挂载,既不会被跳过,也不会重复播报。',
25
25
  propDescriptions: {
26
26
  names: '正在输入的人名。一个名字渲染 "is typing",两个渲染双名,更多折叠为 "and N others"。语句取自 i18n 词条,人名由 Intl.ListFormat 按当前语言拼接,因此措辞与连接词随语言而变。省略或为空时仅渲染动画圆点。',
27
27
  },
@@ -30,7 +30,7 @@ export const docsZh = {
30
30
  export const docsDense = {
31
31
  name: 'ChatTypingIndicator',
32
32
  displayName: 'Chat Typing Indicator',
33
- description: 'bouncing three-dot typing hint; label grammar 1/2/N names; reduced-motion safe keyframes; role=status polite announce; dots-only when names empty',
33
+ description: 'bouncing three-dot typing hint; label grammar 1/2/N names; reduced-motion safe keyframes; announces each new label once via useAnnounce (polite), or leaves it to an enclosing role=log; no live role on the element; dots-only when names empty',
34
34
  propDescriptions: {
35
35
  names:
36
36
  "string[]; 1='X is typing', 2='X and Y', 3+='X and N others'; phrases from i18n catalog, names joined by Intl.ListFormat; empty=dots only",
@@ -1,10 +1,21 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
- import {describe, it, expect} from 'vitest';
3
+ import {StrictMode} from 'react';
4
+ import {beforeEach, describe, it, expect, vi} from 'vitest';
4
5
  import {render, screen} from '@testing-library/react';
5
6
  import {ChatTypingIndicator} from './ChatTypingIndicator';
6
7
  import {InternationalizationProvider} from '@astryxdesign/core/i18n';
7
8
 
9
+ const announce = vi.fn();
10
+ vi.mock('@astryxdesign/core/hooks', async importOriginal => ({
11
+ ...(await importOriginal<typeof import('@astryxdesign/core/hooks')>()),
12
+ useAnnounce: () => announce,
13
+ }));
14
+
15
+ beforeEach(() => {
16
+ announce.mockClear();
17
+ });
18
+
8
19
  describe('ChatTypingIndicator', () => {
9
20
  it('renders "X is typing…" for one name', () => {
10
21
  render(<ChatTypingIndicator names={['Ana']} />);
@@ -84,4 +95,58 @@ describe('ChatTypingIndicator', () => {
84
95
  'astryx-chat-typing-indicator',
85
96
  );
86
97
  });
98
+
99
+ describe('status announcement (spec:AST-086)', () => {
100
+ it('mounts with its label but carries no live role (FR2)', () => {
101
+ const {container} = render(<ChatTypingIndicator names={['Ana']} />);
102
+ const root = container.firstElementChild as HTMLElement;
103
+ expect(root).toHaveTextContent('Ana is typing…');
104
+ expect(root).not.toHaveAttribute('role');
105
+ expect(root).not.toHaveAttribute('aria-live');
106
+ expect(screen.queryByRole('status')).toBeNull();
107
+ });
108
+
109
+ it('announces a mounted label once through the polite channel, even under StrictMode (FR2, FR6)', () => {
110
+ const {rerender} = render(
111
+ <StrictMode>
112
+ <ChatTypingIndicator names={['Ana']} />
113
+ </StrictMode>,
114
+ );
115
+ rerender(
116
+ <StrictMode>
117
+ <ChatTypingIndicator names={['Ana']} />
118
+ </StrictMode>,
119
+ );
120
+ expect(announce.mock.calls).toEqual([['Ana is typing…', 'polite']]);
121
+ });
122
+
123
+ it('announces each new label, nothing on clear, and the same people again as a new occasion (FR6)', () => {
124
+ const {rerender} = render(<ChatTypingIndicator names={[]} />);
125
+ expect(announce).not.toHaveBeenCalled();
126
+ rerender(<ChatTypingIndicator names={['Ana']} />);
127
+ rerender(<ChatTypingIndicator names={['Ana', 'Ben']} />);
128
+ rerender(<ChatTypingIndicator names={[]} />);
129
+ rerender(<ChatTypingIndicator names={['Ana', 'Ben']} />);
130
+ expect(announce.mock.calls).toEqual([
131
+ ['Ana is typing…', 'polite'],
132
+ ['Ana and Ben are typing…', 'polite'],
133
+ ['Ana and Ben are typing…', 'polite'],
134
+ ]);
135
+ });
136
+
137
+ it('leaves delivery to an enclosing chat log (FR4)', () => {
138
+ const {rerender} = render(
139
+ <div role="log">
140
+ <ChatTypingIndicator names={['Ana']} />
141
+ </div>,
142
+ );
143
+ rerender(
144
+ <div role="log">
145
+ <ChatTypingIndicator names={['Ana', 'Ben']} />
146
+ </div>,
147
+ );
148
+ expect(announce).not.toHaveBeenCalled();
149
+ expect(screen.queryByRole('status')).toBeNull();
150
+ });
151
+ });
87
152
  });
@@ -10,8 +10,10 @@
10
10
  *
11
11
  * Three staggered bouncing dots (stylex.keyframes, reduced-motion safe)
12
12
  * with a grammar-aware label: "Ana is typing…", "Ana and Ben are typing…",
13
- * or "Ana and 2 others are typing…". Announced politely via role="status".
14
- * Renders dots only when no names are provided.
13
+ * or "Ana and 2 others are typing…". The label is announced once per change
14
+ * through the shared polite channel (`useAnnounce`), or left to an enclosing
15
+ * live container such as a chat log; the element itself carries no live role
16
+ * (spec:AST-086 FR2, FR4). Renders dots only when no names are provided.
15
17
  *
16
18
  * SYNC: When modified, update these files to stay in sync:
17
19
  * - /packages/lab/src/Chat/index.ts (exports)
@@ -19,7 +21,7 @@
19
21
  * - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
20
22
  */
21
23
 
22
- import {useMemo} from 'react';
24
+ import {useEffect, useMemo, useRef} from 'react';
23
25
  import * as stylex from '@stylexjs/stylex';
24
26
  import {
25
27
  colorVars,
@@ -30,7 +32,8 @@ import {
30
32
  typeScaleVars,
31
33
  } from '@astryxdesign/core/theme/tokens.stylex';
32
34
  import type {BaseProps} from '@astryxdesign/core';
33
- import {mergeProps} from '@astryxdesign/core/utils';
35
+ import {mergeProps, mergeRefs} from '@astryxdesign/core/utils';
36
+ import {useAnnounce} from '@astryxdesign/core/hooks';
34
37
  import {themeProps} from '@astryxdesign/core/utils';
35
38
  import {useLocale, useTranslator} from '@astryxdesign/core/i18n';
36
39
  import type {TranslatorFn} from '@astryxdesign/core/i18n';
@@ -132,6 +135,14 @@ function typingLabel(
132
135
  });
133
136
  }
134
137
 
138
+ /**
139
+ * An enclosing live container (a chat transcript's role="log", or any element
140
+ * the page made live) already delivers content added inside it, so the
141
+ * indicator must not announce the same words again (spec:AST-086 FR4).
142
+ */
143
+ const LIVE_CONTAINER_SELECTOR =
144
+ '[role="log"], [role="status"], [role="alert"], [aria-live="polite"], [aria-live="assertive"]';
145
+
135
146
  // =============================================================================
136
147
  // Component
137
148
  // =============================================================================
@@ -140,8 +151,9 @@ function typingLabel(
140
151
  * Animated three-dot typing hint with a name-aware label.
141
152
  *
142
153
  * The dots bounce with staggered delays (disabled under
143
- * prefers-reduced-motion) and the label is announced politely to
144
- * screen readers via role="status".
154
+ * prefers-reduced-motion). Each new label is announced politely to screen
155
+ * readers through the shared announcement channel, unless an enclosing live
156
+ * container such as a chat log delivers it.
145
157
  *
146
158
  * @example
147
159
  * ```
@@ -164,11 +176,33 @@ export function ChatTypingIndicator({
164
176
  [locale],
165
177
  );
166
178
  const label = typingLabel(names, t, listFormat);
179
+
180
+ const rootRef = useRef<HTMLDivElement>(null);
181
+ const mergedRef = useMemo(() => mergeRefs(rootRef, ref), [ref]);
182
+ const announce = useAnnounce();
183
+ // The label last announced, so a rerender or a StrictMode effect replay does
184
+ // not repeat it; cleared when nobody is typing, so the same people starting
185
+ // again is a new occasion (spec:AST-086 FR6).
186
+ const announcedLabelRef = useRef<string | null>(null);
187
+ useEffect(() => {
188
+ if (label == null) {
189
+ announcedLabelRef.current = null;
190
+ return;
191
+ }
192
+ if (announcedLabelRef.current === label) {
193
+ return;
194
+ }
195
+ announcedLabelRef.current = label;
196
+ const isInsideLiveContainer =
197
+ rootRef.current?.parentElement?.closest(LIVE_CONTAINER_SELECTOR) != null;
198
+ if (!isInsideLiveContainer) {
199
+ announce(label, 'polite');
200
+ }
201
+ }, [announce, label]);
202
+
167
203
  return (
168
204
  <div
169
- ref={ref}
170
- role="status"
171
- aria-live="polite"
205
+ ref={mergedRef}
172
206
  data-testid={testId}
173
207
  {...mergeProps(
174
208
  themeProps('chat-typing-indicator'),
@@ -4,7 +4,8 @@
4
4
 
5
5
  /** Shared jsdom and Storybook renderings for ChatTypingIndicator status states. */
6
6
 
7
- import {useState, type ReactElement} from 'react';
7
+ import {useEffect, useState, type ReactElement} from 'react';
8
+ import {useAnnounce} from '@astryxdesign/core/hooks';
8
9
  import {ChatTypingIndicator} from '../ChatTypingIndicator';
9
10
  import type {ChatTypingStatusStateId} from './ChatTypingIndicator.a11y.states';
10
11
 
@@ -27,12 +28,35 @@ function TransitionButton({
27
28
  );
28
29
  }
29
30
 
30
- function TypingHarness({startsNamed}: {startsNamed: boolean}) {
31
- const initial = startsNamed ? ['Ana'] : [];
32
- const [names, setNames] = useState<string[]>(initial);
31
+ /**
32
+ * Establishes the singleton polite channel the indicator announces through,
33
+ * then clears the seed, so the binding inspects the pre-existing empty region.
34
+ */
35
+ function PrimePoliteChannel() {
36
+ const announce = useAnnounce();
37
+ const [isReady, setIsReady] = useState(false);
38
+ useEffect(() => {
39
+ let secondFrame = 0;
40
+ announce('Preparing status channel', 'polite');
41
+ const firstFrame = requestAnimationFrame(() => {
42
+ secondFrame = requestAnimationFrame(() => {
43
+ announce('', 'polite');
44
+ setIsReady(true);
45
+ });
46
+ });
47
+ return () => {
48
+ cancelAnimationFrame(firstFrame);
49
+ cancelAnimationFrame(secondFrame);
50
+ };
51
+ }, [announce]);
52
+ return <span data-a11y-ready={String(isReady)} hidden />;
53
+ }
54
+
55
+ function TypingHarness() {
56
+ const [names, setNames] = useState<string[]>([]);
33
57
  return (
34
58
  <div>
35
- <span data-a11y-ready="true" hidden />
59
+ <PrimePoliteChannel />
36
60
  <button data-a11y-relation="focus-anchor" type="button">
37
61
  Keep focus
38
62
  </button>
@@ -53,8 +77,7 @@ export const CHAT_TYPING_STATUS_STATE_RENDERS: Record<
53
77
  ChatTypingStatusStateId,
54
78
  ChatTypingStatusStateRender
55
79
  > = {
56
- 'chat-typing-empty-to-message': () => <TypingHarness startsNamed={false} />,
57
- 'chat-typing-name-mounted': () => <TypingHarness startsNamed />,
80
+ 'chat-typing-empty-to-message': () => <TypingHarness />,
58
81
  };
59
82
 
60
83
  export {transitionTestId};