@astryxdesign/core 0.7.2-canary.fe2c655 → 0.7.2
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 +156 -2
- package/README.md +4 -4
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -1
- package/dist/AspectRatio/AspectRatio.d.ts +6 -8
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +44 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +8 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +1 -0
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/CheckboxList/CheckboxList.d.ts +1 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +6 -2
- package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +4 -2
- package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +37 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +5 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +20 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +84 -113
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +15 -4
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
- package/dist/DropdownMenu/menuRegionStops.d.ts +38 -0
- package/dist/DropdownMenu/menuRegionStops.d.ts.map +1 -0
- package/dist/DropdownMenu/menuRegionStops.js +131 -0
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +4 -10
- package/dist/Field/Field.d.ts +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +8 -4
- package/dist/FormLayout/FormLayout.d.ts +5 -4
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -1
- package/dist/FormLayout/FormLayoutContext.d.ts +3 -3
- package/dist/FormLayout/FormLayoutContext.js +3 -3
- package/dist/Grid/Grid.d.ts +8 -7
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +5 -4
- package/dist/Grid/GridSpan.d.ts +1 -1
- package/dist/Grid/GridSpan.d.ts.map +1 -1
- package/dist/Grid/GridSpan.js +1 -0
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +4 -2
- package/dist/Item/Item.d.ts +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +19 -4
- package/dist/List/List.d.ts +46 -19
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +28 -10
- package/dist/List/ListContext.d.ts +8 -3
- package/dist/List/ListContext.d.ts.map +1 -1
- package/dist/List/ListContext.js +7 -2
- package/dist/List/ListItem.d.ts +13 -5
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +21 -8
- package/dist/List/index.d.ts +1 -1
- package/dist/List/index.d.ts.map +1 -1
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +8 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +8 -2
- package/dist/Markdown/plugins/headingLinks.d.ts.map +1 -1
- package/dist/Markdown/plugins/headingLinks.js +9 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +5 -1
- package/dist/MetadataList/MetadataListContext.d.ts +1 -1
- package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.d.ts +9 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +30 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +5 -0
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +45 -11
- package/dist/RadioList/RadioList.d.ts +1 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +6 -2
- package/dist/RadioList/RadioListItem.d.ts +9 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +2 -0
- package/dist/SideNav/SideNavItem.d.ts +8 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +76 -10
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -2
- package/dist/ToggleButton/ToggleButton.d.ts +3 -2
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +12 -1
- package/dist/Token/Token.js +1 -1
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +5 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +5 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +2 -2
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +57 -9
- package/dist/astryx.css +27 -4
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +18 -13
- package/dist/reset.a11y.chromium.spec.js +50 -1
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +15 -9
- package/docs.mjs +4 -4
- package/package.json +4 -4
- package/scripts/agent-doc-state.mjs +66 -6
- package/scripts/postinstall.mjs +44 -3
- package/src/AppShell/AppShell.tsx +45 -1
- package/src/AspectRatio/AspectRatio.test.tsx +44 -43
- package/src/AspectRatio/AspectRatio.tsx +38 -18
- package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
- package/src/Avatar/Avatar.doc.mjs +5 -3
- package/src/Avatar/Avatar.tsx +11 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
- package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +24 -1
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Chat/ChatLayoutScrollButton.test.tsx +13 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +64 -19
- package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +3 -1
- package/src/CheckboxList/CheckboxList.fr10.test.tsx +29 -2
- package/src/CheckboxList/CheckboxList.test.tsx +22 -0
- package/src/CheckboxList/CheckboxList.tsx +4 -1
- package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
- package/src/CheckboxList/CheckboxListItem.tsx +10 -0
- package/src/Code/Code.doc.mjs +1 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
- package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
- package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
- package/src/ContextMenu/ContextMenu.test.tsx +12 -2
- package/src/ContextMenu/ContextMenu.tsx +4 -1
- package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
- package/src/Dialog/Dialog.test.tsx +46 -0
- package/src/Dialog/Dialog.tsx +20 -7
- package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
- package/src/Drawer/Drawer.doc.mjs +1 -0
- package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +79 -6
- package/src/DropdownMenu/DropdownMenu.tsx +88 -144
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
- package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
- package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
- package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +17 -3
- package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
- package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
- package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
- package/src/DropdownMenu/menuRegionStops.ts +167 -0
- package/src/EmptyState/EmptyState.doc.mjs +3 -2
- package/src/EmptyState/EmptyState.spec.md +189 -0
- package/src/EmptyState/EmptyState.test.tsx +74 -3
- package/src/EmptyState/EmptyState.tsx +4 -8
- package/src/Field/Field.doc.mjs +14 -8
- package/src/Field/Field.spec.md +217 -0
- package/src/Field/Field.test.tsx +60 -2
- package/src/Field/Field.tsx +17 -8
- package/src/Field/FieldLabel.doc.mjs +133 -26
- package/src/Field/FieldLabel.spec.md +172 -0
- package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
- package/src/FormLayout/FormLayout.doc.mjs +6 -3
- package/src/FormLayout/FormLayout.spec.md +192 -0
- package/src/FormLayout/FormLayout.test.tsx +15 -0
- package/src/FormLayout/FormLayout.tsx +7 -6
- package/src/FormLayout/FormLayoutContext.ts +3 -3
- package/src/Grid/Grid.doc.mjs +7 -1
- package/src/Grid/Grid.spec.md +93 -113
- package/src/Grid/Grid.test.tsx +22 -0
- package/src/Grid/Grid.tsx +10 -9
- package/src/Grid/GridSpan.doc.mjs +3 -3
- package/src/Grid/GridSpan.tsx +4 -1
- package/src/Heading/Heading.test.tsx +57 -0
- package/src/Heading/Heading.tsx +6 -2
- package/src/Icon/Icon.doc.mjs +36 -0
- package/src/Indicator/Indicator.doc.mjs +3 -3
- package/src/Item/Item.doc.mjs +12 -8
- package/src/Item/Item.test.tsx +61 -0
- package/src/Item/Item.tsx +40 -4
- package/src/Layout/LayoutHeader.doc.mjs +5 -0
- package/src/List/List.doc.mjs +14 -1
- package/src/List/List.spec.md +129 -77
- package/src/List/List.test.tsx +163 -94
- package/src/List/List.tsx +74 -18
- package/src/List/ListContext.tsx +9 -3
- package/src/List/ListItem.doc.mjs +16 -0
- package/src/List/ListItem.tsx +56 -27
- package/src/List/index.ts +1 -1
- package/src/Markdown/Markdown.tsx +14 -1
- package/src/Markdown/parser.ts +8 -2
- package/src/Markdown/plugins/headingLinks.ts +9 -1
- package/src/MetadataList/MetadataList.doc.mjs +3 -3
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -1
- package/src/MobileNav/MobileNav.tsx +30 -3
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
- package/src/MoreMenu/MoreMenu.test.tsx +5 -1
- package/src/PowerSearch/PowerSearch.test.tsx +62 -0
- package/src/PowerSearch/PowerSearch.tsx +9 -0
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +71 -0
- package/src/PowerSearch/PowerSearchValueEditor.tsx +45 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
- package/src/RadioList/RadioList.fr10.test.tsx +45 -2
- package/src/RadioList/RadioList.test.tsx +22 -0
- package/src/RadioList/RadioList.tsx +4 -1
- package/src/RadioList/RadioListItem.doc.mjs +16 -0
- package/src/RadioList/RadioListItem.tsx +10 -1
- package/src/Resizable/useResizable.doc.mjs +2 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
- package/src/SideNav/SideNav.spec.md +227 -0
- package/src/SideNav/SideNav.test.tsx +220 -0
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +99 -17
- package/src/Slider/Slider.spec.md +39 -18
- package/src/Spinner/Spinner.doc.mjs +1 -1
- package/src/TabList/TabList.spec.md +57 -44
- package/src/Table/Table.spec.md +6 -1
- package/src/Table/plugins/activation/useTableActivation.spec.md +254 -0
- package/src/Text/Text.test.tsx +57 -0
- package/src/Text/Text.tsx +6 -2
- package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
- package/src/Token/Token.tsx +1 -1
- package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +86 -0
- package/src/Toolbar/Toolbar.test.tsx +0 -8
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +47 -0
- package/src/TopNav/TopNavMegaMenu.tsx +9 -3
- package/src/TopNav/TopNavMenu.test.tsx +46 -1
- package/src/TopNav/TopNavMenu.tsx +13 -3
- package/src/TreeList/TreeList.doc.mjs +1 -0
- package/src/TreeList/TreeList.test.tsx +327 -6
- package/src/TreeList/TreeList.tsx +5 -1
- package/src/TreeList/TreeListBranches.tsx +6 -0
- package/src/TreeList/TreeListItem.tsx +88 -13
- package/src/docPropLiterals.test.ts +36 -10
- package/src/hooks/useMenuHover.ts +18 -13
- package/src/postinstall.test.mjs +79 -0
- package/src/reset.a11y.chromium.spec.ts +52 -1
- package/src/reset.css +9 -29
- package/src/reset.test.ts +11 -0
- package/src/theme/derivedVarRegistry.test.ts +7 -0
- package/src/unicodePropertyEscapes.source-build.test.mjs +69 -0
- package/src/utils/plainDate.test.ts +37 -1
- package/src/utils/plainDate.ts +15 -5
|
@@ -48,9 +48,10 @@ export const docs = {
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
name: 'size',
|
|
51
|
-
type:
|
|
51
|
+
type:
|
|
52
|
+
"'xsm' | 'sm' | 'md' | 'lg' | 'xl' | 16 | 20 | 24 | 32 | 36 | 40 | 48 | 60 | 64 | 72 | 96 | 128 | 144 | 180",
|
|
52
53
|
description:
|
|
53
|
-
"Size applied to all avatars
|
|
54
|
+
"Size applied to all avatars: named sizes are 'xsm' (20px), 'sm' (24px), 'md' (36px), 'lg' (48px), and 'xl' (128px); numeric sizes must be one of the pixel values listed in the type. The group's size always overrides each child Avatar's size, including when the group uses its default size.",
|
|
54
55
|
default: "'md'",
|
|
55
56
|
},
|
|
56
57
|
{
|
|
@@ -21,6 +21,29 @@ describe('AvatarGroupOverflow', () => {
|
|
|
21
21
|
expect(overflow).toHaveTextContent('+5');
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
it('exposes the static overflow as a named image', () => {
|
|
25
|
+
render(
|
|
26
|
+
<AvatarGroup>
|
|
27
|
+
<Avatar name="Alice" />
|
|
28
|
+
<AvatarGroupOverflow count={5} />
|
|
29
|
+
</AvatarGroup>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const overflow = screen.getByRole('img', {name: '5 more'});
|
|
33
|
+
expect(overflow).toHaveAttribute('role', 'img');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps a caller-supplied role on the static overflow', () => {
|
|
37
|
+
render(
|
|
38
|
+
<AvatarGroup>
|
|
39
|
+
<Avatar name="Alice" />
|
|
40
|
+
<AvatarGroupOverflow count={5} role="note" />
|
|
41
|
+
</AvatarGroup>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(screen.getByLabelText('5 more')).toHaveAttribute('role', 'note');
|
|
45
|
+
});
|
|
46
|
+
|
|
24
47
|
it('reflects the group size on the overflow chip', () => {
|
|
25
48
|
render(
|
|
26
49
|
<AvatarGroup size="lg">
|
|
@@ -66,7 +89,7 @@ describe('AvatarGroupOverflow', () => {
|
|
|
66
89
|
</AvatarGroup>,
|
|
67
90
|
);
|
|
68
91
|
|
|
69
|
-
const overflow = screen.
|
|
92
|
+
const overflow = screen.getByRole('button', {name: '3 more'});
|
|
70
93
|
expect(overflow.tagName).toBe('BUTTON');
|
|
71
94
|
});
|
|
72
95
|
|
|
@@ -56,6 +56,7 @@ import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
|
56
56
|
import {renderDropdownItems} from '../DropdownMenu/renderDropdownItems';
|
|
57
57
|
import {
|
|
58
58
|
DropdownMenuContext,
|
|
59
|
+
DropdownMenuRootOpenContext,
|
|
59
60
|
type DropdownMenuContextValue,
|
|
60
61
|
type DropdownMenuSize,
|
|
61
62
|
} from '../DropdownMenu/DropdownMenuContext';
|
|
@@ -673,7 +674,9 @@ function BreadcrumbMenuTrigger({
|
|
|
673
674
|
stylex.props(menuStyles.menu),
|
|
674
675
|
)}>
|
|
675
676
|
<DropdownMenuContext value={contextValue}>
|
|
676
|
-
{
|
|
677
|
+
<DropdownMenuRootOpenContext value={popover.isOpen}>
|
|
678
|
+
{menuContent}
|
|
679
|
+
</DropdownMenuRootOpenContext>
|
|
677
680
|
</DropdownMenuContext>
|
|
678
681
|
</div>,
|
|
679
682
|
{
|
|
@@ -7,12 +7,13 @@ import {Breadcrumbs} from './Breadcrumbs';
|
|
|
7
7
|
import {BreadcrumbItem} from './BreadcrumbItem';
|
|
8
8
|
import {
|
|
9
9
|
BreadcrumbMenuItem,
|
|
10
|
+
BreadcrumbMenuDivider,
|
|
10
11
|
BreadcrumbMenuGroup,
|
|
11
12
|
BreadcrumbMenuCheckboxItem,
|
|
12
13
|
BreadcrumbMenuRadioGroup,
|
|
13
14
|
BreadcrumbMenuRadioItem,
|
|
14
15
|
} from './index';
|
|
15
|
-
import type {BreadcrumbMenuOption} from './index';
|
|
16
|
+
import type {BreadcrumbMenuDividerProps, BreadcrumbMenuOption} from './index';
|
|
16
17
|
import {LinkProvider} from '../Link/LinkProvider';
|
|
17
18
|
|
|
18
19
|
function CustomLink({
|
|
@@ -771,6 +772,34 @@ describe('BreadcrumbItem menu', () => {
|
|
|
771
772
|
expect(separators).toHaveLength(3);
|
|
772
773
|
});
|
|
773
774
|
|
|
775
|
+
it('renders the separator and props types through the BreadcrumbMenuDivider aliases', async () => {
|
|
776
|
+
const user = userEvent.setup();
|
|
777
|
+
const dividerProps: BreadcrumbMenuDividerProps = {
|
|
778
|
+
'data-testid': 'breadcrumb-menu-divider',
|
|
779
|
+
};
|
|
780
|
+
render(
|
|
781
|
+
<Breadcrumbs>
|
|
782
|
+
<BreadcrumbItem
|
|
783
|
+
menu={
|
|
784
|
+
<>
|
|
785
|
+
<BreadcrumbMenuItem label="Overview" />
|
|
786
|
+
<BreadcrumbMenuDivider {...dividerProps} />
|
|
787
|
+
<BreadcrumbMenuItem label="Settings" />
|
|
788
|
+
</>
|
|
789
|
+
}>
|
|
790
|
+
Project
|
|
791
|
+
</BreadcrumbItem>
|
|
792
|
+
<BreadcrumbItem isCurrent>Current</BreadcrumbItem>
|
|
793
|
+
</Breadcrumbs>,
|
|
794
|
+
);
|
|
795
|
+
await user.click(screen.getByRole('button', {name: 'Project'}));
|
|
796
|
+
|
|
797
|
+
expect(screen.getByTestId('breadcrumb-menu-divider')).toHaveAttribute(
|
|
798
|
+
'role',
|
|
799
|
+
'separator',
|
|
800
|
+
);
|
|
801
|
+
});
|
|
802
|
+
|
|
774
803
|
it('carries the group semantics through the BreadcrumbMenuGroup alias', async () => {
|
|
775
804
|
// The alias must bring the semantics, not just the component: a group
|
|
776
805
|
// that renders but is not named leaves a screen reader with a run of
|
package/src/Card/Card.doc.mjs
CHANGED
|
@@ -83,7 +83,7 @@ export const docs = {
|
|
|
83
83
|
code: `
|
|
84
84
|
import {Card} from '@astryxdesign/core/Card';
|
|
85
85
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
86
|
-
import {VStack} from '@astryxdesign/core/
|
|
86
|
+
import {VStack} from '@astryxdesign/core/VStack';
|
|
87
87
|
import {Heading} from '@astryxdesign/core/Heading';
|
|
88
88
|
import {Text} from '@astryxdesign/core/Text';
|
|
89
89
|
|
|
@@ -70,6 +70,19 @@ describe('ChatLayoutScrollButton', () => {
|
|
|
70
70
|
).toHaveFocus();
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it('makes the pill inert whenever the visible state turns off', () => {
|
|
74
|
+
const {container, rerender} = render(
|
|
75
|
+
<ChatLayoutScrollButton isVisible onClick={() => {}} />,
|
|
76
|
+
);
|
|
77
|
+
const pill = container.querySelector('.astryx-chat-layout-scroll-button');
|
|
78
|
+
|
|
79
|
+
expect(pill).not.toHaveAttribute('inert');
|
|
80
|
+
|
|
81
|
+
rerender(<ChatLayoutScrollButton isVisible={false} onClick={() => {}} />);
|
|
82
|
+
|
|
83
|
+
expect(pill).toHaveAttribute('inert');
|
|
84
|
+
});
|
|
85
|
+
|
|
73
86
|
it('is not keyboard reachable while hidden', async () => {
|
|
74
87
|
const user = userEvent.setup();
|
|
75
88
|
render(
|
|
@@ -86,8 +86,9 @@ const styles = stylex.create({
|
|
|
86
86
|
opacity: 0,
|
|
87
87
|
pointerEvents: 'none',
|
|
88
88
|
// The hidden pill paints nothing, so focus landing on it would have no
|
|
89
|
-
// visible indicator (WCAG 2.2 SC 2.4.7). `
|
|
90
|
-
//
|
|
89
|
+
// visible indicator (WCAG 2.2 SC 2.4.7). `visibility` removes it from
|
|
90
|
+
// sequential navigation after the fade; `inert` immediately prevents new
|
|
91
|
+
// focus and makes an already-focused descendant relinquish focus.
|
|
91
92
|
visibility: 'hidden',
|
|
92
93
|
maxWidth: sizeVars['--size-element-md'],
|
|
93
94
|
},
|
|
@@ -151,6 +152,7 @@ export function ChatLayoutScrollButton({
|
|
|
151
152
|
{...mergeProps(stylex.props(styles.wrapper, xstyle), className, style)}
|
|
152
153
|
{...rest}>
|
|
153
154
|
<div
|
|
155
|
+
inert={!isVisible}
|
|
154
156
|
{...mergeProps(
|
|
155
157
|
themeProps('chat-layout-scroll-button'),
|
|
156
158
|
stylex.props(
|
|
@@ -44,6 +44,7 @@ const STORY_ID = 'core-chatlayout--scroll-affordance-states';
|
|
|
44
44
|
* a stable handle for it.
|
|
45
45
|
*/
|
|
46
46
|
const PILL = '.astryx-chat-layout-scroll-button';
|
|
47
|
+
const AFFORDANCE = `${PILL} button`;
|
|
47
48
|
|
|
48
49
|
/** One observed tab stop, identified structurally rather than by its text. */
|
|
49
50
|
type FocusStop = {
|
|
@@ -126,7 +127,11 @@ test.afterAll(async () => {
|
|
|
126
127
|
|
|
127
128
|
async function openStory(
|
|
128
129
|
page: Page,
|
|
129
|
-
{
|
|
130
|
+
{
|
|
131
|
+
theme = 'neutral',
|
|
132
|
+
colorMode = 'light',
|
|
133
|
+
holdMotion = true,
|
|
134
|
+
}: {theme?: string; colorMode?: string; holdMotion?: boolean} = {},
|
|
130
135
|
): Promise<Locator> {
|
|
131
136
|
page.on('pageerror', error => pageErrors.push(String(error)));
|
|
132
137
|
browserVersion = page.context().browser()?.version() ?? 'unknown';
|
|
@@ -136,7 +141,9 @@ async function openStory(
|
|
|
136
141
|
);
|
|
137
142
|
const root = page.locator('.astryx-chat-layout');
|
|
138
143
|
await root.waitFor({state: 'visible'});
|
|
139
|
-
|
|
144
|
+
if (holdMotion) {
|
|
145
|
+
await holdMotionStill(page);
|
|
146
|
+
}
|
|
140
147
|
await page.evaluate(async () => document.fonts.ready);
|
|
141
148
|
return root;
|
|
142
149
|
}
|
|
@@ -169,21 +176,25 @@ async function pillVisibility(page: Page): Promise<string> {
|
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
/**
|
|
172
|
-
* Can the affordance take focus at all? This is the mechanism
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* is used rather than a role query because a hidden button
|
|
176
|
-
* accessibility tree and a role query would not resolve it.
|
|
179
|
+
* Can the affordance take new focus at all? This is one half of the mechanism
|
|
180
|
+
* under test: `inert` refuses programmatic focus immediately, while delayed
|
|
181
|
+
* `visibility: hidden` removes the control from sequential navigation after the
|
|
182
|
+
* fade. A CSS locator is used rather than a role query because a hidden button
|
|
183
|
+
* is not in the accessibility tree and a role query would not resolve it.
|
|
177
184
|
*/
|
|
178
|
-
async function affordanceAcceptsFocus(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
async function affordanceAcceptsFocus(
|
|
186
|
+
page: Page,
|
|
187
|
+
resetFocus: Locator,
|
|
188
|
+
): Promise<boolean> {
|
|
189
|
+
// Start outside the affordance so this probe proves a NEW focus transfer;
|
|
190
|
+
// equality alone would also be true when a button stayed active as it hid.
|
|
191
|
+
await resetFocus.focus();
|
|
192
|
+
return page.locator(AFFORDANCE).evaluate(element => {
|
|
193
|
+
// preventScroll: focusing must not move the scroll position, or the
|
|
194
|
+
// probe would change the very state the next assertion reads.
|
|
195
|
+
(element as HTMLElement).focus({preventScroll: true});
|
|
196
|
+
return document.activeElement === element;
|
|
197
|
+
});
|
|
187
198
|
}
|
|
188
199
|
|
|
189
200
|
/**
|
|
@@ -340,6 +351,36 @@ async function capture(
|
|
|
340
351
|
return receipt;
|
|
341
352
|
}
|
|
342
353
|
|
|
354
|
+
test('the hiding affordance releases focus before its fade finishes', async ({
|
|
355
|
+
page,
|
|
356
|
+
}) => {
|
|
357
|
+
const root = await openStory(page, {holdMotion: false});
|
|
358
|
+
const pill = page.locator(PILL);
|
|
359
|
+
const affordance = page.locator(AFFORDANCE);
|
|
360
|
+
|
|
361
|
+
await scrollToTop(root);
|
|
362
|
+
await expect(pill).toHaveCSS('visibility', 'visible');
|
|
363
|
+
await affordance.focus();
|
|
364
|
+
await expect(affordance).toBeFocused();
|
|
365
|
+
|
|
366
|
+
await page.keyboard.press('Enter');
|
|
367
|
+
await expect
|
|
368
|
+
.poll(
|
|
369
|
+
async () =>
|
|
370
|
+
affordance.evaluate(element => document.activeElement === element),
|
|
371
|
+
{timeout: 5000},
|
|
372
|
+
)
|
|
373
|
+
.toBe(false);
|
|
374
|
+
|
|
375
|
+
// `visibility` flips only after the fade. Focus leaving while it is still
|
|
376
|
+
// visible proves the immediate `inert` state rather than the delayed CSS.
|
|
377
|
+
await expect(pill).toHaveCSS('visibility', 'visible');
|
|
378
|
+
await expect
|
|
379
|
+
.poll(async () => distanceFromBottom(root), {timeout: 5000})
|
|
380
|
+
.toBeLessThan(2);
|
|
381
|
+
await expect(pill).toHaveCSS('visibility', 'hidden');
|
|
382
|
+
});
|
|
383
|
+
|
|
343
384
|
test('the scroll affordance is keyboard reachable exactly while it is visible', async ({
|
|
344
385
|
page,
|
|
345
386
|
}) => {
|
|
@@ -367,7 +408,7 @@ test('the scroll affordance is keyboard reachable exactly while it is visible',
|
|
|
367
408
|
expect(await distanceFromBottom(root)).toBeLessThan(2);
|
|
368
409
|
await expect(pill).toHaveCSS('visibility', 'hidden');
|
|
369
410
|
expect(
|
|
370
|
-
await affordanceAcceptsFocus(page),
|
|
411
|
+
await affordanceAcceptsFocus(page, before),
|
|
371
412
|
'a control that paints nothing must refuse focus (WCAG 2.2 SC 2.4.7)',
|
|
372
413
|
).toBe(false);
|
|
373
414
|
const restFrame = await capture(page, 'rest-hidden', {
|
|
@@ -384,7 +425,7 @@ test('the scroll affordance is keyboard reachable exactly while it is visible',
|
|
|
384
425
|
await expect(pill).toHaveCSS('visibility', 'visible');
|
|
385
426
|
expect(await distanceFromBottom(root)).toBeGreaterThan(100);
|
|
386
427
|
expect(
|
|
387
|
-
await affordanceAcceptsFocus(page),
|
|
428
|
+
await affordanceAcceptsFocus(page, before),
|
|
388
429
|
'the visible affordance must keep its keyboard access',
|
|
389
430
|
).toBe(true);
|
|
390
431
|
await expect(
|
|
@@ -415,8 +456,12 @@ test('the scroll affordance is keyboard reachable exactly while it is visible',
|
|
|
415
456
|
.poll(async () => distanceFromBottom(root), {timeout: 5000})
|
|
416
457
|
.toBeLessThan(2);
|
|
417
458
|
await expect(pill).toHaveCSS('visibility', 'hidden');
|
|
459
|
+
await expect(
|
|
460
|
+
page.locator(AFFORDANCE),
|
|
461
|
+
'the affordance must relinquish focus when it hides',
|
|
462
|
+
).not.toBeFocused();
|
|
418
463
|
expect(
|
|
419
|
-
await affordanceAcceptsFocus(page),
|
|
464
|
+
await affordanceAcceptsFocus(page, before),
|
|
420
465
|
'after returning to the bottom the affordance must refuse focus again',
|
|
421
466
|
).toBe(false);
|
|
422
467
|
const returnedFrame = await capture(page, 'returned-hidden', {
|
|
@@ -395,7 +395,9 @@ test('captures narrow touch reflow in both modes', async ({browser}) => {
|
|
|
395
395
|
try {
|
|
396
396
|
const page = await context.newPage();
|
|
397
397
|
const scenario = CASES.find(item => item.name === 'narrow-overflow');
|
|
398
|
-
if (!scenario) {
|
|
398
|
+
if (!scenario) {
|
|
399
|
+
throw new Error('Narrow metadata fixture is missing');
|
|
400
|
+
}
|
|
399
401
|
const failures: string[] = [];
|
|
400
402
|
for (const mode of ['light', 'dark'] as const) {
|
|
401
403
|
failures.push(
|
|
@@ -209,6 +209,40 @@ describe('CheckboxInput', () => {
|
|
|
209
209
|
);
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
it('preserves a caller-provided id on the input and its label', () => {
|
|
213
|
+
render(
|
|
214
|
+
<CheckboxInput
|
|
215
|
+
id="accept-terms"
|
|
216
|
+
label="Accept terms"
|
|
217
|
+
value={false}
|
|
218
|
+
onChange={() => {}}
|
|
219
|
+
/>,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const checkbox = screen.getByRole('checkbox');
|
|
223
|
+
expect(checkbox).toHaveAttribute('id', 'accept-terms');
|
|
224
|
+
expect(screen.getByText('Accept terms').closest('label')).toHaveAttribute(
|
|
225
|
+
'for',
|
|
226
|
+
'accept-terms',
|
|
227
|
+
);
|
|
228
|
+
expect(screen.getByLabelText('Accept terms')).toBe(checkbox);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('generates an id the label resolves when the caller passes an empty id', () => {
|
|
232
|
+
render(
|
|
233
|
+
<CheckboxInput
|
|
234
|
+
id=""
|
|
235
|
+
label="Accept terms"
|
|
236
|
+
value={false}
|
|
237
|
+
onChange={() => {}}
|
|
238
|
+
/>,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const checkbox = screen.getByRole('checkbox');
|
|
242
|
+
expect(checkbox.id).not.toBe('');
|
|
243
|
+
expect(screen.getByLabelText('Accept terms')).toBe(checkbox);
|
|
244
|
+
});
|
|
245
|
+
|
|
212
246
|
it('forwards arbitrary data-* attributes to the input', () => {
|
|
213
247
|
render(
|
|
214
248
|
<CheckboxInput
|
|
@@ -311,6 +311,7 @@ const dynamicWidthStyles = stylex.create({
|
|
|
311
311
|
* ```
|
|
312
312
|
*/
|
|
313
313
|
export function CheckboxInput({
|
|
314
|
+
id: idProp,
|
|
314
315
|
label,
|
|
315
316
|
isLabelHidden = false,
|
|
316
317
|
description,
|
|
@@ -340,7 +341,8 @@ export function CheckboxInput({
|
|
|
340
341
|
// The row is the pressable: the overlay's pressed arm reads the row's
|
|
341
342
|
// scope marker, the way the indicator's hover tint does.
|
|
342
343
|
const pressable = usePressFeedback();
|
|
343
|
-
const
|
|
344
|
+
const generatedId = useId();
|
|
345
|
+
const id = idProp || generatedId;
|
|
344
346
|
const descriptionID = useId();
|
|
345
347
|
const statusMessageID = useId();
|
|
346
348
|
// Announce the effective required state (form default included) while the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
-
import {describe, it} from 'vitest';
|
|
4
|
-
import {screen} from '@testing-library/react';
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
import {render, screen} from '@testing-library/react';
|
|
5
5
|
import {expectFieldInputProps} from '../Field/fieldInputFamily.testing';
|
|
6
6
|
import {describeFieldInputParts} from '../Field/fieldInputFamily.render-testing';
|
|
7
7
|
import {CheckboxList, type CheckboxListProps} from './CheckboxList';
|
|
@@ -22,3 +22,30 @@ describeFieldInputParts('CheckboxList', {
|
|
|
22
22
|
getControl: () => screen.getByRole('group'),
|
|
23
23
|
inGroup: false,
|
|
24
24
|
});
|
|
25
|
+
|
|
26
|
+
describe('CheckboxList FR10 details', () => {
|
|
27
|
+
it("names the group by its label and then a caller's aria-labelledby", () => {
|
|
28
|
+
render(
|
|
29
|
+
<>
|
|
30
|
+
<span id="caller-name">Billing</span>
|
|
31
|
+
<CheckboxList
|
|
32
|
+
label="Preferences"
|
|
33
|
+
value={[]}
|
|
34
|
+
onChange={() => {}}
|
|
35
|
+
aria-labelledby="caller-name">
|
|
36
|
+
<CheckboxListItem label="A" value="a" />
|
|
37
|
+
</CheckboxList>
|
|
38
|
+
</>,
|
|
39
|
+
);
|
|
40
|
+
const group = screen.getByRole('group');
|
|
41
|
+
const ids = group.getAttribute('aria-labelledby')?.split(' ');
|
|
42
|
+
expect(ids).toHaveLength(2);
|
|
43
|
+
expect(document.getElementById(ids?.[0] ?? '')?.textContent).toContain(
|
|
44
|
+
'Preferences',
|
|
45
|
+
);
|
|
46
|
+
expect(ids?.[1]).toBe('caller-name');
|
|
47
|
+
expect(screen.getByRole('group', {name: 'Preferences Billing'})).toBe(
|
|
48
|
+
group,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -59,6 +59,28 @@ beforeEach(() => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
describe('CheckboxList', () => {
|
|
62
|
+
it('forwards CheckboxListItem align to its Item row unchanged', () => {
|
|
63
|
+
render(
|
|
64
|
+
<CheckboxList label="Toppings" value={[]} onChange={() => {}}>
|
|
65
|
+
<CheckboxListItem label="Cheese" value="cheese" data-testid="cheese" />
|
|
66
|
+
<CheckboxListItem
|
|
67
|
+
label="Olives"
|
|
68
|
+
value="olives"
|
|
69
|
+
align="first-line"
|
|
70
|
+
data-testid="olives"
|
|
71
|
+
/>
|
|
72
|
+
</CheckboxList>,
|
|
73
|
+
);
|
|
74
|
+
expect(screen.getByTestId('cheese')).toHaveAttribute(
|
|
75
|
+
'data-align',
|
|
76
|
+
'center',
|
|
77
|
+
);
|
|
78
|
+
expect(screen.getByTestId('olives')).toHaveAttribute(
|
|
79
|
+
'data-align',
|
|
80
|
+
'first-line',
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
62
84
|
it('renders with label', () => {
|
|
63
85
|
render(
|
|
64
86
|
<CheckboxList label="Preferences" value={[]} onChange={() => {}}>
|
|
@@ -38,6 +38,7 @@ import {useTooltip} from '../Tooltip';
|
|
|
38
38
|
import * as stylex from '@stylexjs/stylex';
|
|
39
39
|
import {mergeProps} from '../utils';
|
|
40
40
|
import {themeProps} from '../utils/themeProps';
|
|
41
|
+
import {joinAriaIDs} from '../utils/inputAria';
|
|
41
42
|
import {
|
|
42
43
|
CheckboxListContext,
|
|
43
44
|
type CheckboxListContextValue,
|
|
@@ -170,6 +171,7 @@ export function CheckboxList({
|
|
|
170
171
|
controlXstyle,
|
|
171
172
|
controlClassName,
|
|
172
173
|
controlStyle,
|
|
174
|
+
'aria-labelledby': ariaLabelledByProp,
|
|
173
175
|
'aria-describedby': ariaDescribedByProp,
|
|
174
176
|
...restProps
|
|
175
177
|
}: CheckboxListProps) {
|
|
@@ -290,7 +292,8 @@ export function CheckboxList({
|
|
|
290
292
|
controlStyle,
|
|
291
293
|
)}
|
|
292
294
|
role="group"
|
|
293
|
-
aria-labelledby
|
|
295
|
+
// A caller aria-labelledby names the group together with its label.
|
|
296
|
+
aria-labelledby={joinAriaIDs(labelID, ariaLabelledByProp)}
|
|
294
297
|
aria-describedby={
|
|
295
298
|
[
|
|
296
299
|
ariaDescribedByProp,
|
|
@@ -55,6 +55,13 @@ export const docs = {
|
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
name: 'align',
|
|
60
|
+
type: "'center' | 'start' | 'first-line'",
|
|
61
|
+
description:
|
|
62
|
+
'Vertical alignment of the start and end content against the label, passed through to Item unchanged. `first-line` keeps a small mark (a checkbox) level with the first line of a wrapped label. See Item.align.',
|
|
63
|
+
default: "'center'",
|
|
64
|
+
},
|
|
58
65
|
{
|
|
59
66
|
name: 'isDisabled',
|
|
60
67
|
type: 'boolean',
|
|
@@ -130,6 +137,13 @@ export const docsZh = {
|
|
|
130
137
|
type: 'ReactNode',
|
|
131
138
|
description: '在标签区域后渲染的内容。',
|
|
132
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: 'align',
|
|
142
|
+
type: "'center' | 'start' | 'first-line'",
|
|
143
|
+
description:
|
|
144
|
+
'起始和结束内容相对于标签的垂直对齐方式,原样传递给 Item。`first-line` 让小标记(复选框)与换行标签的第一行保持水平。参见 Item.align。',
|
|
145
|
+
default: "'center'",
|
|
146
|
+
},
|
|
133
147
|
{
|
|
134
148
|
name: 'isDisabled',
|
|
135
149
|
type: 'boolean',
|
|
@@ -165,6 +179,8 @@ export const docsDense = {
|
|
|
165
179
|
description:
|
|
166
180
|
'Individual checkbox item w/ label, description, end content slot. Collection mode inside CheckboxList with value; standalone inside List or CheckboxList without value.',
|
|
167
181
|
propDescriptions: {
|
|
182
|
+
align:
|
|
183
|
+
"Start/end content alignment, passed to Item: 'center' (default), 'start', or 'first-line' (centered on the label's first line).",
|
|
168
184
|
label:
|
|
169
185
|
'Primary label. String or ReactNode; nested controls keep their behavior. A ReactNode names the checkbox from its visible text.',
|
|
170
186
|
'aria-label':
|
|
@@ -29,6 +29,7 @@ import {ListContext} from '../List/ListContext';
|
|
|
29
29
|
import {CheckboxListContext} from './CheckboxListContext';
|
|
30
30
|
import {useTranslator} from '../i18n';
|
|
31
31
|
import {ItemDescriptionContext} from '../Item/ItemDescriptionContext';
|
|
32
|
+
import type {ItemAlign} from '../Item';
|
|
32
33
|
|
|
33
34
|
// =============================================================================
|
|
34
35
|
// Styles
|
|
@@ -45,6 +46,13 @@ const styles = stylex.create({
|
|
|
45
46
|
// =============================================================================
|
|
46
47
|
|
|
47
48
|
export interface CheckboxListItemProps extends BaseProps<HTMLLIElement> {
|
|
49
|
+
/**
|
|
50
|
+
* Vertical alignment of the start and end content against the label,
|
|
51
|
+
* forwarded to Item. Use `first-line` to keep the checkbox level with the
|
|
52
|
+
* first line of a wrapped label.
|
|
53
|
+
* @default 'center'
|
|
54
|
+
*/
|
|
55
|
+
align?: ItemAlign;
|
|
48
56
|
/**
|
|
49
57
|
* Primary text label for the item.
|
|
50
58
|
*
|
|
@@ -192,6 +200,7 @@ export function CheckboxListItem({
|
|
|
192
200
|
className,
|
|
193
201
|
style,
|
|
194
202
|
onClick: onClickProp,
|
|
203
|
+
align,
|
|
195
204
|
...restProps
|
|
196
205
|
}: CheckboxListItemProps) {
|
|
197
206
|
const t = useTranslator();
|
|
@@ -297,6 +306,7 @@ export function CheckboxListItem({
|
|
|
297
306
|
label={namesFromVisibleLabel ? <span id={labelID}>{label}</span> : label}
|
|
298
307
|
description={description}
|
|
299
308
|
endContent={endContent}
|
|
309
|
+
align={align}
|
|
300
310
|
isDisabled={effectiveDisabled}
|
|
301
311
|
// Delegate row clicks to the checkbox instead of wiring onClick (which
|
|
302
312
|
// would add an invisible row button = a second tab stop). The checkbox
|
package/src/Code/Code.doc.mjs
CHANGED
|
@@ -8,12 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {createRef} from 'react';
|
|
11
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
12
|
-
import {render, screen, fireEvent} from '@testing-library/react';
|
|
11
|
+
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
12
|
+
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
13
|
+
import * as stylex from '@stylexjs/stylex';
|
|
13
14
|
import {CommandPaletteInput} from './CommandPaletteInput';
|
|
14
15
|
import {CommandPaletteContext} from './CommandPaletteContext';
|
|
15
16
|
import {Dialog} from '../Dialog';
|
|
16
17
|
import type {CommandPaletteContextValue} from './CommandPaletteContext';
|
|
18
|
+
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
19
|
+
import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
__resetInteractionModalityForTest();
|
|
23
|
+
});
|
|
17
24
|
|
|
18
25
|
function makeContext(
|
|
19
26
|
overrides: Partial<CommandPaletteContextValue> = {},
|
|
@@ -279,6 +286,45 @@ describe('CommandPaletteInput', () => {
|
|
|
279
286
|
},
|
|
280
287
|
);
|
|
281
288
|
|
|
289
|
+
it('draws the shared ring only when keyboard navigation acquired focus', () => {
|
|
290
|
+
render(<CommandPaletteInput hasAutoFocus={false} />);
|
|
291
|
+
const input = screen.getByRole('searchbox');
|
|
292
|
+
const ringClasses = stylex
|
|
293
|
+
.props(focusOutlineStyles.focusVisible)
|
|
294
|
+
.className!.split(' ');
|
|
295
|
+
const isRinged = () =>
|
|
296
|
+
ringClasses.every(className => input.classList.contains(className));
|
|
297
|
+
|
|
298
|
+
fireEvent.pointerDown(input);
|
|
299
|
+
fireEvent.focus(input);
|
|
300
|
+
expect(isRinged()).toBe(false);
|
|
301
|
+
|
|
302
|
+
fireEvent.blur(input);
|
|
303
|
+
fireEvent.keyDown(input, {key: 'Tab'});
|
|
304
|
+
fireEvent.focus(input);
|
|
305
|
+
expect(isRinged()).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('preserves caller focus and blur handlers while tracking modality', () => {
|
|
309
|
+
const onFocus = vi.fn();
|
|
310
|
+
const onBlur = vi.fn();
|
|
311
|
+
render(
|
|
312
|
+
<CommandPaletteInput
|
|
313
|
+
hasAutoFocus={false}
|
|
314
|
+
onFocus={onFocus}
|
|
315
|
+
onBlur={onBlur}
|
|
316
|
+
/>,
|
|
317
|
+
);
|
|
318
|
+
const input = screen.getByRole('searchbox');
|
|
319
|
+
|
|
320
|
+
fireEvent.keyDown(input, {key: 'Tab'});
|
|
321
|
+
fireEvent.focus(input);
|
|
322
|
+
fireEvent.blur(input);
|
|
323
|
+
|
|
324
|
+
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
325
|
+
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
326
|
+
});
|
|
327
|
+
|
|
282
328
|
it('does not schedule focus when hasAutoFocus is false', async () => {
|
|
283
329
|
const focusSpy = vi.spyOn(HTMLElement.prototype, 'focus');
|
|
284
330
|
render(<CommandPaletteInput hasAutoFocus={false} />);
|
|
@@ -481,8 +527,11 @@ describe('CommandPaletteInput popup context', () => {
|
|
|
481
527
|
</CommandPaletteContext>,
|
|
482
528
|
);
|
|
483
529
|
|
|
484
|
-
await
|
|
485
|
-
|
|
530
|
+
await act(async () => {
|
|
531
|
+
await new Promise<void>(resolve =>
|
|
532
|
+
requestAnimationFrame(() => resolve()),
|
|
533
|
+
);
|
|
486
534
|
});
|
|
535
|
+
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
487
536
|
});
|
|
488
537
|
});
|