@astryxdesign/core 0.7.2-canary.621784f → 0.7.2-canary.6a3ff0f

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.
Files changed (44) hide show
  1. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  2. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  3. package/dist/DropdownMenu/DropdownMenu.js +36 -7
  4. package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
  5. package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
  6. package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
  7. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
  8. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
  9. package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
  10. package/package.json +3 -3
  11. package/scripts/agent-doc-state.mjs +38 -0
  12. package/scripts/postinstall.mjs +1 -1
  13. package/src/Code/Code.doc.mjs +1 -0
  14. package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
  15. package/src/Drawer/Drawer.doc.mjs +1 -0
  16. package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
  17. package/src/DropdownMenu/DropdownMenu.test.tsx +17 -5
  18. package/src/DropdownMenu/DropdownMenu.tsx +36 -8
  19. package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
  20. package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
  21. package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
  22. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
  23. package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
  24. package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
  25. package/src/Field/FieldLabel.doc.mjs +133 -26
  26. package/src/Field/FieldLabel.spec.md +172 -0
  27. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
  28. package/src/Icon/Icon.doc.mjs +36 -0
  29. package/src/Layout/LayoutHeader.doc.mjs +5 -0
  30. package/src/MetadataList/MetadataList.doc.mjs +1 -1
  31. package/src/MoreMenu/MoreMenu.test.tsx +5 -1
  32. package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
  33. package/src/Resizable/useResizable.doc.mjs +2 -0
  34. package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
  35. package/src/SideNav/SideNav.spec.md +227 -0
  36. package/src/Spinner/Spinner.doc.mjs +1 -1
  37. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  38. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
  39. package/src/Toolbar/Toolbar.test.tsx +0 -8
  40. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
  41. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
  42. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
  43. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
  44. package/src/postinstall.test.mjs +46 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file DropdownMenu.tsx
3
- * @input Uses React, StyleX, usePopover, MenuBottomSheet, Button, List,
3
+ * @input Uses React, StyleX, usePopover, MenuBottomSheet (lazy), Button, List,
4
4
  * useListFocus, and the shared viewport-safe menu-width resolver
5
5
  * @output Exports DropdownMenu with caller-selected popover or bottom-sheet
6
6
  * presentation
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"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"}
@@ -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
@@ -58,8 +58,8 @@ import { resolveMenuWidth } from "./menuWidth.js";
58
58
  import "../Layer/layerViewportInset.stylex.js";
59
59
  import { layerViewportInset } from "../Layer/layerViewportInset.stylex.js";
60
60
  import { useAdaptivePresentation } from "../hooks/useAdaptivePresentation.js";
61
- import { MenuBottomSheet } from "./MenuBottomSheet.js";
62
- import { MenuBottomSheetActionList } from "./MenuBottomSheetActionList.js";
61
+ // Loaded on demand: only the bottom-sheet presentation renders the sheet.
62
+ import { MenuBottomSheet, MenuBottomSheetActionList } from "./lazyMenuBottomSheet.js";
63
63
  import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
64
64
  import "../theme/tokens.stylex.js";
65
65
  import { spacingVars, colorVars, radiusVars, durationVars, easeVars } from "../theme/tokens.stylex.js";
