@astryxdesign/lab 0.7.6-canary.ec128c4 → 0.7.6-canary.f73623d
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.
- package/README.md +1 -1
- package/dist/Comments/CommentComposer.d.ts.map +1 -1
- package/dist/Comments/CommentComposer.js +10 -3
- package/dist/Comments/CommentParts.d.ts +9 -2
- package/dist/Comments/CommentParts.d.ts.map +1 -1
- package/dist/Comments/CommentParts.js +32 -15
- package/dist/Comments/CommentRow.d.ts.map +1 -1
- package/dist/Comments/CommentRow.js +5 -2
- package/dist/Comments/Comments.d.ts.map +1 -1
- package/dist/Comments/Comments.js +10 -1
- package/dist/Comments/CommentsContext.d.ts +2 -0
- package/dist/Comments/CommentsContext.d.ts.map +1 -1
- package/dist/Comments/CommentsContext.js +7 -3
- package/dist/Comments/useStatusAnnouncement.d.ts +26 -0
- package/dist/Comments/useStatusAnnouncement.d.ts.map +1 -0
- package/dist/Comments/useStatusAnnouncement.js +48 -0
- package/package.json +4 -4
- package/src/Comments/CommentComposer.test.tsx +54 -4
- package/src/Comments/CommentComposer.tsx +10 -2
- package/src/Comments/CommentParts.tsx +50 -14
- package/src/Comments/CommentRow.tsx +5 -6
- package/src/Comments/Comments.doc.mjs +1 -1
- package/src/Comments/Comments.test.tsx +339 -13
- package/src/Comments/Comments.tsx +16 -1
- package/src/Comments/CommentsContext.tsx +13 -1
- package/src/Comments/useStatusAnnouncement.ts +57 -0
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ See the **[Component Lifecycle](https://github.com/meta/astryx/wiki/Component-Li
|
|
|
24
24
|
|
|
25
25
|
The [Accessibility Checklist](https://github.com/meta/astryx/wiki/Accessibility-Checklist) is a hard requirement for graduating to core — every item, verified in the promotion PR, alongside the hardening bar linked above.
|
|
26
26
|
|
|
27
|
-
Lab components get the same scan coverage as core (the `pr-a11y` axe audit and the
|
|
27
|
+
Lab components get the same scan coverage as core (the `pr-a11y` axe audit and the full release-check scan run against lab too), but a11y findings on a lab component do not block lab merges — they block **promotion**. Iterate freely in lab; clear the checklist to graduate.
|
|
28
28
|
|
|
29
29
|
## Usage
|
|
30
30
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAKL,KAAK,mBAAmB,EAEzB,MAAM,yBAAyB,CAAC;AAqBjC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAkGjB,0DAA0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,cAAc,EAAE,qBAAqB,CAAC;IACtC,cAAc,CAAC,EAAE,CACf,KAAK,EAAE,SAAS,IAAI,EAAE,KAEtB,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC7E,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACzC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC9C,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAClE,OAAO,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3B,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC;CACnE;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,aAAa,EACb,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAY,EACZ,KAAK,EACL,qBAAqB,EACrB,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,OAAO,EACP,QAAQ,EAAE,YAAY,EACtB,KAAK,GACN,EAAE,wBAAwB,+
|
|
1
|
+
{"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -169,6 +169,13 @@ export function CommentComposerBase({
|
|
|
169
169
|
}
|
|
170
170
|
}, [focusKey]);
|
|
171
171
|
const [actionError, setActionError] = useState();
|
|
172
|
+
// Counts action failures so each one mounts its own Banner: a second failure
|
|
173
|
+
// with the same words is a new occasion and is announced again (AST-086 FR6).
|
|
174
|
+
const [actionErrorSerial, setActionErrorSerial] = useState(0);
|
|
175
|
+
const reportActionError = content => {
|
|
176
|
+
setActionError(content);
|
|
177
|
+
setActionErrorSerial(serial => serial + 1);
|
|
178
|
+
};
|
|
172
179
|
const [isActionPending, setIsActionPending] = useState(false);
|
|
173
180
|
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
174
181
|
const updateActionPending = useCallback(nextPending => {
|
|
@@ -205,7 +212,7 @@ export function CommentComposerBase({
|
|
|
205
212
|
}
|
|
206
213
|
} catch {
|
|
207
214
|
if (isMountedRef.current) {
|
|
208
|
-
|
|
215
|
+
reportActionError({
|
|
209
216
|
title: t('@astryx.commentComposer.uploadFailed'),
|
|
210
217
|
actionLabel: t('@astryx.comments.retry'),
|
|
211
218
|
onAction: () => beginImageUpload(files)
|
|
@@ -223,7 +230,7 @@ export function CommentComposerBase({
|
|
|
223
230
|
}
|
|
224
231
|
const images = files.filter(file => file.type.startsWith('image/'));
|
|
225
232
|
if (images.length !== files.length) {
|
|
226
|
-
|
|
233
|
+
reportActionError({
|
|
227
234
|
title: t('@astryx.commentComposer.imagesOnly'),
|
|
228
235
|
actionLabel: t('@astryx.commentComposer.chooseImages'),
|
|
229
236
|
onAction: () => {
|
|
@@ -276,7 +283,7 @@ export function CommentComposerBase({
|
|
|
276
283
|
,
|
|
277
284
|
onBeforeAction: () => inputRef.current?.focus(),
|
|
278
285
|
"data-composer-error": "banner"
|
|
279
|
-
}), /*#__PURE__*/_jsx(ChatComposer, {
|
|
286
|
+
}, error != null ? 'error' : `action-${actionErrorSerial}`), /*#__PURE__*/_jsx(ChatComposer, {
|
|
280
287
|
value: value,
|
|
281
288
|
onChange: onChange,
|
|
282
289
|
onSubmit: submitInput,
|
|
@@ -132,10 +132,17 @@ export type ErrorBannerContent = {
|
|
|
132
132
|
actionLabel?: string;
|
|
133
133
|
onAction?: () => void;
|
|
134
134
|
isActionDisabled?: boolean;
|
|
135
|
+
/** Its own retry is running: say nothing until the outcome (FR6). */
|
|
136
|
+
isRetrying?: boolean;
|
|
137
|
+
/** The failure was already announced elsewhere; this Banner stays silent. */
|
|
138
|
+
isAnnounced?: boolean;
|
|
135
139
|
};
|
|
136
140
|
/**
|
|
137
|
-
* An inline failure as an error Banner.
|
|
138
|
-
*
|
|
141
|
+
* An inline failure as an error Banner. The Banner carries no live role: it
|
|
142
|
+
* mounts already holding its text, which may not be announced. The failure is
|
|
143
|
+
* announced once, assertively, through the shared channel instead, unless the
|
|
144
|
+
* Banner sits inside a live container such as the chat log, which delivers it
|
|
145
|
+
* (spec:AST-086 FR2, FR4, FR5).
|
|
139
146
|
*/
|
|
140
147
|
export declare function ErrorBanner({ actionRef, content, onBeforeAction, ...dataProps }: {
|
|
141
148
|
actionRef?: Ref<HTMLButtonElement>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentParts.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentParts.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"CommentParts.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentParts.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAsC3C,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;IAC3B,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;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,+BAuCA;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,+BA2B5E;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,sCAkFA;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"}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* reply rail, activity event row, edit history dialog, and loading skeletons
|
|
13
13
|
* @position Internal building blocks of the Comments family
|
|
14
14
|
*/
|
|
15
|
-
import { useState } from 'react';
|
|
15
|
+
import { useRef, useState } from 'react';
|
|
16
16
|
import * as stylex from '@stylexjs/stylex';
|
|
17
17
|
import { Avatar } from '@astryxdesign/core/Avatar';
|
|
18
18
|
import { Badge } from '@astryxdesign/core/Badge';
|
|
@@ -38,6 +38,7 @@ import { useTranslator } from '@astryxdesign/core/i18n';
|
|
|
38
38
|
import '@astryxdesign/core/theme/tokens.stylex';
|
|
39
39
|
import { borderVars, colorVars, durationVars, easeVars, radiusVars, spacingVars } from '@astryxdesign/core/theme/tokens.stylex';
|
|
40
40
|
import { CommentIcon } from "./icons.js";
|
|
41
|
+
import { useStatusAnnouncement } from "./useStatusAnnouncement.js";
|
|
41
42
|
import { eventAnchorId } from "./utils.js";
|
|
42
43
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
43
44
|
/** The 28px avatar column every comment row, reply, and event aligns to. */
|
|
@@ -243,8 +244,11 @@ const highlightRules = {
|
|
|
243
244
|
/** A failed action as an error Banner with its one recovery action. */
|
|
244
245
|
|
|
245
246
|
/**
|
|
246
|
-
* An inline failure as an error Banner.
|
|
247
|
-
*
|
|
247
|
+
* An inline failure as an error Banner. The Banner carries no live role: it
|
|
248
|
+
* mounts already holding its text, which may not be announced. The failure is
|
|
249
|
+
* announced once, assertively, through the shared channel instead, unless the
|
|
250
|
+
* Banner sits inside a live container such as the chat log, which delivers it
|
|
251
|
+
* (spec:AST-086 FR2, FR4, FR5).
|
|
248
252
|
*/
|
|
249
253
|
export function ErrorBanner({
|
|
250
254
|
actionRef,
|
|
@@ -259,9 +263,15 @@ export function ErrorBanner({
|
|
|
259
263
|
onAction,
|
|
260
264
|
title
|
|
261
265
|
} = content;
|
|
266
|
+
const t = useTranslator();
|
|
267
|
+
const bannerRef = useRef(null);
|
|
268
|
+
useStatusAnnouncement(bannerRef, content.isRetrying === true || content.isAnnounced === true ? null : description != null ? t('@astryx.comments.errorAnnouncement', {
|
|
269
|
+
title,
|
|
270
|
+
description
|
|
271
|
+
}) : title, 'assertive');
|
|
262
272
|
return /*#__PURE__*/_jsx(Banner, {
|
|
273
|
+
ref: bannerRef,
|
|
263
274
|
status: "error",
|
|
264
|
-
role: "alert",
|
|
265
275
|
container: "card",
|
|
266
276
|
title: title,
|
|
267
277
|
description: description,
|
|
@@ -405,6 +415,11 @@ export function CommentAnchorQuote({
|
|
|
405
415
|
export function CommentProgressStatus({
|
|
406
416
|
progress
|
|
407
417
|
}) {
|
|
418
|
+
const labelRef = useRef(null);
|
|
419
|
+
// The label carries no live role: it mounts already holding its text. It is
|
|
420
|
+
// announced politely through the shared channel, unless the chat log around
|
|
421
|
+
// it delivers it (spec:AST-086 FR2, FR4).
|
|
422
|
+
useStatusAnnouncement(labelRef, progress.label, 'polite');
|
|
408
423
|
return /*#__PURE__*/_jsxs(HStack, {
|
|
409
424
|
gap: 1,
|
|
410
425
|
align: "center",
|
|
@@ -412,9 +427,9 @@ export function CommentProgressStatus({
|
|
|
412
427
|
width: "100%",
|
|
413
428
|
"data-comment-progress": "shimmer",
|
|
414
429
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
430
|
+
ref: labelRef,
|
|
415
431
|
type: "body",
|
|
416
432
|
color: "secondary",
|
|
417
|
-
role: "status",
|
|
418
433
|
xstyle: partStyles.progressLabel,
|
|
419
434
|
children: progress.label
|
|
420
435
|
}), progress.link != null && /*#__PURE__*/_jsx(Link, {
|
|
@@ -629,17 +644,19 @@ export function HistoryDialog({
|
|
|
629
644
|
onOpenChange: open => !open && onClose(),
|
|
630
645
|
hasDivider: true
|
|
631
646
|
}),
|
|
632
|
-
content: /*#__PURE__*/
|
|
633
|
-
children:
|
|
647
|
+
content: /*#__PURE__*/_jsxs(LayoutContent, {
|
|
648
|
+
children: [/*#__PURE__*/_jsx("div", {
|
|
649
|
+
role: "alert",
|
|
650
|
+
children: !history.isLoading && history.error != null && /*#__PURE__*/_jsx(Banner, {
|
|
651
|
+
status: "error",
|
|
652
|
+
title: t('@astryx.comments.historyFailed'),
|
|
653
|
+
description: history.error,
|
|
654
|
+
container: "section"
|
|
655
|
+
})
|
|
656
|
+
}), history.isLoading ? /*#__PURE__*/_jsx(Spinner, {
|
|
634
657
|
size: "lg",
|
|
635
658
|
label: t('@astryx.comments.historyLoading')
|
|
636
|
-
}) : history.error != null ? /*#__PURE__*/_jsx(
|
|
637
|
-
status: "error",
|
|
638
|
-
role: "alert",
|
|
639
|
-
title: t('@astryx.comments.historyFailed'),
|
|
640
|
-
description: history.error,
|
|
641
|
-
container: "section"
|
|
642
|
-
}) : history.versions.length === 0 ? /*#__PURE__*/_jsx(EmptyState, {
|
|
659
|
+
}) : history.error != null ? null : history.versions.length === 0 ? /*#__PURE__*/_jsx(EmptyState, {
|
|
643
660
|
title: t('@astryx.comments.historyEmptyTitle'),
|
|
644
661
|
description: t('@astryx.comments.historyEmptyDescription'),
|
|
645
662
|
isCompact: true
|
|
@@ -671,7 +688,7 @@ export function HistoryDialog({
|
|
|
671
688
|
}), renderVersionBody(version)]
|
|
672
689
|
})
|
|
673
690
|
}, version.id))
|
|
674
|
-
})
|
|
691
|
+
})]
|
|
675
692
|
})
|
|
676
693
|
})
|
|
677
694
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentRow.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentRow.tsx"],"names":[],"mappings":"AAuEA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AAOjB,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAC;AAQ7C,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAAsB,CAAC;AA6M3E;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,KAAK,EACL,QAA0B,EAC1B,oBAA4B,EAC5B,sBAA8B,EAC9B,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kEAAkE;IAClE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;CAClB,+
|
|
1
|
+
{"version":3,"file":"CommentRow.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentRow.tsx"],"names":[],"mappings":"AAuEA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AAOjB,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAC;AAQ7C,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAAsB,CAAC;AA6M3E;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,KAAK,EACL,QAA0B,EAC1B,oBAA4B,EAC5B,sBAA8B,EAC9B,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kEAAkE;IAClE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;CAClB,+BAikBA"}
|
|
@@ -371,7 +371,9 @@ export function CommentRow({
|
|
|
371
371
|
description: actionError,
|
|
372
372
|
actionLabel: can.retry ? t('@astryx.comments.retry') : undefined,
|
|
373
373
|
onAction: can.retry ? () => scope.retry(comment) : undefined,
|
|
374
|
-
isActionDisabled: rowActionsBusy
|
|
374
|
+
isActionDisabled: rowActionsBusy,
|
|
375
|
+
isRetrying: isBusy,
|
|
376
|
+
isAnnounced: actionFailure?.isAnnounced
|
|
375
377
|
}
|
|
376
378
|
// The Banner unmounts once the retry starts; keep focus on the comment.
|
|
377
379
|
,
|
|
@@ -397,7 +399,8 @@ export function CommentRow({
|
|
|
397
399
|
content: {
|
|
398
400
|
title: actionFailure.message,
|
|
399
401
|
actionLabel: actionFailure.retry != null ? t('@astryx.comments.retry') : undefined,
|
|
400
|
-
onAction: actionFailure.retry
|
|
402
|
+
onAction: actionFailure.retry,
|
|
403
|
+
isAnnounced: actionFailure.isAnnounced
|
|
401
404
|
},
|
|
402
405
|
onBeforeAction: () => articleRef.current?.focus(),
|
|
403
406
|
"data-comment-action-error": "banner"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Comments.d.ts","sourceRoot":"","sources":["../../src/Comments/Comments.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"Comments.d.ts","sourceRoot":"","sources":["../../src/Comments/Comments.tsx"],"names":[],"mappings":"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"}
|
|
@@ -31,6 +31,7 @@ import { useTranslator } from '@astryxdesign/core/i18n';
|
|
|
31
31
|
import { CommentComposerBase } from "./CommentComposer.js";
|
|
32
32
|
import { ActivityEventRow, CommentComposerLoadingSkeleton, CommentThreadLoadingSkeleton, ErrorBanner } from "./CommentParts.js";
|
|
33
33
|
import { CommentRow, THREAD_DIVIDERS } from "./CommentRow.js";
|
|
34
|
+
import { useStatusAnnouncement } from "./useStatusAnnouncement.js";
|
|
34
35
|
import { CommentsContextProvider, useCommentsController } from "./CommentsContext.js";
|
|
35
36
|
import { timelineTime } from "./utils.js";
|
|
36
37
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -148,6 +149,14 @@ export function Comments({
|
|
|
148
149
|
const loadingOlderRef = useRef(false);
|
|
149
150
|
const loadOlderButtonRef = useRef(null);
|
|
150
151
|
const olderRetryRef = useRef(null);
|
|
152
|
+
// The load-failure Banner carries no live role: it mounts already holding
|
|
153
|
+
// its text. It is announced once, assertively, through the shared channel
|
|
154
|
+
// (spec:AST-086 FR2, FR5).
|
|
155
|
+
const loadErrorRef = useRef(null);
|
|
156
|
+
useStatusAnnouncement(loadErrorRef, loadError != null ? t('@astryx.comments.errorAnnouncement', {
|
|
157
|
+
title: t('@astryx.comments.loadFailed'),
|
|
158
|
+
description: loadError
|
|
159
|
+
}) : null, 'assertive');
|
|
151
160
|
const rootRef = useRef(null);
|
|
152
161
|
const setRootRef = useCallback(element => {
|
|
153
162
|
rootRef.current = element;
|
|
@@ -380,8 +389,8 @@ export function Comments({
|
|
|
380
389
|
container
|
|
381
390
|
}), stylex.props(!isEmbedded && styles.root, xstyle), className, style),
|
|
382
391
|
children: [loadError != null && /*#__PURE__*/_jsx(Banner, {
|
|
392
|
+
ref: loadErrorRef,
|
|
383
393
|
status: "error",
|
|
384
|
-
role: "alert",
|
|
385
394
|
title: t('@astryx.comments.loadFailed'),
|
|
386
395
|
description: loadError,
|
|
387
396
|
container: "section",
|
|
@@ -32,6 +32,8 @@ export type EditTarget = {
|
|
|
32
32
|
export type ActionFailure = {
|
|
33
33
|
message: string;
|
|
34
34
|
retry?: () => void;
|
|
35
|
+
/** Already spoken by whoever raised it, so the row's Banner stays silent. */
|
|
36
|
+
isAnnounced?: boolean;
|
|
35
37
|
};
|
|
36
38
|
export interface CommentsContextValue {
|
|
37
39
|
currentUser?: CommentUser;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentsContext.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentsContext.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,qCAAqC,CAAC;AAO7E,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAEjB,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAAC,OAAO,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAAC;AAc9D,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG;
|
|
1
|
+
{"version":3,"file":"CommentsContext.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentsContext.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,qCAAqC,CAAC;AAO7E,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAEjB,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAAC,OAAO,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAAC;AAc9D,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AA4CF,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;IAC9B,qEAAqE;IACrE,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,eAAe,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD,cAAc,EAAE,qBAAqB,CAAC;IACtC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC9C,gBAAgB,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,eAAe,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,qBAAqB,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACxD,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,wBAAwB,CAAC,YAAY,CAAC,CAAC;IACpD,uBAAuB,CAAC,EAAE,wBAAwB,CAAC,yBAAyB,CAAC,CAAC;IAC9E,cAAc,CAAC,EAAE,wBAAwB,CAAC,gBAAgB,CAAC,CAAC;IAC5D,sCAAsC;IACtC,GAAG,EAAE;QACH,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,OAAO,CAAC;QAClB,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,OAAO,CAAC;QACf,KAAK,EAAE,OAAO,CAAC;QACf,YAAY,EAAE,OAAO,CAAC;QACtB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;IACF,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IACtD,YAAY,EAAE,OAAO,CAAC;IACtB,uBAAuB,EAAE,OAAO,CAAC;IACjC,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,CACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC,KACxB,IAAI,CAAC;IACV,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,WAAW,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IACzC,UAAU,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,QAAQ,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,UAAU,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,KAAK,EAAE,CACL,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,qBAAqB,KAC3B,IAAI,CAAC;IACV,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,MAAM,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IAC7C,WAAW,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,2BAA2B,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3D;AAID,eAAO,MAAM,uBAAuB,uDAA2B,CAAC;AAEhE,8DAA8D;AAC9D,wBAAgB,kBAAkB,IAAI,oBAAoB,CAQzD;AAED,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB;IACzE,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3D,0EAA0E;IAC1E,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACpC,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,WAAW,EACX,IAAoB,EACpB,SAAqB,EACrB,mBAAmB,EACnB,cAAyB,EACzB,eAA2C,EAC3C,aAAiC,EACjC,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,cAAqB,EACrB,YAAoB,EACpB,qBAAqB,EACrB,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,OAAO,EACP,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,uBAAuB,EACvB,cAAc,EACd,gBAAgB,EAAE,oBAAoB,EACtC,sBAAsB,EACtB,KAAK,EACL,OAAO,GACR,EAAE,yBAAyB;;;sBAiIf,MAAM,aACJ,MAAM,KAChB,OAAO,CAAC,YAAY,CAAC;;EAyXzB"}
|
|
@@ -183,7 +183,7 @@ export function useCommentsController({
|
|
|
183
183
|
delete next[commentId];
|
|
184
184
|
return next;
|
|
185
185
|
});
|
|
186
|
-
const runCommentAction = async (commentId, action, failureMessage, retry) => {
|
|
186
|
+
const runCommentAction = async (commentId, action, failureMessage, retry, isAnnounced) => {
|
|
187
187
|
setPendingActionIds(previous => new Set(previous).add(commentId));
|
|
188
188
|
clearActionError(commentId);
|
|
189
189
|
try {
|
|
@@ -194,7 +194,8 @@ export function useCommentsController({
|
|
|
194
194
|
...previous,
|
|
195
195
|
[commentId]: {
|
|
196
196
|
message: failureMessage,
|
|
197
|
-
retry: retry ?? (() => void runCommentAction(commentId, action, failureMessage))
|
|
197
|
+
retry: retry ?? (() => void runCommentAction(commentId, action, failureMessage)),
|
|
198
|
+
isAnnounced
|
|
198
199
|
}
|
|
199
200
|
}));
|
|
200
201
|
return false;
|
|
@@ -288,7 +289,10 @@ export function useCommentsController({
|
|
|
288
289
|
setDeleteError(undefined);
|
|
289
290
|
const succeeded = await runCommentAction(target.id, () => onDelete(target.id), t('@astryx.comments.deleteFailed'),
|
|
290
291
|
// Retrying a delete asks for confirmation again.
|
|
291
|
-
() => setDeleteTarget(target)
|
|
292
|
+
() => setDeleteTarget(target),
|
|
293
|
+
// The dialog announces a failed delete (AST-086 FR1, FR6), so the stored
|
|
294
|
+
// failure the row shows is marked before it first renders.
|
|
295
|
+
true);
|
|
292
296
|
if (succeeded) {
|
|
293
297
|
commitAndFocus(() => setDeleteTarget(null), neighborId, neighborId == null ? 'thread' : 'article', rootRef);
|
|
294
298
|
} else {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file useStatusAnnouncement.ts
|
|
3
|
+
* @input Uses React, useAnnounce from core hooks
|
|
4
|
+
* @output Exports useStatusAnnouncement hook
|
|
5
|
+
* @position Internal to Comments; announces a transient status element
|
|
6
|
+
* (error banner, progress label) once instead of mounting it as a live region
|
|
7
|
+
*
|
|
8
|
+
* A live region that mounts already holding its text may not be announced, so
|
|
9
|
+
* the visible element carries no live role (spec:AST-086 FR2). This hook says
|
|
10
|
+
* the words once through the shared channel instead, unless a live container
|
|
11
|
+
* such as a chat log already delivers content added inside it (FR4).
|
|
12
|
+
*/
|
|
13
|
+
import { type RefObject } from 'react';
|
|
14
|
+
import type { AnnouncePoliteness } from '@astryxdesign/core/hooks';
|
|
15
|
+
/**
|
|
16
|
+
* Announce `message` once per occasion: when the element first shows it, and
|
|
17
|
+
* again whenever the message changes. A rerender or a StrictMode effect replay
|
|
18
|
+
* does not repeat it, and a message that goes away and comes back is a new
|
|
19
|
+
* occasion (spec:AST-086 FR6).
|
|
20
|
+
*
|
|
21
|
+
* @param ref The visible element, used to find an enclosing live container.
|
|
22
|
+
* @param message The words to say, or `null` for nothing.
|
|
23
|
+
* @param politeness Errors are `'assertive'`, progress is `'polite'` (FR5).
|
|
24
|
+
*/
|
|
25
|
+
export declare function useStatusAnnouncement(ref: RefObject<HTMLElement | null>, message: string | null, politeness: AnnouncePoliteness): void;
|
|
26
|
+
//# sourceMappingURL=useStatusAnnouncement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStatusAnnouncement.d.ts","sourceRoot":"","sources":["../../src/Comments/useStatusAnnouncement.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAExD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,0BAA0B,CAAC;AAKjE;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EAClC,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,UAAU,EAAE,kBAAkB,GAC7B,IAAI,CAkBN"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useStatusAnnouncement.ts
|
|
7
|
+
* @input Uses React, useAnnounce from core hooks
|
|
8
|
+
* @output Exports useStatusAnnouncement hook
|
|
9
|
+
* @position Internal to Comments; announces a transient status element
|
|
10
|
+
* (error banner, progress label) once instead of mounting it as a live region
|
|
11
|
+
*
|
|
12
|
+
* A live region that mounts already holding its text may not be announced, so
|
|
13
|
+
* the visible element carries no live role (spec:AST-086 FR2). This hook says
|
|
14
|
+
* the words once through the shared channel instead, unless a live container
|
|
15
|
+
* such as a chat log already delivers content added inside it (FR4).
|
|
16
|
+
*/
|
|
17
|
+
import { useEffect, useRef } from 'react';
|
|
18
|
+
import { useAnnounce } from '@astryxdesign/core/hooks';
|
|
19
|
+
const LIVE_CONTAINER_SELECTOR = '[role="log"], [role="status"], [role="alert"], [aria-live="polite"], [aria-live="assertive"]';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Announce `message` once per occasion: when the element first shows it, and
|
|
23
|
+
* again whenever the message changes. A rerender or a StrictMode effect replay
|
|
24
|
+
* does not repeat it, and a message that goes away and comes back is a new
|
|
25
|
+
* occasion (spec:AST-086 FR6).
|
|
26
|
+
*
|
|
27
|
+
* @param ref The visible element, used to find an enclosing live container.
|
|
28
|
+
* @param message The words to say, or `null` for nothing.
|
|
29
|
+
* @param politeness Errors are `'assertive'`, progress is `'polite'` (FR5).
|
|
30
|
+
*/
|
|
31
|
+
export function useStatusAnnouncement(ref, message, politeness) {
|
|
32
|
+
const announce = useAnnounce();
|
|
33
|
+
const announcedRef = useRef(null);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (message == null || message === '') {
|
|
36
|
+
announcedRef.current = null;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (announcedRef.current === message) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
announcedRef.current = message;
|
|
43
|
+
const isInsideLiveContainer = ref.current?.parentElement?.closest(LIVE_CONTAINER_SELECTOR) != null;
|
|
44
|
+
if (!isInsideLiveContainer) {
|
|
45
|
+
announce(message, politeness);
|
|
46
|
+
}
|
|
47
|
+
}, [announce, message, politeness, ref]);
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.7.6-canary.
|
|
3
|
+
"version": "0.7.6-canary.f73623d",
|
|
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.6-canary.
|
|
52
|
+
"@astryxdesign/core": "0.7.6-canary.f73623d",
|
|
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.6-canary.
|
|
63
|
-
"@astryxdesign/cli": "0.7.6-canary.
|
|
62
|
+
"@astryxdesign/a11y-spec": "0.7.6-canary.f73623d",
|
|
63
|
+
"@astryxdesign/cli": "0.7.6-canary.f73623d",
|
|
64
64
|
"@babel/cli": "^7.29.7",
|
|
65
65
|
"@babel/core": "^7.29.7",
|
|
66
66
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
it,
|
|
13
13
|
vi,
|
|
14
14
|
} from 'vitest';
|
|
15
|
+
const announce = vi.hoisted(() => vi.fn());
|
|
16
|
+
vi.mock('@astryxdesign/core/hooks', async importOriginal => ({
|
|
17
|
+
...(await importOriginal<typeof import('@astryxdesign/core/hooks')>()),
|
|
18
|
+
useAnnounce: () => announce,
|
|
19
|
+
}));
|
|
15
20
|
// The full emoji set renders slowly in jsdom; the composer only needs a few.
|
|
16
21
|
vi.mock('../EmojiPicker/emojiData', () => ({
|
|
17
22
|
EMOJI_GROUPS: [
|
|
@@ -70,6 +75,7 @@ class ResizeObserverStub {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
beforeEach(() => {
|
|
78
|
+
announce.mockClear();
|
|
73
79
|
vi.stubGlobal('ResizeObserver', ResizeObserverStub);
|
|
74
80
|
Element.prototype.scrollIntoView = () => {};
|
|
75
81
|
});
|
|
@@ -78,6 +84,17 @@ afterEach(() => {
|
|
|
78
84
|
vi.unstubAllGlobals();
|
|
79
85
|
});
|
|
80
86
|
|
|
87
|
+
/** The composer's failure Banner, which carries no live role. */
|
|
88
|
+
function composerErrorBanner(): HTMLElement {
|
|
89
|
+
const banner = document.querySelector<HTMLElement>(
|
|
90
|
+
'[data-composer-error="banner"]',
|
|
91
|
+
);
|
|
92
|
+
if (banner == null) {
|
|
93
|
+
throw new Error('Composer error Banner not found');
|
|
94
|
+
}
|
|
95
|
+
return banner;
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
async function settle() {
|
|
82
99
|
await act(() => new Promise<void>(resolve => setTimeout(resolve, 0)));
|
|
83
100
|
}
|
|
@@ -289,9 +306,41 @@ describe('CommentComposer', () => {
|
|
|
289
306
|
});
|
|
290
307
|
});
|
|
291
308
|
expect(onUploadImages).not.toHaveBeenCalled();
|
|
292
|
-
|
|
293
|
-
|
|
309
|
+
const banner = composerErrorBanner();
|
|
310
|
+
expect(banner.textContent).toContain('Only image files can be added');
|
|
311
|
+
// The Banner has no live role: it is announced once through the channel.
|
|
312
|
+
expect(banner).not.toHaveAttribute('role');
|
|
313
|
+
expect(announce).toHaveBeenCalledTimes(1);
|
|
314
|
+
expect(announce).toHaveBeenCalledWith(
|
|
315
|
+
expect.stringContaining('Only image files can be added'),
|
|
316
|
+
'assertive',
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('announces a second refusal with the same words again', async () => {
|
|
321
|
+
render(
|
|
322
|
+
<CommentComposer
|
|
323
|
+
value=""
|
|
324
|
+
onChange={() => {}}
|
|
325
|
+
onSubmit={() => {}}
|
|
326
|
+
onUploadImages={vi.fn()}
|
|
327
|
+
/>,
|
|
294
328
|
);
|
|
329
|
+
const input = document.querySelector('input[type="file"]');
|
|
330
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
331
|
+
throw new Error('File input not found');
|
|
332
|
+
}
|
|
333
|
+
for (const name of ['first.pdf', 'second.pdf']) {
|
|
334
|
+
await act(async () => {
|
|
335
|
+
fireEvent.change(input, {
|
|
336
|
+
target: {
|
|
337
|
+
files: [new File(['%PDF'], name, {type: 'application/pdf'})],
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Each refusal is its own occasion (AST-086 FR6), though the words match.
|
|
343
|
+
expect(announce).toHaveBeenCalledTimes(2);
|
|
295
344
|
});
|
|
296
345
|
|
|
297
346
|
it('shows an error with Retry, and a quoted passage the viewer can remove', () => {
|
|
@@ -309,8 +358,9 @@ describe('CommentComposer', () => {
|
|
|
309
358
|
highlightColor="green"
|
|
310
359
|
/>,
|
|
311
360
|
);
|
|
312
|
-
|
|
313
|
-
|
|
361
|
+
fireEvent.click(
|
|
362
|
+
within(composerErrorBanner()).getByRole('button', {name: 'Retry'}),
|
|
363
|
+
);
|
|
314
364
|
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
315
365
|
expect(
|
|
316
366
|
document
|
|
@@ -259,6 +259,13 @@ export function CommentComposerBase({
|
|
|
259
259
|
[focusKey],
|
|
260
260
|
);
|
|
261
261
|
const [actionError, setActionError] = useState<ErrorBannerContent>();
|
|
262
|
+
// Counts action failures so each one mounts its own Banner: a second failure
|
|
263
|
+
// with the same words is a new occasion and is announced again (AST-086 FR6).
|
|
264
|
+
const [actionErrorSerial, setActionErrorSerial] = useState(0);
|
|
265
|
+
const reportActionError = (content: ErrorBannerContent) => {
|
|
266
|
+
setActionError(content);
|
|
267
|
+
setActionErrorSerial(serial => serial + 1);
|
|
268
|
+
};
|
|
262
269
|
const [isActionPending, setIsActionPending] = useState(false);
|
|
263
270
|
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
264
271
|
|
|
@@ -303,7 +310,7 @@ export function CommentComposerBase({
|
|
|
303
310
|
}
|
|
304
311
|
} catch {
|
|
305
312
|
if (isMountedRef.current) {
|
|
306
|
-
|
|
313
|
+
reportActionError({
|
|
307
314
|
title: t('@astryx.commentComposer.uploadFailed'),
|
|
308
315
|
actionLabel: t('@astryx.comments.retry'),
|
|
309
316
|
onAction: () => beginImageUpload(files),
|
|
@@ -322,7 +329,7 @@ export function CommentComposerBase({
|
|
|
322
329
|
}
|
|
323
330
|
const images = files.filter(file => file.type.startsWith('image/'));
|
|
324
331
|
if (images.length !== files.length) {
|
|
325
|
-
|
|
332
|
+
reportActionError({
|
|
326
333
|
title: t('@astryx.commentComposer.imagesOnly'),
|
|
327
334
|
actionLabel: t('@astryx.commentComposer.chooseImages'),
|
|
328
335
|
onAction: () => {
|
|
@@ -378,6 +385,7 @@ export function CommentComposerBase({
|
|
|
378
385
|
<VStack gap={1} width="100%" aria-busy={isBusy || undefined}>
|
|
379
386
|
{errorBanner != null && (
|
|
380
387
|
<ErrorBanner
|
|
388
|
+
key={error != null ? 'error' : `action-${actionErrorSerial}`}
|
|
381
389
|
content={errorBanner}
|
|
382
390
|
// The Banner unmounts once the retry starts; keep focus in the input.
|
|
383
391
|
onBeforeAction={() => inputRef.current?.focus()}
|