@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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file DropdownMenu.ssr.test.tsx
|
|
5
|
+
* @input DropdownMenu server markup and React hydration
|
|
6
|
+
* @output Proves a bottom-sheet DropdownMenu hydrates without a recoverable
|
|
7
|
+
* error while its on-demand sheet has not loaded (spec:AST-013 FR9)
|
|
8
|
+
* @position SSR acceptance test for DropdownMenu
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {act} from 'react';
|
|
12
|
+
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
13
|
+
import {hydrateRoot} from 'react-dom/client';
|
|
14
|
+
import {renderToString} from 'react-dom/server';
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
document.body.replaceChildren();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function serverRenderThenHydrate(): Promise<unknown[]> {
|
|
22
|
+
// A fresh module graph is a fresh server process: the sheet has not loaded.
|
|
23
|
+
vi.resetModules();
|
|
24
|
+
const {DropdownMenu} = await import('./DropdownMenu');
|
|
25
|
+
const tree = (
|
|
26
|
+
<DropdownMenu
|
|
27
|
+
button={{label: 'Actions'}}
|
|
28
|
+
presentation="bottom-sheet"
|
|
29
|
+
items={[{label: 'Edit project'}, {label: 'Delete'}]}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
const container = document.createElement('div');
|
|
33
|
+
container.innerHTML = renderToString(tree);
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
|
|
36
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
37
|
+
const recoverableErrors: unknown[] = [];
|
|
38
|
+
let root!: ReturnType<typeof hydrateRoot>;
|
|
39
|
+
await act(async () => {
|
|
40
|
+
root = hydrateRoot(container, tree, {
|
|
41
|
+
onRecoverableError: error => recoverableErrors.push(error),
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
await act(async () => {
|
|
45
|
+
root.unmount();
|
|
46
|
+
});
|
|
47
|
+
container.remove();
|
|
48
|
+
return recoverableErrors;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('DropdownMenu — SSR', () => {
|
|
52
|
+
it('hydrates a bottom-sheet menu cleanly on the first renders of a server process', async () => {
|
|
53
|
+
expect(await serverRenderThenHydrate()).toEqual([]);
|
|
54
|
+
expect(await serverRenderThenHydrate()).toEqual([]);
|
|
55
|
+
expect(await serverRenderThenHydrate()).toEqual([]);
|
|
56
|
+
}, 30000);
|
|
57
|
+
});
|
|
@@ -9,21 +9,31 @@
|
|
|
9
9
|
* SYNC: When DropdownMenu.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
describe,
|
|
14
|
+
it,
|
|
15
|
+
expect,
|
|
16
|
+
vi,
|
|
17
|
+
beforeAll,
|
|
18
|
+
beforeEach,
|
|
19
|
+
afterEach,
|
|
20
|
+
} from 'vitest';
|
|
13
21
|
import {readFileSync} from 'node:fs';
|
|
14
22
|
import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
|
|
15
23
|
import userEvent from '@testing-library/user-event';
|
|
16
|
-
import {useState} from 'react';
|
|
24
|
+
import {createRef, useState} from 'react';
|
|
17
25
|
import * as stylex from '@stylexjs/stylex';
|
|
18
26
|
import {DropdownMenu, type DropdownMenuTriggerProps} from './DropdownMenu';
|
|
19
27
|
import {DropdownMenuItem} from './DropdownMenuItem';
|
|
20
28
|
import {DropdownMenuDivider} from './DropdownMenuDivider';
|
|
21
29
|
import {DropdownMenuGroup} from './DropdownMenuGroup';
|
|
30
|
+
import {warmMenuBottomSheet} from './__tests__/warmMenuBottomSheet';
|
|
22
31
|
import {DropdownMenuSubMenu} from './DropdownMenuSubMenu';
|
|
23
32
|
import {Divider} from '../Divider';
|
|
24
33
|
import {rtlStyles} from '../utils';
|
|
25
34
|
import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
26
35
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
36
|
+
import {spacingVars} from '../theme/tokens.stylex';
|
|
27
37
|
|
|
28
38
|
// Mock showPopover and hidePopover methods since they're not implemented in jsdom
|
|
29
39
|
beforeEach(() => {
|
|
@@ -73,6 +83,10 @@ beforeEach(() => {
|
|
|
73
83
|
};
|
|
74
84
|
});
|
|
75
85
|
|
|
86
|
+
// The touch sheet is loaded lazily; resolve it once so sheets below render
|
|
87
|
+
// synchronously.
|
|
88
|
+
beforeAll(warmMenuBottomSheet, 20000);
|
|
89
|
+
|
|
76
90
|
describe('DropdownMenu', () => {
|
|
77
91
|
it('renders trigger button with label', () => {
|
|
78
92
|
render(
|
|
@@ -1553,10 +1567,9 @@ describe('DropdownMenuItem host attributes', () => {
|
|
|
1553
1567
|
|
|
1554
1568
|
describe('DropdownMenuGroup (compound mode)', () => {
|
|
1555
1569
|
it('a focusable node in the heading is not reachable by the arrow keys', async () => {
|
|
1556
|
-
//
|
|
1557
|
-
//
|
|
1558
|
-
//
|
|
1559
|
-
// pinned rather than discovered.
|
|
1570
|
+
// The public ReactNode type permits a focusable descendant, but the menu's
|
|
1571
|
+
// roving focus model cannot reach it. Pin that shipped gap while the public
|
|
1572
|
+
// title contract remains unsettled.
|
|
1560
1573
|
const user = userEvent.setup();
|
|
1561
1574
|
render(
|
|
1562
1575
|
<DropdownMenu button={{label: 'Actions'}}>
|
|
@@ -2508,6 +2521,66 @@ describe('DropdownMenu data/compound parity', () => {
|
|
|
2508
2521
|
expect(fromCompound).toHaveClass('astryx-dropdown-menu-divider');
|
|
2509
2522
|
});
|
|
2510
2523
|
|
|
2524
|
+
it('forwards host attributes to a compound divider while keeping its role', () => {
|
|
2525
|
+
render(
|
|
2526
|
+
<DropdownMenu button={{label: 'Actions'}}>
|
|
2527
|
+
<DropdownMenuItem label="Edit" />
|
|
2528
|
+
<DropdownMenuDivider
|
|
2529
|
+
id="menu-sep"
|
|
2530
|
+
data-testid="menu-separator"
|
|
2531
|
+
aria-label="Danger zone"
|
|
2532
|
+
// @ts-expect-error role is component-owned
|
|
2533
|
+
role="presentation"
|
|
2534
|
+
/>
|
|
2535
|
+
<DropdownMenuItem label="Delete" />
|
|
2536
|
+
</DropdownMenu>,
|
|
2537
|
+
);
|
|
2538
|
+
const separator = screen.getByTestId('menu-separator');
|
|
2539
|
+
expect(separator).toHaveAttribute('id', 'menu-sep');
|
|
2540
|
+
expect(separator).toHaveAttribute('aria-label', 'Danger zone');
|
|
2541
|
+
expect(separator).toHaveAttribute('role', 'separator');
|
|
2542
|
+
expect(separator).toHaveClass('astryx-dropdown-menu-divider');
|
|
2543
|
+
});
|
|
2544
|
+
|
|
2545
|
+
it('composes the complete public root surface on the separator', () => {
|
|
2546
|
+
const ref = createRef<HTMLDivElement>();
|
|
2547
|
+
const onClick = vi.fn();
|
|
2548
|
+
const customStyles = stylex.create({
|
|
2549
|
+
divider: {marginBlock: spacingVars['--spacing-5']},
|
|
2550
|
+
});
|
|
2551
|
+
const customClass = stylex
|
|
2552
|
+
.props(customStyles.divider)
|
|
2553
|
+
.className!.split(' ')
|
|
2554
|
+
.pop()!;
|
|
2555
|
+
|
|
2556
|
+
render(
|
|
2557
|
+
<DropdownMenu button={{label: 'Actions'}}>
|
|
2558
|
+
<DropdownMenuItem label="Edit" />
|
|
2559
|
+
<DropdownMenuDivider
|
|
2560
|
+
ref={ref}
|
|
2561
|
+
data-testid="menu-separator"
|
|
2562
|
+
className="consumer-divider"
|
|
2563
|
+
style={{backgroundColor: 'rgb(1, 2, 3)'}}
|
|
2564
|
+
xstyle={customStyles.divider}
|
|
2565
|
+
onClick={onClick}
|
|
2566
|
+
/>
|
|
2567
|
+
<DropdownMenuItem label="Delete" />
|
|
2568
|
+
</DropdownMenu>,
|
|
2569
|
+
);
|
|
2570
|
+
|
|
2571
|
+
const separator = screen.getByTestId('menu-separator');
|
|
2572
|
+
expect(ref.current).toBe(separator);
|
|
2573
|
+
expect(separator).toHaveClass(
|
|
2574
|
+
'astryx-dropdown-menu-divider',
|
|
2575
|
+
'astryx-divider',
|
|
2576
|
+
'consumer-divider',
|
|
2577
|
+
customClass,
|
|
2578
|
+
);
|
|
2579
|
+
expect(separator).toHaveStyle({backgroundColor: 'rgb(1, 2, 3)'});
|
|
2580
|
+
fireEvent.click(separator);
|
|
2581
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
2582
|
+
});
|
|
2583
|
+
|
|
2511
2584
|
it('skips a compound divider in the arrow-key order', async () => {
|
|
2512
2585
|
const user = userEvent.setup();
|
|
2513
2586
|
render(
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file DropdownMenu.tsx
|
|
7
|
-
* @input Uses React, StyleX, usePopover, MenuBottomSheet, Button, List,
|
|
7
|
+
* @input Uses React, StyleX, usePopover, MenuBottomSheet (lazy), Button, List,
|
|
8
8
|
* useListFocus, and the shared viewport-safe menu-width resolver
|
|
9
9
|
* @output Exports DropdownMenu with caller-selected popover or bottom-sheet
|
|
10
10
|
* presentation
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
} from './menuItemRoles';
|
|
61
61
|
import {
|
|
62
62
|
DropdownMenuContext,
|
|
63
|
+
DropdownMenuRootOpenContext,
|
|
63
64
|
type DropdownMenuContextValue,
|
|
64
65
|
} from './DropdownMenuContext';
|
|
65
66
|
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
|
|
@@ -71,7 +72,7 @@ import {useFocusReturnVisibility} from '../hooks/useFocusReturnVisibility';
|
|
|
71
72
|
import {focusForLastInput} from '../utils/focusReturn';
|
|
72
73
|
import {useMenuOverflow} from './useMenuOverflow';
|
|
73
74
|
import {MenuRegionsContext} from './menuRegionsContext';
|
|
74
|
-
import {
|
|
75
|
+
import {moveRegionsTab} from './menuRegionStops';
|
|
75
76
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
76
77
|
import {useMenuDrillIn} from './useMenuDrillIn';
|
|
77
78
|
import {resolveMenuWidth} from './menuWidth';
|
|
@@ -80,8 +81,11 @@ import {
|
|
|
80
81
|
useAdaptivePresentation,
|
|
81
82
|
type AdaptivePresentation,
|
|
82
83
|
} from '../hooks/useAdaptivePresentation';
|
|
83
|
-
|
|
84
|
-
import {
|
|
84
|
+
// Loaded on demand: only the bottom-sheet presentation renders the sheet.
|
|
85
|
+
import {
|
|
86
|
+
MenuBottomSheet,
|
|
87
|
+
MenuBottomSheetActionList,
|
|
88
|
+
} from './lazyMenuBottomSheet';
|
|
85
89
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
86
90
|
import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
|
|
87
91
|
import {
|
|
@@ -108,102 +112,6 @@ const MENU_MAX_BLOCK_SIZE = `min(${MENU_BLOCK_CAP}, ${layerViewportInset.maxBloc
|
|
|
108
112
|
const MENU_MAX_BLOCK_SIZE_FALLBACK = `min(${MENU_BLOCK_CAP}, ${layerViewportInset.maxBlockSizeFallback})`;
|
|
109
113
|
const MENU_TRIGGER_OPEN_BACKGROUND = `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`;
|
|
110
114
|
|
|
111
|
-
/**
|
|
112
|
-
* The elements Tab reaches inside a header or footer region, in order. Hidden
|
|
113
|
-
* controls (`hidden`, `display: none`, `visibility: hidden`, inert or
|
|
114
|
-
* aria-hidden subtrees) are not reachable, judged by the popover focus trap's
|
|
115
|
-
* own test so the two never disagree about where Tab can go.
|
|
116
|
-
*/
|
|
117
|
-
/**
|
|
118
|
-
* Collapse each named radio group to the one Tab stop a browser gives it: the
|
|
119
|
-
* focused radio, else the checked one, else the first radio moving forward or
|
|
120
|
-
* the last moving backward. The stop takes the group's first position.
|
|
121
|
-
*/
|
|
122
|
-
function collapseRadioGroups(
|
|
123
|
-
elements: HTMLElement[],
|
|
124
|
-
active: Element | null,
|
|
125
|
-
isBackward: boolean,
|
|
126
|
-
): HTMLElement[] {
|
|
127
|
-
const groups = new Map<string, HTMLInputElement[]>();
|
|
128
|
-
const groupKey = (el: HTMLElement): string | null =>
|
|
129
|
-
el instanceof HTMLInputElement &&
|
|
130
|
-
el.getAttribute('type') === 'radio' &&
|
|
131
|
-
el.name !== ''
|
|
132
|
-
? `${el.form?.id ?? ''}\u0000${el.name}`
|
|
133
|
-
: null;
|
|
134
|
-
for (const el of elements) {
|
|
135
|
-
const key = groupKey(el);
|
|
136
|
-
if (key != null) {
|
|
137
|
-
const members = groups.get(key) ?? [];
|
|
138
|
-
members.push(el as HTMLInputElement);
|
|
139
|
-
groups.set(key, members);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const placed = new Set<string>();
|
|
143
|
-
const result: HTMLElement[] = [];
|
|
144
|
-
for (const el of elements) {
|
|
145
|
-
const key = groupKey(el);
|
|
146
|
-
if (key == null) {
|
|
147
|
-
result.push(el);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (placed.has(key)) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
placed.add(key);
|
|
154
|
-
const members = groups.get(key) ?? [];
|
|
155
|
-
const stop =
|
|
156
|
-
members.find(radio => radio === active) ??
|
|
157
|
-
members.find(radio => radio.checked) ??
|
|
158
|
-
(isBackward ? members[members.length - 1] : members[0]);
|
|
159
|
-
result.push(stop);
|
|
160
|
-
}
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function tabbablesIn(region: HTMLElement | null): HTMLElement[] {
|
|
165
|
-
if (region == null) {
|
|
166
|
-
return [];
|
|
167
|
-
}
|
|
168
|
-
return getFocusableElements(region).filter(
|
|
169
|
-
element => element.tabIndex >= 0 && !isInsideHiddenWrapper(element, region),
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Whether a `display: none` ancestor between the element and its region hides
|
|
175
|
-
* it — a control in a wrapper hidden at some widths, say. The trap's test reads
|
|
176
|
-
* only the element's own display.
|
|
177
|
-
*/
|
|
178
|
-
function isInsideHiddenWrapper(
|
|
179
|
-
element: HTMLElement,
|
|
180
|
-
region: HTMLElement,
|
|
181
|
-
): boolean {
|
|
182
|
-
if (typeof window === 'undefined' || !window.getComputedStyle) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
for (
|
|
186
|
-
let node = element.parentElement;
|
|
187
|
-
node != null && node !== region.parentElement;
|
|
188
|
-
node = node.parentElement
|
|
189
|
-
) {
|
|
190
|
-
if (window.getComputedStyle(node).display === 'none') {
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Move focus to a Tab stop in the dialog. Reports whether focus actually
|
|
199
|
-
* arrived, so a caller can fall back to the menu instead of stranding focus on
|
|
200
|
-
* the page.
|
|
201
|
-
*/
|
|
202
|
-
function focusRegionControl(element: HTMLElement): boolean {
|
|
203
|
-
element.focus();
|
|
204
|
-
return element.ownerDocument.activeElement === element;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
115
|
const styles = stylex.create({
|
|
208
116
|
triggerOpen: {
|
|
209
117
|
backgroundImage: MENU_TRIGGER_OPEN_BACKGROUND,
|
|
@@ -758,12 +666,37 @@ function DropdownMenuBottomSheet({
|
|
|
758
666
|
if (!isOpen || openModalityRef.current !== 'keyboard') {
|
|
759
667
|
return;
|
|
760
668
|
}
|
|
761
|
-
const
|
|
762
|
-
actionListRef.current
|
|
763
|
-
|
|
764
|
-
|
|
669
|
+
const findFirstAction = () =>
|
|
670
|
+
actionListRef.current?.querySelector<HTMLElement>(
|
|
671
|
+
'button:not(:disabled), a[href]',
|
|
672
|
+
) ?? null;
|
|
673
|
+
let observer: MutationObserver | null = null;
|
|
674
|
+
let frame = requestAnimationFrame(() => {
|
|
675
|
+
const action = findFirstAction();
|
|
676
|
+
if (action != null) {
|
|
677
|
+
action.focus({preventScroll: true});
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
// The sheet loads on demand, so a keyboard open can arrive before its
|
|
681
|
+
// rows exist. Wait for them, then focus a frame later so the sheet's
|
|
682
|
+
// own open-focus has already run.
|
|
683
|
+
observer = new MutationObserver(() => {
|
|
684
|
+
const loaded = findFirstAction();
|
|
685
|
+
if (loaded == null) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
observer?.disconnect();
|
|
689
|
+
observer = null;
|
|
690
|
+
frame = requestAnimationFrame(() => {
|
|
691
|
+
loaded.focus({preventScroll: true});
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
observer.observe(document.body, {childList: true, subtree: true});
|
|
765
695
|
});
|
|
766
|
-
return () =>
|
|
696
|
+
return () => {
|
|
697
|
+
cancelAnimationFrame(frame);
|
|
698
|
+
observer?.disconnect();
|
|
699
|
+
};
|
|
767
700
|
}, [currentItems, isOpen]);
|
|
768
701
|
|
|
769
702
|
useEffect(() => {
|
|
@@ -987,6 +920,43 @@ function DropdownMenuPopover({
|
|
|
987
920
|
// above it ends the gesture in flight through this ref.
|
|
988
921
|
const cancelMenuPressRef = useRef<() => void>(() => {});
|
|
989
922
|
|
|
923
|
+
// Submenu flyouts are `popover="manual"` elements nested inside the menu's
|
|
924
|
+
// `popover="auto"` element, so the browser's light dismiss of the menu never
|
|
925
|
+
// reaches them: without a sweep they keep their open state and their
|
|
926
|
+
// top-layer entry while the menu is hidden, and re-anchor to the viewport
|
|
927
|
+
// corner when the menu reopens (#6893). Closing them through the popover
|
|
928
|
+
// API lets useLayer's toggle handling reset their state. Only flyouts that
|
|
929
|
+
// are actually open are touched: `:popover-open` ships wherever
|
|
930
|
+
// `hidePopover` does (they are one API), and the display fallback keeps its
|
|
931
|
+
// own open signal (`display: block`) so it is guarded the same way.
|
|
932
|
+
const hideOpenFlyouts = useCallback(() => {
|
|
933
|
+
const menuPopover = contentRef.current;
|
|
934
|
+
if (!menuPopover) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
// Innermost first: hiding a flyout may unmount the flyouts nested inside
|
|
938
|
+
// it, and a detached element no longer has the toggle listener that
|
|
939
|
+
// resets its open state.
|
|
940
|
+
const flyouts = [
|
|
941
|
+
...menuPopover.querySelectorAll<HTMLElement>('[popover]'),
|
|
942
|
+
].reverse();
|
|
943
|
+
for (const flyout of flyouts) {
|
|
944
|
+
if (typeof flyout.hidePopover === 'function') {
|
|
945
|
+
if (flyout.matches(':popover-open')) {
|
|
946
|
+
flyout.hidePopover();
|
|
947
|
+
}
|
|
948
|
+
} else if (flyout.style.display === 'block') {
|
|
949
|
+
flyout.style.display = 'none';
|
|
950
|
+
const toggle = new Event('toggle', {bubbles: false});
|
|
951
|
+
Object.defineProperty(toggle, 'newState', {
|
|
952
|
+
value: 'closed',
|
|
953
|
+
configurable: true,
|
|
954
|
+
});
|
|
955
|
+
flyout.dispatchEvent(toggle);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}, []);
|
|
959
|
+
|
|
990
960
|
// Focus lands somewhere stated when the menu closes. A press outside that
|
|
991
961
|
// landed on a focusable control has already moved focus there; leave it.
|
|
992
962
|
// Otherwise — a row acted, Escape, a press on nothing — focus returns to
|
|
@@ -994,6 +964,9 @@ function DropdownMenuPopover({
|
|
|
994
964
|
// keyboard (the modality tracker decides; the suppressed style hides the
|
|
995
965
|
// pointer case, which Safari would otherwise paint after a touch pick).
|
|
996
966
|
const handleLayerHide = useCallback(() => {
|
|
967
|
+
// Whatever closed the menu — light dismiss, the dismissal stack, Escape
|
|
968
|
+
// or a controlled close — its flyouts close with it.
|
|
969
|
+
hideOpenFlyouts();
|
|
997
970
|
if (suppressControlledRollbackHideRef.current) {
|
|
998
971
|
suppressControlledRollbackHideRef.current = false;
|
|
999
972
|
return;
|
|
@@ -1024,7 +997,7 @@ function DropdownMenuPopover({
|
|
|
1024
997
|
// also reaches a control the caller renders, which the browser would
|
|
1025
998
|
// otherwise ring (Chromium after a mouse pick, WebKit after a touch).
|
|
1026
999
|
focusForLastInput(trigger, {preventScroll: true});
|
|
1027
|
-
}, [isControlled, onOpenChange, prepareFocusReturn]);
|
|
1000
|
+
}, [hideOpenFlyouts, isControlled, onOpenChange, prepareFocusReturn]);
|
|
1028
1001
|
|
|
1029
1002
|
// Defer item focus until the layer has committed open, so focus restore
|
|
1030
1003
|
// captures the trigger instead of the first menu item.
|
|
@@ -1202,44 +1175,13 @@ function DropdownMenuPopover({
|
|
|
1202
1175
|
// second time.
|
|
1203
1176
|
const moveDialogTab = useCallback(
|
|
1204
1177
|
(e: React.KeyboardEvent, isFromFlyout = false) => {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
// A named radio group is one stop, as the browser's own Tab order has it.
|
|
1212
|
-
const cycle = collapseRadioGroups(
|
|
1213
|
-
[
|
|
1214
|
-
...tabbablesIn(headerRef.current),
|
|
1215
|
-
menu,
|
|
1216
|
-
...tabbablesIn(footerRef.current),
|
|
1217
|
-
],
|
|
1218
|
-
document.activeElement,
|
|
1219
|
-
e.shiftKey,
|
|
1178
|
+
moveRegionsTab(
|
|
1179
|
+
e,
|
|
1180
|
+
listRef.current,
|
|
1181
|
+
headerRef.current,
|
|
1182
|
+
footerRef.current,
|
|
1183
|
+
isFromFlyout,
|
|
1220
1184
|
);
|
|
1221
|
-
if (cycle.length === 1) {
|
|
1222
|
-
// Nothing else to reach: focus stays in the menu. From a flyout that
|
|
1223
|
-
// has just closed itself, that is the menu's own stop.
|
|
1224
|
-
if (isFromFlyout) {
|
|
1225
|
-
menu.focus();
|
|
1226
|
-
}
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
const active = document.activeElement;
|
|
1230
|
-
const menuIndex = cycle.indexOf(menu);
|
|
1231
|
-
// A row, or a row of an open flyout, counts as the menu's stop.
|
|
1232
|
-
let index = menu.contains(active)
|
|
1233
|
-
? menuIndex
|
|
1234
|
-
: cycle.findIndex(el => el === active || el.contains(active));
|
|
1235
|
-
if (index < 0) {
|
|
1236
|
-
index = menuIndex;
|
|
1237
|
-
}
|
|
1238
|
-
const step = e.shiftKey ? -1 : 1;
|
|
1239
|
-
const next = cycle[(index + step + cycle.length) % cycle.length];
|
|
1240
|
-
if (!focusRegionControl(next)) {
|
|
1241
|
-
menu.focus();
|
|
1242
|
-
}
|
|
1243
1185
|
},
|
|
1244
1186
|
[listRef],
|
|
1245
1187
|
);
|
|
@@ -1694,7 +1636,9 @@ function DropdownMenuPopover({
|
|
|
1694
1636
|
)}>
|
|
1695
1637
|
<MenuRegionsContext value={hasRegions}>
|
|
1696
1638
|
<DropdownMenuContext value={contextValue}>
|
|
1697
|
-
{
|
|
1639
|
+
<DropdownMenuRootOpenContext value={isOpen}>
|
|
1640
|
+
{menuContent}
|
|
1641
|
+
</DropdownMenuRootOpenContext>
|
|
1698
1642
|
</DropdownMenuContext>
|
|
1699
1643
|
</MenuRegionsContext>
|
|
1700
1644
|
</div>,
|
|
@@ -51,7 +51,7 @@ export const docs = {
|
|
|
51
51
|
name: 'icon',
|
|
52
52
|
type: 'IconType',
|
|
53
53
|
description:
|
|
54
|
-
'Icon to display before the label. See `
|
|
54
|
+
'Icon to display before the label. See `astryx docs icons` for valid semantic names.',
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
name: 'value',
|
|
@@ -98,7 +98,7 @@ export interface DropdownMenuCheckboxItemProps extends Omit<
|
|
|
98
98
|
description?: ReactNode;
|
|
99
99
|
/**
|
|
100
100
|
* Icon to display before the label. Accepts a semantic icon name (see
|
|
101
|
-
* `
|
|
101
|
+
* `astryx docs icons`) or a rendered node.
|
|
102
102
|
*/
|
|
103
103
|
icon?: ReactNode | IconType;
|
|
104
104
|
/**
|
|
@@ -65,6 +65,30 @@ export interface DropdownMenuContextValue {
|
|
|
65
65
|
menuLabel?: string;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Whether the root menu of this layer tree is open; internal companion to
|
|
70
|
+
* `DropdownMenuContext`, on its own unexported context so the public
|
|
71
|
+
* `DropdownMenuContextValue` surface stays unchanged. A sub-menu's hover
|
|
72
|
+
* intent must die with the menu: a hover-open scheduled on its trigger
|
|
73
|
+
* before the menu closed from elsewhere would otherwise fire into the
|
|
74
|
+
* hidden menu and reopen the flyout behind it (#6893). `null` means no
|
|
75
|
+
* signal, treated as open, matching providers that have no state to give.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
export const DropdownMenuRootOpenContext = createLayerScopedContext<
|
|
79
|
+
boolean | null
|
|
80
|
+
>(null);
|
|
81
|
+
DropdownMenuRootOpenContext.displayName = 'DropdownMenuRootOpenContext';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Whether the root menu backing these compound items is open. True when no
|
|
85
|
+
* provider gave a signal.
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export function useDropdownMenuRootOpen(): boolean {
|
|
89
|
+
return use(DropdownMenuRootOpenContext) !== false;
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
export const DropdownMenuContext =
|
|
69
93
|
createLayerScopedContext<DropdownMenuContextValue | null>(null);
|
|
70
94
|
DropdownMenuContext.displayName = 'DropdownMenuContext';
|
|
@@ -8,7 +8,7 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Dropdown Menu Divider',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description:
|
|
11
|
-
'A horizontal rule separating groups of rows in a compound menu. Renders role="separator", so it is never a stop in the arrow-key order. The data-driven equivalent is the `{type: "divider"}` entry in `items`; both modes render this component, so they look and theme identically.',
|
|
11
|
+
'A horizontal rule separating groups of rows in a compound menu. Renders role="separator", so it is never a stop in the arrow-key order. The data-driven equivalent is the `{type: "divider"}` entry in `items`; both modes render this component, so they look and theme identically. Host attributes and DOM handlers (id, data-*, aria-*) reach the separator; its role and orientation stay component-owned.',
|
|
12
12
|
props: [
|
|
13
13
|
{
|
|
14
14
|
name: 'xstyle',
|
|
@@ -27,7 +27,7 @@ export const docsDense = {
|
|
|
27
27
|
isHiddenFromOverview: true,
|
|
28
28
|
displayName: 'Dropdown Menu Divider',
|
|
29
29
|
description:
|
|
30
|
-
'separator row for compound menus; compound peer of the data API\'s {type: "divider"}',
|
|
30
|
+
'separator row for compound menus; compound peer of the data API\'s {type: "divider"}; host attributes reach the separator, role/orientation stay owned',
|
|
31
31
|
propDescriptions: {
|
|
32
32
|
xstyle: 'StyleX styles applied after the menu spacing',
|
|
33
33
|
},
|