@@ -491,12 +491,41 @@ function DropdownMenuBottomSheet({
491
491
  if (!isOpen || openModalityRef.current !== 'keyboard') {
492
492
  return;
493
493
  }
494
- const frame = requestAnimationFrame(() => {
495
- actionListRef.current?.querySelector('button:not(:disabled), a[href]')?.focus({
496
- preventScroll: true
494
+ const findFirstAction = () => actionListRef.current?.querySelector('button:not(:disabled), a[href]') ?? null;
495
+ let observer = null;
496
+ let frame = requestAnimationFrame(() => {
497
+ const action = findFirstAction();
498
+ if (action != null) {
499
+ action.focus({
500
+ preventScroll: true
501
+ });
502
+ return;
503
+ }
504
+ // The sheet loads on demand, so a keyboard open can arrive before its
505
+ // rows exist. Wait for them, then focus a frame later so the sheet's
506
+ // own open-focus has already run.
507
+ observer = new MutationObserver(() => {
508
+ const loaded = findFirstAction();
509
+ if (loaded == null) {
510
+ return;
511
+ }
512
+ observer?.disconnect();
513
+ observer = null;
514
+ frame = requestAnimationFrame(() => {
515
+ loaded.focus({
516
+ preventScroll: true
517
+ });
518
+ });
519
+ });
520
+ observer.observe(document.body, {
521
+ childList: true,
522
+ subtree: true
497
523
  });
498
524
  });
499
- return () => cancelAnimationFrame(frame);
525
+ return () => {
526
+ cancelAnimationFrame(frame);
527
+ observer?.disconnect();
528
+ };
500
529
  }, [currentItems, isOpen]);
501
530
  useEffect(() => {
502
531
  if (!isOpen) {
@@ -23,7 +23,8 @@ import type { BaseProps } from '../BaseProps';
23
23
  export interface DropdownMenuGroupProps extends Pick<BaseProps, 'xstyle' | 'className' | 'style'> {
24
24
  /**
25
25
  * Heading shown above the group's rows and used as the group's accessible
26
- * name (`aria-labelledby`). Omit it for an unnamed group.
26
+ * name (`aria-labelledby`). Omit it for an unnamed group. Keep this content
27
+ * non-interactive; controls belong in DropdownMenu's `header` or `footer`.
27
28
  */
28
29
  title?: ReactNode;
29
30
  /** The rows of the group: menu items, selectable items, sub-menus. */
@@ -34,9 +35,10 @@ export interface DropdownMenuGroupProps extends Pick<BaseProps, 'xstyle' | 'clas
34
35
  /**
35
36
  * A titled group of menu rows.
36
37
  *
37
- * Renders `role="group"` named by its heading. The heading is plain text, not
38
- * a `menuitem`, so it is never a stop in the menu's arrow-key order and
39
- * typeahead never lands on it. The compound peer of
38
+ * Renders `role="group"` named by its heading. The heading is not a
39
+ * `menuitem`, so arrow-key navigation and typeahead skip it. Keep heading
40
+ * content non-interactive; place controls in DropdownMenu's `header` or
41
+ * `footer`. The compound peer of
40
42
  * `{type: 'section', title, items}` in the `items` data API.
41
43
  *
42
44
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenuGroup.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAQ,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAQ5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAgB5C,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAClD,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,GACJ,EAAE,sBAAsB,+BAuBxB;yBA9Be,iBAAiB"}
1
+ {"version":3,"file":"DropdownMenuGroup.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAQ,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAQ5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAgB5C,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAClD,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,GACJ,EAAE,sBAAsB,+BAuBxB;yBA9Be,iBAAiB"}
@@ -32,9 +32,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
32
32
  /**
33
33
  * A titled group of menu rows.
34
34
  *
35
- * Renders `role="group"` named by its heading. The heading is plain text, not
36
- * a `menuitem`, so it is never a stop in the menu's arrow-key order and
37
- * typeahead never lands on it. The compound peer of
35
+ * Renders `role="group"` named by its heading. The heading is not a
36
+ * `menuitem`, so arrow-key navigation and typeahead skip it. Keep heading
37
+ * content non-interactive; place controls in DropdownMenu's `header` or
38
+ * `footer`. The compound peer of
38
39
  * `{type: 'section', title, items}` in the `items` data API.
39
40
  *
40
41
  * @example
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @file lazyMenuBottomSheet.ts
3
+ * @input Uses React lazy and Suspense, useIsHydrating, MenuBottomSheet, and
4
+ * MenuBottomSheetActionList
5
+ * @output Exports MenuBottomSheet and MenuBottomSheetActionList wrappers that
6
+ * load the touch sheet on first render
7
+ * @position Internal to DropdownMenu
8
+ *
9
+ * The touch sheet (BottomSheet, its gestures, and the touch action list) only
10
+ * renders for the bottom-sheet presentation. Loading it on demand keeps it out
11
+ * of pages whose dropdowns only open as anchored popovers. Each wrapper keeps
12
+ * the wrapped component's props and renders nothing until its module loads.
13
+ *
14
+ * Neither wrapper renders on the server or in the render that hydrates server
15
+ * HTML (spec:AST-013 FR9): a lazy component that has not loaded yet would
16
+ * leave server markup incomplete and abort hydration. The closed sheet has no
17
+ * visible markup, so it first renders right after hydration.
18
+ */
19
+ import { type ComponentProps, type ReactElement } from 'react';
20
+ import type { MenuBottomSheet as EagerMenuBottomSheet } from './MenuBottomSheet';
21
+ import type { MenuBottomSheetActionList as EagerMenuBottomSheetActionList } from './MenuBottomSheetActionList';
22
+ export declare function MenuBottomSheet(props: ComponentProps<typeof EagerMenuBottomSheet>): ReactElement | null;
23
+ export declare function MenuBottomSheetActionList(props: ComponentProps<typeof EagerMenuBottomSheetActionList>): ReactElement | null;
24
+ //# sourceMappingURL=lazyMenuBottomSheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazyMenuBottomSheet.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/lazyMenuBottomSheet.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,eAAe,IAAI,oBAAoB,EAAC,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAC,yBAAyB,IAAI,8BAA8B,EAAC,MAAM,6BAA6B,CAAC;AAc7G,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,CAAC,OAAO,oBAAoB,CAAC,GACjD,YAAY,GAAG,IAAI,CASrB;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,cAAc,CAAC,OAAO,8BAA8B,CAAC,GAC3D,YAAY,GAAG,IAAI,CASrB"}
@@ -0,0 +1,46 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file lazyMenuBottomSheet.ts
7
+ * @input Uses React lazy and Suspense, useIsHydrating, MenuBottomSheet, and
8
+ * MenuBottomSheetActionList
9
+ * @output Exports MenuBottomSheet and MenuBottomSheetActionList wrappers that
10
+ * load the touch sheet on first render
11
+ * @position Internal to DropdownMenu
12
+ *
13
+ * The touch sheet (BottomSheet, its gestures, and the touch action list) only
14
+ * renders for the bottom-sheet presentation. Loading it on demand keeps it out
15
+ * of pages whose dropdowns only open as anchored popovers. Each wrapper keeps
16
+ * the wrapped component's props and renders nothing until its module loads.
17
+ *
18
+ * Neither wrapper renders on the server or in the render that hydrates server
19
+ * HTML (spec:AST-013 FR9): a lazy component that has not loaded yet would
20
+ * leave server markup incomplete and abort hydration. The closed sheet has no
21
+ * visible markup, so it first renders right after hydration.
22
+ */
23
+ import { createElement, lazy, Suspense } from 'react';
24
+ import { useIsHydrating } from "../hooks/useIsHydrating.js";
25
+ const LazyMenuBottomSheet = /*#__PURE__*/lazy(async () => import("./MenuBottomSheet.js").then(module => ({
26
+ default: module.MenuBottomSheet
27
+ })));
28
+ const LazyMenuBottomSheetActionList = /*#__PURE__*/lazy(async () => import("./MenuBottomSheetActionList.js").then(module => ({
29
+ default: module.MenuBottomSheetActionList
30
+ })));
31
+ export function MenuBottomSheet(props) {
32
+ if (useIsHydrating()) {
33
+ return null;
34
+ }
35
+ return /*#__PURE__*/createElement(Suspense, {
36
+ fallback: null
37
+ }, /*#__PURE__*/createElement(LazyMenuBottomSheet, props));
38
+ }
39
+ export function MenuBottomSheetActionList(props) {
40
+ if (useIsHydrating()) {
41
+ return null;
42
+ }
43
+ return /*#__PURE__*/createElement(Suspense, {
44
+ fallback: null
45
+ }, /*#__PURE__*/createElement(LazyMenuBottomSheetActionList, props));
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.2-canary.621784f",
3
+ "version": "0.7.2-canary.6a3ff0f",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -792,8 +792,8 @@
792
792
  "react-dom": ">=19.0.0"
793
793
  },
794
794
  "devDependencies": {
795
- "@astryxdesign/a11y-spec": "0.7.2-canary.621784f",
796
- "@astryxdesign/cli": "0.7.2-canary.621784f",
795
+ "@astryxdesign/a11y-spec": "0.7.2-canary.6a3ff0f",
796
+ "@astryxdesign/cli": "0.7.2-canary.6a3ff0f",
797
797
  "@babel/cli": "^7.29.7",
798
798
  "@babel/core": "^7.29.7",
799
799
  "@babel/preset-react": "^7.29.7",
@@ -106,6 +106,44 @@ export function isAstryxInitialized(targetDir = process.cwd()) {
106
106
  return false;
107
107
  }
108
108
 
109
+ /**
110
+ * Does an agent working in `startDir` already have the Astryx prompt in reach?
111
+ *
112
+ * Coding agents read the agent docs between the repository root and the folder
113
+ * they work in. So inside a git repository, init's block in any folder from
114
+ * `startDir` up to the repository root counts: a workspace package of a
115
+ * monorepo initialized at its root is set up. Outside a repository only
116
+ * `startDir` itself is checked, as {@link isAstryxInitialized} does. Both the
117
+ * per-command nudge and the postinstall nudges ask this.
118
+ *
119
+ * @param {string} [startDir=process.cwd()]
120
+ * @returns {boolean}
121
+ */
122
+ export function isAstryxPromptInReach(startDir = process.cwd()) {
123
+ const start = path.resolve(startDir);
124
+ const repositoryRoot = findRepositoryRoot(start);
125
+ if (!repositoryRoot) return isAstryxInitialized(start);
126
+ for (let dir = start; ; dir = path.dirname(dir)) {
127
+ if (isAstryxInitialized(dir)) return true;
128
+ if (dir === repositoryRoot) return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * The nearest folder at or above `start` that holds `.git` (a folder, or the
134
+ * file a worktree uses), or null outside a repository.
135
+ * @param {string} start
136
+ * @returns {string | null}
137
+ */
138
+ function findRepositoryRoot(start) {
139
+ for (let dir = start; ; ) {
140
+ if (fs.existsSync(path.join(dir, '.git'))) return dir;
141
+ const parent = path.dirname(dir);
142
+ if (parent === dir) return null;
143
+ dir = parent;
144
+ }
145
+ }
146
+
109
147
  /**
110
148
  * The one-line next-step both postinstall nudges print. Kept here so layer 1
111
149
  * (core) and layer 2 (cli) can never word it differently.
@@ -45,7 +45,7 @@ async function main() {
45
45
  contract.shouldNudge({
46
46
  scriptPath: HERE,
47
47
  npmCommand: process.env.npm_command,
48
- isSetUp: contract.isAstryxInitialized(root),
48
+ isSetUp: contract.isAstryxPromptInReach(root),
49
49
  })
50
50
  ) {
51
51
  process.stdout.write(contract.SETUP_NUDGE);
@@ -4,6 +4,7 @@
4
4
  export const docs = {
5
5
  name: 'Code',
6
6
  displayName: 'Code',
7
+ keywords: ['inlinecode', 'monospace'],
7
8
  group: 'Code',
8
9
  category: 'Content',
9
10
  description:
@@ -87,6 +87,8 @@ export const docs = {
87
87
  'modal',
88
88
  'dialog',
89
89
  'navigation',
90
+ 'commandmenu',
91
+ 'cmdk',
90
92
  ],
91
93
  description:
92
94
  'Root component. Manages open state, search, keyboard navigation, and composition slots.',
@@ -55,6 +55,7 @@ export const docs = {
55
55
  'sidebar',
56
56
  'dialog',
57
57
  'side drawer',
58
+ 'sidesheet',
58
59
  ],
59
60
  // No isInline docs-preview prop (the drawer has no inline containment), so
60
61
  // this is the overlay preview: closed on load with an open trigger, knobs
@@ -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,7 +9,15 @@
9
9
  * SYNC: When DropdownMenu.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
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';
@@ -19,6 +27,7 @@ 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';
@@ -74,6 +83,10 @@ beforeEach(() => {
74
83
  };
75
84
  });
76
85
 
86
+ // The touch sheet is loaded lazily; resolve it once so sheets below render
87
+ // synchronously.
88
+ beforeAll(warmMenuBottomSheet, 20000);
89
+
77
90
  describe('DropdownMenu', () => {
78
91
  it('renders trigger button with label', () => {
79
92
  render(
@@ -1554,10 +1567,9 @@ describe('DropdownMenuItem host attributes', () => {
1554
1567
 
1555
1568
  describe('DropdownMenuGroup (compound mode)', () => {
1556
1569
  it('a focusable node in the heading is not reachable by the arrow keys', async () => {
1557
- // A heading is not a menu row, so a control inside one lands in the
1558
- // group but outside the roving focus order. The type does not prevent
1559
- // this — a rich heading is a legitimate need — so the behavior is
1560
- // 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.
1561
1573
  const user = userEvent.setup();
1562
1574
  render(
1563
1575
  <DropdownMenu button={{label: 'Actions'}}>
@@ -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
@@ -81,8 +81,11 @@ import {
81
81
  useAdaptivePresentation,
82
82
  type AdaptivePresentation,
83
83
  } from '../hooks/useAdaptivePresentation';
84
- import {MenuBottomSheet} from './MenuBottomSheet';
85
- import {MenuBottomSheetActionList} from './MenuBottomSheetActionList';
84
+ // Loaded on demand: only the bottom-sheet presentation renders the sheet.
85
+ import {
86
+ MenuBottomSheet,
87
+ MenuBottomSheetActionList,
88
+ } from './lazyMenuBottomSheet';
86
89
  import {layerAnimations} from '../Layer/layerAnimations.stylex';
87
90
  import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
88
91
  import {
@@ -759,12 +762,37 @@ function DropdownMenuBottomSheet({
759
762
  if (!isOpen || openModalityRef.current !== 'keyboard') {
760
763
  return;
761
764
  }
762
- const frame = requestAnimationFrame(() => {
763
- actionListRef.current
764
- ?.querySelector<HTMLElement>('button:not(:disabled), a[href]')
765
- ?.focus({preventScroll: true});
765
+ const findFirstAction = () =>
766
+ actionListRef.current?.querySelector<HTMLElement>(
767
+ 'button:not(:disabled), a[href]',
768
+ ) ?? null;
769
+ let observer: MutationObserver | null = null;
770
+ let frame = requestAnimationFrame(() => {
771
+ const action = findFirstAction();
772
+ if (action != null) {
773
+ action.focus({preventScroll: true});
774
+ return;
775
+ }
776
+ // The sheet loads on demand, so a keyboard open can arrive before its
777
+ // rows exist. Wait for them, then focus a frame later so the sheet's
778
+ // own open-focus has already run.
779
+ observer = new MutationObserver(() => {
780
+ const loaded = findFirstAction();
781
+ if (loaded == null) {
782
+ return;
783
+ }
784
+ observer?.disconnect();
785
+ observer = null;
786
+ frame = requestAnimationFrame(() => {
787
+ loaded.focus({preventScroll: true});
788
+ });
789
+ });
790
+ observer.observe(document.body, {childList: true, subtree: true});
766
791
  });
767
- return () => cancelAnimationFrame(frame);
792
+ return () => {
793
+ cancelAnimationFrame(frame);
794
+ observer?.disconnect();
795
+ };
768
796
  }, [currentItems, isOpen]);
769
797
 
770
798
  useEffect(() => {