@astryxdesign/core 0.7.3 → 0.7.4-canary.1a552f2
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/BottomSheet/BottomSheet.d.ts +18 -6
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +268 -58
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +41 -12
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +294 -165
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +51 -1
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +7 -11
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +8 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +46 -7
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -0
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +6 -11
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +7 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +6 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +12 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +6 -1
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +6 -1
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +9 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +6 -1
- 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/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +6 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +6 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +6 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +6 -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/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +9 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +7 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +9 -1
- package/dist/astryx.css +2 -0
- package/dist/hooks/useBusyAnnouncement.d.ts +22 -0
- package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -0
- package/dist/hooks/useBusyAnnouncement.js +87 -0
- package/dist/hooks/useMenuPress.d.ts +2 -2
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +5 -1
- package/dist/hooks/useTypeahead.d.ts.map +1 -1
- package/dist/hooks/useTypeahead.js +7 -2
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
- package/locales/en.json +6 -2
- package/locales/pseudo.json +3 -0
- package/package.json +4 -4
- package/src/BottomSheet/BottomSheet.doc.mjs +2 -2
- package/src/BottomSheet/BottomSheet.spec.md +3 -5
- package/src/BottomSheet/BottomSheet.tsx +260 -47
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +29 -4
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +11 -10
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +35 -41
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +1051 -49
- package/src/BottomSheet/BottomSheetSwitcher.tsx +396 -215
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +57 -2
- package/src/BottomSheet/__tests__/BottomSheetNativeModal.a11y.browser.spec.ts +461 -0
- package/src/Carousel/Carousel.test.tsx +8 -12
- package/src/Carousel/Carousel.tsx +6 -13
- package/src/Chat/ChatComposerInput.test.tsx +167 -1
- 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/Chat/useTriggerMenu.tsx +55 -6
- package/src/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/CommandPalette/CommandPalette.test.tsx +4 -2
- package/src/CommandPalette/CommandPalette.tsx +6 -11
- package/src/CommandPalette/CommandPaletteInput.test.tsx +3 -1
- package/src/CommandPalette/CommandPaletteInput.tsx +10 -1
- package/src/ComplexSelector/ComplexSelector.tsx +5 -1
- package/src/ContextMenu/ContextMenu.test.tsx +34 -0
- package/src/ContextMenu/ContextMenu.tsx +13 -1
- package/src/DateInput/DateInput.tsx +5 -1
- package/src/DateInput/DateInputTouch.test.tsx +45 -0
- package/src/DateInput/NativeDateField.test.tsx +10 -2
- package/src/DateInput/NativeDateField.tsx +5 -1
- package/src/DateInput/TouchDateField.tsx +14 -2
- package/src/DateRangeInput/DateRangeInput.tsx +5 -1
- package/src/DateTimeInput/DateTimeInput.tsx +5 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +14 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +10 -1
- package/src/FileInput/FileInput.tsx +18 -2
- package/src/MultiSelector/MultiSelector.test.tsx +18 -4
- package/src/MultiSelector/MultiSelector.tsx +5 -1
- package/src/ProgressBar/ProgressBar.test.tsx +4 -21
- package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +114 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.known-failures.ts +12 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.renders.tsx +38 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.states.ts +123 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.test.tsx +107 -0
- package/src/Selector/Selector.test.tsx +22 -5
- package/src/Selector/Selector.tsx +5 -1
- package/src/TextArea/TextArea.test.tsx +9 -1
- package/src/TextArea/TextArea.tsx +5 -1
- package/src/TextInput/TextInput.tsx +5 -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/Tokenizer/Tokenizer.test.tsx +17 -12
- package/src/Tokenizer/Tokenizer.tsx +10 -1
- package/src/Typeahead/BaseTypeahead.tsx +14 -1
- package/src/Typeahead/Typeahead.test.tsx +32 -41
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/BusyOwnerAnnouncement.test.tsx +792 -0
- package/src/hooks/__tests__/MenuPressShadowRoot.a11y.chromium.spec.ts +50 -0
- package/src/hooks/useBusyAnnouncement.ts +100 -0
- package/src/hooks/useMenuPress.test.tsx +27 -1
- package/src/hooks/useMenuPress.ts +12 -3
- package/src/hooks/useTypeahead.test.tsx +42 -1
- package/src/hooks/useTypeahead.ts +7 -2
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
|
@@ -62,6 +62,7 @@ import {themeProps} from '../utils/themeProps';
|
|
|
62
62
|
import {useTranslator} from '../i18n';
|
|
63
63
|
|
|
64
64
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
65
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
65
66
|
import {layerViewportInset} from '../Layer/layerViewportInset.stylex';
|
|
66
67
|
import {clampInlineSize} from '../Layer/clampInlineSize';
|
|
67
68
|
// =============================================================================
|
|
@@ -561,6 +562,14 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
561
562
|
// A wrapper that owns the inline-end lane subscribes through context; see
|
|
562
563
|
// busyIndicatorLane.tsx for why this is not a prop.
|
|
563
564
|
const busyLane = useBusyIndicatorLane();
|
|
565
|
+
// Without a wrapper that took the indicator over, this input announces each
|
|
566
|
+
// search's busy start (spec:AST-086 FR8); its Spinner stays named but is not
|
|
567
|
+
// a live region (family:input-fields FR7).
|
|
568
|
+
useBusyAnnouncement(
|
|
569
|
+
isLoading && busyLane == null,
|
|
570
|
+
t('@astryx.typeahead.loading'),
|
|
571
|
+
inputRef,
|
|
572
|
+
);
|
|
564
573
|
const onLoadingChangeRef = useRef(busyLane?.onBusyChange);
|
|
565
574
|
useIsomorphicLayoutEffect(() => {
|
|
566
575
|
onLoadingChangeRef.current = busyLane?.onBusyChange;
|
|
@@ -1218,7 +1227,11 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
1218
1227
|
/>
|
|
1219
1228
|
{isLoading && busyLane == null && (
|
|
1220
1229
|
<span {...stylex.props(styles.loadingStatus)}>
|
|
1221
|
-
<Spinner
|
|
1230
|
+
<Spinner
|
|
1231
|
+
size="sm"
|
|
1232
|
+
aria-label={t('@astryx.typeahead.loading')}
|
|
1233
|
+
role="img"
|
|
1234
|
+
/>
|
|
1222
1235
|
</span>
|
|
1223
1236
|
)}
|
|
1224
1237
|
{popover.render(
|
|
@@ -99,6 +99,17 @@ const fruitSource: SearchSource = {
|
|
|
99
99
|
bootstrap: () => fruits.slice(0, 3),
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The search Spinner: named but not live (family:input-fields FR7,
|
|
104
|
+
* spec:AST-086 FR8), so it is found as an image named "Loading".
|
|
105
|
+
*/
|
|
106
|
+
function queryBusySpinner(): Element | null {
|
|
107
|
+
return screen.queryByRole('img', {name: 'Loading'});
|
|
108
|
+
}
|
|
109
|
+
function getBusySpinner(): Element {
|
|
110
|
+
return screen.getByRole('img', {name: 'Loading'});
|
|
111
|
+
}
|
|
112
|
+
|
|
102
113
|
describe('BaseTypeahead', () => {
|
|
103
114
|
it('renders input with combobox role', () => {
|
|
104
115
|
render(
|
|
@@ -929,14 +940,12 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
929
940
|
|
|
930
941
|
fireEvent.change(input, {target: {value: 'a'}});
|
|
931
942
|
await waitFor(() => {
|
|
932
|
-
expect(
|
|
943
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
933
944
|
});
|
|
934
945
|
|
|
935
946
|
fireEvent.change(input, {target: {value: ''}});
|
|
936
947
|
await waitFor(() => {
|
|
937
|
-
expect(
|
|
938
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
939
|
-
).not.toBeInTheDocument();
|
|
948
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
940
949
|
});
|
|
941
950
|
|
|
942
951
|
await act(async () => {
|
|
@@ -966,16 +975,14 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
966
975
|
|
|
967
976
|
fireEvent.focus(input);
|
|
968
977
|
await waitFor(() => {
|
|
969
|
-
expect(
|
|
978
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
970
979
|
});
|
|
971
980
|
|
|
972
981
|
await act(async () => {
|
|
973
982
|
settle([]);
|
|
974
983
|
await Promise.resolve();
|
|
975
984
|
});
|
|
976
|
-
expect(
|
|
977
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
978
|
-
).not.toBeInTheDocument();
|
|
985
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
979
986
|
});
|
|
980
987
|
|
|
981
988
|
it('clears the loading state when the search source is replaced during an in-flight load', async () => {
|
|
@@ -1004,7 +1011,7 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
1004
1011
|
|
|
1005
1012
|
fireEvent.focus(input);
|
|
1006
1013
|
await waitFor(() => {
|
|
1007
|
-
expect(
|
|
1014
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
1008
1015
|
});
|
|
1009
1016
|
|
|
1010
1017
|
rerender(
|
|
@@ -1017,18 +1024,14 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
1017
1024
|
);
|
|
1018
1025
|
|
|
1019
1026
|
await waitFor(() => {
|
|
1020
|
-
expect(
|
|
1021
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
1022
|
-
).not.toBeInTheDocument();
|
|
1027
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
1023
1028
|
});
|
|
1024
1029
|
|
|
1025
1030
|
await act(async () => {
|
|
1026
1031
|
settle([]);
|
|
1027
1032
|
await Promise.resolve();
|
|
1028
1033
|
});
|
|
1029
|
-
expect(
|
|
1030
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
1031
|
-
).not.toBeInTheDocument();
|
|
1034
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
1032
1035
|
});
|
|
1033
1036
|
|
|
1034
1037
|
it('keeps the loading state under StrictMode for a pending autofocus bootstrap', async () => {
|
|
@@ -1052,15 +1055,13 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
1052
1055
|
</StrictMode>,
|
|
1053
1056
|
);
|
|
1054
1057
|
|
|
1055
|
-
expect(
|
|
1058
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
1056
1059
|
|
|
1057
1060
|
await act(async () => {
|
|
1058
1061
|
settle([]);
|
|
1059
1062
|
await Promise.resolve();
|
|
1060
1063
|
});
|
|
1061
|
-
expect(
|
|
1062
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
1063
|
-
).not.toBeInTheDocument();
|
|
1064
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
1064
1065
|
});
|
|
1065
1066
|
|
|
1066
1067
|
let settlePendingTransitionA: (items: SearchableItem[]) => void = () => {};
|
|
@@ -1125,7 +1126,7 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
1125
1126
|
await waitFor(() => {
|
|
1126
1127
|
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
1127
1128
|
});
|
|
1128
|
-
expect(
|
|
1129
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
1129
1130
|
|
|
1130
1131
|
// Create the pending promise for suspension
|
|
1131
1132
|
isSuspended = true;
|
|
@@ -1160,9 +1161,7 @@ describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
|
1160
1161
|
|
|
1161
1162
|
// The combobox must not keep aria-busy and the spinner must be gone
|
|
1162
1163
|
expect(input).not.toHaveAttribute('aria-busy');
|
|
1163
|
-
expect(
|
|
1164
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
1165
|
-
).not.toBeInTheDocument();
|
|
1164
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
1166
1165
|
});
|
|
1167
1166
|
|
|
1168
1167
|
it('shows bootstrap results on mouse click', async () => {
|
|
@@ -1443,7 +1442,7 @@ describe('BaseTypeahead minQueryLength', () => {
|
|
|
1443
1442
|
|
|
1444
1443
|
fireEvent.change(input, {target: {value: 'App'}});
|
|
1445
1444
|
await waitFor(() => {
|
|
1446
|
-
expect(
|
|
1445
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
1447
1446
|
});
|
|
1448
1447
|
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
1449
1448
|
|
|
@@ -1453,9 +1452,7 @@ describe('BaseTypeahead minQueryLength', () => {
|
|
|
1453
1452
|
await Promise.resolve();
|
|
1454
1453
|
});
|
|
1455
1454
|
|
|
1456
|
-
expect(
|
|
1457
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
1458
|
-
).not.toBeInTheDocument();
|
|
1455
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
1459
1456
|
expect(input).not.toHaveAttribute('aria-busy');
|
|
1460
1457
|
expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
1461
1458
|
});
|
|
@@ -2185,12 +2182,12 @@ describe('busy indicator ownership', () => {
|
|
|
2185
2182
|
};
|
|
2186
2183
|
};
|
|
2187
2184
|
|
|
2188
|
-
it('renders its own
|
|
2185
|
+
it('renders its own indicator for a direct caller', async () => {
|
|
2189
2186
|
// BaseTypeaheadProps is re-exported from the package entry point, so the
|
|
2190
2187
|
// base has direct callers this repo cannot see. They painted no indicator
|
|
2191
|
-
// of their own — the base did it for them — so it keeps doing it
|
|
2192
|
-
//
|
|
2193
|
-
//
|
|
2188
|
+
// of their own — the base did it for them — so it keeps doing it. The
|
|
2189
|
+
// Spinner is decorative and the base announces the search's busy start
|
|
2190
|
+
// through the shared channel (spec:AST-086 FR8).
|
|
2194
2191
|
const {source, settle} = pendingSource();
|
|
2195
2192
|
render(
|
|
2196
2193
|
<BaseTypeahead
|
|
@@ -2204,22 +2201,18 @@ describe('busy indicator ownership', () => {
|
|
|
2204
2201
|
|
|
2205
2202
|
fireEvent.change(input, {target: {value: 'App'}});
|
|
2206
2203
|
await waitFor(() => {
|
|
2207
|
-
expect(
|
|
2204
|
+
expect(getBusySpinner()).toBeInTheDocument();
|
|
2208
2205
|
});
|
|
2209
2206
|
// A Spinner, not the static clock glyph this used to render: `clock`
|
|
2210
2207
|
// means *time* everywhere else in core, and nothing about it moved while
|
|
2211
2208
|
// a search was out.
|
|
2212
|
-
expect(
|
|
2213
|
-
screen.getByRole('status', {name: 'Loading'}).querySelector('svg'),
|
|
2214
|
-
).toBeInTheDocument();
|
|
2209
|
+
expect(getBusySpinner().querySelector('svg')).toBeInTheDocument();
|
|
2215
2210
|
|
|
2216
2211
|
await act(async () => {
|
|
2217
2212
|
settle();
|
|
2218
2213
|
await Promise.resolve();
|
|
2219
2214
|
});
|
|
2220
|
-
expect(
|
|
2221
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
2222
|
-
).not.toBeInTheDocument();
|
|
2215
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
2223
2216
|
});
|
|
2224
2217
|
|
|
2225
2218
|
it('hands the indicator over to a field that takes it, and renders none itself', async () => {
|
|
@@ -2250,9 +2243,7 @@ describe('busy indicator ownership', () => {
|
|
|
2250
2243
|
});
|
|
2251
2244
|
// Scoped by name: the announcer for result counts is a role="status"
|
|
2252
2245
|
// live region too, and it is not the indicator.
|
|
2253
|
-
expect(
|
|
2254
|
-
screen.queryByRole('status', {name: 'Loading'}),
|
|
2255
|
-
).not.toBeInTheDocument();
|
|
2246
|
+
expect(queryBusySpinner()).not.toBeInTheDocument();
|
|
2256
2247
|
expect(onLoadingChange).toHaveBeenLastCalledWith(true);
|
|
2257
2248
|
|
|
2258
2249
|
await act(async () => {
|
|
@@ -25,6 +25,7 @@ import React, {
|
|
|
25
25
|
useMemo,
|
|
26
26
|
useState,
|
|
27
27
|
type ReactNode,
|
|
28
|
+
type RefObject,
|
|
28
29
|
} from 'react';
|
|
29
30
|
import * as stylex from '@stylexjs/stylex';
|
|
30
31
|
import {
|
|
@@ -70,6 +71,7 @@ import {themeProps} from '../utils/themeProps';
|
|
|
70
71
|
import {useTranslator} from '../i18n';
|
|
71
72
|
|
|
72
73
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
74
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
73
75
|
export type {
|
|
74
76
|
InputStatus as TypeaheadStatus,
|
|
75
77
|
InputStatusType as TypeaheadStatusType,
|
|
@@ -349,19 +351,25 @@ const wrapperSizeStyles = stylex.create({
|
|
|
349
351
|
function EndLane({
|
|
350
352
|
lane,
|
|
351
353
|
loadingLabel,
|
|
354
|
+
anchorRef,
|
|
352
355
|
clear,
|
|
353
356
|
}: {
|
|
354
357
|
lane: BusyIndicatorLane;
|
|
355
358
|
loadingLabel: string;
|
|
359
|
+
anchorRef: RefObject<Element | null>;
|
|
356
360
|
clear: ReactNode;
|
|
357
361
|
}) {
|
|
358
362
|
const isBusy = useIsBusy(lane);
|
|
363
|
+
// The search Spinner stays named (family:input-fields FR7) but is not a
|
|
364
|
+
// live region; the field announces each search's busy start instead
|
|
365
|
+
// (spec:AST-086 FR8).
|
|
366
|
+
useBusyAnnouncement(isBusy, loadingLabel, anchorRef);
|
|
359
367
|
if (!isBusy && clear == null) {
|
|
360
368
|
return null;
|
|
361
369
|
}
|
|
362
370
|
return (
|
|
363
371
|
<div {...stylex.props(styles.endLane)}>
|
|
364
|
-
{isBusy && <Spinner size="sm" aria-label={loadingLabel} />}
|
|
372
|
+
{isBusy && <Spinner size="sm" aria-label={loadingLabel} role="img" />}
|
|
365
373
|
{clear}
|
|
366
374
|
</div>
|
|
367
375
|
);
|
|
@@ -679,6 +687,7 @@ export function Typeahead<T extends SearchableItem>({
|
|
|
679
687
|
<EndLane
|
|
680
688
|
lane={busyLane}
|
|
681
689
|
loadingLabel={t('@astryx.typeahead.loading')}
|
|
690
|
+
anchorRef={wrapperRef}
|
|
682
691
|
clear={
|
|
683
692
|
hasClear && value && !isDisabled ? (
|
|
684
693
|
<InputClearButton
|