@astryxdesign/core 0.6.0 → 0.6.1
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 +41 -3
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
- package/dist/Center/Center.d.ts +23 -16
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +7 -5
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +12 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +1 -0
- package/dist/Field/InputClearButton.d.ts +2 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +16 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +12 -1
- package/dist/HoverCard/useHoverCard.js +2 -2
- package/dist/Indicator/CheckboxIndicator.js +2 -2
- package/dist/Indicator/RadioIndicator.js +2 -2
- package/dist/Layer/layerStack.d.ts +10 -0
- package/dist/Layer/layerStack.d.ts.map +1 -1
- package/dist/Layer/layerStack.js +21 -9
- package/dist/Layer/useLayerDismissal.d.ts +2 -3
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
- package/dist/Layer/useLayerDismissal.js +2 -3
- package/dist/NavIcon/NavIcon.js +2 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +12 -2
- package/dist/Popover/usePopover.d.ts +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +4 -2
- package/dist/ProgressBar/ProgressBar.js +2 -2
- package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
- package/dist/ScrollableArea/ScrollableArea.js +144 -0
- package/dist/ScrollableArea/index.d.ts +11 -0
- package/dist/ScrollableArea/index.d.ts.map +1 -0
- package/dist/ScrollableArea/index.js +11 -0
- package/dist/StatusDot/StatusDot.js +2 -2
- package/dist/TextArea/TextArea.js +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +19 -4
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +12 -2
- package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +56 -20
- package/dist/astryx.css +15 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scrollGeometry.d.ts +24 -0
- package/dist/hooks/scrollGeometry.d.ts.map +1 -0
- package/dist/hooks/scrollGeometry.js +86 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
- package/dist/hooks/scrollOwnerRegistry.js +24 -0
- package/dist/hooks/useFocusTrap.d.ts +8 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +22 -11
- package/dist/hooks/useScrollableArea.d.ts +51 -0
- package/dist/hooks/useScrollableArea.d.ts.map +1 -0
- package/dist/hooks/useScrollableArea.js +287 -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 +2 -6
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/derivedVarRegistry.js +1 -1
- package/dist/theme/localTokens.d.ts +8 -11
- package/dist/theme/localTokens.d.ts.map +1 -1
- package/dist/theme/localTokens.js +17 -71
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +4 -4
- package/dist/utils/themeProps.d.ts +10 -10
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +27 -10
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +7 -2
- package/src/AppShell/AppShell.test.tsx +36 -0
- package/src/AppShell/AppShell.tsx +4 -1
- package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +3 -1
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
- package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
- package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
- package/src/Center/Center.doc.mjs +32 -28
- package/src/Center/Center.spec.md +225 -0
- package/src/Center/Center.test.tsx +42 -4
- package/src/Center/Center.tsx +24 -17
- package/src/Chat/ChatSystemMessage.test.tsx +2 -9
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +4 -4
- package/src/DateInput/DateInput.tsx +15 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
- package/src/DateTimeInput/DateTimeInput.tsx +18 -7
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
- package/src/Field/Field.test.tsx +42 -0
- package/src/Field/Field.tsx +6 -0
- package/src/Field/InputClearButton.test.tsx +35 -1
- package/src/Field/InputClearButton.tsx +7 -3
- package/src/Field/PanelSearchInput.tsx +21 -8
- package/src/FieldStatus/FieldStatus.spec.md +27 -17
- package/src/FieldStatus/FieldStatus.test.tsx +7 -5
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
- package/src/FileInput/FileInput.tsx +10 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +4 -4
- package/src/HoverCard/useHoverCard.tsx +2 -2
- package/src/Indicator/CheckboxIndicator.tsx +2 -2
- package/src/Indicator/Indicator.doc.mjs +2 -2
- package/src/Indicator/Indicator.test.tsx +1 -1
- package/src/Indicator/RadioIndicator.tsx +2 -2
- package/src/Layer/layerStack.ts +20 -9
- package/src/Layer/useLayerDismissal.ts +2 -3
- package/src/MultiSelector/MultiSelector.test.tsx +4 -4
- package/src/NavIcon/NavIcon.doc.mjs +4 -4
- package/src/NavIcon/NavIcon.tsx +2 -2
- package/src/NumberInput/NumberInput.tsx +18 -7
- package/src/Popover/Popover.doc.mjs +10 -10
- package/src/Popover/Popover.spec.md +55 -65
- package/src/Popover/Popover.test.tsx +29 -0
- package/src/Popover/usePopover.doc.mjs +4 -4
- package/src/Popover/usePopover.tsx +7 -4
- package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
- package/src/ProgressBar/ProgressBar.test.tsx +1 -31
- package/src/ProgressBar/ProgressBar.tsx +2 -2
- package/src/RadioList/RadioList.test.tsx +5 -144
- package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
- package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
- package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
- package/src/ScrollableArea/ScrollableArea.tsx +259 -0
- package/src/ScrollableArea/index.ts +26 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
- package/src/Selector/Selector.test.tsx +4 -4
- package/src/Spinner/Spinner.test.tsx +0 -18
- package/src/StatusDot/StatusDot.doc.mjs +4 -4
- package/src/StatusDot/StatusDot.tsx +2 -2
- package/src/TabList/TabList.test.tsx +5 -9
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
- package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
- package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
- package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
- package/src/Table/Table.doc.mjs +2 -2
- package/src/TextArea/TextArea.doc.mjs +4 -4
- package/src/TextArea/TextArea.tsx +2 -2
- package/src/TextInput/TextInput.doc.mjs +2 -1
- package/src/TextInput/TextInput.test.tsx +94 -0
- package/src/TextInput/TextInput.tsx +22 -6
- package/src/TimeInput/TimeInput.tsx +18 -7
- package/src/Toast/ToastViewport.test.tsx +1 -39
- package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
- package/src/Typeahead/BaseTypeahead.spec.md +269 -0
- package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
- package/src/Typeahead/BaseTypeahead.tsx +99 -30
- package/src/hooks/index.ts +13 -0
- package/src/hooks/scrollGeometry.ts +155 -0
- package/src/hooks/scrollOwnerRegistry.ts +47 -0
- package/src/hooks/useFocusTrap.ts +22 -11
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
- package/src/hooks/useScrollableArea.doc.mjs +108 -0
- package/src/hooks/useScrollableArea.test.tsx +437 -0
- package/src/hooks/useScrollableArea.ts +469 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +65 -105
- package/src/theme/defineTheme.ts +3 -9
- package/src/theme/derivedVarRegistry.ts +1 -1
- package/src/theme/localTokens.ts +25 -96
- package/src/theme/publicThemeHelperContract.test.ts +2 -2
- package/src/theme/themeAdaptations.test.ts +16 -42
- package/src/theme/themeAdaptations.ts +6 -9
- package/src/utils/themeProps.test.ts +29 -10
- package/src/utils/themeProps.ts +36 -17
|
@@ -560,6 +560,51 @@ describe('TextInput', () => {
|
|
|
560
560
|
expect(handleEnter).toHaveBeenCalledTimes(1);
|
|
561
561
|
});
|
|
562
562
|
|
|
563
|
+
it('does not call onEnter when Enter commits an IME conversion', () => {
|
|
564
|
+
// The browser fires this composing keydown for the Enter that commits an
|
|
565
|
+
// IME candidate (isComposing: true, or the legacy keyCode 229) before
|
|
566
|
+
// compositionend writes the conversion. It must NOT trigger onEnter —
|
|
567
|
+
// submit/save actions would fire before the user intends to submit.
|
|
568
|
+
// See utils/ime.ts and #6082.
|
|
569
|
+
const handleEnter = vi.fn();
|
|
570
|
+
const {container} = render(
|
|
571
|
+
<TextInput
|
|
572
|
+
label="Name"
|
|
573
|
+
value="にほんご"
|
|
574
|
+
onChange={() => {}}
|
|
575
|
+
onEnter={handleEnter}
|
|
576
|
+
/>,
|
|
577
|
+
);
|
|
578
|
+
const input = container.querySelector('input')!;
|
|
579
|
+
fireEvent.keyDown(input, {key: 'Enter', isComposing: true});
|
|
580
|
+
expect(handleEnter).not.toHaveBeenCalled();
|
|
581
|
+
fireEvent.keyDown(input, {key: 'Enter', keyCode: 229});
|
|
582
|
+
expect(handleEnter).not.toHaveBeenCalled();
|
|
583
|
+
|
|
584
|
+
// A real, non-composing Enter after composition ends still submits.
|
|
585
|
+
fireEvent.keyDown(input, {key: 'Enter'});
|
|
586
|
+
expect(handleEnter).toHaveBeenCalledTimes(1);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('still calls onKeyDown for composing keydowns', () => {
|
|
590
|
+
// onKeyDown is the raw escape hatch: it keeps receiving IME keydowns so
|
|
591
|
+
// consumers with custom composition handling are unaffected by the
|
|
592
|
+
// onEnter guard.
|
|
593
|
+
const handleKeyDown = vi.fn();
|
|
594
|
+
const {container} = render(
|
|
595
|
+
<TextInput
|
|
596
|
+
label="Name"
|
|
597
|
+
value="にほんご"
|
|
598
|
+
onChange={() => {}}
|
|
599
|
+
onKeyDown={handleKeyDown}
|
|
600
|
+
onEnter={() => {}}
|
|
601
|
+
/>,
|
|
602
|
+
);
|
|
603
|
+
const input = container.querySelector('input')!;
|
|
604
|
+
fireEvent.keyDown(input, {key: 'Enter', isComposing: true});
|
|
605
|
+
expect(handleKeyDown).toHaveBeenCalledTimes(1);
|
|
606
|
+
});
|
|
607
|
+
|
|
563
608
|
it('does not call onEnter for other keys', async () => {
|
|
564
609
|
const user = userEvent.setup();
|
|
565
610
|
const handleEnter = vi.fn();
|
|
@@ -1019,3 +1064,52 @@ describe('TextInput readonly theme state', () => {
|
|
|
1019
1064
|
expect(root).not.toHaveAttribute('data-readonly');
|
|
1020
1065
|
});
|
|
1021
1066
|
});
|
|
1067
|
+
|
|
1068
|
+
describe('TextInput clear button focus behavior', () => {
|
|
1069
|
+
it('synchronously restores focus to the input on keyboard activation (detail === 0)', () => {
|
|
1070
|
+
const handleChange = vi.fn();
|
|
1071
|
+
render(
|
|
1072
|
+
<TextInput
|
|
1073
|
+
label="Search"
|
|
1074
|
+
value="test"
|
|
1075
|
+
hasClear
|
|
1076
|
+
onChange={handleChange}
|
|
1077
|
+
/>,
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
const input = screen.getByRole('textbox');
|
|
1081
|
+
const clearButton = screen.getByRole('button', {name: /clear/i});
|
|
1082
|
+
|
|
1083
|
+
clearButton.focus();
|
|
1084
|
+
expect(document.activeElement).toBe(clearButton);
|
|
1085
|
+
|
|
1086
|
+
// Keyboard activation (e.g. Enter / Space on focused button yields detail 0)
|
|
1087
|
+
fireEvent.click(clearButton, {detail: 0});
|
|
1088
|
+
|
|
1089
|
+
expect(handleChange).toHaveBeenCalledWith('', expect.any(Object));
|
|
1090
|
+
// Must be synchronously focused without waiting for animation frames
|
|
1091
|
+
expect(document.activeElement).toBe(input);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
it('defers focus restoration via requestAnimationFrame on pointer activation', () => {
|
|
1095
|
+
const handleChange = vi.fn();
|
|
1096
|
+
const rafSpy = vi.spyOn(window, 'requestAnimationFrame');
|
|
1097
|
+
render(
|
|
1098
|
+
<TextInput
|
|
1099
|
+
label="Search"
|
|
1100
|
+
value="test"
|
|
1101
|
+
hasClear
|
|
1102
|
+
onChange={handleChange}
|
|
1103
|
+
/>,
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
const clearButton = screen.getByRole('button', {name: /clear/i});
|
|
1107
|
+
|
|
1108
|
+
// Pointer activation (detail > 0)
|
|
1109
|
+
fireEvent.click(clearButton, {detail: 1});
|
|
1110
|
+
|
|
1111
|
+
expect(handleChange).toHaveBeenCalledWith('', expect.any(Object));
|
|
1112
|
+
expect(rafSpy).toHaveBeenCalled();
|
|
1113
|
+
rafSpy.mockRestore();
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
@@ -47,7 +47,7 @@ import {renderIconSlot, type IconType} from '../Icon';
|
|
|
47
47
|
import {Spinner} from '../Spinner';
|
|
48
48
|
import {useTooltip} from '../Tooltip';
|
|
49
49
|
import {VisuallyHidden} from '../VisuallyHidden';
|
|
50
|
-
import {getInputARIA} from '../utils';
|
|
50
|
+
import {getInputARIA, isImeKeyEvent} from '../utils';
|
|
51
51
|
|
|
52
52
|
const styles = stylex.create({
|
|
53
53
|
input: {
|
|
@@ -381,10 +381,21 @@ export function TextInput({
|
|
|
381
381
|
};
|
|
382
382
|
|
|
383
383
|
// Handle clear button click
|
|
384
|
-
const handleClear = useCallback(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
384
|
+
const handleClear = useCallback(
|
|
385
|
+
(e?: React.MouseEvent<HTMLButtonElement>) => {
|
|
386
|
+
onChange?.('', null as unknown as ChangeEvent<HTMLInputElement>);
|
|
387
|
+
if (!e || e.detail === 0) {
|
|
388
|
+
inputRef.current?.focus();
|
|
389
|
+
} else {
|
|
390
|
+
// Defer focus restoration past the button's unmount task so iOS Safari
|
|
391
|
+
// and touch browsers don't jump the page scroll to 0 on tap.
|
|
392
|
+
requestAnimationFrame(() => {
|
|
393
|
+
inputRef.current?.focus({preventScroll: true});
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
[onChange],
|
|
398
|
+
);
|
|
388
399
|
|
|
389
400
|
// Focus input when clicking anywhere on the wrapper (icons, padding, etc.)
|
|
390
401
|
const {onClick: handleWrapperClick, onMouseUp: handleWrapperMouseUp} =
|
|
@@ -438,7 +449,12 @@ export function TextInput({
|
|
|
438
449
|
onKeyDown={
|
|
439
450
|
onEnter || onKeyDown
|
|
440
451
|
? e => {
|
|
441
|
-
|
|
452
|
+
// The composing keydown fires before compositionend, so without
|
|
453
|
+
// this guard pressing Enter to commit a Japanese/Chinese/Korean
|
|
454
|
+
// IME conversion would trigger onEnter (submit/save actions)
|
|
455
|
+
// before the user intends to submit. onKeyDown still receives
|
|
456
|
+
// the raw event. See utils/ime.ts (#6082).
|
|
457
|
+
if (e.key === 'Enter' && !isImeKeyEvent(e.nativeEvent)) {
|
|
442
458
|
onEnter?.();
|
|
443
459
|
}
|
|
444
460
|
onKeyDown?.(e);
|
|
@@ -622,13 +622,24 @@ export function TimeInput({
|
|
|
622
622
|
);
|
|
623
623
|
|
|
624
624
|
// Handle clear button click
|
|
625
|
-
const handleClear = useCallback(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
625
|
+
const handleClear = useCallback(
|
|
626
|
+
(e?: React.MouseEvent<HTMLButtonElement>) => {
|
|
627
|
+
fireChange(undefined);
|
|
628
|
+
// Focusing a native time control reopens the OS picker on iOS.
|
|
629
|
+
if (!usesNativeTimePicker) {
|
|
630
|
+
if (!e || e.detail === 0) {
|
|
631
|
+
inputRef.current?.focus();
|
|
632
|
+
} else {
|
|
633
|
+
// Defer focus restoration past the button's unmount task so iOS Safari
|
|
634
|
+
// and touch browsers don't jump the page scroll to 0 on tap.
|
|
635
|
+
requestAnimationFrame(() => {
|
|
636
|
+
inputRef.current?.focus({preventScroll: true});
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
[fireChange, usesNativeTimePicker],
|
|
642
|
+
);
|
|
632
643
|
|
|
633
644
|
// Focus input when clicking anywhere on the wrapper (icons, padding, etc.)
|
|
634
645
|
const {onClick: handleWrapperClick, onMouseUp: handleWrapperMouseUp} =
|
|
@@ -34,7 +34,7 @@ import React from 'react';
|
|
|
34
34
|
import {readFileSync} from 'node:fs';
|
|
35
35
|
import {type AnnounceFn, __resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
36
36
|
import {Button} from '../Button';
|
|
37
|
-
import
|
|
37
|
+
import type {ToastProps} from './Toast';
|
|
38
38
|
import {ToastViewport} from './ToastViewport';
|
|
39
39
|
import {useToast} from './useToast';
|
|
40
40
|
import type {ToastOptions} from './types';
|
|
@@ -1720,44 +1720,6 @@ describe('Toast swipe dismissal', () => {
|
|
|
1720
1720
|
});
|
|
1721
1721
|
});
|
|
1722
1722
|
|
|
1723
|
-
describe('Toast live-region fallback semantics', () => {
|
|
1724
|
-
it('keeps standalone info Toast content in a polite status region', () => {
|
|
1725
|
-
render(
|
|
1726
|
-
<Toast
|
|
1727
|
-
type="info"
|
|
1728
|
-
body="Saved"
|
|
1729
|
-
isAutoHide={false}
|
|
1730
|
-
autoHideDuration={5000}
|
|
1731
|
-
onDismiss={() => {}}
|
|
1732
|
-
/>,
|
|
1733
|
-
);
|
|
1734
|
-
|
|
1735
|
-
const visualToast = screen.getByText('Saved').closest('[data-type]');
|
|
1736
|
-
expect(visualToast).toHaveAttribute('role', 'status');
|
|
1737
|
-
expect(visualToast).toHaveAttribute('aria-live', 'polite');
|
|
1738
|
-
expect(visualToast).toHaveAttribute('aria-atomic', 'true');
|
|
1739
|
-
});
|
|
1740
|
-
|
|
1741
|
-
it('keeps standalone error Toast content in an assertive alert region', () => {
|
|
1742
|
-
render(
|
|
1743
|
-
<Toast
|
|
1744
|
-
type="error"
|
|
1745
|
-
body="Upload failed"
|
|
1746
|
-
isAutoHide={false}
|
|
1747
|
-
autoHideDuration={5000}
|
|
1748
|
-
onDismiss={() => {}}
|
|
1749
|
-
/>,
|
|
1750
|
-
);
|
|
1751
|
-
|
|
1752
|
-
const visualToast = screen
|
|
1753
|
-
.getByText('Upload failed')
|
|
1754
|
-
.closest('[data-type]');
|
|
1755
|
-
expect(visualToast).toHaveAttribute('role', 'alert');
|
|
1756
|
-
expect(visualToast).toHaveAttribute('aria-live', 'assertive');
|
|
1757
|
-
expect(visualToast).toHaveAttribute('aria-atomic', 'true');
|
|
1758
|
-
});
|
|
1759
|
-
});
|
|
1760
|
-
|
|
1761
1723
|
describe('ToastViewport region ARIA', () => {
|
|
1762
1724
|
it('does not expose an empty notifications landmark', () => {
|
|
1763
1725
|
renderViewport(<ShowToastButton />);
|
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
|
|
4
|
+
const anatomy = [
|
|
5
|
+
{
|
|
6
|
+
name: 'Input',
|
|
7
|
+
required: true,
|
|
8
|
+
description:
|
|
9
|
+
'Bare combobox input. The caller supplies its visible field chrome and accessible name.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Loading status',
|
|
13
|
+
required: false,
|
|
14
|
+
description:
|
|
15
|
+
'Named Spinner shown beside the input while an asynchronous source is pending, unless a composed owner takes over the busy indicator lane.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Dropdown',
|
|
19
|
+
required: false,
|
|
20
|
+
description:
|
|
21
|
+
'Anchored listbox surface containing current search or bootstrap results.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Empty state',
|
|
25
|
+
required: false,
|
|
26
|
+
description:
|
|
27
|
+
'Disabled listbox option shown after a completed search returns no results.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Result group heading',
|
|
31
|
+
required: false,
|
|
32
|
+
description: 'Visible label for a group of result options.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Result row',
|
|
36
|
+
required: false,
|
|
37
|
+
description:
|
|
38
|
+
'Option wrapper that owns highlight, selection, pointer, and keyboard behavior.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Default item content',
|
|
42
|
+
required: false,
|
|
43
|
+
description:
|
|
44
|
+
'TypeaheadItem label and optional supporting content rendered inside a result row.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Caller-rendered item content',
|
|
48
|
+
required: false,
|
|
49
|
+
description:
|
|
50
|
+
'Caller content supplied through renderItem or item.element inside the stable result row.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Selected result state',
|
|
54
|
+
required: false,
|
|
55
|
+
description:
|
|
56
|
+
'Selected row weight and trailing check shown when a result matches value.',
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
3
60
|
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
61
|
|
|
5
62
|
export const docs = {
|
|
@@ -7,15 +64,43 @@ export const docs = {
|
|
|
7
64
|
subComponentOf: 'Typeahead',
|
|
8
65
|
displayName: 'Base Typeahead',
|
|
9
66
|
isHiddenFromOverview: true,
|
|
10
|
-
description:
|
|
67
|
+
description:
|
|
68
|
+
'Composable combobox engine providing a bare input, search, keyboard navigation, and a styled result dropdown. It renders no input wrapper, border, or selected-value token. Typeahead and Tokenizer compose it for standard fields.',
|
|
11
69
|
usage: {
|
|
12
|
-
|
|
70
|
+
anatomy,
|
|
71
|
+
description:
|
|
72
|
+
'Composable combobox engine providing a bare input, search, keyboard navigation, and a styled result dropdown. It renders no input wrapper, border, or selected-value token. Typeahead and Tokenizer compose it for standard fields.',
|
|
13
73
|
bestPractices: [
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
74
|
+
{
|
|
75
|
+
guidance: true,
|
|
76
|
+
description:
|
|
77
|
+
'Use Typeahead or Tokenizer for standard fields; they wrap BaseTypeahead with input chrome and selected-value rendering it intentionally omits.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
guidance: true,
|
|
81
|
+
description:
|
|
82
|
+
'Provide your own visible label or aria-label and custom input wrapper so the bare combobox has an accessible name, focus treatment, border, and layout.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
guidance: true,
|
|
86
|
+
description:
|
|
87
|
+
'Pass anchorRef pointing to your wrapper so the dropdown positions against your custom input chrome, not just the bare input element.',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
guidance: false,
|
|
91
|
+
description:
|
|
92
|
+
'Expect input chrome or selected-value rendering. BaseTypeahead is an engine; the caller owns those visible parts.',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
guidance: false,
|
|
96
|
+
description:
|
|
97
|
+
'Use BaseTypeahead when Typeahead or Tokenizer would suffice; the extra wrapper and styling work is only justified for truly custom compositions.',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
guidance: false,
|
|
101
|
+
description:
|
|
102
|
+
'Treat Escape as cancellation of pending source work. It hides the current popup, but a late response can reopen it.',
|
|
103
|
+
},
|
|
19
104
|
],
|
|
20
105
|
},
|
|
21
106
|
props: [
|
|
@@ -46,7 +131,7 @@ export const docs = {
|
|
|
46
131
|
name: 'placeholder',
|
|
47
132
|
type: 'string',
|
|
48
133
|
description: 'Input placeholder text.',
|
|
49
|
-
default: "'Search
|
|
134
|
+
default: "'Search…'",
|
|
50
135
|
},
|
|
51
136
|
{
|
|
52
137
|
name: 'hasEntriesOnFocus',
|
|
@@ -63,12 +148,14 @@ export const docs = {
|
|
|
63
148
|
{
|
|
64
149
|
name: 'menuWidth',
|
|
65
150
|
type: 'number',
|
|
66
|
-
description:
|
|
151
|
+
description:
|
|
152
|
+
'Requested dropdown width in pixels before viewport clamping.',
|
|
67
153
|
},
|
|
68
154
|
{
|
|
69
155
|
name: 'minQueryLength',
|
|
70
156
|
type: 'number',
|
|
71
|
-
description:
|
|
157
|
+
description:
|
|
158
|
+
'Minimum query length before the search source is queried. Below it no search runs and the menu stays closed.',
|
|
72
159
|
default: '1',
|
|
73
160
|
},
|
|
74
161
|
{
|
|
@@ -83,6 +170,13 @@ export const docs = {
|
|
|
83
170
|
description: 'Whether the input is disabled.',
|
|
84
171
|
default: 'false',
|
|
85
172
|
},
|
|
173
|
+
{
|
|
174
|
+
name: 'isFocusableDisabled',
|
|
175
|
+
type: 'boolean',
|
|
176
|
+
description:
|
|
177
|
+
'Keep a disabled input focusable with aria-disabled and readOnly so a caller-owned disabled reason remains discoverable. It blocks text entry, but when applied after results are already open, Enter can still select the highlighted option.',
|
|
178
|
+
default: 'false',
|
|
179
|
+
},
|
|
86
180
|
{
|
|
87
181
|
name: 'hasAutoFocus',
|
|
88
182
|
type: 'boolean',
|
|
@@ -92,23 +186,44 @@ export const docs = {
|
|
|
92
186
|
{
|
|
93
187
|
name: 'debounceMs',
|
|
94
188
|
type: 'number',
|
|
95
|
-
description:
|
|
189
|
+
description:
|
|
190
|
+
'Debounce delay in ms before triggering search. Set to 0 for synchronous sources.',
|
|
96
191
|
default: '150',
|
|
97
192
|
},
|
|
193
|
+
{
|
|
194
|
+
name: 'size',
|
|
195
|
+
type: "'sm' | 'md' | 'lg'",
|
|
196
|
+
description: 'Size used to scale dropdown option padding.',
|
|
197
|
+
default: "'md'",
|
|
198
|
+
},
|
|
98
199
|
{
|
|
99
200
|
name: 'anchorRef',
|
|
100
201
|
type: 'RefObject<HTMLElement | null>',
|
|
101
|
-
description:
|
|
202
|
+
description:
|
|
203
|
+
'Ref to the anchor element for dropdown positioning. If not provided, the input itself is used.',
|
|
102
204
|
},
|
|
103
205
|
{
|
|
104
206
|
name: 'inputXStyle',
|
|
105
207
|
type: 'StyleXStyles',
|
|
106
208
|
description: 'Additional StyleX styles for the input element.',
|
|
107
209
|
},
|
|
210
|
+
{
|
|
211
|
+
name: 'xstyle',
|
|
212
|
+
type: 'StyleXStyles',
|
|
213
|
+
description:
|
|
214
|
+
'Standard BaseProps StyleX styles applied to the input. Must be a stylex.create() value, not an inline style object.',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: 'inputTabIndex',
|
|
218
|
+
type: 'number',
|
|
219
|
+
description:
|
|
220
|
+
'Legacy input-specific alias for native tabIndex. When provided, it takes precedence; otherwise native tabIndex is preserved.',
|
|
221
|
+
},
|
|
108
222
|
{
|
|
109
223
|
name: 'onKeyDown',
|
|
110
224
|
type: '(e: React.KeyboardEvent<HTMLInputElement>) => void',
|
|
111
|
-
description:
|
|
225
|
+
description:
|
|
226
|
+
'Additional keydown handler called before internal keyboard navigation. Call e.preventDefault() to skip internal handling.',
|
|
112
227
|
},
|
|
113
228
|
{
|
|
114
229
|
name: 'onChangeQuery',
|
|
@@ -123,12 +238,20 @@ export const docs = {
|
|
|
123
238
|
{
|
|
124
239
|
name: 'inputId',
|
|
125
240
|
type: 'string',
|
|
126
|
-
description:
|
|
241
|
+
description:
|
|
242
|
+
'Legacy input-specific alias for native id. When provided, it takes precedence; otherwise native id is preserved.',
|
|
127
243
|
},
|
|
128
244
|
{
|
|
129
245
|
name: 'ariaDescribedBy',
|
|
130
246
|
type: 'string',
|
|
131
|
-
description:
|
|
247
|
+
description:
|
|
248
|
+
'Legacy input-specific alias for native aria-describedby. When provided, it takes precedence; otherwise the native attribute is preserved.',
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'ariaLabelledBy',
|
|
252
|
+
type: 'string',
|
|
253
|
+
description:
|
|
254
|
+
'Legacy input-specific alias for native aria-labelledby. When provided, it takes precedence; otherwise the native attribute is preserved.',
|
|
132
255
|
},
|
|
133
256
|
],
|
|
134
257
|
};
|
|
@@ -137,7 +260,8 @@ export const docsZh = {
|
|
|
137
260
|
name: 'BaseTypeahead',
|
|
138
261
|
isHiddenFromOverview: true,
|
|
139
262
|
displayName: 'Base Typeahead',
|
|
140
|
-
description:
|
|
263
|
+
description:
|
|
264
|
+
'可组合的组合框引擎,提供裸输入框、搜索、键盘导航和带样式的结果下拉列表。它不渲染输入框包装、边框或已选值标记;Typeahead 和 Tokenizer 将其组合成标准字段。',
|
|
141
265
|
props: [
|
|
142
266
|
{
|
|
143
267
|
name: 'searchSource',
|
|
@@ -166,7 +290,7 @@ export const docsZh = {
|
|
|
166
290
|
name: 'placeholder',
|
|
167
291
|
type: 'string',
|
|
168
292
|
description: '输入框占位文本。',
|
|
169
|
-
default: "'Search
|
|
293
|
+
default: "'Search…'",
|
|
170
294
|
},
|
|
171
295
|
{
|
|
172
296
|
name: 'hasEntriesOnFocus',
|
|
@@ -183,12 +307,13 @@ export const docsZh = {
|
|
|
183
307
|
{
|
|
184
308
|
name: 'menuWidth',
|
|
185
309
|
type: 'number',
|
|
186
|
-
description: '
|
|
310
|
+
description: '视口限制前请求的下拉菜单像素宽度。',
|
|
187
311
|
},
|
|
188
312
|
{
|
|
189
313
|
name: 'minQueryLength',
|
|
190
314
|
type: 'number',
|
|
191
|
-
description:
|
|
315
|
+
description:
|
|
316
|
+
'查询搜索源前的最小查询长度。低于该长度不会发起搜索,菜单保持关闭。',
|
|
192
317
|
default: '1',
|
|
193
318
|
},
|
|
194
319
|
{
|
|
@@ -203,6 +328,13 @@ export const docsZh = {
|
|
|
203
328
|
description: '输入框是否被禁用。',
|
|
204
329
|
default: 'false',
|
|
205
330
|
},
|
|
331
|
+
{
|
|
332
|
+
name: 'isFocusableDisabled',
|
|
333
|
+
type: 'boolean',
|
|
334
|
+
description:
|
|
335
|
+
'使用 aria-disabled 和只读状态保持禁用输入框可聚焦,以便访问调用方提供的禁用原因。它会阻止文本输入,但如果结果已打开,按 Enter 仍可选择高亮选项。',
|
|
336
|
+
default: 'false',
|
|
337
|
+
},
|
|
206
338
|
{
|
|
207
339
|
name: 'hasAutoFocus',
|
|
208
340
|
type: 'boolean',
|
|
@@ -215,6 +347,12 @@ export const docsZh = {
|
|
|
215
347
|
description: '触发搜索前的防抖延迟(毫秒)。同步数据源设置为 0。',
|
|
216
348
|
default: '150',
|
|
217
349
|
},
|
|
350
|
+
{
|
|
351
|
+
name: 'size',
|
|
352
|
+
type: "'sm' | 'md' | 'lg'",
|
|
353
|
+
description: '用于调整下拉选项内边距的尺寸。',
|
|
354
|
+
default: "'md'",
|
|
355
|
+
},
|
|
218
356
|
{
|
|
219
357
|
name: 'anchorRef',
|
|
220
358
|
type: 'RefObject<HTMLElement | null>',
|
|
@@ -225,10 +363,22 @@ export const docsZh = {
|
|
|
225
363
|
type: 'StyleXStyles',
|
|
226
364
|
description: '输入元素的附加 StyleX 样式。',
|
|
227
365
|
},
|
|
366
|
+
{
|
|
367
|
+
name: 'xstyle',
|
|
368
|
+
type: 'StyleXStyles',
|
|
369
|
+
description: '应用于输入元素的标准 BaseProps StyleX 样式。',
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'inputTabIndex',
|
|
373
|
+
type: 'number',
|
|
374
|
+
description:
|
|
375
|
+
'原生 tabIndex 的旧输入专用别名。提供时优先;未提供时保留原生属性。',
|
|
376
|
+
},
|
|
228
377
|
{
|
|
229
378
|
name: 'onKeyDown',
|
|
230
379
|
type: '(e: React.KeyboardEvent<HTMLInputElement>) => void',
|
|
231
|
-
description:
|
|
380
|
+
description:
|
|
381
|
+
'在内部键盘导航之前调用的附加 keydown 处理函数。调用 e.preventDefault() 可跳过内部处理。',
|
|
232
382
|
},
|
|
233
383
|
{
|
|
234
384
|
name: 'onChangeQuery',
|
|
@@ -243,12 +393,20 @@ export const docsZh = {
|
|
|
243
393
|
{
|
|
244
394
|
name: 'inputId',
|
|
245
395
|
type: 'string',
|
|
246
|
-
description:
|
|
396
|
+
description:
|
|
397
|
+
'原生 id 的旧输入专用别名。提供时优先;未提供时保留原生属性。',
|
|
247
398
|
},
|
|
248
399
|
{
|
|
249
400
|
name: 'ariaDescribedBy',
|
|
250
401
|
type: 'string',
|
|
251
|
-
description:
|
|
402
|
+
description:
|
|
403
|
+
'原生 aria-describedby 的旧输入专用别名。提供时优先;未提供时保留原生属性。',
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'ariaLabelledBy',
|
|
407
|
+
type: 'string',
|
|
408
|
+
description:
|
|
409
|
+
'原生 aria-labelledby 的旧输入专用别名。提供时优先;未提供时保留原生属性。',
|
|
252
410
|
},
|
|
253
411
|
],
|
|
254
412
|
};
|
|
@@ -257,14 +415,40 @@ export const docsDense = {
|
|
|
257
415
|
name: 'BaseTypeahead',
|
|
258
416
|
isHiddenFromOverview: true,
|
|
259
417
|
displayName: 'Base Typeahead',
|
|
260
|
-
description:
|
|
418
|
+
description:
|
|
419
|
+
'Composable combobox engine providing a bare input and a styled result dropdown. Callers own input chrome and selected-value presentation.',
|
|
261
420
|
usage: {
|
|
262
421
|
bestPractices: [
|
|
263
|
-
{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
422
|
+
{
|
|
423
|
+
guidance: true,
|
|
424
|
+
description:
|
|
425
|
+
'Use Typeahead or Tokenizer for standard fields; they add the input chrome and selected-value rendering BaseTypeahead omits.',
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
guidance: true,
|
|
429
|
+
description:
|
|
430
|
+
'Provide a visible label or aria-label plus a custom wrapper with focus treatment, border, and layout.',
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
guidance: true,
|
|
434
|
+
description:
|
|
435
|
+
'Pass anchorRef to your wrapper so the dropdown positions against your input chrome, not the bare input.',
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
guidance: false,
|
|
439
|
+
description:
|
|
440
|
+
'Expect input chrome or selected-value rendering. The caller owns those visible parts.',
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
guidance: false,
|
|
444
|
+
description:
|
|
445
|
+
'Use BaseTypeahead when Typeahead or Tokenizer suffice; extra work only pays off for custom compositions.',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
guidance: false,
|
|
449
|
+
description:
|
|
450
|
+
'Treat Escape as pending-work cancellation. It hides the popup, but a late response can reopen it.',
|
|
451
|
+
},
|
|
268
452
|
],
|
|
269
453
|
},
|
|
270
454
|
propDescriptions: {
|
|
@@ -275,18 +459,30 @@ export const docsDense = {
|
|
|
275
459
|
placeholder: 'Input placeholder.',
|
|
276
460
|
hasEntriesOnFocus: 'Bootstrap results on focus.',
|
|
277
461
|
maxMenuItems: 'Max dropdown items.',
|
|
278
|
-
menuWidth: '
|
|
279
|
-
minQueryLength:
|
|
462
|
+
menuWidth: 'Requested px width before viewport clamping.',
|
|
463
|
+
minQueryLength:
|
|
464
|
+
'Min query length before searching. Menu stays closed below it.',
|
|
280
465
|
emptySearchResultsText: 'Text when no results.',
|
|
281
466
|
isDisabled: 'Whether input disabled.',
|
|
467
|
+
isFocusableDisabled:
|
|
468
|
+
'Keeps disabled input focusable and blocks text entry; an already-open highlight can still be selected with Enter.',
|
|
282
469
|
hasAutoFocus: 'Auto-focus on mount.',
|
|
283
470
|
debounceMs: 'Search debounce ms. 0 for sync.',
|
|
471
|
+
size: 'Dropdown option padding size.',
|
|
284
472
|
anchorRef: 'Anchor for dropdown positioning. Defaults to input.',
|
|
285
473
|
inputXStyle: 'Additional StyleX styles for input.',
|
|
286
|
-
|
|
474
|
+
xstyle: 'Standard BaseProps StyleX styles for input.',
|
|
475
|
+
inputTabIndex:
|
|
476
|
+
'Legacy tabIndex alias; defined alias wins, otherwise native tabIndex passes through.',
|
|
477
|
+
onKeyDown:
|
|
478
|
+
'Keydown before internal nav. preventDefault() skips internal handling.',
|
|
287
479
|
onChangeQuery: 'Fired on query text change.',
|
|
288
480
|
onOpenChange: 'Fired on dropdown open/close.',
|
|
289
|
-
inputId:
|
|
290
|
-
|
|
481
|
+
inputId:
|
|
482
|
+
'Legacy id alias; defined alias wins, otherwise native id passes through.',
|
|
483
|
+
ariaDescribedBy:
|
|
484
|
+
'Legacy aria-describedby alias; defined alias wins, otherwise native attribute passes through.',
|
|
485
|
+
ariaLabelledBy:
|
|
486
|
+
'Legacy aria-labelledby alias; defined alias wins, otherwise native attribute passes through.',
|
|
291
487
|
},
|
|
292
488
|
};
|