@astryxdesign/core 0.4.2 → 0.4.3-canary.00bed29
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/CHANGELOG.md +45 -0
- package/README.md +79 -45
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +3 -19
- package/dist/Banner/Banner.d.ts +9 -2
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +84 -33
- package/dist/Banner/index.d.ts +14 -12
- package/dist/Banner/index.d.ts.map +1 -1
- package/dist/Banner/index.js +10 -8
- package/dist/BottomSheet/BottomSheet.d.ts +66 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheet.js +392 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +60 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetPanel.js +398 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +61 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.js +410 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +21 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -0
- package/dist/BottomSheet/index.d.ts +11 -0
- package/dist/BottomSheet/index.d.ts.map +1 -0
- package/dist/BottomSheet/index.js +12 -0
- package/dist/BottomSheet/snapOffsets.d.ts +72 -0
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -0
- package/dist/BottomSheet/snapOffsets.js +130 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts +35 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +1 -0
- package/dist/BottomSheet/useMobileKeyboard.js +373 -0
- package/dist/BottomSheet/useSheetGestures.d.ts +180 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -0
- package/dist/BottomSheet/useSheetGestures.js +942 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +3 -4
- package/dist/Chat/ChatTokenizedText.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +98 -31
- package/dist/ComplexSelector/index.d.ts +2 -2
- package/dist/ComplexSelector/index.d.ts.map +1 -1
- package/dist/ComplexSelector/index.js +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +9 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +31 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +9 -3
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +55 -12
- package/dist/MobileNav/MobileNav.d.ts +12 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +102 -30
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +21 -7
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +9 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +4 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +2 -4
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +14 -3
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -1
- package/dist/Table/columnUtils.d.ts.map +1 -1
- package/dist/Table/columnUtils.js +4 -1
- package/dist/TextArea/TextArea.d.ts +6 -3
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +17 -6
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +8 -1
- package/dist/Tokenizer/Tokenizer.js +11 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +4 -1
- package/dist/TreeList/TreeList.js +2 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +10 -1
- package/dist/astryx.css +25 -2
- package/dist/hooks/index.d.ts +8 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +8 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -11
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -11
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +30 -7
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +36 -47
- package/dist/theme/expandColorScale.d.ts +17 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +63 -33
- package/dist/theme/mergeComponents.d.ts +20 -0
- package/dist/theme/mergeComponents.d.ts.map +1 -0
- package/dist/theme/mergeComponents.js +56 -0
- package/dist/theme/onMediaTokens.d.ts +6 -1
- package/dist/theme/onMediaTokens.d.ts.map +1 -1
- package/dist/theme/onMediaTokens.js +11 -3
- package/dist/utils/characters.d.ts +27 -0
- package/dist/utils/characters.d.ts.map +1 -0
- package/dist/utils/characters.js +83 -0
- package/dist/utils/ime.d.ts +5 -0
- package/dist/utils/ime.d.ts.map +1 -0
- package/dist/utils/ime.js +63 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/locales/en.json +48 -0
- package/locales/pseudo.json +36 -0
- package/package.json +9 -8
- package/src/Avatar/Avatar.test.tsx +6 -1
- package/src/Avatar/Avatar.tsx +3 -21
- package/src/Banner/Banner.doc.mjs +12 -10
- package/src/Banner/Banner.test.tsx +108 -0
- package/src/Banner/Banner.tsx +124 -38
- package/src/Banner/index.ts +15 -13
- package/src/BottomSheet/BottomSheet.doc.mjs +266 -0
- package/src/BottomSheet/BottomSheet.test.tsx +2114 -0
- package/src/BottomSheet/BottomSheet.tsx +545 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +240 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +547 -0
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +219 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +850 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +628 -0
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +51 -0
- package/src/BottomSheet/index.ts +15 -0
- package/src/BottomSheet/snapOffsets.test.ts +187 -0
- package/src/BottomSheet/snapOffsets.ts +159 -0
- package/src/BottomSheet/useMobileKeyboard.ts +499 -0
- package/src/BottomSheet/useSheetGestures.test.ts +1014 -0
- package/src/BottomSheet/useSheetGestures.ts +1407 -0
- package/src/Chat/ChatComposerInput.tsx +3 -4
- package/src/Chat/ChatTokenizedText.tsx +1 -1
- package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
- package/src/ComplexSelector/ComplexSelector.tsx +156 -28
- package/src/ComplexSelector/index.ts +3 -1
- package/src/ContextMenu/ContextMenu.tsx +4 -2
- package/src/DateInput/DateInput.test.tsx +21 -0
- package/src/DateInput/DateInput.tsx +9 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +4 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +137 -0
- package/src/DateTimeInput/DateTimeInput.tsx +39 -2
- package/src/Dialog/Dialog.tsx +2 -1
- package/src/FileInput/FileInput.test.tsx +53 -9
- package/src/Lightbox/Lightbox.test.tsx +49 -28
- package/src/Lightbox/Lightbox.tsx +7 -3
- package/src/Markdown/parser.test.ts +53 -0
- package/src/Markdown/parser.ts +53 -12
- package/src/MobileNav/MobileNav.tsx +128 -37
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +426 -0
- package/src/MobileNav/MobileNavCloseTiming.test.ts +58 -0
- package/src/MobileNav/MobileNavCloseVisibility.test.tsx +172 -0
- package/src/MultiSelector/MultiSelector.test.tsx +195 -51
- package/src/MultiSelector/MultiSelector.tsx +22 -8
- package/src/NumberInput/NumberInput.test.tsx +18 -0
- package/src/NumberInput/NumberInput.tsx +9 -1
- package/src/PowerSearch/PowerSearch.test.tsx +48 -3
- package/src/PowerSearch/PowerSearch.tsx +4 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/formatFilterValue.test.ts +22 -0
- package/src/PowerSearch/formatFilterValue.ts +2 -4
- package/src/Selector/Selector.test.tsx +85 -26
- package/src/Selector/Selector.tsx +13 -4
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +7 -0
- package/src/Table/Table.test.tsx +6 -0
- package/src/Table/columnUtils.ts +3 -1
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +72 -0
- package/src/TextArea/TextArea.tsx +26 -8
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +8 -0
- package/src/Tokenizer/Tokenizer.test.tsx +50 -32
- package/src/Tokenizer/Tokenizer.tsx +5 -5
- package/src/Tooltip/useTooltip.tsx +4 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/TreeList/TreeList.tsx +1 -1
- package/src/Typeahead/BaseTypeahead.tsx +10 -3
- package/src/Typeahead/Typeahead.test.tsx +116 -7
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/stubMatchMedia.test.ts +79 -0
- package/src/__tests__/stubMatchMedia.ts +95 -0
- package/src/hooks/index.ts +9 -5
- package/src/hooks/useFocusTrap.ts +1 -14
- package/src/hooks/useInputStatusIcon.test.tsx +19 -0
- package/src/hooks/useInputStatusIcon.tsx +7 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +127 -0
- package/src/theme/defineTheme.ts +70 -55
- package/src/theme/expandColorScale.test.ts +138 -0
- package/src/theme/expandColorScale.ts +89 -44
- package/src/theme/extensibleAxes.test.ts +365 -0
- package/src/theme/mergeComponents.ts +59 -0
- package/src/theme/onMediaTokens.ts +9 -2
- package/src/utils/characters.test.ts +141 -0
- package/src/utils/characters.ts +88 -0
- package/src/utils/ime.test.ts +32 -0
- package/src/utils/ime.ts +69 -0
- package/src/utils/index.ts +4 -0
- package/dist/astryx.umd.js +0 -147
- package/dist/astryx.umd.js.map +0 -7
|
@@ -16,6 +16,16 @@ import {Tokenizer} from './Tokenizer';
|
|
|
16
16
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
17
17
|
import type {SearchSource, SearchableItem} from '../Typeahead/types';
|
|
18
18
|
import {TestIcon} from '../__tests__/TestIcon';
|
|
19
|
+
import {InternationalizationProvider} from '../i18n';
|
|
20
|
+
|
|
21
|
+
// Test-supplied announcement strings: the assertions below depend on no
|
|
22
|
+
// catalog, and no hardcoded English in the component can satisfy them.
|
|
23
|
+
const TOKEN_MESSAGES = {
|
|
24
|
+
fr: {
|
|
25
|
+
'@astryx.tokenizer.tokenAdded': 'Ajouté : {label}',
|
|
26
|
+
'@astryx.tokenizer.tokenRemoved': 'Retiré : {label}',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
19
29
|
|
|
20
30
|
function politeRegion(): HTMLElement | null {
|
|
21
31
|
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
@@ -980,12 +990,14 @@ describe('Tokenizer', () => {
|
|
|
980
990
|
it('announces removal politely on Backspace with an empty input', async () => {
|
|
981
991
|
const onChange = vi.fn();
|
|
982
992
|
render(
|
|
983
|
-
<
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
993
|
+
<InternationalizationProvider locale="fr" overrides={TOKEN_MESSAGES}>
|
|
994
|
+
<Tokenizer
|
|
995
|
+
label="Members"
|
|
996
|
+
searchSource={userSource}
|
|
997
|
+
value={[users[0], users[1]]}
|
|
998
|
+
onChange={onChange}
|
|
999
|
+
/>
|
|
1000
|
+
</InternationalizationProvider>,
|
|
989
1001
|
);
|
|
990
1002
|
const input = screen.getByRole('combobox');
|
|
991
1003
|
fireEvent.keyDown(input, {key: 'Backspace'});
|
|
@@ -994,35 +1006,39 @@ describe('Tokenizer', () => {
|
|
|
994
1006
|
type: 'remove',
|
|
995
1007
|
});
|
|
996
1008
|
await waitFor(() => {
|
|
997
|
-
expect(politeRegion()).
|
|
1009
|
+
expect(politeRegion()?.textContent).toBe('Retiré : Bob');
|
|
998
1010
|
});
|
|
999
1011
|
});
|
|
1000
1012
|
|
|
1001
1013
|
it("announces removal politely when clicking a token's remove button", async () => {
|
|
1002
1014
|
render(
|
|
1003
|
-
<
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1015
|
+
<InternationalizationProvider locale="fr" overrides={TOKEN_MESSAGES}>
|
|
1016
|
+
<Tokenizer
|
|
1017
|
+
label="Members"
|
|
1018
|
+
searchSource={userSource}
|
|
1019
|
+
value={[users[0], users[1]]}
|
|
1020
|
+
onChange={() => {}}
|
|
1021
|
+
/>
|
|
1022
|
+
</InternationalizationProvider>,
|
|
1009
1023
|
);
|
|
1010
1024
|
fireEvent.click(screen.getByRole('button', {name: 'Remove Alice'}));
|
|
1011
1025
|
await waitFor(() => {
|
|
1012
|
-
expect(politeRegion()).
|
|
1026
|
+
expect(politeRegion()?.textContent).toBe('Retiré : Alice');
|
|
1013
1027
|
});
|
|
1014
1028
|
});
|
|
1015
1029
|
|
|
1016
1030
|
it('announces addition politely when selecting a search result', async () => {
|
|
1017
1031
|
render(
|
|
1018
|
-
<
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1032
|
+
<InternationalizationProvider locale="fr" overrides={TOKEN_MESSAGES}>
|
|
1033
|
+
<Tokenizer
|
|
1034
|
+
label="Members"
|
|
1035
|
+
searchSource={userSource}
|
|
1036
|
+
value={[]}
|
|
1037
|
+
onChange={() => {}}
|
|
1038
|
+
hasEntriesOnFocus
|
|
1039
|
+
debounceMs={0}
|
|
1040
|
+
/>
|
|
1041
|
+
</InternationalizationProvider>,
|
|
1026
1042
|
);
|
|
1027
1043
|
const input = screen.getByRole('combobox');
|
|
1028
1044
|
fireEvent.focus(input);
|
|
@@ -1031,7 +1047,7 @@ describe('Tokenizer', () => {
|
|
|
1031
1047
|
});
|
|
1032
1048
|
fireEvent.click(screen.getByText('Alice'));
|
|
1033
1049
|
await waitFor(() => {
|
|
1034
|
-
expect(politeRegion()).
|
|
1050
|
+
expect(politeRegion()?.textContent).toBe('Ajouté : Alice');
|
|
1035
1051
|
});
|
|
1036
1052
|
});
|
|
1037
1053
|
|
|
@@ -1041,14 +1057,16 @@ describe('Tokenizer', () => {
|
|
|
1041
1057
|
bootstrap: () => [],
|
|
1042
1058
|
};
|
|
1043
1059
|
render(
|
|
1044
|
-
<
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1060
|
+
<InternationalizationProvider locale="fr" overrides={TOKEN_MESSAGES}>
|
|
1061
|
+
<Tokenizer
|
|
1062
|
+
label="Tags"
|
|
1063
|
+
searchSource={emptySource}
|
|
1064
|
+
value={[]}
|
|
1065
|
+
onChange={() => {}}
|
|
1066
|
+
hasCreate
|
|
1067
|
+
debounceMs={0}
|
|
1068
|
+
/>
|
|
1069
|
+
</InternationalizationProvider>,
|
|
1052
1070
|
);
|
|
1053
1071
|
const input = screen.getByRole('combobox');
|
|
1054
1072
|
await act(async () => {
|
|
@@ -1059,7 +1077,7 @@ describe('Tokenizer', () => {
|
|
|
1059
1077
|
});
|
|
1060
1078
|
fireEvent.click(screen.getByText('Create "new-tag"'));
|
|
1061
1079
|
await waitFor(() => {
|
|
1062
|
-
expect(politeRegion()).
|
|
1080
|
+
expect(politeRegion()?.textContent).toBe('Ajouté : new-tag');
|
|
1063
1081
|
});
|
|
1064
1082
|
});
|
|
1065
1083
|
|
|
@@ -600,7 +600,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
600
600
|
const realItem = base as T;
|
|
601
601
|
const newItems = [...value, realItem];
|
|
602
602
|
onChange(newItems, {item: realItem, type: 'create'});
|
|
603
|
-
announce(
|
|
603
|
+
announce(t('@astryx.tokenizer.tokenAdded', {label: createdValue}));
|
|
604
604
|
return;
|
|
605
605
|
}
|
|
606
606
|
|
|
@@ -609,9 +609,9 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
609
609
|
}
|
|
610
610
|
const newItems = [...value, item];
|
|
611
611
|
onChange(newItems, {item, type: 'add'});
|
|
612
|
-
announce(
|
|
612
|
+
announce(t('@astryx.tokenizer.tokenAdded', {label: item.label}));
|
|
613
613
|
},
|
|
614
|
-
[value, onChange, isAtMax, selectedIds, hasCreate, announce],
|
|
614
|
+
[value, onChange, isAtMax, selectedIds, hasCreate, announce, t],
|
|
615
615
|
);
|
|
616
616
|
|
|
617
617
|
// Handle removing an item. Single removal path: both Backspace on an empty
|
|
@@ -621,10 +621,10 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
621
621
|
(item: T) => {
|
|
622
622
|
const newItems = value.filter(v => v.id !== item.id);
|
|
623
623
|
onChange(newItems, {item, type: 'remove'});
|
|
624
|
-
announce(
|
|
624
|
+
announce(t('@astryx.tokenizer.tokenRemoved', {label: item.label}));
|
|
625
625
|
inputRef.current?.focus();
|
|
626
626
|
},
|
|
627
|
-
[value, onChange, announce],
|
|
627
|
+
[value, onChange, announce, t],
|
|
628
628
|
);
|
|
629
629
|
|
|
630
630
|
// Handle clearing all items
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../Layer/useLayer';
|
|
29
29
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
30
30
|
import {themeProps} from '../utils/themeProps';
|
|
31
|
+
import {isImeKeyEvent} from '../utils/ime';
|
|
31
32
|
import {
|
|
32
33
|
colorVars,
|
|
33
34
|
radiusVars,
|
|
@@ -454,7 +455,9 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
454
455
|
if (e.key !== 'Escape') {
|
|
455
456
|
return;
|
|
456
457
|
}
|
|
457
|
-
if (e
|
|
458
|
+
if (isImeKeyEvent(e)) {
|
|
459
|
+
// Ignore Escape that is committing/cancelling an IME composition;
|
|
460
|
+
// see utils/ime.ts for why.
|
|
458
461
|
return;
|
|
459
462
|
}
|
|
460
463
|
clearTimeouts();
|
|
@@ -24,7 +24,7 @@ export const docs = {
|
|
|
24
24
|
},
|
|
25
25
|
theming: {
|
|
26
26
|
targets: [
|
|
27
|
-
{className: 'astryx-tree-list', visualProps: ['density']},
|
|
27
|
+
{className: 'astryx-tree-list', visualProps: ['density', 'variant']},
|
|
28
28
|
{className: 'astryx-tree-list-item', visualProps: ['density'], states: ['selected', 'disabled']},
|
|
29
29
|
{className: 'astryx-tree-list-chevron', states: ['state']},
|
|
30
30
|
{className: 'astryx-tree-list-item-label', states: ['selected']},
|
|
@@ -104,7 +104,7 @@ export const docsZh = {
|
|
|
104
104
|
group: 'TreeList',
|
|
105
105
|
theming: {
|
|
106
106
|
targets: [
|
|
107
|
-
{className: 'astryx-tree-list', visualProps: ['density']},
|
|
107
|
+
{className: 'astryx-tree-list', visualProps: ['density', 'variant']},
|
|
108
108
|
{className: 'astryx-tree-list-item', visualProps: ['density'], states: ['selected', 'disabled']},
|
|
109
109
|
{className: 'astryx-tree-list-chevron', states: ['state']},
|
|
110
110
|
{className: 'astryx-tree-list-item-label', states: ['selected']},
|
|
@@ -30,7 +30,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
30
30
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
31
31
|
import {usePopover} from '../Popover/usePopover';
|
|
32
32
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
33
|
-
import {isImeKeyEvent} from '../
|
|
33
|
+
import {isImeKeyEvent} from '../utils/ime';
|
|
34
34
|
import {TypeaheadItem} from './TypeaheadItem';
|
|
35
35
|
import {Icon} from '../Icon';
|
|
36
36
|
import {
|
|
@@ -449,7 +449,7 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
449
449
|
announce(
|
|
450
450
|
shown.length === 0
|
|
451
451
|
? emptySearchResultsText
|
|
452
|
-
:
|
|
452
|
+
: t('@astryx.typeahead.resultCount', {count: shown.length}),
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
455
|
} catch {
|
|
@@ -464,7 +464,14 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
466
|
},
|
|
467
|
-
[
|
|
467
|
+
[
|
|
468
|
+
searchSource,
|
|
469
|
+
maxMenuItems,
|
|
470
|
+
showLayer,
|
|
471
|
+
announce,
|
|
472
|
+
emptySearchResultsText,
|
|
473
|
+
t,
|
|
474
|
+
],
|
|
468
475
|
);
|
|
469
476
|
|
|
470
477
|
// Perform bootstrap
|
|
@@ -23,6 +23,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
23
23
|
import {Typeahead} from './Typeahead';
|
|
24
24
|
import {BaseTypeahead} from './BaseTypeahead';
|
|
25
25
|
import type {SearchSource, SearchableItem} from './types';
|
|
26
|
+
import {InternationalizationProvider} from '../i18n';
|
|
26
27
|
|
|
27
28
|
// Store original matches to restore later
|
|
28
29
|
const originalMatches = HTMLElement.prototype.matches;
|
|
@@ -132,21 +133,94 @@ describe('BaseTypeahead', () => {
|
|
|
132
133
|
|
|
133
134
|
it('announces the result count to a live region (comboboxes-6)', async () => {
|
|
134
135
|
render(
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
<InternationalizationProvider
|
|
137
|
+
locale="fr"
|
|
138
|
+
overrides={{
|
|
139
|
+
fr: {
|
|
140
|
+
'@astryx.typeahead.resultCount':
|
|
141
|
+
'{count, number} {count, plural, one {résultat} other {résultats}}',
|
|
142
|
+
},
|
|
143
|
+
}}>
|
|
144
|
+
<BaseTypeahead
|
|
145
|
+
searchSource={fruitSource}
|
|
146
|
+
value={null}
|
|
147
|
+
onChange={() => {}}
|
|
148
|
+
debounceMs={0}
|
|
149
|
+
/>
|
|
150
|
+
</InternationalizationProvider>,
|
|
141
151
|
);
|
|
142
152
|
const input = screen.getByRole('combobox');
|
|
153
|
+
// "Ap" matches Apple only — the singular ICU branch.
|
|
143
154
|
fireEvent.change(input, {target: {value: 'Ap'}});
|
|
144
155
|
|
|
145
156
|
await waitFor(() => {
|
|
146
157
|
const region = document.querySelector(
|
|
147
158
|
'[data-astryx-live-region="polite"]',
|
|
148
159
|
);
|
|
149
|
-
|
|
160
|
+
// Exact, so the plural branch ("1 résultats") would fail.
|
|
161
|
+
expect(region?.textContent).toBe('1 résultat');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('announces the plural result count', async () => {
|
|
166
|
+
render(
|
|
167
|
+
<InternationalizationProvider
|
|
168
|
+
locale="fr"
|
|
169
|
+
overrides={{
|
|
170
|
+
fr: {
|
|
171
|
+
'@astryx.typeahead.resultCount':
|
|
172
|
+
'{count, number} {count, plural, one {résultat} other {résultats}}',
|
|
173
|
+
},
|
|
174
|
+
}}>
|
|
175
|
+
<BaseTypeahead
|
|
176
|
+
searchSource={fruitSource}
|
|
177
|
+
value={null}
|
|
178
|
+
onChange={() => {}}
|
|
179
|
+
debounceMs={0}
|
|
180
|
+
/>
|
|
181
|
+
</InternationalizationProvider>,
|
|
182
|
+
);
|
|
183
|
+
const input = screen.getByRole('combobox');
|
|
184
|
+
// "err" matches Cherry and Elderberry.
|
|
185
|
+
fireEvent.change(input, {target: {value: 'err'}});
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
const region = document.querySelector(
|
|
189
|
+
'[data-astryx-live-region="polite"]',
|
|
190
|
+
);
|
|
191
|
+
expect(region?.textContent).toBe('2 résultats');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('speaks the result count from a provider catalog', async () => {
|
|
196
|
+
render(
|
|
197
|
+
<InternationalizationProvider
|
|
198
|
+
locale="fr"
|
|
199
|
+
messages={{
|
|
200
|
+
fr: {
|
|
201
|
+
'@astryx.typeahead.resultCount': {
|
|
202
|
+
defaultMessage:
|
|
203
|
+
'{count, number} {count, plural, one {résultat} other {résultats}}',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
}}>
|
|
207
|
+
<BaseTypeahead
|
|
208
|
+
searchSource={fruitSource}
|
|
209
|
+
value={null}
|
|
210
|
+
onChange={() => {}}
|
|
211
|
+
debounceMs={0}
|
|
212
|
+
/>
|
|
213
|
+
</InternationalizationProvider>,
|
|
214
|
+
);
|
|
215
|
+
const input = screen.getByRole('combobox');
|
|
216
|
+
fireEvent.change(input, {target: {value: 'err'}});
|
|
217
|
+
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
const region = document.querySelector(
|
|
220
|
+
'[data-astryx-live-region="polite"]',
|
|
221
|
+
);
|
|
222
|
+
// Same key through the catalog path rather than `overrides`.
|
|
223
|
+
expect(region?.textContent).toBe('2 résultats');
|
|
150
224
|
});
|
|
151
225
|
});
|
|
152
226
|
|
|
@@ -796,6 +870,41 @@ describe('Typeahead edit mode', () => {
|
|
|
796
870
|
// onChange should not have been called — value restored
|
|
797
871
|
expect(onChange).not.toHaveBeenCalled();
|
|
798
872
|
});
|
|
873
|
+
|
|
874
|
+
it('does not exit edit mode on a composing Escape (IME)', async () => {
|
|
875
|
+
const onChange = vi.fn();
|
|
876
|
+
render(
|
|
877
|
+
<Typeahead
|
|
878
|
+
label="Fruit"
|
|
879
|
+
searchSource={fruitSource}
|
|
880
|
+
value={fruits[0]}
|
|
881
|
+
onChange={onChange}
|
|
882
|
+
/>,
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
// Enter edit mode by clicking the token; the input uncollapses and
|
|
886
|
+
// rejoins the Tab order (tabindex is cleared).
|
|
887
|
+
const tokenText = screen.getByText(fruits[0].label);
|
|
888
|
+
fireEvent.click(tokenText.closest('div')!);
|
|
889
|
+
await act(async () => {
|
|
890
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
891
|
+
});
|
|
892
|
+
const input = screen.getByRole('combobox');
|
|
893
|
+
expect(input).not.toHaveAttribute('tabindex', '-1');
|
|
894
|
+
|
|
895
|
+
// BaseTypeahead invokes this external handler before its own IME guard, so
|
|
896
|
+
// a composing Escape (isComposing / legacy keyCode 229) — which an IME uses
|
|
897
|
+
// to cancel the pending candidate — must not exit edit mode here.
|
|
898
|
+
fireEvent.keyDown(input, {key: 'Escape', isComposing: true});
|
|
899
|
+
expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex', '-1');
|
|
900
|
+
fireEvent.keyDown(input, {key: 'Escape', keyCode: 229});
|
|
901
|
+
expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex', '-1');
|
|
902
|
+
|
|
903
|
+
// A real, non-composing Escape still exits edit mode: the token is
|
|
904
|
+
// restored and the collapsed input drops back out of the Tab order.
|
|
905
|
+
fireEvent.keyDown(input, {key: 'Escape'});
|
|
906
|
+
expect(screen.getByRole('combobox')).toHaveAttribute('tabindex', '-1');
|
|
907
|
+
});
|
|
799
908
|
});
|
|
800
909
|
|
|
801
910
|
describe('Typeahead collapsed input tab order', () => {
|
|
@@ -45,7 +45,7 @@ import {VisuallyHidden} from '../VisuallyHidden';
|
|
|
45
45
|
import {spacingVars, sizeVars} from '../theme/tokens.stylex';
|
|
46
46
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
47
47
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
48
|
-
import {getInputARIA, mergeProps, mergeRefs} from '../utils';
|
|
48
|
+
import {getInputARIA, isImeKeyEvent, mergeProps, mergeRefs} from '../utils';
|
|
49
49
|
import type {BaseProps} from '../BaseProps';
|
|
50
50
|
import type {SizeValue} from '../utils/types';
|
|
51
51
|
import type {SearchableItem, SearchSource} from './types';
|
|
@@ -369,6 +369,15 @@ export function Typeahead<T extends SearchableItem>({
|
|
|
369
369
|
// Handle Escape during edit mode — restore token
|
|
370
370
|
const handleKeyDown = useCallback(
|
|
371
371
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
372
|
+
// BaseTypeahead invokes this external handler *before* its own IME
|
|
373
|
+
// guard, so we must guard here too: an IME candidate window uses Escape
|
|
374
|
+
// to cancel the pending composition, and that composing Escape fires
|
|
375
|
+
// before compositionend. Without this, a Korean/Japanese/Chinese user
|
|
376
|
+
// cancelling a candidate would instead exit edit mode and blur the
|
|
377
|
+
// field. See utils/ime.ts.
|
|
378
|
+
if (isImeKeyEvent(e.nativeEvent)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
372
381
|
if (e.key === 'Escape' && editingValue) {
|
|
373
382
|
e.preventDefault();
|
|
374
383
|
setIsEditing(false);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file stubMatchMedia.test.ts
|
|
5
|
+
* @input Uses vitest, stubMatchMedia
|
|
6
|
+
* @output Unit coverage for the shared query-aware matchMedia stub
|
|
7
|
+
* @position Testing; guards the helper other suites trust to route queries
|
|
8
|
+
*
|
|
9
|
+
* The helper exists because a stub that ignores its query silently reroutes
|
|
10
|
+
* code branching on a different one — the failure mode leaves every test
|
|
11
|
+
* green. A helper sold on that promise has to be held to it, so these assert
|
|
12
|
+
* the routing itself rather than any component behaviour.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {describe, it, expect, afterEach, vi} from 'vitest';
|
|
16
|
+
import {stubMatchMedia} from './stubMatchMedia';
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.unstubAllGlobals();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('stubMatchMedia', () => {
|
|
23
|
+
it('routes prefers-reduced-motion independently of every other query', () => {
|
|
24
|
+
// The exact combination the blanket stub could not express: below a
|
|
25
|
+
// breakpoint (matches) while motion is NOT reduced.
|
|
26
|
+
stubMatchMedia({reduceMotion: false, matches: true});
|
|
27
|
+
|
|
28
|
+
expect(matchMedia('(prefers-reduced-motion: reduce)').matches).toBe(false);
|
|
29
|
+
expect(matchMedia('(max-width: 768px)').matches).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('answers no-preference as the opposite of reduce', () => {
|
|
33
|
+
// Two spellings, opposite meanings. A substring test on
|
|
34
|
+
// 'prefers-reduced-motion' alone would hand both the same answer.
|
|
35
|
+
stubMatchMedia({reduceMotion: true});
|
|
36
|
+
|
|
37
|
+
expect(matchMedia('(prefers-reduced-motion: reduce)').matches).toBe(true);
|
|
38
|
+
expect(matchMedia('(prefers-reduced-motion: no-preference)').matches).toBe(
|
|
39
|
+
false,
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('treats the bare preference query as reduce', () => {
|
|
44
|
+
stubMatchMedia({reduceMotion: true});
|
|
45
|
+
expect(matchMedia('(prefers-reduced-motion)').matches).toBe(true);
|
|
46
|
+
|
|
47
|
+
stubMatchMedia({reduceMotion: false});
|
|
48
|
+
expect(matchMedia('(prefers-reduced-motion)').matches).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('defaults to full motion and matching everything else', () => {
|
|
52
|
+
stubMatchMedia();
|
|
53
|
+
|
|
54
|
+
expect(matchMedia('(prefers-reduced-motion: reduce)').matches).toBe(false);
|
|
55
|
+
expect(matchMedia('(max-width: 768px)').matches).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('lets a non-motion query be answered false', () => {
|
|
59
|
+
// Above the breakpoint, motion still reduced — the other axis of the
|
|
60
|
+
// combination, so neither knob can be quietly driving both.
|
|
61
|
+
stubMatchMedia({reduceMotion: true, matches: false});
|
|
62
|
+
|
|
63
|
+
expect(matchMedia('(prefers-reduced-motion: reduce)').matches).toBe(true);
|
|
64
|
+
expect(matchMedia('(max-width: 768px)').matches).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('echoes the query and exposes the listener API components subscribe to', () => {
|
|
68
|
+
stubMatchMedia();
|
|
69
|
+
const mql = matchMedia('(max-width: 768px)');
|
|
70
|
+
|
|
71
|
+
// useMediaQuery subscribes; a stub without these throws on mount rather
|
|
72
|
+
// than failing an assertion, which reads as an unrelated crash.
|
|
73
|
+
expect(mql.media).toBe('(max-width: 768px)');
|
|
74
|
+
expect(typeof mql.addEventListener).toBe('function');
|
|
75
|
+
expect(typeof mql.removeEventListener).toBe('function');
|
|
76
|
+
expect(typeof mql.addListener).toBe('function');
|
|
77
|
+
expect(typeof mql.removeListener).toBe('function');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file stubMatchMedia.ts
|
|
5
|
+
* @input Uses vitest
|
|
6
|
+
* @output stubMatchMedia — a query-aware window.matchMedia stub
|
|
7
|
+
* @position Shared test helper; imported by component tests that render below
|
|
8
|
+
* a breakpoint while also exercising motion-sensitive behaviour
|
|
9
|
+
*
|
|
10
|
+
* jsdom has no matchMedia, so tests stub it. The usual stub answers every query
|
|
11
|
+
* with one boolean:
|
|
12
|
+
*
|
|
13
|
+
* vi.stubGlobal('matchMedia', vi.fn().mockReturnValue({matches: true, ...}))
|
|
14
|
+
*
|
|
15
|
+
* That is enough to put AppShell below its breakpoint — and it silently also
|
|
16
|
+
* matches `prefers-reduced-motion`, rerouting any code that branches on motion.
|
|
17
|
+
* MobileNav's close delay is capped at 0 under reduced motion, so a suite using
|
|
18
|
+
* the blanket stub runs against an immediate close while its test names claim
|
|
19
|
+
* it is exercising the real delay. The failure mode is invisible: every test
|
|
20
|
+
* still passes.
|
|
21
|
+
*
|
|
22
|
+
* So the two answers are separate knobs here. `reduceMotion` answers
|
|
23
|
+
* `prefers-reduced-motion` only; `matches` answers everything else (breakpoint
|
|
24
|
+
* width queries, which tests below a breakpoint want to be true).
|
|
25
|
+
*
|
|
26
|
+
* SYNC: When modified, update this header.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {vi} from 'vitest';
|
|
30
|
+
|
|
31
|
+
export interface StubMatchMediaOptions {
|
|
32
|
+
/** Whether the user prefers reduced motion. */
|
|
33
|
+
reduceMotion?: boolean;
|
|
34
|
+
/** Answer for every other query — width breakpoints, hover, etc. */
|
|
35
|
+
matches?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Answer a `prefers-reduced-motion` query, or null if it isn't one.
|
|
40
|
+
*
|
|
41
|
+
* The preference has two spellings and they are opposites, so a single
|
|
42
|
+
* `includes('prefers-reduced-motion')` would answer `no-preference` with the
|
|
43
|
+
* value meant for `reduce` — the same answer-without-reading mistake this
|
|
44
|
+
* helper exists to prevent. Core only queries `reduce` today (MobileNav,
|
|
45
|
+
* Carousel); handling both is what keeps that true if one changes.
|
|
46
|
+
*/
|
|
47
|
+
function answerReducedMotion(
|
|
48
|
+
query: string,
|
|
49
|
+
reduceMotion: boolean,
|
|
50
|
+
): boolean | null {
|
|
51
|
+
if (query.includes('prefers-reduced-motion: reduce')) {
|
|
52
|
+
return reduceMotion;
|
|
53
|
+
}
|
|
54
|
+
if (query.includes('prefers-reduced-motion: no-preference')) {
|
|
55
|
+
return !reduceMotion;
|
|
56
|
+
}
|
|
57
|
+
// Bare `(prefers-reduced-motion)` is true for any value other than
|
|
58
|
+
// no-preference, i.e. the same as `reduce`.
|
|
59
|
+
if (query.includes('prefers-reduced-motion')) {
|
|
60
|
+
return reduceMotion;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Install a `window.matchMedia` stub whose answer depends on the query.
|
|
67
|
+
*
|
|
68
|
+
* Call from `beforeEach` (or inside a test that needs different motion
|
|
69
|
+
* settings) and pair with `vi.unstubAllGlobals()` in `afterEach`.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```
|
|
73
|
+
* beforeEach(() => {
|
|
74
|
+
* stubMatchMedia({reduceMotion: false});
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function stubMatchMedia({
|
|
79
|
+
reduceMotion = false,
|
|
80
|
+
matches = true,
|
|
81
|
+
}: StubMatchMediaOptions = {}): void {
|
|
82
|
+
vi.stubGlobal(
|
|
83
|
+
'matchMedia',
|
|
84
|
+
vi.fn((query: string) => ({
|
|
85
|
+
matches: answerReducedMotion(query, reduceMotion) ?? matches,
|
|
86
|
+
media: query,
|
|
87
|
+
onchange: null,
|
|
88
|
+
addEventListener: vi.fn(),
|
|
89
|
+
removeEventListener: vi.fn(),
|
|
90
|
+
addListener: vi.fn(),
|
|
91
|
+
removeListener: vi.fn(),
|
|
92
|
+
dispatchEvent: vi.fn(),
|
|
93
|
+
})),
|
|
94
|
+
);
|
|
95
|
+
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -11,13 +11,17 @@
|
|
|
11
11
|
* SYNC: When modified, update this header
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export {
|
|
15
|
-
hasActiveFocusTrapEscape,
|
|
16
|
-
isImeKeyEvent,
|
|
17
|
-
useFocusTrap,
|
|
18
|
-
} from './useFocusTrap';
|
|
14
|
+
export {hasActiveFocusTrapEscape, useFocusTrap} from './useFocusTrap';
|
|
19
15
|
export type {UseFocusTrapOptions, UseFocusTrapReturn} from './useFocusTrap';
|
|
20
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Import from `@astryxdesign/core/utils` instead — this is a pure
|
|
19
|
+
* predicate, not a hook, and the `hooks` barrel is a `'use client'` boundary.
|
|
20
|
+
* Re-exported here for one release so consumers can move; will be removed in
|
|
21
|
+
* an upcoming major.
|
|
22
|
+
*/
|
|
23
|
+
export {isImeKeyEvent} from '../utils/ime';
|
|
24
|
+
|
|
21
25
|
export {useAnnounce} from './useAnnounce';
|
|
22
26
|
export type {AnnounceFn, AnnouncePoliteness} from './useAnnounce';
|
|
23
27
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import {useCallback, useEffect, useRef} from 'react';
|
|
20
20
|
|
|
21
21
|
import {FOCUSABLE_SELECTOR} from './focusableSelector';
|
|
22
|
+
import {isImeKeyEvent} from '../utils/ime';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Module-level stack of active focus-trap Escape handlers.
|
|
@@ -94,20 +95,6 @@ export function hasActiveFocusTrapEscape(): boolean {
|
|
|
94
95
|
return escapeStack.length > 0;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
/**
|
|
98
|
-
* Whether an Escape keydown should be ignored because it is cancelling an
|
|
99
|
-
* in-progress IME composition. CJK/IME users press Escape to cancel
|
|
100
|
-
* composition; that must not close the surrounding overlay. `keyCode === 229`
|
|
101
|
-
* covers browsers that fire keydown before `isComposing` is set. Exported so
|
|
102
|
-
* other overlays (Dialog, Drawer, CommandPalette) share one definition.
|
|
103
|
-
*/
|
|
104
|
-
export function isImeKeyEvent(event: {
|
|
105
|
-
isComposing?: boolean;
|
|
106
|
-
keyCode?: number;
|
|
107
|
-
}): boolean {
|
|
108
|
-
return event.isComposing === true || event.keyCode === 229;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
98
|
/**
|
|
112
99
|
* Whether an element is currently perceivable/focusable — excludes ones hidden
|
|
113
100
|
* via `display:none`/`visibility:hidden` or inside an `inert`/`hidden` subtree,
|