@astryxdesign/core 0.7.4-canary.e5c5a87 → 0.7.4-canary.faa934f
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/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +8 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -0
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +6 -1
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +9 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +7 -1
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +9 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +9 -1
- package/dist/hooks/useTypeahead.d.ts.map +1 -1
- package/dist/hooks/useTypeahead.js +7 -2
- package/package.json +3 -3
- package/src/Chat/ChatMessageList.test.tsx +6 -2
- package/src/Chat/ChatMessageList.tsx +7 -1
- package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +20 -12
- package/src/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/DateTimeInput/DateTimeInput.tsx +5 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +14 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +10 -1
- package/src/Thumbnail/Thumbnail.test.tsx +6 -1
- package/src/Thumbnail/Thumbnail.tsx +12 -3
- package/src/TimeInput/TimeInput.tsx +5 -1
- package/src/TimeInput/TouchTimeField.tsx +12 -2
- package/src/__tests__/BusyOwnerAnnouncement.test.tsx +209 -2
- package/src/hooks/useTypeahead.test.tsx +42 -1
- package/src/hooks/useTypeahead.ts +7 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatMessageList.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatMessageList.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAC,KAAK,SAAS,EAA4C,MAAM,OAAO,CAAC;AAGhF,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatMessageList.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatMessageList.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAC,KAAK,SAAS,EAA4C,MAAM,OAAO,CAAC;AAGhF,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAKhD,MAAM,WAAW,oBAAqB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACrE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,WAAW,CAAC;IAElB;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA4FD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,OAAoB,EACpB,GAAG,EACH,KAAgB,EAChB,WAAmB,EACnB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,GAAG,IAAI,EACR,EAAE,oBAAoB,+BAoGtB;yBAlHe,eAAe"}
|
|
@@ -31,6 +31,8 @@ import { ChatListContext, useChatLayoutContext } from "./ChatContext.js";
|
|
|
31
31
|
import { isRenderable, mergeProps } from "../utils/index.js";
|
|
32
32
|
import { Spinner } from "../Spinner/index.js";
|
|
33
33
|
import { themeProps } from "../utils/themeProps.js";
|
|
34
|
+
import { useTranslator } from "../i18n/index.js";
|
|
35
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
34
36
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
35
37
|
// =============================================================================
|
|
36
38
|
// Styles
|
|
@@ -161,6 +163,10 @@ export function ChatMessageList({
|
|
|
161
163
|
const sentinelRef = useRef(null);
|
|
162
164
|
const innerRef = useRef(null);
|
|
163
165
|
const [isLoadingTop, startTransition] = useTransition();
|
|
166
|
+
const t = useTranslator();
|
|
167
|
+
// The top loading Spinner sits inside the log and is silent; the list
|
|
168
|
+
// announces its loading through the channel (spec:AST-086 FR4, FR8).
|
|
169
|
+
useBusyAnnouncement(isLoadingTop, t('@astryx.spinner.loading'), innerRef);
|
|
164
170
|
|
|
165
171
|
// Register inner content element with the layout for height observation
|
|
166
172
|
useEffect(() => {
|
|
@@ -219,7 +225,8 @@ export function ChatMessageList({
|
|
|
219
225
|
className: "x78zum5 xl56j7k x8o8v82"
|
|
220
226
|
},
|
|
221
227
|
children: /*#__PURE__*/_jsx(Spinner, {
|
|
222
|
-
size: "md"
|
|
228
|
+
size: "md",
|
|
229
|
+
"aria-hidden": "true"
|
|
223
230
|
})
|
|
224
231
|
}), align === 'bottom' && /*#__PURE__*/_jsx("div", {
|
|
225
232
|
...{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxInput.d.ts","sourceRoot":"","sources":["../../src/CheckboxInput/CheckboxInput.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAS3C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAG9C,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AACtC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"CheckboxInput.d.ts","sourceRoot":"","sources":["../../src/CheckboxInput/CheckboxInput.tsx"],"names":[],"mappings":"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"}
|
|
@@ -27,7 +27,9 @@ import { mergeProps, rtlStyles } from "../utils/index.js";
|
|
|
27
27
|
import "../Indicator/indicator.markers.stylex.js";
|
|
28
28
|
import { indicatorScope } from "../Indicator/indicator.markers.stylex.js";
|
|
29
29
|
import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
|
|
30
|
+
import { useTranslator } from "../i18n/index.js";
|
|
30
31
|
import { useIndicatorFocusRing } from "../hooks/useIndicatorFocusRing.js";
|
|
32
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
31
33
|
import { usePressFeedback } from "../hooks/usePressFeedback.js";
|
|
32
34
|
import { useResolvedRequired } from "../hooks/useResolvedRequired.js";
|
|
33
35
|
import { useIndicator } from "../Indicator/index.js";
|
|
@@ -195,6 +197,10 @@ export function CheckboxInput({
|
|
|
195
197
|
// `opacity: 0`, and only the indicator's own element can shape the outline
|
|
196
198
|
// to match it. See useIndicatorFocusRing.
|
|
197
199
|
const indicatorRef = useRef(null);
|
|
200
|
+
const t = useTranslator();
|
|
201
|
+
// The busy Spinner sits in the decorative, aria-hidden indicator, so this
|
|
202
|
+
// control announces its busy start itself (spec:AST-086 FR8).
|
|
203
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), indicatorRef);
|
|
198
204
|
const {
|
|
199
205
|
focusProps
|
|
200
206
|
} = useIndicatorFocusRing(indicatorRef);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateTimeInput.d.ts","sourceRoot":"","sources":["../../src/DateTimeInput/DateTimeInput.tsx"],"names":[],"mappings":"AA4CA,OAAO,EAGL,KAAK,WAAW,EAKjB,MAAM,UAAU,CAAC;AAIlB,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"DateTimeInput.d.ts","sourceRoot":"","sources":["../../src/DateTimeInput/DateTimeInput.tsx"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,7 @@ import { useCalendarConstraints } from "../Calendar/hooks/index.js";
|
|
|
32
32
|
import { usePopover } from "../Popover/index.js";
|
|
33
33
|
import { useTooltip } from "../Tooltip/index.js";
|
|
34
34
|
import { useInputContainer } from "../hooks/useInputContainer.js";
|
|
35
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
35
36
|
import { useInputStatusIcon } from "../hooks/useInputStatusIcon.js";
|
|
36
37
|
import { parseDateInput, parseISOTime, parseTimeInput, formatDisplayTime12h, formatDisplayTime24h, formatISOTime, isTimeInRange, adjustTime, isImeKeyEvent, mergeProps, isFocusDetached } from "../utils/index.js";
|
|
37
38
|
import { chainEventHandlers } from "../utils/chainEventHandlers.js";
|
|
@@ -317,6 +318,9 @@ function PointerDateTimeField({
|
|
|
317
318
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
318
319
|
const isPendingChange = optimisticValue !== value;
|
|
319
320
|
const isBusy = isLoading || isPendingChange;
|
|
321
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
322
|
+
// (spec:AST-086 FR8).
|
|
323
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), dateInputRef);
|
|
320
324
|
// A native wheel emits several edits while it remains open. Disabling the
|
|
321
325
|
// control after the first optimistic edit detaches the iOS picker, so pending
|
|
322
326
|
// changeAction work stays interactive just like TouchDateTimeField. Explicit
|
|
@@ -1130,7 +1134,8 @@ function PointerDateTimeField({
|
|
|
1130
1134
|
}),
|
|
1131
1135
|
onClick: handleClear
|
|
1132
1136
|
}), isBusy && /*#__PURE__*/_jsx(Spinner, {
|
|
1133
|
-
size: "sm"
|
|
1137
|
+
size: "sm",
|
|
1138
|
+
"aria-hidden": "true"
|
|
1134
1139
|
}), statusIcon]
|
|
1135
1140
|
}), /*#__PURE__*/_jsx("div", {
|
|
1136
1141
|
ref: timeContainerRef,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TouchDateTimeField.d.ts","sourceRoot":"","sources":["../../src/DateTimeInput/TouchDateTimeField.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TouchDateTimeField.d.ts","sourceRoot":"","sources":["../../src/DateTimeInput/TouchDateTimeField.tsx"],"names":[],"mappings":"AA8GA,OAAO,KAAK,EAAC,kBAAkB,EAAoB,MAAM,iBAAiB,CAAC;AAuV3E,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAe,EACf,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAiB,EACjB,GAAG,EACH,GAAG,EACH,eAAe,EACf,UAAkB,EAClB,UAAkB,EAElB,aAAa,EAAE,cAAc,EAC7B,kBAAkB,EAAE,mBAAmB,EACvC,QAAgB,EAChB,WAAW,EAAE,oBAAoB,EACjC,eAAe,EAAE,wBAAwB,EACzC,SAAS,EACT,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,YAAY,EAEZ,cAAc,EAAE,eAAe,EAC/B,YAAY,EAAE,gBAAoB,EAClC,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,GAAG,SAAS,EACb,EAAE,kBAAkB,+BAkgCpB;yBAziCe,kBAAkB"}
|
|
@@ -29,6 +29,7 @@ import { useCalendarConstraints } from "../Calendar/index.js";
|
|
|
29
29
|
import { Field, InputClearButton, inputWrapperStyles, inputStatusBorderStyles, inputStatusHoverShadowStyles, inputStatusFocusWithinStyles } from "../Field/index.js";
|
|
30
30
|
import { useInputStatusIcon, useMergedRefs } from "../hooks/index.js";
|
|
31
31
|
import { useResolvedRequired } from "../hooks/useResolvedRequired.js";
|
|
32
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
32
33
|
import { Icon } from "../Icon/index.js";
|
|
33
34
|
import { IconButton } from "../IconButton/index.js";
|
|
34
35
|
import { useTranslator, InternationalizationContext } from "../i18n/index.js";
|
|
@@ -338,6 +339,11 @@ export function TouchDateTimeField({
|
|
|
338
339
|
dateConstraints
|
|
339
340
|
});
|
|
340
341
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
342
|
+
const sheetBodyRef = useRef(null);
|
|
343
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
344
|
+
// (spec:AST-086 FR8). A change made in the open modal sheet starts it while
|
|
345
|
+
// the sheet is open, so it is announced from inside the sheet.
|
|
346
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), isSheetOpen ? sheetBodyRef : dateInputRef);
|
|
341
347
|
const [activePanel, setActivePanel] = useState('date');
|
|
342
348
|
const [isWheelOpen, setIsWheelOpen] = useState(false);
|
|
343
349
|
const [draftTime, setDraftTime] = useState(() => normalizeISOTime(splitDateTime(value).time, hasSeconds));
|
|
@@ -990,7 +996,8 @@ export function TouchDateTimeField({
|
|
|
990
996
|
}),
|
|
991
997
|
onClick: handleClear
|
|
992
998
|
}), isBusy && /*#__PURE__*/_jsx(Spinner, {
|
|
993
|
-
size: "sm"
|
|
999
|
+
size: "sm",
|
|
1000
|
+
"aria-hidden": "true"
|
|
994
1001
|
}), isRenderable(statusIcon) && statusIcon]
|
|
995
1002
|
}), /*#__PURE__*/_jsxs("div", {
|
|
996
1003
|
onClick: event => {
|
|
@@ -1058,6 +1065,7 @@ export function TouchDateTimeField({
|
|
|
1058
1065
|
,
|
|
1059
1066
|
padding: 0,
|
|
1060
1067
|
children: /*#__PURE__*/_jsx("div", {
|
|
1068
|
+
ref: sheetBodyRef,
|
|
1061
1069
|
...{
|
|
1062
1070
|
className: "x9f619 xh8yej3 xeuugli x1pzlopt x1adxfkp x1awphl8"
|
|
1063
1071
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenuSubMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuSubMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,EASZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownMenuSubMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuSubMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,EASZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAyC5D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA6N5C,MAAM,MAAM,+BAA+B,GACzC,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;AAErC,UAAU,4BAA6B,SAAQ,IAAI,CACjD,SAAS,EACT,MAAM,GAAG,UAAU,CACpB;IACC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,8CAA8C;IAC9C,KAAK,EAAE,SAAS,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,+BAA+B,CAAC;IAC/C;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAyB,SAAQ,4BAA4B;IAC5E;;;;;;;;OAQG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AA6CD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,SAAS,CAg5BX;yBAl5Be,mBAAmB"}
|
|
@@ -61,6 +61,7 @@ import { Item } from "../Item/index.js";
|
|
|
61
61
|
import { useLayer } from "../Layer/useLayer.js";
|
|
62
62
|
import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
|
|
63
63
|
import { useListFocus } from "../hooks/useListFocus.js";
|
|
64
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
64
65
|
import { useMenuHover } from "../hooks/useMenuHover.js";
|
|
65
66
|
import { isMenuPressActivation, MENU_PRESS_SURFACE_ATTR, useMenuPress } from "../hooks/useMenuPress.js";
|
|
66
67
|
import { useTypeahead } from "../hooks/useTypeahead.js";
|
|
@@ -461,6 +462,10 @@ export function DropdownMenuSubMenu(props) {
|
|
|
461
462
|
// closed from elsewhere is cancelled instead of firing into the hidden
|
|
462
463
|
// menu (#6893).
|
|
463
464
|
const isRootMenuOpen = useDropdownMenuRootOpen();
|
|
465
|
+
// The loading Spinner on the trigger is decorative; the submenu announces
|
|
466
|
+
// its busy start instead (spec:AST-086 FR8), once its level is shown: a
|
|
467
|
+
// closed menu stays mounted but hidden, and announces nothing.
|
|
468
|
+
useBusyAnnouncement(hasSpinner && isRootMenuOpen, t('@astryx.spinner.loading'), triggerRef);
|
|
464
469
|
const {
|
|
465
470
|
triggerProps,
|
|
466
471
|
contentProps,
|
|
@@ -761,7 +766,8 @@ export function DropdownMenuSubMenu(props) {
|
|
|
761
766
|
className: "x78zum5 x6s0dn4"
|
|
762
767
|
},
|
|
763
768
|
children: /*#__PURE__*/_jsx(Spinner, {
|
|
764
|
-
size: "sm"
|
|
769
|
+
size: "sm",
|
|
770
|
+
"aria-hidden": "true"
|
|
765
771
|
})
|
|
766
772
|
}) : /*#__PURE__*/_jsx("span", {
|
|
767
773
|
...{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thumbnail.d.ts","sourceRoot":"","sources":["../../src/Thumbnail/Thumbnail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Thumbnail.d.ts","sourceRoot":"","sources":["../../src/Thumbnail/Thumbnail.tsx"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAQ5C,MAAM,WAAW,cAAe,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;OASG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAClC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA6LD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,EACxB,GAAG,EACH,GAAG,EACH,KAAK,EACL,QAAQ,EACR,OAAO,EACP,SAAiB,EACjB,UAAkB,EAClB,YAAsB,EACtB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,cAAc,+BA2JhB;yBA1Ke,SAAS"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* - /apps/storybook/stories/Thumbnail.stories.tsx
|
|
25
25
|
* - /packages/cli/assets/templates/blocks/components/Thumbnail/ (showcase blocks)
|
|
26
26
|
*/
|
|
27
|
-
import { useState } from 'react';
|
|
27
|
+
import { useRef, useState } from 'react';
|
|
28
28
|
import * as stylex from '@stylexjs/stylex';
|
|
29
29
|
import "../theme/tokens.stylex.js";
|
|
30
30
|
import { colorVars, radiusVars, spacingVars, durationVars, easeVars } from "../theme/tokens.stylex.js";
|
|
@@ -34,6 +34,7 @@ import { Skeleton } from "../Skeleton/index.js";
|
|
|
34
34
|
import { Spinner } from "../Spinner/index.js";
|
|
35
35
|
import { Tooltip } from "../Tooltip/Tooltip.js";
|
|
36
36
|
import { useDevWarning } from "../hooks/useDevWarning.js";
|
|
37
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
37
38
|
import { useContainerReveal } from "../hooks/useContainerReveal.js";
|
|
38
39
|
import { mergeProps } from "../utils/index.js";
|
|
39
40
|
import { themeProps } from "../utils/themeProps.js";
|
|
@@ -175,6 +176,10 @@ export function Thumbnail({
|
|
|
175
176
|
const showSkeleton = isLoading && !hasSrc;
|
|
176
177
|
const showImage = hasSrc && !showSkeleton && !hasError;
|
|
177
178
|
const showUploadOverlay = isLoading && hasSrc;
|
|
179
|
+
const uploadOverlayRef = useRef(null);
|
|
180
|
+
// The upload Spinner is decorative; the thumbnail announces its busy start
|
|
181
|
+
// instead (spec:AST-086 FR8).
|
|
182
|
+
useBusyAnnouncement(showUploadOverlay, t('@astryx.spinner.loading'), uploadOverlayRef);
|
|
178
183
|
const showPlaceholder = !isLoading && !hasSrc || hasError;
|
|
179
184
|
const isInteractive = onClick != null && !isDisabled && !isLoading;
|
|
180
185
|
const hasRemove = onRemove != null && !isDisabled;
|
|
@@ -273,12 +278,14 @@ export function Thumbnail({
|
|
|
273
278
|
className: "x10l6tqk x10a8y8t x1pjcqnp x1er153n x47corl"
|
|
274
279
|
}
|
|
275
280
|
}), showUploadOverlay && /*#__PURE__*/_jsx("div", {
|
|
281
|
+
ref: uploadOverlayRef,
|
|
276
282
|
...{
|
|
277
283
|
className: "x10l6tqk x10a8y8t x78zum5 x6s0dn4 xl56j7k x1u4601x x1pjcqnp x1vjfegm x14ju556"
|
|
278
284
|
},
|
|
279
285
|
children: /*#__PURE__*/_jsx(Spinner, {
|
|
280
286
|
size: "sm",
|
|
281
|
-
shade: "onMedia"
|
|
287
|
+
shade: "onMedia",
|
|
288
|
+
"aria-hidden": "true"
|
|
282
289
|
})
|
|
283
290
|
}), removeButtonEl]
|
|
284
291
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeInput.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TimeInput.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAO3C,OAAO,EAGL,KAAK,WAAW,EAKhB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAIlB,OAAO,EACL,KAAK,aAAa,EAUnB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"TimeInput.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TimeInput.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAO3C,OAAO,EAGL,KAAK,WAAW,EAKhB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAIlB,OAAO,EACL,KAAK,aAAa,EAUnB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAiB9C,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAC;AAyDpC,QAAA,MAAM,UAAU;;;;;;;;;;;;;EAad,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,UAAU,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,KAAK,CAAC;AAEhD,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjE,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAItD,YAAY,EACV,WAAW,IAAI,eAAe,EAC9B,eAAe,IAAI,mBAAmB,GACvC,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,UAAU,GAAG,cAAc,CAC5B;IACC,qEAAqE;IACrE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,CAAC;IAEtD;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1E;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;OAEG;IACH,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAEjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;IAErB;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IAEnC;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA8hBD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAyC9C;yBAzCe,SAAS"}
|
|
@@ -30,6 +30,7 @@ import { joinAriaIDs } from "../utils/inputAria.js";
|
|
|
30
30
|
import { useFieldInputStyling } from "../Field/fieldParts.js";
|
|
31
31
|
import { useSize } from "../SizeContext/SizeContext.js";
|
|
32
32
|
import { useAnnounce } from "../hooks/useAnnounce.js";
|
|
33
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
33
34
|
import { useInputContainer } from "../hooks/useInputContainer.js";
|
|
34
35
|
import { useMediaQuery } from "../hooks/useMediaQuery.js";
|
|
35
36
|
import { useInputStatusIcon } from "../hooks/useInputStatusIcon.js";
|
|
@@ -169,6 +170,9 @@ function TimeField({
|
|
|
169
170
|
const [, startTransition] = useTransition();
|
|
170
171
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
171
172
|
const isBusy = isLoading || optimisticValue !== value;
|
|
173
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
174
|
+
// (spec:AST-086 FR8).
|
|
175
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), containerRef);
|
|
172
176
|
|
|
173
177
|
// In grouped mode the status message renders as a visually-hidden node that
|
|
174
178
|
// exists only for aria-describedby. Announce it through the persistent
|
|
@@ -504,7 +508,8 @@ function TimeField({
|
|
|
504
508
|
children: !isInputValid ? t('@astryx.timeInput.invalidTime') : ''
|
|
505
509
|
})]
|
|
506
510
|
}), isBusy && /*#__PURE__*/_jsx(Spinner, {
|
|
507
|
-
size: "sm"
|
|
511
|
+
size: "sm",
|
|
512
|
+
"aria-hidden": "true"
|
|
508
513
|
}), hasClear && optimisticValue && !isDisabled && /*#__PURE__*/_jsx(InputClearButton, {
|
|
509
514
|
label: t('@astryx.timeInput.clearLabel', {
|
|
510
515
|
label
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TouchTimeField.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TouchTimeField.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TouchTimeField.d.ts","sourceRoot":"","sources":["../../src/TimeInput/TouchTimeField.tsx"],"names":[],"mappings":"AAqEA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAqHhD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAe,EACf,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAiB,EACjB,GAAG,EACH,GAAG,EACH,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EAGlB,SAAS,EAAE,UAAU,EACrB,YAAY,EAAE,aAAa,EAC3B,WAAW,EAAE,oBAAoB,EACjC,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,aAA0B,EAC1B,YAAY,EACZ,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,GAAG,SAAS,EACb,EAAE,cAAc,+BA0ZhB;yBA7be,cAAc"}
|
|
@@ -25,6 +25,7 @@ import { useFieldInputStyling } from "../Field/fieldParts.js";
|
|
|
25
25
|
import { joinAriaIDs } from "../utils/inputAria.js";
|
|
26
26
|
import { groupStyles } from "../InputGroup/groupStyles.js";
|
|
27
27
|
import { useInputStatusIcon } from "../hooks/useInputStatusIcon.js";
|
|
28
|
+
import { useBusyAnnouncement } from "../hooks/useBusyAnnouncement.js";
|
|
28
29
|
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
29
30
|
import { useResolvedRequired } from "../hooks/useResolvedRequired.js";
|
|
30
31
|
import { Icon } from "../Icon/index.js";
|
|
@@ -180,6 +181,11 @@ export function TouchTimeField({
|
|
|
180
181
|
const [, startTransition] = useTransition();
|
|
181
182
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
182
183
|
const isBusy = isLoading || optimisticValue !== value;
|
|
184
|
+
const sheetBodyRef = useRef(null);
|
|
185
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
186
|
+
// (spec:AST-086 FR8). A change made in the open modal sheet starts it while
|
|
187
|
+
// the sheet is open, so it is announced from inside the sheet.
|
|
188
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), isSheetOpen ? sheetBodyRef : inputRef);
|
|
183
189
|
const isEffectivelyDisabled = isDisabled || isLoading;
|
|
184
190
|
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
185
191
|
const disabledMessageTooltip = useTooltip({
|
|
@@ -362,7 +368,8 @@ export function TouchTimeField({
|
|
|
362
368
|
className: "x1lliihq x98rzlu xeuugli xc342km xng3xce x1717udv xjbqb8w x1a2a7pz x1ypdohk x16khyan"
|
|
363
369
|
}
|
|
364
370
|
}), isBusy && /*#__PURE__*/_jsx(Spinner, {
|
|
365
|
-
size: "sm"
|
|
371
|
+
size: "sm",
|
|
372
|
+
"aria-hidden": "true"
|
|
366
373
|
}), hasClear && optimisticValue && !isEffectivelyDisabled && /*#__PURE__*/_jsx(InputClearButton, {
|
|
367
374
|
label: t('@astryx.timeInput.clearLabel', {
|
|
368
375
|
label
|
|
@@ -377,6 +384,7 @@ export function TouchTimeField({
|
|
|
377
384
|
,
|
|
378
385
|
padding: 0,
|
|
379
386
|
children: /*#__PURE__*/_jsxs("div", {
|
|
387
|
+
ref: sheetBodyRef,
|
|
380
388
|
...{
|
|
381
389
|
className: "x9f619 x78zum5 xdt5ytf xh8yej3 xeuugli x1txdalj x1pzlopt x1adxfkp x1awphl8"
|
|
382
390
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTypeahead.d.ts","sourceRoot":"","sources":["../../src/hooks/useTypeahead.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,aAAa,EAAE,MAAM,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAE9D;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,aAAa,KAAK,OAAO,CAAC;IAC/D,gDAAgD;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AA+BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"useTypeahead.d.ts","sourceRoot":"","sources":["../../src/hooks/useTypeahead.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,aAAa,EAAE,MAAM,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAE9D;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,aAAa,KAAK,OAAO,CAAC;IAC/D,gDAAgD;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AA+BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAmG7E"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useTypeahead.ts
|
|
7
|
-
* @input Uses React useCallback/useRef
|
|
7
|
+
* @input Uses React useCallback/useEffect/useRef
|
|
8
8
|
* @output Exports useTypeahead hook for first-character (type-to-focus) search
|
|
9
9
|
* @position Core hook; adds APG typeahead to menus, listboxes, and other
|
|
10
10
|
* collections. Composes with useListFocus/useGridFocus (or any collection)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* SYNC: When modified, update:
|
|
14
14
|
* - /packages/core/src/hooks/index.ts
|
|
15
15
|
*/
|
|
16
|
-
import { useCallback, useRef } from 'react';
|
|
16
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
17
17
|
/**
|
|
18
18
|
* Whether a key event represents a single printable character (a type-to-focus
|
|
19
19
|
* candidate) rather than a control/navigation key or a shortcut chord.
|
|
@@ -79,6 +79,11 @@ export function useTypeahead(options) {
|
|
|
79
79
|
timeoutRef.current = undefined;
|
|
80
80
|
}
|
|
81
81
|
}, []);
|
|
82
|
+
|
|
83
|
+
// A collection that unmounts, or that an `<Activity>` boundary hides,
|
|
84
|
+
// mid-typing leaves no reset timer running and comes back with an empty
|
|
85
|
+
// buffer, as the reset would have left it.
|
|
86
|
+
useEffect(() => reset, [reset]);
|
|
82
87
|
const scheduleReset = useCallback(() => {
|
|
83
88
|
if (timeoutRef.current) {
|
|
84
89
|
clearTimeout(timeoutRef.current);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.4-canary.
|
|
3
|
+
"version": "0.7.4-canary.faa934f",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -792,8 +792,8 @@
|
|
|
792
792
|
"react-dom": ">=19.0.0"
|
|
793
793
|
},
|
|
794
794
|
"devDependencies": {
|
|
795
|
-
"@astryxdesign/a11y-spec": "0.7.4-canary.
|
|
796
|
-
"@astryxdesign/cli": "0.7.4-canary.
|
|
795
|
+
"@astryxdesign/a11y-spec": "0.7.4-canary.faa934f",
|
|
796
|
+
"@astryxdesign/cli": "0.7.4-canary.faa934f",
|
|
797
797
|
"@babel/cli": "^7.29.7",
|
|
798
798
|
"@babel/core": "^7.29.7",
|
|
799
799
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -182,9 +182,13 @@ describe('ChatMessageList', () => {
|
|
|
182
182
|
);
|
|
183
183
|
});
|
|
184
184
|
expect(action).toHaveBeenCalledTimes(1);
|
|
185
|
-
|
|
185
|
+
// The loader Spinner inside the log is silent (spec:AST-086 FR4, FR8).
|
|
186
|
+
expect(document.querySelector('.astryx-spinner')).toHaveAttribute(
|
|
187
|
+
'aria-hidden',
|
|
188
|
+
'true',
|
|
189
|
+
);
|
|
186
190
|
await act(async () => complete());
|
|
187
|
-
expect(
|
|
191
|
+
expect(document.querySelector('.astryx-spinner')).toBeNull();
|
|
188
192
|
unmount();
|
|
189
193
|
expect(disconnect).toHaveBeenCalledTimes(1);
|
|
190
194
|
} finally {
|
|
@@ -37,6 +37,8 @@ import {Spinner} from '../Spinner';
|
|
|
37
37
|
import type {BaseProps} from '../BaseProps';
|
|
38
38
|
import type {SpacingStep} from '../utils/types';
|
|
39
39
|
import {themeProps} from '../utils/themeProps';
|
|
40
|
+
import {useTranslator} from '../i18n';
|
|
41
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
40
42
|
|
|
41
43
|
export interface ChatMessageListProps extends BaseProps<HTMLDivElement> {
|
|
42
44
|
/** Ref forwarded to the root element */
|
|
@@ -238,6 +240,10 @@ export function ChatMessageList({
|
|
|
238
240
|
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
239
241
|
const innerRef = useRef<HTMLDivElement>(null);
|
|
240
242
|
const [isLoadingTop, startTransition] = useTransition();
|
|
243
|
+
const t = useTranslator();
|
|
244
|
+
// The top loading Spinner sits inside the log and is silent; the list
|
|
245
|
+
// announces its loading through the channel (spec:AST-086 FR4, FR8).
|
|
246
|
+
useBusyAnnouncement(isLoadingTop, t('@astryx.spinner.loading'), innerRef);
|
|
241
247
|
|
|
242
248
|
// Register inner content element with the layout for height observation
|
|
243
249
|
useEffect(() => {
|
|
@@ -309,7 +315,7 @@ export function ChatMessageList({
|
|
|
309
315
|
{/* Loading spinner at top */}
|
|
310
316
|
{isLoadingTop && (
|
|
311
317
|
<div {...stylex.props(styles.loadingTop)}>
|
|
312
|
-
<Spinner size="md" />
|
|
318
|
+
<Spinner size="md" aria-hidden="true" />
|
|
313
319
|
</div>
|
|
314
320
|
)}
|
|
315
321
|
|
|
@@ -42,7 +42,8 @@ interface Case {
|
|
|
42
42
|
direction?: 'ltr' | 'rtl';
|
|
43
43
|
coarsePointer?: boolean;
|
|
44
44
|
requiresOverflow?: boolean;
|
|
45
|
-
|
|
45
|
+
/** The top loader is shown, silent inside the log (spec:AST-086 FR4, FR8). */
|
|
46
|
+
hiddenLoader?: boolean;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Expectations are authored from the public fixture and component source, not
|
|
@@ -153,7 +154,7 @@ const CASES: Case[] = [
|
|
|
153
154
|
density: 'balanced',
|
|
154
155
|
busy: false,
|
|
155
156
|
text: 'Earlier message remains readable',
|
|
156
|
-
|
|
157
|
+
hiddenLoader: true,
|
|
157
158
|
},
|
|
158
159
|
];
|
|
159
160
|
|
|
@@ -228,8 +229,10 @@ async function capture(page: Page, scenario: Case, mode: 'light' | 'dark') {
|
|
|
228
229
|
);
|
|
229
230
|
const logs = page.getByRole('log');
|
|
230
231
|
await logs.first().waitFor();
|
|
231
|
-
if (scenario.
|
|
232
|
-
await page
|
|
232
|
+
if (scenario.hiddenLoader) {
|
|
233
|
+
await page
|
|
234
|
+
.locator('[role="log"] .astryx-spinner[aria-hidden="true"]')
|
|
235
|
+
.waitFor();
|
|
233
236
|
}
|
|
234
237
|
await holdMotionStill(page);
|
|
235
238
|
await page.evaluate(async () => document.fonts.ready);
|
|
@@ -260,17 +263,21 @@ async function capture(page: Page, scenario: Case, mode: 'light' | 'dark') {
|
|
|
260
263
|
parentClientHeight: node.parentElement?.clientHeight ?? 0,
|
|
261
264
|
}
|
|
262
265
|
: {}),
|
|
263
|
-
...(measures.
|
|
266
|
+
...(measures.loader
|
|
264
267
|
? {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
+
hiddenLoader:
|
|
269
|
+
node
|
|
270
|
+
.querySelector('.astryx-spinner')
|
|
271
|
+
?.closest('[aria-hidden="true"]') != null,
|
|
272
|
+
exposedStatus: [...node.querySelectorAll('[role="status"]')].some(
|
|
273
|
+
status => status.closest('[aria-hidden="true"]') == null,
|
|
274
|
+
),
|
|
268
275
|
}
|
|
269
276
|
: {}),
|
|
270
277
|
}),
|
|
271
278
|
{
|
|
272
279
|
overflow: Boolean(scenario.requiresOverflow),
|
|
273
|
-
|
|
280
|
+
loader: Boolean(scenario.hiddenLoader),
|
|
274
281
|
},
|
|
275
282
|
);
|
|
276
283
|
const environment = await page.evaluate(() => ({
|
|
@@ -323,8 +330,9 @@ async function capture(page: Page, scenario: Case, mode: 'light' | 'dark') {
|
|
|
323
330
|
if (scenario.text != null) {
|
|
324
331
|
expect(observed.text).toContain(scenario.text);
|
|
325
332
|
}
|
|
326
|
-
if (scenario.
|
|
327
|
-
expect(observed.
|
|
333
|
+
if (scenario.hiddenLoader) {
|
|
334
|
+
expect(observed.hiddenLoader).toBe(true);
|
|
335
|
+
expect(observed.exposedStatus).toBe(false);
|
|
328
336
|
}
|
|
329
337
|
expect(observed.width).toBeGreaterThan(0);
|
|
330
338
|
expect(observed.height).toBeGreaterThan(0);
|
|
@@ -371,7 +379,7 @@ async function capture(page: Page, scenario: Case, mode: 'light' | 'dark') {
|
|
|
371
379
|
text: scenario.text,
|
|
372
380
|
coarsePointer: scenario.coarsePointer ?? false,
|
|
373
381
|
...(scenario.requiresOverflow ? {requiresOverflow: true} : {}),
|
|
374
|
-
...(scenario.
|
|
382
|
+
...(scenario.hiddenLoader ? {hiddenLoader: true} : {}),
|
|
375
383
|
},
|
|
376
384
|
observed: {
|
|
377
385
|
build: storybookSha,
|
|
@@ -46,7 +46,9 @@ import {useTooltip} from '../Tooltip';
|
|
|
46
46
|
import {mergeProps, rtlStyles} from '../utils';
|
|
47
47
|
import {indicatorScope} from '../Indicator/indicator.markers.stylex';
|
|
48
48
|
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
49
|
+
import {useTranslator} from '../i18n';
|
|
49
50
|
import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
|
|
51
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
50
52
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
51
53
|
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
52
54
|
import {useIndicator} from '../Indicator';
|
|
@@ -371,6 +373,10 @@ export function CheckboxInput({
|
|
|
371
373
|
// `opacity: 0`, and only the indicator's own element can shape the outline
|
|
372
374
|
// to match it. See useIndicatorFocusRing.
|
|
373
375
|
const indicatorRef = useRef<HTMLSpanElement>(null);
|
|
376
|
+
const t = useTranslator();
|
|
377
|
+
// The busy Spinner sits in the decorative, aria-hidden indicator, so this
|
|
378
|
+
// control announces its busy start itself (spec:AST-086 FR8).
|
|
379
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), indicatorRef);
|
|
374
380
|
const {focusProps} = useIndicatorFocusRing(indicatorRef);
|
|
375
381
|
|
|
376
382
|
const isIndeterminate = optimisticValue === 'indeterminate';
|
|
@@ -65,6 +65,7 @@ import {useCalendarConstraints} from '../Calendar/hooks';
|
|
|
65
65
|
import {usePopover} from '../Popover';
|
|
66
66
|
import {useTooltip} from '../Tooltip';
|
|
67
67
|
import {useInputContainer} from '../hooks/useInputContainer';
|
|
68
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
68
69
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
69
70
|
import {
|
|
70
71
|
type ISOTimeString,
|
|
@@ -709,6 +710,9 @@ function PointerDateTimeField({
|
|
|
709
710
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
710
711
|
const isPendingChange = optimisticValue !== value;
|
|
711
712
|
const isBusy = isLoading || isPendingChange;
|
|
713
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
714
|
+
// (spec:AST-086 FR8).
|
|
715
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), dateInputRef);
|
|
712
716
|
// A native wheel emits several edits while it remains open. Disabling the
|
|
713
717
|
// control after the first optimistic edit detaches the iOS picker, so pending
|
|
714
718
|
// changeAction work stays interactive just like TouchDateTimeField. Explicit
|
|
@@ -1801,7 +1805,7 @@ function PointerDateTimeField({
|
|
|
1801
1805
|
onClick={handleClear}
|
|
1802
1806
|
/>
|
|
1803
1807
|
)}
|
|
1804
|
-
{isBusy && <Spinner size="sm" />}
|
|
1808
|
+
{isBusy && <Spinner size="sm" aria-hidden="true" />}
|
|
1805
1809
|
{statusIcon}
|
|
1806
1810
|
</div>
|
|
1807
1811
|
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
} from '../Field';
|
|
48
48
|
import {useInputStatusIcon, useMergedRefs} from '../hooks';
|
|
49
49
|
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
50
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
50
51
|
import {Icon} from '../Icon';
|
|
51
52
|
import {IconButton} from '../IconButton';
|
|
52
53
|
import {useTranslator, InternationalizationContext} from '../i18n';
|
|
@@ -576,6 +577,15 @@ export function TouchDateTimeField({
|
|
|
576
577
|
});
|
|
577
578
|
|
|
578
579
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
580
|
+
const sheetBodyRef = useRef<HTMLDivElement | null>(null);
|
|
581
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
582
|
+
// (spec:AST-086 FR8). A change made in the open modal sheet starts it while
|
|
583
|
+
// the sheet is open, so it is announced from inside the sheet.
|
|
584
|
+
useBusyAnnouncement(
|
|
585
|
+
isBusy,
|
|
586
|
+
t('@astryx.spinner.loading'),
|
|
587
|
+
isSheetOpen ? sheetBodyRef : dateInputRef,
|
|
588
|
+
);
|
|
579
589
|
const [activePanel, setActivePanel] = useState<TouchDateTimePanel>('date');
|
|
580
590
|
const [isWheelOpen, setIsWheelOpen] = useState(false);
|
|
581
591
|
const [draftTime, setDraftTime] = useState<ISOTimeString | undefined>(() =>
|
|
@@ -1412,7 +1422,7 @@ export function TouchDateTimeField({
|
|
|
1412
1422
|
onClick={handleClear}
|
|
1413
1423
|
/>
|
|
1414
1424
|
)}
|
|
1415
|
-
{isBusy && <Spinner size="sm" />}
|
|
1425
|
+
{isBusy && <Spinner size="sm" aria-hidden="true" />}
|
|
1416
1426
|
{isRenderable(statusIcon) && statusIcon}
|
|
1417
1427
|
</div>
|
|
1418
1428
|
|
|
@@ -1497,7 +1507,9 @@ export function TouchDateTimeField({
|
|
|
1497
1507
|
// touchSheetBody owns the inset, including the band under the grab
|
|
1498
1508
|
// handle.
|
|
1499
1509
|
padding={0}>
|
|
1500
|
-
<div {...stylex.props(styles.touchSheetBody)}>
|
|
1510
|
+
<div ref={sheetBodyRef} {...stylex.props(styles.touchSheetBody)}>
|
|
1511
|
+
{surface}
|
|
1512
|
+
</div>
|
|
1501
1513
|
</BottomSheet>
|
|
1502
1514
|
{showsDisabledMessage &&
|
|
1503
1515
|
disabledMessageTooltip.renderTooltip(disabledMessage)}
|
|
@@ -72,6 +72,7 @@ import {Item} from '../Item';
|
|
|
72
72
|
import {useLayer} from '../Layer/useLayer';
|
|
73
73
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
74
74
|
import {useListFocus} from '../hooks/useListFocus';
|
|
75
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
75
76
|
import {useMenuHover} from '../hooks/useMenuHover';
|
|
76
77
|
import {
|
|
77
78
|
isMenuPressActivation,
|
|
@@ -682,6 +683,14 @@ export function DropdownMenuSubMenu(
|
|
|
682
683
|
// closed from elsewhere is cancelled instead of firing into the hidden
|
|
683
684
|
// menu (#6893).
|
|
684
685
|
const isRootMenuOpen = useDropdownMenuRootOpen();
|
|
686
|
+
// The loading Spinner on the trigger is decorative; the submenu announces
|
|
687
|
+
// its busy start instead (spec:AST-086 FR8), once its level is shown: a
|
|
688
|
+
// closed menu stays mounted but hidden, and announces nothing.
|
|
689
|
+
useBusyAnnouncement(
|
|
690
|
+
hasSpinner && isRootMenuOpen,
|
|
691
|
+
t('@astryx.spinner.loading'),
|
|
692
|
+
triggerRef,
|
|
693
|
+
);
|
|
685
694
|
const {triggerProps, contentProps, confirmHoverOpen} =
|
|
686
695
|
useMenuHover<HTMLDivElement>({
|
|
687
696
|
show: showLayer,
|
|
@@ -1033,7 +1042,7 @@ export function DropdownMenuSubMenu(
|
|
|
1033
1042
|
|
|
1034
1043
|
const endAffordance = hasSpinner ? (
|
|
1035
1044
|
<span {...stylex.props(triggerStyles.caret)}>
|
|
1036
|
-
<Spinner size="sm" />
|
|
1045
|
+
<Spinner size="sm" aria-hidden="true" />
|
|
1037
1046
|
</span>
|
|
1038
1047
|
) : (
|
|
1039
1048
|
<span {...stylex.props(triggerStyles.caret)}>
|
|
@@ -38,7 +38,12 @@ describe('Thumbnail', () => {
|
|
|
38
38
|
);
|
|
39
39
|
const img = screen.getByRole('img');
|
|
40
40
|
expect(img).toHaveAttribute('src', '/local.jpg');
|
|
41
|
-
|
|
41
|
+
// The upload Spinner is decorative; the thumbnail announces its busy
|
|
42
|
+
// start instead (spec:AST-086 FR8).
|
|
43
|
+
expect(document.querySelector('.astryx-spinner')).toHaveAttribute(
|
|
44
|
+
'aria-hidden',
|
|
45
|
+
'true',
|
|
46
|
+
);
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
it('exposes the label as an accessible name via a valid group role', () => {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* - /packages/cli/assets/templates/blocks/components/Thumbnail/ (showcase blocks)
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import {useState} from 'react';
|
|
28
|
+
import {useRef, useState} from 'react';
|
|
29
29
|
import * as stylex from '@stylexjs/stylex';
|
|
30
30
|
import {
|
|
31
31
|
colorVars,
|
|
@@ -40,6 +40,7 @@ import {Skeleton} from '../Skeleton';
|
|
|
40
40
|
import {Spinner} from '../Spinner';
|
|
41
41
|
import {Tooltip} from '../Tooltip/Tooltip';
|
|
42
42
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
43
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
43
44
|
import {useContainerReveal} from '../hooks/useContainerReveal';
|
|
44
45
|
import type {BaseProps} from '../BaseProps';
|
|
45
46
|
import {mergeProps} from '../utils';
|
|
@@ -352,6 +353,14 @@ export function Thumbnail({
|
|
|
352
353
|
const showSkeleton = isLoading && !hasSrc;
|
|
353
354
|
const showImage = hasSrc && !showSkeleton && !hasError;
|
|
354
355
|
const showUploadOverlay = isLoading && hasSrc;
|
|
356
|
+
const uploadOverlayRef = useRef<HTMLDivElement>(null);
|
|
357
|
+
// The upload Spinner is decorative; the thumbnail announces its busy start
|
|
358
|
+
// instead (spec:AST-086 FR8).
|
|
359
|
+
useBusyAnnouncement(
|
|
360
|
+
showUploadOverlay,
|
|
361
|
+
t('@astryx.spinner.loading'),
|
|
362
|
+
uploadOverlayRef,
|
|
363
|
+
);
|
|
355
364
|
const showPlaceholder = (!isLoading && !hasSrc) || hasError;
|
|
356
365
|
const isInteractive = onClick != null && !isDisabled && !isLoading;
|
|
357
366
|
const hasRemove = onRemove != null && !isDisabled;
|
|
@@ -472,8 +481,8 @@ export function Thumbnail({
|
|
|
472
481
|
)}
|
|
473
482
|
{showImage && <div {...stylex.props(styles.insetBorder)} />}
|
|
474
483
|
{showUploadOverlay && (
|
|
475
|
-
<div {...stylex.props(styles.uploadOverlay)}>
|
|
476
|
-
<Spinner size="sm" shade="onMedia" />
|
|
484
|
+
<div ref={uploadOverlayRef} {...stylex.props(styles.uploadOverlay)}>
|
|
485
|
+
<Spinner size="sm" shade="onMedia" aria-hidden="true" />
|
|
477
486
|
</div>
|
|
478
487
|
)}
|
|
479
488
|
{removeButtonEl}
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
import type {SizeValue} from '../utils/types';
|
|
71
71
|
import {useSize} from '../SizeContext/SizeContext';
|
|
72
72
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
73
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
73
74
|
import {useInputContainer} from '../hooks/useInputContainer';
|
|
74
75
|
import {useMediaQuery} from '../hooks/useMediaQuery';
|
|
75
76
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
@@ -475,6 +476,9 @@ function TimeField({
|
|
|
475
476
|
const [, startTransition] = useTransition();
|
|
476
477
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
477
478
|
const isBusy = isLoading || optimisticValue !== value;
|
|
479
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
480
|
+
// (spec:AST-086 FR8).
|
|
481
|
+
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), containerRef);
|
|
478
482
|
|
|
479
483
|
// In grouped mode the status message renders as a visually-hidden node that
|
|
480
484
|
// exists only for aria-describedby. Announce it through the persistent
|
|
@@ -865,7 +869,7 @@ function TimeField({
|
|
|
865
869
|
</VisuallyHidden>
|
|
866
870
|
</>
|
|
867
871
|
)}
|
|
868
|
-
{isBusy && <Spinner size="sm" />}
|
|
872
|
+
{isBusy && <Spinner size="sm" aria-hidden="true" />}
|
|
869
873
|
{hasClear && optimisticValue && !isDisabled && (
|
|
870
874
|
<InputClearButton
|
|
871
875
|
label={t('@astryx.timeInput.clearLabel', {label})}
|
|
@@ -42,6 +42,7 @@ import {useFieldInputStyling} from '../Field/fieldParts';
|
|
|
42
42
|
import {joinAriaIDs} from '../utils/inputAria';
|
|
43
43
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
44
44
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
45
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
45
46
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
46
47
|
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
47
48
|
import {Icon} from '../Icon';
|
|
@@ -254,6 +255,15 @@ export function TouchTimeField({
|
|
|
254
255
|
const [, startTransition] = useTransition();
|
|
255
256
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
256
257
|
const isBusy = isLoading || optimisticValue !== value;
|
|
258
|
+
const sheetBodyRef = useRef<HTMLDivElement | null>(null);
|
|
259
|
+
// The busy Spinner is decorative; this control announces its busy start
|
|
260
|
+
// (spec:AST-086 FR8). A change made in the open modal sheet starts it while
|
|
261
|
+
// the sheet is open, so it is announced from inside the sheet.
|
|
262
|
+
useBusyAnnouncement(
|
|
263
|
+
isBusy,
|
|
264
|
+
t('@astryx.spinner.loading'),
|
|
265
|
+
isSheetOpen ? sheetBodyRef : inputRef,
|
|
266
|
+
);
|
|
257
267
|
const isEffectivelyDisabled = isDisabled || isLoading;
|
|
258
268
|
|
|
259
269
|
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
@@ -516,7 +526,7 @@ export function TouchTimeField({
|
|
|
516
526
|
}
|
|
517
527
|
{...stylex.props(styles.input)}
|
|
518
528
|
/>
|
|
519
|
-
{isBusy && <Spinner size="sm" />}
|
|
529
|
+
{isBusy && <Spinner size="sm" aria-hidden="true" />}
|
|
520
530
|
{hasClear && optimisticValue && !isEffectivelyDisabled && (
|
|
521
531
|
<InputClearButton
|
|
522
532
|
label={t('@astryx.timeInput.clearLabel', {label})}
|
|
@@ -531,7 +541,7 @@ export function TouchTimeField({
|
|
|
531
541
|
height="hug"
|
|
532
542
|
// sheetBody owns the inset, including the band under the grab handle.
|
|
533
543
|
padding={0}>
|
|
534
|
-
<div {...stylex.props(styles.sheetBody)}>
|
|
544
|
+
<div ref={sheetBodyRef} {...stylex.props(styles.sheetBody)}>
|
|
535
545
|
<div {...stylex.props(styles.wheels)}>
|
|
536
546
|
<Wheel
|
|
537
547
|
label={t('@astryx.dateTimeInput.hourWheel')}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
it,
|
|
20
20
|
vi,
|
|
21
21
|
} from 'vitest';
|
|
22
|
-
import {act, fireEvent, render, screen} from '@testing-library/react';
|
|
22
|
+
import {act, fireEvent, render, screen, within} from '@testing-library/react';
|
|
23
23
|
import {TextInput} from '../TextInput';
|
|
24
24
|
import {TextArea} from '../TextArea';
|
|
25
25
|
import {Selector} from '../Selector';
|
|
@@ -27,8 +27,19 @@ import {MultiSelector} from '../MultiSelector';
|
|
|
27
27
|
import {ComplexSelector} from '../ComplexSelector';
|
|
28
28
|
import {DateInput} from '../DateInput';
|
|
29
29
|
import {DateRangeInput} from '../DateRangeInput';
|
|
30
|
+
import {DateTimeInput} from '../DateTimeInput';
|
|
31
|
+
import {TimeInput} from '../TimeInput';
|
|
32
|
+
import {CheckboxInput} from '../CheckboxInput';
|
|
33
|
+
import {Thumbnail} from '../Thumbnail';
|
|
34
|
+
import {
|
|
35
|
+
DropdownMenu,
|
|
36
|
+
DropdownMenuItem,
|
|
37
|
+
DropdownMenuSubMenu,
|
|
38
|
+
} from '../DropdownMenu';
|
|
39
|
+
import {ChatMessageList} from '../Chat';
|
|
30
40
|
import {BaseTypeahead, Typeahead} from '../Typeahead';
|
|
31
41
|
import type {SearchableItem, SearchSource} from '../Typeahead';
|
|
42
|
+
import type {ISOTimeString} from '../utils';
|
|
32
43
|
import {Tokenizer} from '../Tokenizer';
|
|
33
44
|
|
|
34
45
|
const announce = vi.fn();
|
|
@@ -104,6 +115,47 @@ const OWNERS: ReadonlyArray<
|
|
|
104
115
|
),
|
|
105
116
|
] as const,
|
|
106
117
|
),
|
|
118
|
+
...(['popover', 'native', 'bottom-sheet'] as const).flatMap(presentation => [
|
|
119
|
+
[
|
|
120
|
+
`DateTimeInput (${presentation})`,
|
|
121
|
+
(isLoading: boolean) => (
|
|
122
|
+
<DateTimeInput
|
|
123
|
+
label="When"
|
|
124
|
+
presentation={presentation}
|
|
125
|
+
onChange={() => {}}
|
|
126
|
+
isLoading={isLoading}
|
|
127
|
+
/>
|
|
128
|
+
),
|
|
129
|
+
] as const,
|
|
130
|
+
[
|
|
131
|
+
`TimeInput (${presentation})`,
|
|
132
|
+
(isLoading: boolean) => (
|
|
133
|
+
<TimeInput
|
|
134
|
+
label="Time"
|
|
135
|
+
presentation={presentation}
|
|
136
|
+
onChange={() => {}}
|
|
137
|
+
isLoading={isLoading}
|
|
138
|
+
/>
|
|
139
|
+
),
|
|
140
|
+
] as const,
|
|
141
|
+
]),
|
|
142
|
+
[
|
|
143
|
+
'CheckboxInput',
|
|
144
|
+
isLoading => (
|
|
145
|
+
<CheckboxInput
|
|
146
|
+
label="Subscribe"
|
|
147
|
+
value={false}
|
|
148
|
+
onChange={() => {}}
|
|
149
|
+
isLoading={isLoading}
|
|
150
|
+
/>
|
|
151
|
+
),
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
'Thumbnail (uploading)',
|
|
155
|
+
isLoading => (
|
|
156
|
+
<Thumbnail src="/local.jpg" alt="Upload" isLoading={isLoading} />
|
|
157
|
+
),
|
|
158
|
+
],
|
|
107
159
|
[
|
|
108
160
|
'DateRangeInput',
|
|
109
161
|
isLoading => (
|
|
@@ -215,7 +267,8 @@ describe.each(OWNERS)('%s busy state (spec:AST-086)', (_name, renderOwner) => {
|
|
|
215
267
|
const {container} = render(renderOwner(true));
|
|
216
268
|
const spinner = container.querySelector('.astryx-spinner');
|
|
217
269
|
expect(spinner).not.toBeNull();
|
|
218
|
-
|
|
270
|
+
// Hidden itself, or inside a decorative hidden indicator (CheckboxInput).
|
|
271
|
+
expect(spinner!.closest('[aria-hidden="true"]')).not.toBeNull();
|
|
219
272
|
expect(screen.queryByRole('status', {name: 'Loading'})).toBeNull();
|
|
220
273
|
});
|
|
221
274
|
|
|
@@ -367,3 +420,157 @@ describe.each(SEARCH_OWNERS)(
|
|
|
367
420
|
});
|
|
368
421
|
},
|
|
369
422
|
);
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Bottom-sheet fields whose sheet stays open while a changeAction is pending:
|
|
426
|
+
* the sheet is a modal dialog, so the busy start must be announced inside it.
|
|
427
|
+
*/
|
|
428
|
+
const SHEET_OWNERS: ReadonlyArray<
|
|
429
|
+
readonly [
|
|
430
|
+
string,
|
|
431
|
+
(changeAction: () => Promise<void>) => ReactElement,
|
|
432
|
+
() => void,
|
|
433
|
+
]
|
|
434
|
+
> = [
|
|
435
|
+
[
|
|
436
|
+
'TimeInput',
|
|
437
|
+
changeAction => (
|
|
438
|
+
<TimeInput
|
|
439
|
+
label="Time"
|
|
440
|
+
presentation="bottom-sheet"
|
|
441
|
+
value={'09:00' as ISOTimeString}
|
|
442
|
+
onChange={() => {}}
|
|
443
|
+
changeAction={changeAction}
|
|
444
|
+
/>
|
|
445
|
+
),
|
|
446
|
+
() => {
|
|
447
|
+
fireEvent.click(screen.getByRole('button', {name: /Time/}));
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
];
|
|
451
|
+
|
|
452
|
+
describe.each(SHEET_OWNERS)(
|
|
453
|
+
'%s busy start from inside its open sheet (spec:AST-086 Platform support)',
|
|
454
|
+
(_name, renderOwner, openSheet) => {
|
|
455
|
+
beforeEach(() => {
|
|
456
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
457
|
+
this: HTMLDialogElement,
|
|
458
|
+
) {
|
|
459
|
+
this.setAttribute('open', '');
|
|
460
|
+
});
|
|
461
|
+
HTMLDialogElement.prototype.close = vi.fn(function (
|
|
462
|
+
this: HTMLDialogElement,
|
|
463
|
+
) {
|
|
464
|
+
this.removeAttribute('open');
|
|
465
|
+
});
|
|
466
|
+
const matches = Element.prototype.matches;
|
|
467
|
+
vi.spyOn(Element.prototype, 'matches').mockImplementation(function (
|
|
468
|
+
this: Element,
|
|
469
|
+
selector: string,
|
|
470
|
+
) {
|
|
471
|
+
return selector === ':modal'
|
|
472
|
+
? this instanceof HTMLDialogElement && this.open
|
|
473
|
+
: matches.call(this, selector);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
afterEach(() => {
|
|
477
|
+
vi.restoreAllMocks();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('announces the busy start in a region inside the open sheet, not behind it', async () => {
|
|
481
|
+
render(renderOwner(async () => new Promise<void>(() => {})));
|
|
482
|
+
openSheet();
|
|
483
|
+
const sheet = document.querySelector('dialog[open]');
|
|
484
|
+
expect(sheet).not.toBeNull();
|
|
485
|
+
act(() => {
|
|
486
|
+
fireEvent.click(within(sheet as HTMLElement).getAllByRole('option')[1]);
|
|
487
|
+
});
|
|
488
|
+
await nextFrame();
|
|
489
|
+
await nextFrame();
|
|
490
|
+
expect(
|
|
491
|
+
sheet!.querySelector('[data-astryx-modal-busy-region]'),
|
|
492
|
+
).toHaveTextContent('Loading');
|
|
493
|
+
expect(busyAnnouncements()).toEqual([]);
|
|
494
|
+
});
|
|
495
|
+
},
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
describe('DropdownMenuSubMenu loading state (spec:AST-086 FR8)', () => {
|
|
499
|
+
function Menu({isLoading}: {isLoading: boolean}) {
|
|
500
|
+
return (
|
|
501
|
+
<DropdownMenu button={{label: 'Actions'}}>
|
|
502
|
+
<DropdownMenuSubMenu label="Move to" hasSpinner={isLoading}>
|
|
503
|
+
<DropdownMenuItem label="Inbox" onClick={() => {}} />
|
|
504
|
+
</DropdownMenuSubMenu>
|
|
505
|
+
</DropdownMenu>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
it('announces nothing while its menu is closed', async () => {
|
|
510
|
+
render(strict(<Menu isLoading />));
|
|
511
|
+
await nextFrame();
|
|
512
|
+
expect(busyAnnouncements()).toEqual([]);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('announces the busy start when its menu opens while loading', async () => {
|
|
516
|
+
render(strict(<Menu isLoading />));
|
|
517
|
+
fireEvent.click(screen.getByRole('button', {name: /Actions/}));
|
|
518
|
+
await nextFrame();
|
|
519
|
+
expect(busyAnnouncements()).toEqual([['Loading', 'polite']]);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('keeps the trigger Spinner silent and announces each busy start once', async () => {
|
|
523
|
+
const {rerender} = render(strict(<Menu isLoading={false} />));
|
|
524
|
+
fireEvent.click(screen.getByRole('button', {name: /Actions/}));
|
|
525
|
+
await nextFrame();
|
|
526
|
+
expect(busyAnnouncements()).toEqual([]);
|
|
527
|
+
rerender(strict(<Menu isLoading />));
|
|
528
|
+
await nextFrame();
|
|
529
|
+
const spinner = document.querySelector('.astryx-spinner');
|
|
530
|
+
expect(spinner).toHaveAttribute('aria-hidden', 'true');
|
|
531
|
+
expect(busyAnnouncements()).toEqual([['Loading', 'polite']]);
|
|
532
|
+
rerender(strict(<Menu isLoading={false} />));
|
|
533
|
+
rerender(strict(<Menu isLoading />));
|
|
534
|
+
await nextFrame();
|
|
535
|
+
expect(busyAnnouncements()).toEqual([
|
|
536
|
+
['Loading', 'polite'],
|
|
537
|
+
['Loading', 'polite'],
|
|
538
|
+
]);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
describe('ChatMessageList top loader (spec:AST-086 FR4, FR8)', () => {
|
|
543
|
+
it('keeps the loader inside the log silent and announces loading once through the channel', async () => {
|
|
544
|
+
let notify!: IntersectionObserverCallback;
|
|
545
|
+
vi.stubGlobal(
|
|
546
|
+
'IntersectionObserver',
|
|
547
|
+
class {
|
|
548
|
+
constructor(callback: IntersectionObserverCallback) {
|
|
549
|
+
notify = callback;
|
|
550
|
+
}
|
|
551
|
+
observe() {}
|
|
552
|
+
disconnect() {}
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
render(
|
|
556
|
+
strict(
|
|
557
|
+
<ChatMessageList
|
|
558
|
+
scrollToTopAction={async () => new Promise<void>(() => {})}>
|
|
559
|
+
<div>Earlier message</div>
|
|
560
|
+
</ChatMessageList>,
|
|
561
|
+
),
|
|
562
|
+
);
|
|
563
|
+
act(() => {
|
|
564
|
+
notify(
|
|
565
|
+
[{isIntersecting: true} as IntersectionObserverEntry],
|
|
566
|
+
{} as IntersectionObserver,
|
|
567
|
+
);
|
|
568
|
+
});
|
|
569
|
+
await nextFrame();
|
|
570
|
+
expect(document.querySelector('.astryx-spinner')).toHaveAttribute(
|
|
571
|
+
'aria-hidden',
|
|
572
|
+
'true',
|
|
573
|
+
);
|
|
574
|
+
expect(busyAnnouncements()).toEqual([['Loading', 'polite']]);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
13
|
-
import {
|
|
13
|
+
import {Activity, type ReactNode} from 'react';
|
|
14
|
+
import {render, renderHook} from '@testing-library/react';
|
|
14
15
|
import {useTypeahead} from './useTypeahead';
|
|
15
16
|
|
|
16
17
|
const LABELS = ['Apple', 'Apricot', 'Banana', 'Cherry'] as const;
|
|
@@ -86,6 +87,46 @@ describe('useTypeahead', () => {
|
|
|
86
87
|
expect(onMatch).toHaveBeenLastCalledWith(2);
|
|
87
88
|
});
|
|
88
89
|
|
|
90
|
+
it('clears the pending buffer reset when the component unmounts', () => {
|
|
91
|
+
const {result, unmount} = renderHook(() =>
|
|
92
|
+
useTypeahead({getItemLabels: () => LABELS, onMatch: vi.fn()}),
|
|
93
|
+
);
|
|
94
|
+
result.current.onKeyDown(key('a'));
|
|
95
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
96
|
+
unmount();
|
|
97
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('empties the buffer when an Activity boundary hides the collection', () => {
|
|
101
|
+
const latest: {api: ReturnType<typeof useTypeahead> | null} = {api: null};
|
|
102
|
+
function Probe(): ReactNode {
|
|
103
|
+
latest.api = useTypeahead({
|
|
104
|
+
getItemLabels: () => LABELS,
|
|
105
|
+
onMatch: vi.fn(),
|
|
106
|
+
});
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const {rerender} = render(
|
|
110
|
+
<Activity mode="visible">
|
|
111
|
+
<Probe />
|
|
112
|
+
</Activity>,
|
|
113
|
+
);
|
|
114
|
+
latest.api?.onKeyDown(key('a'));
|
|
115
|
+
rerender(
|
|
116
|
+
<Activity mode="hidden">
|
|
117
|
+
<Probe />
|
|
118
|
+
</Activity>,
|
|
119
|
+
);
|
|
120
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
121
|
+
rerender(
|
|
122
|
+
<Activity mode="visible">
|
|
123
|
+
<Probe />
|
|
124
|
+
</Activity>,
|
|
125
|
+
);
|
|
126
|
+
// The typed prefix went with the hide, so a bare Space is not typeahead.
|
|
127
|
+
expect(latest.api?.onKeyDown(key(' '))).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
89
130
|
it('skips disabled items', () => {
|
|
90
131
|
const {onMatch, api} = setup({disabledIndices: [0]}); // Apple disabled
|
|
91
132
|
api.onKeyDown(key('a'));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useTypeahead.ts
|
|
7
|
-
* @input Uses React useCallback/useRef
|
|
7
|
+
* @input Uses React useCallback/useEffect/useRef
|
|
8
8
|
* @output Exports useTypeahead hook for first-character (type-to-focus) search
|
|
9
9
|
* @position Core hook; adds APG typeahead to menus, listboxes, and other
|
|
10
10
|
* collections. Composes with useListFocus/useGridFocus (or any collection)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - /packages/core/src/hooks/index.ts
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {useCallback, useRef} from 'react';
|
|
17
|
+
import {useCallback, useEffect, useRef} from 'react';
|
|
18
18
|
|
|
19
19
|
export interface UseTypeaheadOptions {
|
|
20
20
|
/**
|
|
@@ -135,6 +135,11 @@ export function useTypeahead(options: UseTypeaheadOptions): UseTypeaheadReturn {
|
|
|
135
135
|
}
|
|
136
136
|
}, []);
|
|
137
137
|
|
|
138
|
+
// A collection that unmounts, or that an `<Activity>` boundary hides,
|
|
139
|
+
// mid-typing leaves no reset timer running and comes back with an empty
|
|
140
|
+
// buffer, as the reset would have left it.
|
|
141
|
+
useEffect(() => reset, [reset]);
|
|
142
|
+
|
|
138
143
|
const scheduleReset = useCallback(() => {
|
|
139
144
|
if (timeoutRef.current) {
|
|
140
145
|
clearTimeout(timeoutRef.current);
|