@dxos/react-ui-menu 0.11.0 → 0.12.0-next-2be9f24c465d98f4bd186aa03ece788902e6c17a

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 (51) hide show
  1. package/dist/lib/index.mjs +82 -60
  2. package/dist/lib/index.mjs.map +1 -1
  3. package/dist/types/src/components/ActionLabel.d.ts +2 -2
  4. package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
  5. package/dist/types/src/components/DropdownMenu.d.ts +3 -1
  6. package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
  7. package/dist/types/src/components/Menu.d.ts +23 -3
  8. package/dist/types/src/components/Menu.d.ts.map +1 -1
  9. package/dist/types/src/components/Menu.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/MenuContext.d.ts +2 -2
  11. package/dist/types/src/components/MenuContext.d.ts.map +1 -1
  12. package/dist/types/src/components/ToolbarMenu.d.ts +6 -0
  13. package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
  14. package/dist/types/src/components/ToolbarMenu.stories.d.ts +5 -0
  15. package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/action-label.d.ts +2 -2
  17. package/dist/types/src/components/action-label.d.ts.map +1 -1
  18. package/dist/types/src/hooks/useGraphMenuActions.d.ts +8 -7
  19. package/dist/types/src/hooks/useGraphMenuActions.d.ts.map +1 -1
  20. package/dist/types/src/hooks/useMenuActions.d.ts +6 -5
  21. package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
  22. package/dist/types/src/presentation.d.ts +2 -2
  23. package/dist/types/src/presentation.d.ts.map +1 -1
  24. package/dist/types/src/testing/generator.d.ts +5 -4
  25. package/dist/types/src/testing/generator.d.ts.map +1 -1
  26. package/dist/types/src/toolbar.d.ts +2 -2
  27. package/dist/types/src/toolbar.d.ts.map +1 -1
  28. package/dist/types/src/types.d.ts +10 -10
  29. package/dist/types/src/types.d.ts.map +1 -1
  30. package/dist/types/src/util.d.ts +6 -6
  31. package/dist/types/src/util.d.ts.map +1 -1
  32. package/dist/types/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +22 -24
  34. package/src/builder.test.ts +4 -4
  35. package/src/components/ActionLabel.tsx +2 -2
  36. package/src/components/DropdownMenu.tsx +10 -3
  37. package/src/components/Menu.stories.tsx +1 -1
  38. package/src/components/Menu.test.tsx +1 -1
  39. package/src/components/Menu.tsx +7 -3
  40. package/src/components/MenuContext.ts +2 -1
  41. package/src/components/ToolbarMenu.stories.tsx +34 -5
  42. package/src/components/ToolbarMenu.tsx +47 -28
  43. package/src/components/action-label.ts +2 -2
  44. package/src/hooks/useGraphMenuActions.test.ts +14 -13
  45. package/src/hooks/useGraphMenuActions.ts +12 -10
  46. package/src/hooks/useMenuActions.ts +17 -9
  47. package/src/presentation.ts +2 -2
  48. package/src/testing/generator.ts +21 -19
  49. package/src/toolbar.ts +3 -2
  50. package/src/types.ts +11 -10
  51. package/src/util.ts +8 -8
@@ -1,19 +1,22 @@
1
- import { Atom, RegistryContext, useAtomValue } from "@effect-atom/atom-react";
1
+ import { useAtomValue } from "@effect/atom-react/Hooks";
2
2
  import { createContextScope } from "@radix-ui/react-context";
3
+ import * as Atom from "effect/unstable/reactivity/Atom";
3
4
  import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
4
5
  import { log } from "@dxos/log";
5
6
  import { keySymbols } from "@dxos/keyboard";
6
7
  import { DropdownMenu, Icon, Input, Toolbar, Tooltip, composable, composableProps, toLocalizedString, useTranslation } from "@dxos/react-ui";
7
8
  import * as Effect from "effect/Effect";
8
- import { Graph, Node } from "@dxos/app-graph";
9
+ import * as AppGraphNode from "@dxos/app-graph/AppGraphNode";
9
10
  import { EffectEx } from "@dxos/effect";
10
11
  import { getHostPlatform } from "@dxos/util";
12
+ import { RegistryContext } from "@effect/atom-react/RegistryContext";
11
13
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
12
14
  import { mx } from "@dxos/ui-theme";
13
15
  import { translationKey } from "#translations";
14
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
17
  import { useAttention } from "@dxos/react-ui-attention";
16
- import { Atom as Atom$1 } from "@effect-atom/atom";
18
+ import * as GraphNode from "@dxos/graph/GraphNode";
19
+ import * as AppGraph from "@dxos/app-graph/AppGraph";
17
20
  import { invariant } from "@dxos/invariant";
18
21
  //#region src/components/MenuContext.ts
19
22
  var __dxlog_file$1 = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/components/MenuContext.ts";
@@ -45,7 +48,7 @@ var resolveItems = (baseItems, group, entries) => {
45
48
  }, {
46
49
  "~LogMeta": "~LogMeta",
47
50
  F: __dxlog_file$1,
48
- L: 84,
51
+ L: 85,
49
52
  S: void 0
50
53
  });
51
54
  return replacements[0].items;
@@ -85,15 +88,15 @@ var getShortcut = (action) => {
85
88
  };
86
89
  var createMenuAction = (id, invoke, properties) => ({
87
90
  id,
88
- type: Node.ActionType,
91
+ type: AppGraphNode.ActionType,
89
92
  properties,
90
93
  data: (params) => Effect.sync(() => invoke(params))
91
94
  });
92
95
  var createMenuItemGroup = (id, properties) => ({
93
96
  id,
94
- type: Node.ActionGroupType,
97
+ type: AppGraphNode.ActionGroupType,
95
98
  properties,
96
- data: Node.actionGroupSymbol
99
+ data: AppGraphNode.actionGroupSymbol
97
100
  });
98
101
  var createGapSeparator = (id = "gap", source = "root") => ({
99
102
  nodes: [{
@@ -131,7 +134,7 @@ var actionLabel = (action, t) => {
131
134
  //#region src/types.ts
132
135
  var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
133
136
  var isSeparator = (node) => node.type === MenuSeparatorType;
134
- var isMenuGroup = (node) => node.type === Node.ActionGroupType;
137
+ var isMenuGroup = (node) => node.type === AppGraphNode.ActionGroupType;
135
138
  //#endregion
136
139
  //#region src/components/ActionLabel.tsx
137
140
  var ActionLabel = ({ action }) => {
@@ -152,9 +155,12 @@ var DropdownMenuItem = ({ item, group, onClick, __menuScope }) => {
152
155
  const handleClick = useCallback((event) => onClick(action, event), [action, onClick]);
153
156
  const { iconSize } = useMenuScoped("DropdownMenuItem", __menuScope);
154
157
  const checkable = typeof action.properties?.checked === "boolean";
155
- const role = group?.properties?.selectCardinality === "multiple" ? "menuitemcheckbox" : "menuitemradio";
158
+ const multiple = group?.properties?.selectCardinality === "multiple";
159
+ const role = multiple ? "menuitemcheckbox" : "menuitemradio";
160
+ const handleSelect = useCallback((event) => multiple && event.preventDefault(), [multiple]);
156
161
  return /* @__PURE__ */ jsxs(DropdownMenu.Item, {
157
162
  onClick: handleClick,
163
+ onSelect: handleSelect,
158
164
  classNames: "gap-2",
159
165
  disabled: action.properties?.disabled,
160
166
  ...checkable && {
@@ -172,7 +178,7 @@ var DropdownMenuItem = ({ item, group, onClick, __menuScope }) => {
172
178
  action.properties?.checked && /* @__PURE__ */ jsx(Icon, {
173
179
  icon: "ph--check--regular",
174
180
  size: iconSize,
175
- classNames: "mis-auto"
181
+ classNames: "ms-auto"
176
182
  })
177
183
  ]
178
184
  });
@@ -187,7 +193,7 @@ var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenCha
187
193
  const handleActionClick = useCallback((action, event) => {
188
194
  if (action.properties?.disabled) return;
189
195
  event.stopPropagation();
190
- setOptionsMenuOpen(false);
196
+ if (group?.properties?.selectCardinality !== "multiple") setOptionsMenuOpen(false);
191
197
  const params = {
192
198
  parent: group,
193
199
  caller,
@@ -290,28 +296,30 @@ var DropdownMenuToolbarItem = ({ __menuScope, group, items: propsItems }) => {
290
296
  const iconClassNames = applyActive && activeItem?.properties.iconClassNames || groupIconClassNames;
291
297
  const spin = applyActive && activeItem?.properties.spin || groupSpin;
292
298
  const labelAction = applyActive && activeItem ? activeItem : group;
299
+ const trigger = icon ? /* @__PURE__ */ jsx(Toolbar.IconButton, {
300
+ variant: "ghost",
301
+ disabled,
302
+ icon,
303
+ size: iconSize,
304
+ iconOnly,
305
+ iconClassNames: mx(spin && "animate-spin", iconClassNames),
306
+ label: actionLabel(labelAction, t),
307
+ caretDown: caretDown && !disabled,
308
+ ...testId && { "data-testid": testId }
309
+ }) : /* @__PURE__ */ jsx(Toolbar.Button, {
310
+ variant: "ghost",
311
+ disabled,
312
+ caretDown: caretDown && !disabled,
313
+ ...testId && { "data-testid": testId },
314
+ children: /* @__PURE__ */ jsx(ActionLabel, { action: labelAction })
315
+ });
316
+ if (disabled) return trigger;
293
317
  return /* @__PURE__ */ jsx(DropdownMenu$1.Root, {
294
318
  group,
295
319
  items,
296
320
  children: /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, {
297
321
  asChild: true,
298
- children: icon ? /* @__PURE__ */ jsx(Toolbar.IconButton, {
299
- variant: "ghost",
300
- disabled,
301
- icon,
302
- size: iconSize,
303
- iconOnly,
304
- iconClassNames: mx(spin && "animate-spin", iconClassNames),
305
- label: actionLabel(labelAction, t),
306
- caretDown,
307
- ...testId && { "data-testid": testId }
308
- }) : /* @__PURE__ */ jsx(Toolbar.Button, {
309
- variant: "ghost",
310
- disabled,
311
- caretDown,
312
- ...testId && { "data-testid": testId },
313
- children: /* @__PURE__ */ jsx(ActionLabel, { action: labelAction })
314
- })
322
+ children: trigger
315
323
  })
316
324
  });
317
325
  };
@@ -365,20 +373,28 @@ var ToggleGroupToolbarItem = ({ __menuScope, group, items: itemsProp }) => {
365
373
  children
366
374
  });
367
375
  };
376
+ /**
377
+ * Attention-gated toolbar container with no graph items of its own — render {@link ToolbarMenuItems}
378
+ * among its children, whose JSX order controls where the graph items sit.
379
+ */
368
380
  var ToolbarMenu = composable(({ __menuScope, children, ...props }, forwardedRef) => {
369
- const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
370
381
  const { attendableId, alwaysActive } = useMenuScoped("ToolbarMenu", __menuScope);
371
382
  const { hasAttention } = useAttention(attendableId);
372
- return /* @__PURE__ */ jsxs(Toolbar.Root, {
383
+ return /* @__PURE__ */ jsx(Toolbar.Root, {
373
384
  ...composableProps(props, { classNames: attendableId }),
374
385
  disabled: !alwaysActive && !hasAttention,
375
386
  ref: forwardedRef,
376
- children: [items?.map((item) => /* @__PURE__ */ jsx(ToolbarMenuItem, {
377
- __menuScope,
378
- item
379
- }, item.id)), children]
387
+ children
380
388
  });
381
389
  });
390
+ /** The menu graph's toolbar items, container-free, so JSX order controls their placement. */
391
+ var ToolbarMenuItems = ({ __menuScope }) => {
392
+ const items = useMenuItems(void 0, void 0, "ToolbarMenuItems", __menuScope);
393
+ return /* @__PURE__ */ jsx(Fragment, { children: items?.map((item) => /* @__PURE__ */ jsx(ToolbarMenuItem, {
394
+ __menuScope,
395
+ item
396
+ }, item.id)) });
397
+ };
382
398
  var ToolbarMenuItem = ({ __menuScope, item }) => {
383
399
  if (isSeparator(item)) return /* @__PURE__ */ jsx(Toolbar.Separator, { variant: item.properties.variant });
384
400
  if (isMenuGroup(item)) {
@@ -409,28 +425,30 @@ var MenuProvider = ({ children, items = menuContextDefaults.items, iconSize = me
409
425
  const { scope } = useMenuScope(void 0);
410
426
  const registry = useContext(RegistryContext);
411
427
  const menuItemsAtom = useMemo(() => Atom.make(/* @__PURE__ */ new Map()).pipe(Atom.keepAlive), []);
428
+ const addMenuItems = useCallback((props) => {
429
+ const priority = props.priority ?? DEFAULT_PRIORITY;
430
+ const prev = registry.get(menuItemsAtom);
431
+ const next = new Map(prev);
432
+ next.set(props.id, {
433
+ ...props,
434
+ priority
435
+ });
436
+ registry.set(menuItemsAtom, next);
437
+ }, [registry, menuItemsAtom]);
438
+ const removeMenuItems = useCallback((id) => {
439
+ const prev = registry.get(menuItemsAtom);
440
+ const next = new Map(prev);
441
+ next.delete(id);
442
+ registry.set(menuItemsAtom, next);
443
+ }, [registry, menuItemsAtom]);
412
444
  return /* @__PURE__ */ jsx(MenuContextProvider, {
413
445
  items,
414
446
  iconSize,
415
447
  attendableId,
416
448
  alwaysActive,
417
449
  menuItemsAtom,
418
- addMenuItems: useCallback((props) => {
419
- const priority = props.priority ?? DEFAULT_PRIORITY;
420
- const prev = registry.get(menuItemsAtom);
421
- const next = new Map(prev);
422
- next.set(props.id, {
423
- ...props,
424
- priority
425
- });
426
- registry.set(menuItemsAtom, next);
427
- }, [registry, menuItemsAtom]),
428
- removeMenuItems: useCallback((id) => {
429
- const prev = registry.get(menuItemsAtom);
430
- const next = new Map(prev);
431
- next.delete(id);
432
- registry.set(menuItemsAtom, next);
433
- }, [registry, menuItemsAtom]),
450
+ addMenuItems,
451
+ removeMenuItems,
434
452
  onAction,
435
453
  scope,
436
454
  children
@@ -521,14 +539,17 @@ var MenuContent = ({ group, items: propsItems, caller: callerOverride, __menuSco
521
539
  * - `Menu.Root` — context boundary (replaces `MenuProvider`); also provides a dropdown root.
522
540
  * - `Menu.Trigger` / `Menu.VirtualTrigger` — dropdown trigger (use with `Menu.Content`).
523
541
  * - `Menu.Content` — renders graph-backed dropdown items inside a portal.
524
- * - `Menu.Toolbar` — flat toolbar component with graph-backed item rendering.
542
+ * - `Menu.Toolbar` — attention-gated toolbar container; renders only its children.
543
+ * - `Menu.Items` — the graph-backed toolbar items; place it among `Menu.Toolbar`'s children,
544
+ * whose JSX order controls where the items sit.
525
545
  */
526
546
  var Menu = {
527
547
  Root: MenuRoot,
528
548
  Trigger: DropdownMenu.Trigger,
529
549
  Content: MenuContent,
530
550
  VirtualTrigger: DropdownMenu.VirtualTrigger,
531
- Toolbar: ToolbarMenu
551
+ Toolbar: ToolbarMenu,
552
+ Items: ToolbarMenuItems
532
553
  };
533
554
  //#endregion
534
555
  //#region src/presentation.ts
@@ -554,12 +575,13 @@ var useMenuActions = (props) => {
554
575
  const registry = useContext(RegistryContext);
555
576
  const menuGraphProps = useAtomValue(props);
556
577
  const graph = useMemo(() => {
557
- const newGraph = Graph.make({ registry });
558
- newGraph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
578
+ const newGraph = AppGraph.make({ registry });
579
+ AppGraph.addNodes(newGraph, menuGraphProps.nodes);
580
+ AppGraph.addEdges(newGraph, menuGraphProps.edges);
559
581
  return newGraph;
560
582
  }, [registry, menuGraphProps]);
561
583
  return { items: useCallback((group) => {
562
- return graph.connections(group?.id || Node.RootId, "child");
584
+ return graph.connections(group?.id || GraphNode.RootId, "child");
563
585
  }, [graph]) };
564
586
  };
565
587
  /**
@@ -576,7 +598,7 @@ var useMenuBuilder = (build, deps) => {
576
598
  //#endregion
577
599
  //#region src/hooks/useGraphMenuActions.ts
578
600
  /**
579
- * Flatten a node's contributed actions (and nested {@link Node.ActionGroup}s) into the
601
+ * Flatten a node's contributed actions (and nested {@link AppGraphNode.ActionGroup}s) into the
580
602
  * `{ nodes, edges }` an {@link useMenuActions} / `Menu` consumes. The action nodes are spliced
581
603
  * verbatim, so their Effect `data` (and captured `_actionContext`) execute via `executeMenuAction`
582
604
  * without any `onAction` wiring.
@@ -585,7 +607,7 @@ var useMenuBuilder = (build, deps) => {
585
607
  * direct actions for `id` (a node id at the top level, a group id when expanding a group).
586
608
  */
587
609
  var buildGraphMenu = (resolve, nodeId, options = {}) => {
588
- const { rootId = Node.RootId, filter, surface } = options;
610
+ const { rootId = GraphNode.RootId, filter, surface } = options;
589
611
  const nodes = [];
590
612
  const edges = [];
591
613
  const seen = /* @__PURE__ */ new Set();
@@ -600,7 +622,7 @@ var buildGraphMenu = (resolve, nodeId, options = {}) => {
600
622
  if (seen.has(action.id)) continue;
601
623
  seen.add(action.id);
602
624
  nodes.push(surface ? applyPresentation(action, surface) : action);
603
- if (Node.isActionGroup(action)) visit(action.id, action.id);
625
+ if (AppGraphNode.isActionGroup(action)) visit(action.id, action.id);
604
626
  }
605
627
  };
606
628
  visit(rootId, nodeId);
@@ -629,7 +651,7 @@ var graphActions = (graph, get, nodeId, options) => graph && nodeId ? buildGraph
629
651
  * the graph offers for this object" toolbar/menu. Compose with hand-built items via {@link graphActions}.
630
652
  */
631
653
  var useGraphMenuActions = (graph, nodeId, options) => {
632
- return useMenuActions(useMemo(() => Atom$1.make((get) => graphActions(graph, get, nodeId, options)), [
654
+ return useMenuActions(useMemo(() => Atom.make((get) => graphActions(graph, get, nodeId, options)), [
633
655
  graph,
634
656
  nodeId,
635
657
  options?.rootId
@@ -740,7 +762,7 @@ var MenuBuilder = Object.freeze({ make: () => new MenuBuilderImpl({
740
762
  /** `disposition` value an action opts into to appear in an object toolbar (vs context-menu-only). */
741
763
  var TOOLBAR_DISPOSITION = "toolbar";
742
764
  /** Filter for {@link graphActions}: keeps only actions a producer opted into the toolbar. */
743
- var isToolbarAction = (action) => Node.hasDisposition(action, TOOLBAR_DISPOSITION);
765
+ var isToolbarAction = (action) => AppGraphNode.hasDisposition(action, TOOLBAR_DISPOSITION);
744
766
  //#endregion
745
767
  export { Menu, MenuBuilder, MenuSeparatorType, TOOLBAR_DISPOSITION, actionLabel, applyPresentation, buildGraphMenu, createGapSeparator, createLineSeparator, createMenuAction, createMenuItemGroup, graphActions, isMenuGroup, isSeparator, isToolbarAction, menuContextDefaults, useGraphMenuActions, useMenu, useMenuActions, useMenuBuilder, useMenuItems, useMenuScoped };
746
768
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/MenuContext.ts","../../src/util.ts","../../src/components/action-label.ts","../../src/types.ts","../../src/components/ActionLabel.tsx","../../src/components/DropdownMenu.tsx","../../src/components/ToolbarMenu.tsx","../../src/components/Menu.tsx","../../src/presentation.ts","../../src/hooks/useMenuActions.ts","../../src/hooks/useGraphMenuActions.ts","../../src/builder.ts","../../src/toolbar.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Atom, useAtomValue } from '@effect-atom/atom-react';\nimport { type Scope, createContextScope } from '@radix-ui/react-context';\nimport { createContext, useMemo } from 'react';\n\nimport { log } from '@dxos/log';\n\nimport {\n type MenuContextValue,\n type MenuGroupContext,\n type MenuItem,\n type MenuItems,\n type MenuItemsAccessor,\n type MenuItemsMap,\n} from '../types';\n\n// Kept out of `Menu.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so contexts and hooks exported beside them force a full page reload on every edit.\n\n//\n// Scoped context.\n//\n\nexport type MenuScopedProps<P> = P & { __menuScope?: Scope };\n\nexport const MENU_NAME = 'Menu';\n\nexport const [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);\n\nconst nullItemsAtom = Atom.make<MenuItem[] | null>(null);\nconst defaultItemsAccessor: MenuItemsAccessor = () => nullItemsAtom;\n\nexport const menuContextDefaults: MenuContextValue = {\n iconSize: 5,\n items: defaultItemsAccessor,\n onAction: undefined,\n menuItemsAtom: Atom.make<MenuItemsMap>(new Map()),\n addMenuItems: () => {},\n removeMenuItems: () => {},\n};\n\nexport const [MenuContextProvider, useMenuScoped] = createMenuContext<MenuContextValue>(MENU_NAME, menuContextDefaults);\n\nexport const useMenuScope = createMenuScope();\n\n//\n// Dropdown context (internal) — allows Menu.Content to close the parent dropdown.\n//\n\nexport type MenuDropdownContextValue = {\n closeMenu: () => void;\n caller?: string;\n};\n\nexport const MenuDropdownContext = createContext<MenuDropdownContextValue>({\n closeMenu: () => {},\n});\n\n//\n// Item resolution.\n//\n\nconst sortMenuItems = (items: MenuItems[]) =>\n [...items].sort((a, b) => (a.priority !== b.priority ? a.priority - b.priority : a.id.localeCompare(b.id)));\n\nexport const resolveItems = (\n baseItems: MenuItem[] | null,\n group: MenuGroupContext | undefined,\n entries: ReadonlyMap<string, MenuItems>,\n): MenuItem[] | null => {\n const applicable = [...entries.values()].filter((entry) => !entry.groupFilter || entry.groupFilter(group));\n if (applicable.length === 0) {\n return baseItems;\n }\n\n const sorted = sortMenuItems(applicable);\n\n const replacements = sorted.filter((entry) => entry.mode === 'replacement');\n if (replacements.length > 0) {\n if (replacements.length > 1) {\n log.warn('multiple replacement entries found', {\n ids: replacements.map((r) => r.id).join(', '),\n using: replacements[0].id,\n });\n }\n return replacements[0].items;\n }\n\n const additive = sorted.filter((entry) => entry.mode === 'additive');\n const additiveItems = additive.flatMap((entry) => entry.items);\n\n if (!baseItems || baseItems.length === 0) {\n return additiveItems.length > 0 ? additiveItems : null;\n }\n\n return [...baseItems, ...additiveItems];\n};\n\n//\n// Public hooks.\n//\n\nexport const useMenuItems = (\n group?: MenuGroupContext,\n propsItems?: MenuItem[],\n consumerName: string = 'useMenuItemConsumer',\n __menuScope?: Scope,\n) => {\n const { items, menuItemsAtom } = useMenuScoped(consumerName, __menuScope);\n const groupItems = useAtomValue(items(group));\n const entries = useAtomValue(menuItemsAtom) ?? new Map();\n\n const baseItems = useMemo(() => propsItems ?? groupItems ?? null, [propsItems, groupItems]);\n\n const resolved = useMemo(\n () => resolveItems(baseItems, group, entries as ReadonlyMap<string, MenuItems>),\n [baseItems, group, entries],\n );\n\n return resolved ?? undefined;\n};\n\n/** Returns the menu context without Radix scope. */\nexport const useMenu = (consumerName: string): MenuContextValue => {\n return useMenuScoped(consumerName, undefined);\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Node } from '@dxos/app-graph';\nimport { EffectEx } from '@dxos/effect';\nimport { type MenuActionProperties, type MenuItemGroupProperties } from '@dxos/ui-types';\nimport { getHostPlatform } from '@dxos/util';\n\nimport { type MenuAction, type MenuItemGroup, type MenuSeparator } from './types';\n\n/**\n * Execute a menu action's Effect with its captured context.\n * This provides the `_actionContext` layer if available.\n */\nexport const executeMenuAction = async (action: MenuAction, params: Node.InvokeProps = {}): Promise<void> => {\n let effect = action.data(params);\n\n // Provide captured action context if available.\n if (action._actionContext) {\n effect = effect.pipe(Effect.provide(action._actionContext));\n }\n\n await EffectEx.runAndForwardErrors(effect);\n};\n\nexport const getShortcut = (action: Node.ActionLike) => {\n return typeof action.properties?.keyBinding === 'string'\n ? action.properties.keyBinding\n : action.properties?.keyBinding?.[getHostPlatform()];\n};\n\nexport const fallbackIcon = 'ph--circle-dashed--regular';\n\nexport const createMenuAction = <P extends {} = {}>(\n id: string,\n invoke: (params?: Node.InvokeProps) => void,\n properties: P & MenuActionProperties,\n) =>\n ({\n id,\n type: Node.ActionType,\n properties,\n data: (params?: Node.InvokeProps) => Effect.sync(() => invoke(params)),\n }) satisfies MenuAction;\n\nexport const createMenuItemGroup = <P extends MenuItemGroupProperties>(id: string, properties: P) =>\n ({\n id,\n type: Node.ActionGroupType,\n properties,\n data: Node.actionGroupSymbol,\n }) satisfies MenuItemGroup;\n\nexport const createGapSeparator = (id: string = 'gap', source: string = 'root') => ({\n nodes: [\n {\n id,\n type: '@dxos/react-ui-toolbar/separator',\n properties: { variant: 'gap' },\n data: undefined as never,\n } satisfies MenuSeparator,\n ],\n edges: [\n {\n source,\n target: id,\n relation: 'child' as const,\n },\n ],\n});\n\nexport const createLineSeparator = (id: string = 'line', source: string = 'root') => ({\n nodes: [\n {\n id,\n type: '@dxos/react-ui-toolbar/separator',\n properties: { variant: 'line' },\n data: undefined as never,\n } satisfies MenuSeparator,\n ],\n edges: [{ source, target: id, relation: 'child' as const }],\n});\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Node } from '@dxos/app-graph';\nimport { keySymbols } from '@dxos/keyboard';\nimport { type TFunction, toLocalizedString } from '@dxos/react-ui';\nimport { type MenuActionProperties, type MenuItemChrome } from '@dxos/ui-types';\n\nimport { getShortcut } from '../util';\n\ntype Action = Node.Action<MenuActionProperties> | Node.ActionGroup<MenuItemChrome>;\n\n// Kept out of `ActionLabel.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a value exported beside them forces a full page reload on every edit.\n\nexport const actionLabel = (action: Action, t: TFunction) => {\n const shortcut = getShortcut(action);\n return `${toLocalizedString(action.properties!.label, t)}${shortcut ? ` (${keySymbols(shortcut).join('')})` : ''}`;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Atom } from '@effect-atom/atom';\n\nimport { Node } from '@dxos/app-graph';\nimport { type IconButtonProps, type ToolbarSeparatorProps } from '@dxos/react-ui';\nimport { type MenuActionProperties } from '@dxos/ui-types';\n\nexport type MenuAction<P extends {} = {}> = Node.Action<P & MenuActionProperties>;\n\nexport const MenuSeparatorType = '@dxos/react-ui-toolbar/separator' as const;\n\nexport type MenuSeparator = Node.Node<never, Pick<ToolbarSeparatorProps, 'variant'>> & {\n type: typeof MenuSeparatorType;\n};\n\nexport const isSeparator = (node: Node.Node): node is MenuSeparator => node.type === MenuSeparatorType;\n\nexport type MenuItemGroup<P extends Record<string, any> = Record<string, any>> = Node.ActionGroup<P>;\n\nexport const isMenuGroup = (node: Node.Node): node is MenuItemGroup => node.type === Node.ActionGroupType;\n\n/** Graph-sourced menu nodes carry plugin-specific properties validated at runtime. */\nexport type GraphMenuItem = Node.Action | Node.ActionGroup;\n\nexport type MenuItem = MenuSeparator | MenuAction | MenuItemGroup | GraphMenuItem;\n\n/** Group context for graph-backed dropdown menus (any graph node, not only action groups). */\nexport type MenuGroupContext = MenuItemGroup | Node.Node;\n\n/** Atom-family-style accessor: returns an atom of items for a given group (or root when undefined). */\nexport type MenuItemsAccessor = (group?: MenuGroupContext) => Atom.Atom<MenuItem[] | null>;\n\nexport type ActionExecutor = (action: MenuAction, params: Node.InvokeProps) => void;\n\nexport type MenuItemsMode = 'additive' | 'replacement';\n\nexport type AddMenuItemsProps = {\n id: string;\n mode: MenuItemsMode;\n priority?: number;\n items: MenuItem[];\n groupFilter?: (group?: MenuGroupContext) => boolean;\n};\n\nexport type MenuItems = Omit<AddMenuItemsProps, 'priority'> & { priority: number };\nexport type MenuItemsMap = Map<string, MenuItems>;\n\nexport type MenuContextValue = {\n /** Atom-family accessor for base menu items, keyed by group (similar to Tree model). */\n items: MenuItemsAccessor;\n iconSize: IconButtonProps['size'];\n attendableId?: string;\n /** TODO(burdon): Remove and assume always active if attendableId is undefined. */\n /** If true, the menu is always active regardless of attention state. */\n alwaysActive?: boolean;\n /** Atom holding the current set of imperatively added menu items. */\n menuItemsAtom: Atom.Atom<MenuItemsMap>;\n /** Imperatively add menu items to the nearest MenuProvider. */\n addMenuItems: (props: AddMenuItemsProps) => void;\n /** Remove previously added menu items by id. */\n removeMenuItems: (id: string) => void;\n /** Optional action executor. If provided, will be used instead of default execution. */\n onAction?: ActionExecutor;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React from 'react';\n\nimport { type Node } from '@dxos/app-graph';\nimport { keySymbols } from '@dxos/keyboard';\nimport { toLocalizedString, useTranslation } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\nimport { type MenuActionProperties, type MenuItemChrome } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { getShortcut } from '../util';\n\ntype Action = Node.Action<MenuActionProperties> | Node.ActionGroup<MenuItemChrome>;\n\nexport const ActionLabel = ({ action }: { action: Action }) => {\n const { t } = useTranslation(translationKey);\n const shortcut = getShortcut(action);\n return (\n <>\n <span className='grow truncate'>{toLocalizedString(action.properties!.label, t)}</span>\n {shortcut && <span className={mx('shrink-0', 'text-description')}>{keySymbols(shortcut).join('')}</span>}\n </>\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useControllableState } from '@radix-ui/react-use-controllable-state';\nimport React, { type MouseEvent, useCallback } from 'react';\n\nimport { type DropdownMenuRootProps, Icon, DropdownMenu as NaturalDropdownMenu } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type MenuAction, type MenuItem, type MenuItemGroup, isSeparator } from '../types';\nimport { executeMenuAction } from '../util';\nimport { ActionLabel } from './ActionLabel';\nimport { type MenuScopedProps, useMenuItems, useMenuScoped } from './MenuContext';\n\nexport type DropdownMenuProps = DropdownMenuRootProps & {\n group?: MenuItemGroup;\n items?: MenuItem[];\n caller?: string;\n};\n\nconst DropdownMenuItem = ({\n item,\n group,\n onClick,\n __menuScope,\n}: MenuScopedProps<{\n item: MenuItem;\n group?: MenuItemGroup;\n onClick: (action: MenuAction, event: MouseEvent) => void;\n}>) => {\n // TODO(thure): handle other items.\n const action = item as MenuAction;\n const handleClick = useCallback((event: MouseEvent) => onClick(action, event), [action, onClick]);\n const { iconSize } = useMenuScoped('DropdownMenuItem', __menuScope);\n // An item that declares `checked` is a select-group member: expose the checked role + state to AT so\n // the current value is announced, not conveyed by the trailing check icon alone. Mutually-exclusive\n // (single-select) groups use radio semantics; multi-select groups use checkbox.\n const checkable = typeof action.properties?.checked === 'boolean';\n const role = group?.properties?.selectCardinality === 'multiple' ? 'menuitemcheckbox' : 'menuitemradio';\n return (\n <NaturalDropdownMenu.Item\n onClick={handleClick}\n classNames='gap-2'\n disabled={action.properties?.disabled}\n {...(checkable && { role, 'aria-checked': !!action.properties?.checked })}\n {...(action.properties?.testId && { 'data-testid': action.properties.testId })}\n >\n {action.properties?.icon && (\n <Icon\n icon={action.properties.icon}\n size={iconSize}\n classNames={mx(action.properties.spin && 'animate-spin', action.properties.iconClassNames)}\n />\n )}\n <ActionLabel action={action} />\n {/* Trailing check marks the current value of a single-select group (`checked`). */}\n {action.properties?.checked && <Icon icon='ph--check--regular' size={iconSize} classNames='mis-auto' />}\n </NaturalDropdownMenu.Item>\n );\n};\n\nconst DropdownMenuRoot = ({\n items: propsItems,\n group,\n open,\n defaultOpen,\n onOpenChange,\n caller,\n children,\n __menuScope,\n ...naturalProps\n}: MenuScopedProps<DropdownMenuProps>) => {\n const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({\n prop: open,\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n });\n\n const { onAction } = useMenuScoped('DropdownMenuRoot', __menuScope);\n\n const handleActionClick = useCallback(\n (action: MenuAction, event: MouseEvent) => {\n if (action.properties?.disabled) {\n return;\n }\n event.stopPropagation();\n // TODO(thure): Why does Dialog's modal-ness cause issues if we don't explicitly close the menu here?\n setOptionsMenuOpen(false);\n const params = { parent: group, caller, modifiers: { shift: event.shiftKey } };\n if (onAction) {\n onAction(action, params);\n } else {\n void executeMenuAction(action, params);\n }\n },\n [group, caller, onAction],\n );\n\n const items = useMenuItems(group, propsItems);\n\n return (\n <NaturalDropdownMenu.Root open={optionsMenuOpen} onOpenChange={setOptionsMenuOpen} {...naturalProps}>\n {children}\n <NaturalDropdownMenu.Portal>\n <NaturalDropdownMenu.Content>\n <NaturalDropdownMenu.Viewport>\n {items?.map((item) =>\n isSeparator(item) ? (\n <NaturalDropdownMenu.Separator key={item.id} />\n ) : (\n <DropdownMenuItem key={item.id} item={item} group={group} onClick={handleActionClick} />\n ),\n )}\n </NaturalDropdownMenu.Viewport>\n <NaturalDropdownMenu.Arrow />\n </NaturalDropdownMenu.Content>\n </NaturalDropdownMenu.Portal>\n </NaturalDropdownMenu.Root>\n );\n};\n\nexport const DropdownMenu = {\n Root: DropdownMenuRoot,\n Trigger: NaturalDropdownMenu.Trigger,\n VirtualTrigger: NaturalDropdownMenu.VirtualTrigger,\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useCallback, useRef, useState } from 'react';\n\nimport {\n Input,\n Toolbar as NaturalToolbar,\n type ToolbarRootProps,\n Tooltip,\n toLocalizedString,\n useTranslation,\n} from '@dxos/react-ui';\nimport { composable, composableProps } from '@dxos/react-ui';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport { mx } from '@dxos/ui-theme';\nimport { type DropdownMenuItemGroupProperties, type ToggleGroupMenuItemGroupProperties } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type MenuAction, type MenuItem, type MenuItemGroup, isMenuGroup, isSeparator } from '../types';\nimport { executeMenuAction } from '../util';\nimport { actionLabel } from './action-label';\nimport { ActionLabel } from './ActionLabel';\nimport { DropdownMenu } from './DropdownMenu';\nimport { type MenuScopedProps, useMenuItems, useMenuScoped } from './MenuContext';\n\nexport type ToolbarMenuDropdownMenuActionGroup = DropdownMenuItemGroupProperties;\n\nexport type ToolbarMenuToggleGroupActionGroup = ToggleGroupMenuItemGroupProperties;\n\nexport type ToolbarMenuActionGroupProperties = DropdownMenuItemGroupProperties | ToggleGroupMenuItemGroupProperties;\n\nexport type ToolbarMenuProps = ToolbarRootProps;\n\nexport type ToolbarMenuActionGroupProps = {\n group: MenuItemGroup<ToolbarMenuActionGroupProperties>;\n items?: MenuItem[];\n};\n\nexport type ToolbarMenuDropdownGroupProps = {\n group: MenuItemGroup<DropdownMenuItemGroupProperties>;\n items?: MenuItem[];\n};\n\nexport type ToolbarMenuToggleGroupProps = {\n group: MenuItemGroup<ToggleGroupMenuItemGroupProperties>;\n items?: MenuItem[];\n};\n\nexport type ToolbarMenuActionProps = {\n group: MenuItemGroup<ToggleGroupMenuItemGroupProperties>;\n action: MenuAction;\n};\n\nconst ActionToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: MenuAction }>) => {\n const { iconSize, onAction } = useMenuScoped('ActionToolbarItem', __menuScope);\n const { t } = useTranslation(translationKey);\n const [pending, setPending] = useState(false);\n const pendingRef = useRef(false);\n\n const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;\n const buttonVariant = action.properties.variant === 'primary' ? ('primary' as const) : ('ghost' as const);\n\n const handleClick = useCallback(() => {\n if (pendingRef.current) {\n return;\n }\n pendingRef.current = true;\n setPending(true);\n const done = () => {\n pendingRef.current = false;\n setPending(false);\n };\n if (onAction) {\n Promise.resolve(onAction(action, {})).then(done, done);\n } else {\n executeMenuAction(action).then(done, done);\n }\n }, [action, onAction]);\n\n const commonProps = {\n variant: buttonVariant,\n disabled: disabled || pending,\n classNames,\n onClick: handleClick,\n ...(testId && { 'data-testid': testId }),\n };\n\n if (hidden) {\n return null;\n }\n\n return icon ? (\n <NaturalToolbar.IconButton\n key={action.id}\n {...commonProps}\n icon={icon}\n size={iconSize}\n iconOnly={iconOnly}\n iconClassNames={mx(spin && 'animate-spin', iconClassNames)}\n label={actionLabel(action, t)}\n />\n ) : (\n <NaturalToolbar.Button key={action.id} {...commonProps}>\n <ActionLabel action={action} />\n </NaturalToolbar.Button>\n );\n};\n\nconst SwitchToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: MenuAction }>) => {\n const { onAction } = useMenuScoped('SwitchToolbarItem', __menuScope);\n const { t } = useTranslation(translationKey);\n const { label, iconOnly, disabled, testId, hidden, checked } = action.properties;\n const labelStr = toLocalizedString(label, t);\n\n const handleCheckedChange = useCallback(() => {\n if (onAction) {\n onAction(action, {});\n } else {\n void executeMenuAction(action);\n }\n }, [action, onAction]);\n\n if (hidden) {\n return null;\n }\n\n const switchInput = (\n <Input.Switch\n checked={checked}\n disabled={disabled}\n aria-label={iconOnly ? labelStr : undefined}\n onCheckedChange={handleCheckedChange}\n {...(testId && { 'data-testid': testId })}\n />\n );\n\n return (\n <Input.Root>\n {iconOnly ? (\n <Tooltip.Trigger asChild content={labelStr}>\n <Input.Block>{switchInput}</Input.Block>\n </Tooltip.Trigger>\n ) : (\n <Input.Block>{switchInput}</Input.Block>\n )}\n {!iconOnly && <Input.Label>{labelStr}</Input.Label>}\n </Input.Root>\n );\n};\n\nconst DropdownMenuToolbarItem = ({\n __menuScope,\n group,\n items: propsItems,\n}: MenuScopedProps<ToolbarMenuDropdownGroupProps>) => {\n const { t } = useTranslation(translationKey);\n const { iconSize } = useMenuScoped('DropdownMenuToolbarItem', __menuScope);\n const items = useMenuItems(group, propsItems, 'DropdownMenuToolbarItem', __menuScope);\n const {\n iconOnly,\n disabled,\n testId,\n applyActive,\n caretDown = true,\n icon: groupIcon,\n iconClassNames: groupIconClassNames,\n spin: groupSpin,\n } = group.properties;\n const activeItem = items?.find((item) => !!(item as MenuAction).properties.checked) as MenuAction | undefined;\n const icon =\n (applyActive &&\n // TODO(thure): Handle other menu item types.\n activeItem?.properties.icon) ||\n groupIcon;\n // Follow the same `applyActive` rule for `iconClassNames` so a per-item accent (e.g. tag colour) tracks the displayed icon.\n const iconClassNames = (applyActive && activeItem?.properties.iconClassNames) || groupIconClassNames;\n const spin = (applyActive && activeItem?.properties.spin) || groupSpin;\n const labelAction = applyActive && activeItem ? activeItem : group;\n\n return (\n <DropdownMenu.Root group={group} items={items}>\n <DropdownMenu.Trigger asChild>\n {icon ? (\n <NaturalToolbar.IconButton\n variant='ghost'\n disabled={disabled}\n icon={icon}\n size={iconSize}\n iconOnly={iconOnly}\n iconClassNames={mx(spin && 'animate-spin', iconClassNames)}\n label={actionLabel(labelAction, t)}\n caretDown={caretDown}\n {...(testId && { 'data-testid': testId })}\n />\n ) : (\n <NaturalToolbar.Button\n variant='ghost'\n disabled={disabled}\n caretDown={caretDown}\n {...(testId && { 'data-testid': testId })}\n >\n <ActionLabel action={labelAction} />\n </NaturalToolbar.Button>\n )}\n </DropdownMenu.Trigger>\n </DropdownMenu.Root>\n );\n};\n\nconst ToggleGroupItem = ({ __menuScope, group, action }: MenuScopedProps<ToolbarMenuActionProps>) => {\n const { iconSize, onAction } = useMenuScoped('ToggleGroupItem', __menuScope);\n const { t } = useTranslation(translationKey);\n const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;\n\n const handleClick = useCallback(() => {\n if (onAction) {\n onAction(action, { parent: group });\n } else {\n void executeMenuAction(action, { parent: group });\n }\n }, [action, group, onAction]);\n\n const commonProps = {\n value: action.id,\n disabled,\n variant: 'ghost' as const,\n classNames,\n onClick: handleClick,\n ...(testId && { 'data-testid': testId }),\n };\n\n return hidden ? null : icon ? (\n <NaturalToolbar.ToggleGroupIconItem\n {...commonProps}\n icon={icon}\n size={iconSize}\n iconOnly={iconOnly}\n iconClassNames={mx(spin && 'animate-spin', iconClassNames)}\n label={actionLabel(action, t)}\n />\n ) : (\n <NaturalToolbar.ToggleGroupItem {...commonProps}>\n <ActionLabel action={action} />\n </NaturalToolbar.ToggleGroupItem>\n );\n};\n\nconst ToggleGroupToolbarItem = ({\n __menuScope,\n group,\n items: itemsProp,\n}: MenuScopedProps<ToolbarMenuToggleGroupProps>) => {\n const items = useMenuItems(group, itemsProp, 'ToggleGroupToolbarItem', __menuScope);\n const { selectCardinality } = group.properties;\n\n // TODO(thure): Handle other menu item types.\n const children = (items as MenuAction[]).map((action) => (\n <ToggleGroupItem key={action.id} group={group} action={action} />\n ));\n\n if (selectCardinality === 'multiple') {\n return (\n <NaturalToolbar.ToggleGroup type='multiple' value={group.properties.value}>\n {children}\n </NaturalToolbar.ToggleGroup>\n );\n } else {\n return (\n <NaturalToolbar.ToggleGroup type='single' value={group.properties.value}>\n {children}\n </NaturalToolbar.ToggleGroup>\n );\n }\n};\n\nexport const ToolbarMenu = composable<HTMLDivElement, MenuScopedProps<ToolbarMenuProps>>(\n ({ __menuScope, children, ...props }, forwardedRef) => {\n const items = useMenuItems(undefined, undefined, 'ToolbarMenu', __menuScope);\n const { attendableId, alwaysActive } = useMenuScoped('ToolbarMenu', __menuScope);\n const { hasAttention } = useAttention(attendableId);\n\n return (\n <NaturalToolbar.Root\n {...composableProps(props, { classNames: attendableId })}\n disabled={!alwaysActive && !hasAttention}\n ref={forwardedRef}\n >\n {items?.map((item: MenuItem) => (\n <ToolbarMenuItem key={item.id} __menuScope={__menuScope} item={item} />\n ))}\n {children}\n </NaturalToolbar.Root>\n );\n },\n);\n\nconst ToolbarMenuItem = ({ __menuScope, item }: MenuScopedProps<{ item: MenuItem }>) => {\n if (isSeparator(item)) {\n return <NaturalToolbar.Separator variant={item.properties.variant} />;\n }\n\n if (isMenuGroup(item)) {\n if (item.properties.variant === 'dropdownMenu') {\n return (\n <DropdownMenuToolbarItem\n __menuScope={__menuScope}\n group={item as MenuItemGroup<DropdownMenuItemGroupProperties>}\n />\n );\n }\n\n return (\n <ToggleGroupToolbarItem\n __menuScope={__menuScope}\n group={item as MenuItemGroup<ToggleGroupMenuItemGroupProperties>}\n />\n );\n }\n\n const action = item as MenuAction;\n if (action.properties?.variant === 'switch') {\n return <SwitchToolbarItem __menuScope={__menuScope} action={action} />;\n }\n\n // The contributor owns the rendered element (interactions the action model cannot express).\n if (action.properties?.variant === 'custom' && action.properties.render) {\n return <>{action.properties.render()}</>;\n }\n\n return <ActionToolbarItem __menuScope={__menuScope} action={action} />;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { Atom, RegistryContext } from '@effect-atom/atom-react';\nimport { useControllableState } from '@radix-ui/react-use-controllable-state';\nimport React, { type MouseEvent, type PropsWithChildren, useCallback, useContext, useMemo } from 'react';\n\nimport { type DropdownMenuRootProps, Icon, DropdownMenu as NaturalDropdownMenu } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport {\n type AddMenuItemsProps,\n type MenuAction,\n type MenuContextValue,\n type MenuGroupContext,\n type MenuItem,\n type MenuItemsMap,\n isSeparator,\n} from '../types';\nimport { executeMenuAction } from '../util';\nimport { ActionLabel } from './ActionLabel';\nimport {\n MenuContextProvider,\n MenuDropdownContext,\n type MenuScopedProps,\n menuContextDefaults,\n useMenuItems,\n useMenuScope,\n useMenuScoped,\n} from './MenuContext';\nimport { ToolbarMenu } from './ToolbarMenu';\n\n//\n// MenuProvider (internal) — the context provider used by Menu.Root.\n//\n\nconst DEFAULT_PRIORITY = 100;\n\ntype MenuProviderProps = PropsWithChildren<Partial<MenuContextValue>>;\n\nconst MenuProvider = ({\n children,\n items = menuContextDefaults.items,\n iconSize = menuContextDefaults.iconSize,\n attendableId,\n alwaysActive,\n onAction,\n}: MenuProviderProps) => {\n const { scope } = useMenuScope(undefined);\n const registry = useContext(RegistryContext);\n const menuItemsAtom = useMemo(() => Atom.make<MenuItemsMap>(new Map()).pipe(Atom.keepAlive), []);\n\n const addMenuItems = useCallback(\n (props: AddMenuItemsProps) => {\n const priority = props.priority ?? DEFAULT_PRIORITY;\n const prev = registry.get(menuItemsAtom);\n const next = new Map(prev);\n next.set(props.id, { ...props, priority });\n registry.set(menuItemsAtom, next);\n },\n [registry, menuItemsAtom],\n );\n\n const removeMenuItems = useCallback(\n (id: string) => {\n const prev = registry.get(menuItemsAtom);\n const next = new Map(prev);\n next.delete(id);\n registry.set(menuItemsAtom, next);\n },\n [registry, menuItemsAtom],\n );\n\n return (\n <MenuContextProvider\n items={items}\n iconSize={iconSize}\n attendableId={attendableId}\n alwaysActive={alwaysActive}\n menuItemsAtom={menuItemsAtom}\n addMenuItems={addMenuItems}\n removeMenuItems={removeMenuItems}\n onAction={onAction}\n scope={scope}\n >\n {children}\n </MenuContextProvider>\n );\n};\n\n//\n// Menu.Root\n//\n\ntype MenuRootProps = MenuProviderProps &\n Pick<DropdownMenuRootProps, 'children' | 'open' | 'defaultOpen' | 'onOpenChange'> & {\n /** Identifies the component that owns this menu (passed to action handlers). */\n caller?: string;\n };\n\n/**\n * Menu context boundary.\n *\n * NOTE: This component is headless since it's root div has `contents`.\n *\n * Provides the menu context (action dispatch, contribution registry, icon size, etc.)\n * and an optional dropdown root for use with `Menu.Trigger` + `Menu.Content`.\n */\nconst MenuRoot = ({ children, open, defaultOpen, onOpenChange, caller, ...props }: MenuRootProps) => {\n const [menuOpen, setMenuOpen] = useControllableState({\n prop: open,\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n });\n\n const closeMenu = useCallback(() => setMenuOpen(false), [setMenuOpen]);\n\n return (\n <MenuProvider {...props}>\n <MenuDropdownContext.Provider value={{ closeMenu, caller }}>\n <NaturalDropdownMenu.Root open={menuOpen} onOpenChange={setMenuOpen}>\n {children}\n </NaturalDropdownMenu.Root>\n </MenuDropdownContext.Provider>\n </MenuProvider>\n );\n};\n\n//\n// Menu.Content\n//\n\ntype MenuContentProps = {\n group?: MenuGroupContext;\n items?: MenuItem[];\n caller?: string;\n};\n\nconst MenuContentItem = ({\n item,\n onClick,\n __menuScope,\n}: MenuScopedProps<{\n item: MenuItem;\n onClick: (action: MenuAction, event: MouseEvent) => void;\n}>) => {\n const action = item as MenuAction;\n const handleClick = useCallback((event: MouseEvent) => onClick(action, event), [action, onClick]);\n const { iconSize } = useMenuScoped('MenuContentItem', __menuScope);\n return (\n <NaturalDropdownMenu.Item\n onClick={handleClick}\n classNames='gap-2'\n disabled={action.properties?.disabled}\n {...(action.properties?.testId && { 'data-testid': action.properties.testId })}\n >\n {action.properties?.icon && (\n <Icon\n icon={action.properties.icon}\n size={iconSize}\n classNames={mx(action.properties.spin && 'animate-spin', action.properties.iconClassNames)}\n />\n )}\n <ActionLabel action={action} />\n </NaturalDropdownMenu.Item>\n );\n};\n\n/**\n * Renders the dropdown menu portal, content, and graph-backed items.\n *\n * Must be a descendant of `Menu.Root`. Reads items via `useMenuItems` from the\n * nearest menu context, with optional `group`/`items` prop overrides.\n */\nconst MenuContent = ({\n group,\n items: propsItems,\n caller: callerOverride,\n __menuScope,\n}: MenuScopedProps<MenuContentProps>) => {\n const { closeMenu, caller: contextCaller } = useContext(MenuDropdownContext);\n const caller = callerOverride ?? contextCaller;\n const { onAction } = useMenuScoped('MenuContent', __menuScope);\n const resolvedItems = useMenuItems(group, propsItems, 'MenuContent', __menuScope);\n\n const handleActionClick = useCallback(\n (action: MenuAction, event: MouseEvent) => {\n if (action.properties?.disabled) {\n return;\n }\n event.stopPropagation();\n closeMenu();\n const params = { parent: group, caller, modifiers: { shift: event.shiftKey } };\n if (onAction) {\n onAction(action, params);\n } else {\n void executeMenuAction(action, params);\n }\n },\n [group, caller, onAction, closeMenu],\n );\n\n return (\n <NaturalDropdownMenu.Portal>\n <NaturalDropdownMenu.Content>\n <NaturalDropdownMenu.Viewport>\n {resolvedItems?.map((item) =>\n isSeparator(item) ? (\n <NaturalDropdownMenu.Separator key={item.id} />\n ) : (\n <MenuContentItem key={item.id} item={item} onClick={handleActionClick} />\n ),\n )}\n </NaturalDropdownMenu.Viewport>\n <NaturalDropdownMenu.Arrow />\n </NaturalDropdownMenu.Content>\n </NaturalDropdownMenu.Portal>\n );\n};\n\n//\n// Namespace.\n//\n\n/**\n * Primary namespace export for the menu system.\n *\n * - `Menu.Root` — context boundary (replaces `MenuProvider`); also provides a dropdown root.\n * - `Menu.Trigger` / `Menu.VirtualTrigger` — dropdown trigger (use with `Menu.Content`).\n * - `Menu.Content` — renders graph-backed dropdown items inside a portal.\n * - `Menu.Toolbar` — flat toolbar component with graph-backed item rendering.\n */\nconst Menu = {\n Root: MenuRoot,\n Trigger: NaturalDropdownMenu.Trigger,\n Content: MenuContent,\n VirtualTrigger: NaturalDropdownMenu.VirtualTrigger,\n Toolbar: ToolbarMenu,\n};\n\nexport { Menu };\n\nexport type { MenuContentProps, MenuRootProps };\n\nexport type {\n ToolbarMenuActionGroupProperties,\n ToolbarMenuActionGroupProps,\n ToolbarMenuActionProps,\n ToolbarMenuDropdownMenuActionGroup,\n ToolbarMenuProps,\n ToolbarMenuToggleGroupActionGroup,\n} from './ToolbarMenu';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Node } from '@dxos/app-graph';\nimport { type MenuActionProperties } from '@dxos/ui-types';\n\n/**\n * Merges an action's `presentation[surface]` chrome override (if any) into its properties, letting a\n * single action declare multiple dispositions yet render appropriately in each surface — e.g. a\n * primary toolbar button and a plain nav-tree row from the same definition.\n */\nexport const applyPresentation = <A extends Node.ActionLike>(action: A, surface: string): A => {\n const presentation = (action.properties as MenuActionProperties).presentation?.[surface];\n if (!presentation) {\n return action;\n }\n return { ...action, properties: { ...action.properties, ...presentation } };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';\nimport { type DependencyList, useCallback, useContext, useMemo } from 'react';\n\nimport { Graph, Node } from '@dxos/app-graph';\n\nimport { type MenuItem, type MenuItemGroup, type MenuItemsAccessor } from '../types';\n\nexport type ActionGraphNodes = Node.NodeArg<any>[];\nexport type ActionGraphEdges = Graph.Edge[];\nexport type ActionGraphProps = {\n nodes: ActionGraphNodes;\n edges: ActionGraphEdges;\n};\n\nexport type MenuActions = {\n items: MenuItemsAccessor;\n};\n\nexport const useMenuActions = (props: Atom.Atom<ActionGraphProps>): MenuActions => {\n const registry = useContext(RegistryContext);\n const menuGraphProps = useAtomValue(props);\n\n // Create a new graph whenever props change to preserve correct order.\n // (Graph.addEdges appends rather than replaces, which breaks ordering on updates.)\n // NOTE: Using useMemo rather than a ref-mutation pattern to avoid calling registry.set during render,\n // which would trigger atom state updates in other components (setState-in-render React warning).\n const graph = useMemo(() => {\n const newGraph = Graph.make({ registry });\n newGraph.pipe(Graph.addNodes(menuGraphProps.nodes as Node.NodeArg<any>[]), Graph.addEdges(menuGraphProps.edges));\n return newGraph;\n }, [registry, menuGraphProps]);\n\n const items: MenuItemsAccessor = useCallback(\n (group?: MenuItemGroup) => {\n // TODO(wittjosiah): Migrate to using action relation instead of child.\n return graph.connections(group?.id || Node.RootId, 'child') as Atom.Atom<MenuItem[] | null>;\n },\n [graph],\n );\n\n return { items };\n};\n\n/**\n * Convenience wrapper around `useMenuActions` that creates the backing atom inline.\n * Pass a builder thunk and a dependency list — the hook memoizes `Atom.make(build)` and threads\n * it through `useMenuActions`. Saves the `useMemo(() => Atom.make(...), deps)` boilerplate when\n * the action graph is composed from local state (e.g. a toolbar driven by component state).\n *\n * Read reactive state via `get` inside the builder; `deps` should hold only stable references.\n */\nexport const useMenuBuilder = (build: (get: Atom.Context) => ActionGraphProps, deps: DependencyList): MenuActions => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const atom = useMemo(() => Atom.make(build), deps);\n return useMenuActions(atom);\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Atom } from '@effect-atom/atom';\nimport { useMemo } from 'react';\n\nimport { type Graph, Node } from '@dxos/app-graph';\n\nimport { applyPresentation } from '../presentation';\nimport {\n type ActionGraphEdges,\n type ActionGraphNodes,\n type ActionGraphProps,\n type MenuActions,\n useMenuActions,\n} from './useMenuActions';\n\nexport type GraphMenuOptions = {\n /** Group the actions descend from. Defaults to the menu root (top-level toolbar items). */\n rootId?: string;\n /** Keep only the actions the predicate accepts (e.g. by `disposition`). */\n filter?: (action: Node.ActionLike) => boolean;\n /**\n * Disposition key this surface renders — when set, each action's `presentation[surface]` chrome\n * override (if any) is merged into its properties, letting one action multi-target dispositions\n * (e.g. `['toolbar', 'list-item']`) with per-surface chrome.\n */\n surface?: string;\n};\n\n/**\n * Flatten a node's contributed actions (and nested {@link Node.ActionGroup}s) into the\n * `{ nodes, edges }` an {@link useMenuActions} / `Menu` consumes. The action nodes are spliced\n * verbatim, so their Effect `data` (and captured `_actionContext`) execute via `executeMenuAction`\n * without any `onAction` wiring.\n *\n * Pure over a `resolve` function so it is testable without a live graph; `resolve(id)` returns the\n * direct actions for `id` (a node id at the top level, a group id when expanding a group).\n */\nexport const buildGraphMenu = (\n resolve: (id: string) => Node.ActionLike[],\n nodeId: string,\n options: GraphMenuOptions = {},\n): ActionGraphProps => {\n const { rootId = Node.RootId, filter, surface } = options;\n const nodes: ActionGraphNodes = [];\n const edges: ActionGraphEdges = [];\n const seen = new Set<string>();\n\n const visit = (parentId: string, sourceId: string) => {\n for (const action of resolve(sourceId)) {\n if (filter && !filter(action)) {\n continue;\n }\n // Always record the parent→child edge (a shared action may sit under multiple groups).\n edges.push({ source: parentId, target: action.id, relation: 'child' });\n if (seen.has(action.id)) {\n continue;\n }\n seen.add(action.id);\n nodes.push((surface ? applyPresentation(action, surface) : action) as ActionGraphNodes[number]);\n // A group's children are the actions contributed for the group's own id.\n if (Node.isActionGroup(action)) {\n visit(action.id, action.id);\n }\n }\n };\n\n visit(rootId, nodeId);\n return { nodes, edges };\n};\n\n/**\n * Read a node's contributed actions from the app-graph as menu `ActionGraphProps`, for splicing into a\n * hand-built toolbar via `MenuBuilder.subgraph(graphActions(graph, get, nodeId))`. Reactive: reads the\n * graph's action atoms through `get`, so contributed actions appear/disappear as extensions update.\n *\n * @idiom org.dxos.react-ui-menu.graphActionsToolbar\n * applies: Splicing a node's app-graph-contributed toolbar actions into a hand-built object toolbar\n * instead-of: Reading `graph.actions(nodeId)` and filtering/wiring `nodes`/`edges` by hand in every toolbar\n * uses: {@link graphActions}, {@link MenuBuilder}, {@link useMenuBuilder}\n * related: org.dxos.react-ui-menu.toolbarMenu\n */\nexport const graphActions = (\n graph: Graph.ReadableGraph | undefined,\n get: Atom.Context,\n nodeId: string | undefined,\n options?: GraphMenuOptions,\n): ActionGraphProps =>\n graph && nodeId ? buildGraphMenu((id) => get(graph.actions(id)), nodeId, options) : { nodes: [], edges: [] };\n\n/**\n * Menu actions sourced entirely from a node's app-graph contributions — the common \"show every action\n * the graph offers for this object\" toolbar/menu. Compose with hand-built items via {@link graphActions}.\n */\nexport const useGraphMenuActions = (\n graph: Graph.ReadableGraph | undefined,\n nodeId: string | undefined,\n options?: GraphMenuOptions,\n): MenuActions => {\n const atom = useMemo(\n () => Atom.make((get) => graphActions(graph, get, nodeId, options)),\n // `options` is expected to be stable; `rootId` participates since it reshapes the graph.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [graph, nodeId, options?.rootId],\n );\n return useMenuActions(atom);\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\nimport type { DropdownMenuItemGroupProperties, MenuActionProperties, MenuItemGroupProperties } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport type { ActionGraphProps } from './hooks';\nimport { MenuSeparatorType } from './types';\nimport { createMenuAction, createMenuItemGroup } from './util';\n\n/** Default trigger for toolbar overflow (⋮) dropdown menus. */\nconst overflowMenuProperties = {\n label: ['toolbar-overflow.menu', { ns: translationKey }],\n icon: 'ph--dots-three-vertical--regular',\n iconOnly: true,\n caretDown: false,\n variant: 'dropdownMenu',\n} as const satisfies DropdownMenuItemGroupProperties;\n\n/** Callback that populates an action group builder. */\nexport type ActionGroupBuilderFn = (builder: ActionGroupBuilder) => void;\n\n/**\n * Fluent builder for composing menu action graphs.\n * Subgraph functions return a callback that receives the builder, enabling composition via partial application:\n * ```ts\n * const addHeadings = (state): ActionGroupBuilderFn => (builder) => {\n * builder.group('headings', { ... }, (group) => { ... });\n * };\n *\n * MenuBuilder.make()\n * .subgraph(showHeadings && addHeadings(state))\n * .subgraph(showFormatting && addFormatting(state))\n * .build();\n * ```\n */\nexport interface ActionGroupBuilder {\n /** Add an action node as a child of the current group. */\n action<P extends {} = {}>(id: string, props: P & MenuActionProperties, invoke: () => void): this;\n\n /** Add a switch action rendered as a labeled Input.Switch. */\n switch(id: string, props: Omit<MenuActionProperties, 'variant'> & { checked: boolean }, invoke: () => void): this;\n\n /** Add a nested action group. */\n group(id: string, props: MenuItemGroupProperties, cb: ActionGroupBuilderFn): this;\n\n /** Merge pre-built nodes and edges, or build a subgraph via callback scoped to the current group. Falsy values are ignored. */\n subgraph(subgraphOrCb: ActionGraphProps | ActionGroupBuilderFn | false | null | undefined): this;\n\n /** Add a separator. */\n separator(variant?: 'gap' | 'line'): this;\n}\n\n/** Top-level builder that creates the root group and produces the final action graph. */\nexport interface MenuBuilder extends ActionGroupBuilder {\n /**\n * Set properties on the root menu group. May only be called once.\n */\n root(props: MenuItemGroupProperties): this;\n\n /**\n * Add a root-level toolbar overflow dropdown (⋮) with built-in trigger styling.\n */\n menu(id: string, cb: ActionGroupBuilderFn, testId?: string): this;\n\n /**\n * Return the assembled action graph.\n */\n build(): ActionGraphProps;\n}\n\nclass MenuBuilderImpl implements MenuBuilder {\n private _separatorCount = 0;\n\n constructor(\n private readonly _data: ActionGraphProps,\n private readonly _rootId: string,\n ) {}\n\n root(props: MenuItemGroupProperties): this {\n invariant(this._rootId === 'root', 'Root group can only be at the top level');\n invariant(this._data.nodes.find((node) => node.id === 'root') === undefined, 'Root group can only be created once');\n this._data.nodes.push(createMenuItemGroup('root', props));\n return this;\n }\n\n build(): ActionGraphProps {\n return this._data;\n }\n\n action<P extends {} = {}>(id: string, props: P & MenuActionProperties, invoke: () => void): this {\n this._data.nodes.push(createMenuAction(id, invoke, props));\n this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });\n return this;\n }\n\n switch(id: string, props: Omit<MenuActionProperties, 'variant'> & { checked: boolean }, invoke: () => void): this {\n return this.action(id, { ...props, variant: 'switch' }, invoke);\n }\n\n group(id: string, props: MenuItemGroupProperties, cb: ActionGroupBuilderFn): this {\n this._data.nodes.push(createMenuItemGroup(id, props));\n this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });\n cb(new MenuBuilderImpl(this._data, id));\n return this;\n }\n\n menu(id: string, cb: ActionGroupBuilderFn, testId?: string): this {\n return this.group(id, testId === undefined ? overflowMenuProperties : { ...overflowMenuProperties, testId }, cb);\n }\n\n subgraph(subgraphOrCb: ActionGraphProps | ActionGroupBuilderFn | false | null | undefined): this {\n if (!subgraphOrCb) {\n return this;\n }\n if (typeof subgraphOrCb === 'function') {\n subgraphOrCb(new MenuBuilderImpl(this._data, this._rootId));\n } else {\n this._data.nodes.push(...subgraphOrCb.nodes);\n this._data.edges.push(...subgraphOrCb.edges);\n }\n return this;\n }\n\n separator(variant: 'gap' | 'line' = 'gap'): this {\n const id = `separator-${++this._separatorCount}`;\n this._data.nodes.push({\n id,\n type: MenuSeparatorType,\n properties: { variant },\n data: undefined as never,\n });\n this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });\n return this;\n }\n}\n\nexport const MenuBuilder = Object.freeze({\n make: (): MenuBuilder => new MenuBuilderImpl({ nodes: [], edges: [] }, 'root'),\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Node } from '@dxos/app-graph';\n\n/** `disposition` value an action opts into to appear in an object toolbar (vs context-menu-only). */\nexport const TOOLBAR_DISPOSITION = 'toolbar';\n\n/** Filter for {@link graphActions}: keeps only actions a producer opted into the toolbar. */\nexport const isToolbarAction = (action: Node.ActionLike): boolean => Node.hasDisposition(action, TOOLBAR_DISPOSITION);\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/MenuContext.ts","../../src/util.ts","../../src/components/action-label.ts","../../src/types.ts","../../src/components/ActionLabel.tsx","../../src/components/DropdownMenu.tsx","../../src/components/ToolbarMenu.tsx","../../src/components/Menu.tsx","../../src/presentation.ts","../../src/hooks/useMenuActions.ts","../../src/hooks/useGraphMenuActions.ts","../../src/builder.ts","../../src/toolbar.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport { type Scope, createContextScope } from '@radix-ui/react-context';\nimport * as Atom from 'effect/unstable/reactivity/Atom';\nimport { createContext, useMemo } from 'react';\n\nimport { log } from '@dxos/log';\n\nimport {\n type MenuContextValue,\n type MenuGroupContext,\n type MenuItem,\n type MenuItems,\n type MenuItemsAccessor,\n type MenuItemsMap,\n} from '../types';\n\n// Kept out of `Menu.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so contexts and hooks exported beside them force a full page reload on every edit.\n\n//\n// Scoped context.\n//\n\nexport type MenuScopedProps<P> = P & { __menuScope?: Scope };\n\nexport const MENU_NAME = 'Menu';\n\nexport const [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);\n\nconst nullItemsAtom = Atom.make<MenuItem[] | null>(null);\nconst defaultItemsAccessor: MenuItemsAccessor = () => nullItemsAtom;\n\nexport const menuContextDefaults: MenuContextValue = {\n iconSize: 5,\n items: defaultItemsAccessor,\n onAction: undefined,\n menuItemsAtom: Atom.make<MenuItemsMap>(new Map()),\n addMenuItems: () => {},\n removeMenuItems: () => {},\n};\n\nexport const [MenuContextProvider, useMenuScoped] = createMenuContext<MenuContextValue>(MENU_NAME, menuContextDefaults);\n\nexport const useMenuScope = createMenuScope();\n\n//\n// Dropdown context (internal) — allows Menu.Content to close the parent dropdown.\n//\n\nexport type MenuDropdownContextValue = {\n closeMenu: () => void;\n caller?: string;\n};\n\nexport const MenuDropdownContext = createContext<MenuDropdownContextValue>({\n closeMenu: () => {},\n});\n\n//\n// Item resolution.\n//\n\nconst sortMenuItems = (items: MenuItems[]) =>\n [...items].sort((a, b) => (a.priority !== b.priority ? a.priority - b.priority : a.id.localeCompare(b.id)));\n\nexport const resolveItems = (\n baseItems: MenuItem[] | null,\n group: MenuGroupContext | undefined,\n entries: ReadonlyMap<string, MenuItems>,\n): MenuItem[] | null => {\n const applicable = [...entries.values()].filter((entry) => !entry.groupFilter || entry.groupFilter(group));\n if (applicable.length === 0) {\n return baseItems;\n }\n\n const sorted = sortMenuItems(applicable);\n\n const replacements = sorted.filter((entry) => entry.mode === 'replacement');\n if (replacements.length > 0) {\n if (replacements.length > 1) {\n log.warn('multiple replacement entries found', {\n ids: replacements.map((r) => r.id).join(', '),\n using: replacements[0].id,\n });\n }\n return replacements[0].items;\n }\n\n const additive = sorted.filter((entry) => entry.mode === 'additive');\n const additiveItems = additive.flatMap((entry) => entry.items);\n\n if (!baseItems || baseItems.length === 0) {\n return additiveItems.length > 0 ? additiveItems : null;\n }\n\n return [...baseItems, ...additiveItems];\n};\n\n//\n// Public hooks.\n//\n\nexport const useMenuItems = (\n group?: MenuGroupContext,\n propsItems?: MenuItem[],\n consumerName: string = 'useMenuItemConsumer',\n __menuScope?: Scope,\n) => {\n const { items, menuItemsAtom } = useMenuScoped(consumerName, __menuScope);\n const groupItems = useAtomValue(items(group));\n const entries = useAtomValue(menuItemsAtom) ?? new Map();\n\n const baseItems = useMemo(() => propsItems ?? groupItems ?? null, [propsItems, groupItems]);\n\n const resolved = useMemo(\n () => resolveItems(baseItems, group, entries as ReadonlyMap<string, MenuItems>),\n [baseItems, group, entries],\n );\n\n return resolved ?? undefined;\n};\n\n/** Returns the menu context without Radix scope. */\nexport const useMenu = (consumerName: string): MenuContextValue => {\n return useMenuScoped(consumerName, undefined);\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport { EffectEx } from '@dxos/effect';\nimport { type MenuActionProperties, type MenuItemGroupProperties } from '@dxos/ui-types';\nimport { getHostPlatform } from '@dxos/util';\n\nimport { type MenuAction, type MenuItemGroup, type MenuSeparator } from './types';\n\n/**\n * Execute a menu action's Effect with its captured context.\n * This provides the `_actionContext` layer if available.\n */\nexport const executeMenuAction = async (action: MenuAction, params: AppGraphNode.InvokeProps = {}): Promise<void> => {\n let effect = action.data(params);\n\n // Provide captured action context if available.\n if (action._actionContext) {\n effect = effect.pipe(Effect.provide(action._actionContext));\n }\n\n await EffectEx.runAndForwardErrors(effect);\n};\n\nexport const getShortcut = (action: AppGraphNode.ActionLike) => {\n return typeof action.properties?.keyBinding === 'string'\n ? action.properties.keyBinding\n : action.properties?.keyBinding?.[getHostPlatform()];\n};\n\nexport const fallbackIcon = 'ph--circle-dashed--regular';\n\nexport const createMenuAction = <P extends {} = {}>(\n id: string,\n invoke: (params?: AppGraphNode.InvokeProps) => void,\n properties: P & MenuActionProperties,\n) =>\n ({\n id,\n type: AppGraphNode.ActionType,\n properties,\n data: (params?: AppGraphNode.InvokeProps) => Effect.sync(() => invoke(params)),\n }) satisfies MenuAction;\n\nexport const createMenuItemGroup = <P extends MenuItemGroupProperties>(id: string, properties: P) =>\n ({\n id,\n type: AppGraphNode.ActionGroupType,\n properties,\n data: AppGraphNode.actionGroupSymbol,\n }) satisfies MenuItemGroup;\n\nexport const createGapSeparator = (id: string = 'gap', source: string = 'root') => ({\n nodes: [\n {\n id,\n type: '@dxos/react-ui-toolbar/separator',\n properties: { variant: 'gap' },\n data: undefined as never,\n } satisfies MenuSeparator,\n ],\n edges: [\n {\n source,\n target: id,\n relation: 'child' as const,\n },\n ],\n});\n\nexport const createLineSeparator = (id: string = 'line', source: string = 'root') => ({\n nodes: [\n {\n id,\n type: '@dxos/react-ui-toolbar/separator',\n properties: { variant: 'line' },\n data: undefined as never,\n } satisfies MenuSeparator,\n ],\n edges: [{ source, target: id, relation: 'child' as const }],\n});\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport { keySymbols } from '@dxos/keyboard';\nimport { type TFunction, toLocalizedString } from '@dxos/react-ui';\nimport { type MenuActionProperties, type MenuItemChrome } from '@dxos/ui-types';\n\nimport { getShortcut } from '../util';\n\ntype Action = AppGraphNode.Action<MenuActionProperties> | AppGraphNode.ActionGroup<MenuItemChrome>;\n\n// Kept out of `ActionLabel.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a value exported beside them forces a full page reload on every edit.\n\nexport const actionLabel = (action: Action, t: TFunction) => {\n const shortcut = getShortcut(action);\n return `${toLocalizedString(action.properties!.label, t)}${shortcut ? ` (${keySymbols(shortcut).join('')})` : ''}`;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type * as Atom from 'effect/unstable/reactivity/Atom';\n\nimport * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport { type IconButtonProps, type ToolbarSeparatorProps } from '@dxos/react-ui';\nimport { type MenuActionProperties } from '@dxos/ui-types';\n\nexport type MenuAction<P extends {} = {}> = AppGraphNode.Action<P & MenuActionProperties>;\n\nexport const MenuSeparatorType = '@dxos/react-ui-toolbar/separator' as const;\n\nexport type MenuSeparator = AppGraphNode.Node<never, Pick<ToolbarSeparatorProps, 'variant'>> & {\n type: typeof MenuSeparatorType;\n};\n\nexport const isSeparator = (node: AppGraphNode.Node): node is MenuSeparator => node.type === MenuSeparatorType;\n\nexport type MenuItemGroup<P extends Record<string, any> = Record<string, any>> = AppGraphNode.ActionGroup<P>;\n\nexport const isMenuGroup = (node: AppGraphNode.Node): node is MenuItemGroup =>\n node.type === AppGraphNode.ActionGroupType;\n\n/** Graph-sourced menu nodes carry plugin-specific properties validated at runtime. */\nexport type GraphMenuItem = AppGraphNode.Action | AppGraphNode.ActionGroup;\n\nexport type MenuItem = MenuSeparator | MenuAction | MenuItemGroup | GraphMenuItem;\n\n/** Group context for graph-backed dropdown menus (any graph node, not only action groups). */\nexport type MenuGroupContext = MenuItemGroup | AppGraphNode.Node;\n\n/** Atom-family-style accessor: returns an atom of items for a given group (or root when undefined). */\nexport type MenuItemsAccessor = (group?: MenuGroupContext) => Atom.Atom<MenuItem[] | null>;\n\nexport type ActionExecutor = (action: MenuAction, params: AppGraphNode.InvokeProps) => void;\n\nexport type MenuItemsMode = 'additive' | 'replacement';\n\nexport type AddMenuItemsProps = {\n id: string;\n mode: MenuItemsMode;\n priority?: number;\n items: MenuItem[];\n groupFilter?: (group?: MenuGroupContext) => boolean;\n};\n\nexport type MenuItems = Omit<AddMenuItemsProps, 'priority'> & { priority: number };\nexport type MenuItemsMap = Map<string, MenuItems>;\n\nexport type MenuContextValue = {\n /** Atom-family accessor for base menu items, keyed by group (similar to Tree model). */\n items: MenuItemsAccessor;\n iconSize: IconButtonProps['size'];\n attendableId?: string;\n /** TODO(burdon): Remove and assume always active if attendableId is undefined. */\n /** If true, the menu is always active regardless of attention state. */\n alwaysActive?: boolean;\n /** Atom holding the current set of imperatively added menu items. */\n menuItemsAtom: Atom.Atom<MenuItemsMap>;\n /** Imperatively add menu items to the nearest MenuProvider. */\n addMenuItems: (props: AddMenuItemsProps) => void;\n /** Remove previously added menu items by id. */\n removeMenuItems: (id: string) => void;\n /** Optional action executor. If provided, will be used instead of default execution. */\n onAction?: ActionExecutor;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React from 'react';\n\nimport type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport { keySymbols } from '@dxos/keyboard';\nimport { toLocalizedString, useTranslation } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\nimport { type MenuActionProperties, type MenuItemChrome } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { getShortcut } from '../util';\n\ntype Action = AppGraphNode.Action<MenuActionProperties> | AppGraphNode.ActionGroup<MenuItemChrome>;\n\nexport const ActionLabel = ({ action }: { action: Action }) => {\n const { t } = useTranslation(translationKey);\n const shortcut = getShortcut(action);\n return (\n <>\n <span className='grow truncate'>{toLocalizedString(action.properties!.label, t)}</span>\n {shortcut && <span className={mx('shrink-0', 'text-description')}>{keySymbols(shortcut).join('')}</span>}\n </>\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useControllableState } from '@radix-ui/react-use-controllable-state';\nimport React, { type MouseEvent, useCallback } from 'react';\n\nimport { type DropdownMenuRootProps, Icon, DropdownMenu as NaturalDropdownMenu } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type MenuAction, type MenuItem, type MenuItemGroup, isSeparator } from '../types';\nimport { executeMenuAction } from '../util';\nimport { ActionLabel } from './ActionLabel';\nimport { type MenuScopedProps, useMenuItems, useMenuScoped } from './MenuContext';\n\nexport type DropdownMenuProps = DropdownMenuRootProps & {\n group?: MenuItemGroup;\n items?: MenuItem[];\n caller?: string;\n};\n\nconst DropdownMenuItem = ({\n item,\n group,\n onClick,\n __menuScope,\n}: MenuScopedProps<{\n item: MenuItem;\n group?: MenuItemGroup;\n onClick: (action: MenuAction, event: MouseEvent) => void;\n}>) => {\n // TODO(thure): handle other items.\n const action = item as MenuAction;\n const handleClick = useCallback((event: MouseEvent) => onClick(action, event), [action, onClick]);\n const { iconSize } = useMenuScoped('DropdownMenuItem', __menuScope);\n // An item that declares `checked` is a select-group member: expose the checked role + state to AT so\n // the current value is announced, not conveyed by the trailing check icon alone. Mutually-exclusive\n // (single-select) groups use radio semantics; multi-select groups use checkbox.\n const checkable = typeof action.properties?.checked === 'boolean';\n const multiple = group?.properties?.selectCardinality === 'multiple';\n const role = multiple ? 'menuitemcheckbox' : 'menuitemradio';\n // A multi-select group is set by toggling several members, so suppress the primitive's\n // close-on-select; picking one value and closing is single-select behaviour.\n const handleSelect = useCallback((event: Event) => multiple && event.preventDefault(), [multiple]);\n return (\n <NaturalDropdownMenu.Item\n onClick={handleClick}\n onSelect={handleSelect}\n classNames='gap-2'\n disabled={action.properties?.disabled}\n {...(checkable && { role, 'aria-checked': !!action.properties?.checked })}\n {...(action.properties?.testId && { 'data-testid': action.properties.testId })}\n >\n {action.properties?.icon && (\n <Icon\n icon={action.properties.icon}\n size={iconSize}\n classNames={mx(action.properties.spin && 'animate-spin', action.properties.iconClassNames)}\n />\n )}\n <ActionLabel action={action} />\n {/* Trailing check marks the current value of a single-select group (`checked`). */}\n {action.properties?.checked && <Icon icon='ph--check--regular' size={iconSize} classNames='ms-auto' />}\n </NaturalDropdownMenu.Item>\n );\n};\n\nconst DropdownMenuRoot = ({\n items: propsItems,\n group,\n open,\n defaultOpen,\n onOpenChange,\n caller,\n children,\n __menuScope,\n ...naturalProps\n}: MenuScopedProps<DropdownMenuProps>) => {\n const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({\n prop: open,\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n });\n\n const { onAction } = useMenuScoped('DropdownMenuRoot', __menuScope);\n\n const handleActionClick = useCallback(\n (action: MenuAction, event: MouseEvent) => {\n if (action.properties?.disabled) {\n return;\n }\n event.stopPropagation();\n // TODO(thure): Why does Dialog's modal-ness cause issues if we don't explicitly close the menu here?\n if (group?.properties?.selectCardinality !== 'multiple') {\n setOptionsMenuOpen(false);\n }\n const params = { parent: group, caller, modifiers: { shift: event.shiftKey } };\n if (onAction) {\n onAction(action, params);\n } else {\n void executeMenuAction(action, params);\n }\n },\n [group, caller, onAction],\n );\n\n const items = useMenuItems(group, propsItems);\n\n return (\n <NaturalDropdownMenu.Root open={optionsMenuOpen} onOpenChange={setOptionsMenuOpen} {...naturalProps}>\n {children}\n <NaturalDropdownMenu.Portal>\n <NaturalDropdownMenu.Content>\n <NaturalDropdownMenu.Viewport>\n {items?.map((item) =>\n isSeparator(item) ? (\n <NaturalDropdownMenu.Separator key={item.id} />\n ) : (\n <DropdownMenuItem key={item.id} item={item} group={group} onClick={handleActionClick} />\n ),\n )}\n </NaturalDropdownMenu.Viewport>\n <NaturalDropdownMenu.Arrow />\n </NaturalDropdownMenu.Content>\n </NaturalDropdownMenu.Portal>\n </NaturalDropdownMenu.Root>\n );\n};\n\nexport const DropdownMenu = {\n Root: DropdownMenuRoot,\n Trigger: NaturalDropdownMenu.Trigger,\n VirtualTrigger: NaturalDropdownMenu.VirtualTrigger,\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useCallback, useRef, useState } from 'react';\n\nimport {\n Input,\n Toolbar as NaturalToolbar,\n type ToolbarRootProps,\n Tooltip,\n toLocalizedString,\n useTranslation,\n} from '@dxos/react-ui';\nimport { composable, composableProps } from '@dxos/react-ui';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport { mx } from '@dxos/ui-theme';\nimport { type DropdownMenuItemGroupProperties, type ToggleGroupMenuItemGroupProperties } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type MenuAction, type MenuItem, type MenuItemGroup, isMenuGroup, isSeparator } from '../types';\nimport { executeMenuAction } from '../util';\nimport { actionLabel } from './action-label';\nimport { ActionLabel } from './ActionLabel';\nimport { DropdownMenu } from './DropdownMenu';\nimport { type MenuScopedProps, useMenuItems, useMenuScoped } from './MenuContext';\n\nexport type ToolbarMenuDropdownMenuActionGroup = DropdownMenuItemGroupProperties;\n\nexport type ToolbarMenuToggleGroupActionGroup = ToggleGroupMenuItemGroupProperties;\n\nexport type ToolbarMenuActionGroupProperties = DropdownMenuItemGroupProperties | ToggleGroupMenuItemGroupProperties;\n\nexport type ToolbarMenuProps = ToolbarRootProps;\n\nexport type ToolbarMenuActionGroupProps = {\n group: MenuItemGroup<ToolbarMenuActionGroupProperties>;\n items?: MenuItem[];\n};\n\nexport type ToolbarMenuDropdownGroupProps = {\n group: MenuItemGroup<DropdownMenuItemGroupProperties>;\n items?: MenuItem[];\n};\n\nexport type ToolbarMenuToggleGroupProps = {\n group: MenuItemGroup<ToggleGroupMenuItemGroupProperties>;\n items?: MenuItem[];\n};\n\nexport type ToolbarMenuActionProps = {\n group: MenuItemGroup<ToggleGroupMenuItemGroupProperties>;\n action: MenuAction;\n};\n\nconst ActionToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: MenuAction }>) => {\n const { iconSize, onAction } = useMenuScoped('ActionToolbarItem', __menuScope);\n const { t } = useTranslation(translationKey);\n const [pending, setPending] = useState(false);\n const pendingRef = useRef(false);\n\n const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;\n const buttonVariant = action.properties.variant === 'primary' ? ('primary' as const) : ('ghost' as const);\n\n const handleClick = useCallback(() => {\n if (pendingRef.current) {\n return;\n }\n pendingRef.current = true;\n setPending(true);\n const done = () => {\n pendingRef.current = false;\n setPending(false);\n };\n if (onAction) {\n Promise.resolve(onAction(action, {})).then(done, done);\n } else {\n executeMenuAction(action).then(done, done);\n }\n }, [action, onAction]);\n\n const commonProps = {\n variant: buttonVariant,\n disabled: disabled || pending,\n classNames,\n onClick: handleClick,\n ...(testId && { 'data-testid': testId }),\n };\n\n if (hidden) {\n return null;\n }\n\n return icon ? (\n <NaturalToolbar.IconButton\n key={action.id}\n {...commonProps}\n icon={icon}\n size={iconSize}\n iconOnly={iconOnly}\n iconClassNames={mx(spin && 'animate-spin', iconClassNames)}\n label={actionLabel(action, t)}\n />\n ) : (\n <NaturalToolbar.Button key={action.id} {...commonProps}>\n <ActionLabel action={action} />\n </NaturalToolbar.Button>\n );\n};\n\nconst SwitchToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: MenuAction }>) => {\n const { onAction } = useMenuScoped('SwitchToolbarItem', __menuScope);\n const { t } = useTranslation(translationKey);\n const { label, iconOnly, disabled, testId, hidden, checked } = action.properties;\n const labelStr = toLocalizedString(label, t);\n\n const handleCheckedChange = useCallback(() => {\n if (onAction) {\n onAction(action, {});\n } else {\n void executeMenuAction(action);\n }\n }, [action, onAction]);\n\n if (hidden) {\n return null;\n }\n\n const switchInput = (\n <Input.Switch\n checked={checked}\n disabled={disabled}\n aria-label={iconOnly ? labelStr : undefined}\n onCheckedChange={handleCheckedChange}\n {...(testId && { 'data-testid': testId })}\n />\n );\n\n return (\n <Input.Root>\n {iconOnly ? (\n <Tooltip.Trigger asChild content={labelStr}>\n <Input.Block>{switchInput}</Input.Block>\n </Tooltip.Trigger>\n ) : (\n <Input.Block>{switchInput}</Input.Block>\n )}\n {!iconOnly && <Input.Label>{labelStr}</Input.Label>}\n </Input.Root>\n );\n};\n\nconst DropdownMenuToolbarItem = ({\n __menuScope,\n group,\n items: propsItems,\n}: MenuScopedProps<ToolbarMenuDropdownGroupProps>) => {\n const { t } = useTranslation(translationKey);\n const { iconSize } = useMenuScoped('DropdownMenuToolbarItem', __menuScope);\n const items = useMenuItems(group, propsItems, 'DropdownMenuToolbarItem', __menuScope);\n const {\n iconOnly,\n disabled,\n testId,\n applyActive,\n caretDown = true,\n icon: groupIcon,\n iconClassNames: groupIconClassNames,\n spin: groupSpin,\n } = group.properties;\n const activeItem = items?.find((item) => !!(item as MenuAction).properties.checked) as MenuAction | undefined;\n const icon =\n (applyActive &&\n // TODO(thure): Handle other menu item types.\n activeItem?.properties.icon) ||\n groupIcon;\n // Follow the same `applyActive` rule for `iconClassNames` so a per-item accent (e.g. tag colour) tracks the displayed icon.\n const iconClassNames = (applyActive && activeItem?.properties.iconClassNames) || groupIconClassNames;\n const spin = (applyActive && activeItem?.properties.spin) || groupSpin;\n const labelAction = applyActive && activeItem ? activeItem : group;\n\n const trigger = icon ? (\n <NaturalToolbar.IconButton\n variant='ghost'\n disabled={disabled}\n icon={icon}\n size={iconSize}\n iconOnly={iconOnly}\n iconClassNames={mx(spin && 'animate-spin', iconClassNames)}\n label={actionLabel(labelAction, t)}\n caretDown={caretDown && !disabled}\n {...(testId && { 'data-testid': testId })}\n />\n ) : (\n <NaturalToolbar.Button\n variant='ghost'\n disabled={disabled}\n caretDown={caretDown && !disabled}\n {...(testId && { 'data-testid': testId })}\n >\n <ActionLabel action={labelAction} />\n </NaturalToolbar.Button>\n );\n\n // No menu behind a disabled trigger, since `disabled` alone does not gate Radix's open handler and the\n // group presented an empty dropdown.\n if (disabled) {\n return trigger;\n }\n\n return (\n <DropdownMenu.Root group={group} items={items}>\n <DropdownMenu.Trigger asChild>{trigger}</DropdownMenu.Trigger>\n </DropdownMenu.Root>\n );\n};\n\nconst ToggleGroupItem = ({ __menuScope, group, action }: MenuScopedProps<ToolbarMenuActionProps>) => {\n const { iconSize, onAction } = useMenuScoped('ToggleGroupItem', __menuScope);\n const { t } = useTranslation(translationKey);\n const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;\n\n const handleClick = useCallback(() => {\n if (onAction) {\n onAction(action, { parent: group });\n } else {\n void executeMenuAction(action, { parent: group });\n }\n }, [action, group, onAction]);\n\n const commonProps = {\n value: action.id,\n disabled,\n variant: 'ghost' as const,\n classNames,\n onClick: handleClick,\n ...(testId && { 'data-testid': testId }),\n };\n\n return hidden ? null : icon ? (\n <NaturalToolbar.ToggleGroupIconItem\n {...commonProps}\n icon={icon}\n size={iconSize}\n iconOnly={iconOnly}\n iconClassNames={mx(spin && 'animate-spin', iconClassNames)}\n label={actionLabel(action, t)}\n />\n ) : (\n <NaturalToolbar.ToggleGroupItem {...commonProps}>\n <ActionLabel action={action} />\n </NaturalToolbar.ToggleGroupItem>\n );\n};\n\nconst ToggleGroupToolbarItem = ({\n __menuScope,\n group,\n items: itemsProp,\n}: MenuScopedProps<ToolbarMenuToggleGroupProps>) => {\n const items = useMenuItems(group, itemsProp, 'ToggleGroupToolbarItem', __menuScope);\n const { selectCardinality } = group.properties;\n\n // TODO(thure): Handle other menu item types.\n const children = (items as MenuAction[]).map((action) => (\n <ToggleGroupItem key={action.id} group={group} action={action} />\n ));\n\n if (selectCardinality === 'multiple') {\n return (\n <NaturalToolbar.ToggleGroup type='multiple' value={group.properties.value}>\n {children}\n </NaturalToolbar.ToggleGroup>\n );\n } else {\n return (\n <NaturalToolbar.ToggleGroup type='single' value={group.properties.value}>\n {children}\n </NaturalToolbar.ToggleGroup>\n );\n }\n};\n\n/**\n * Attention-gated toolbar container with no graph items of its own — render {@link ToolbarMenuItems}\n * among its children, whose JSX order controls where the graph items sit.\n */\nexport const ToolbarMenu = composable<HTMLDivElement, MenuScopedProps<ToolbarMenuProps>>(\n ({ __menuScope, children, ...props }, forwardedRef) => {\n const { attendableId, alwaysActive } = useMenuScoped('ToolbarMenu', __menuScope);\n const { hasAttention } = useAttention(attendableId);\n\n return (\n <NaturalToolbar.Root\n {...composableProps(props, { classNames: attendableId })}\n disabled={!alwaysActive && !hasAttention}\n ref={forwardedRef}\n >\n {children}\n </NaturalToolbar.Root>\n );\n },\n);\n\n/** The menu graph's toolbar items, container-free, so JSX order controls their placement. */\nexport const ToolbarMenuItems = ({ __menuScope }: MenuScopedProps<{}>) => {\n const items = useMenuItems(undefined, undefined, 'ToolbarMenuItems', __menuScope);\n\n return (\n <>\n {items?.map((item: MenuItem) => (\n <ToolbarMenuItem key={item.id} __menuScope={__menuScope} item={item} />\n ))}\n </>\n );\n};\n\nconst ToolbarMenuItem = ({ __menuScope, item }: MenuScopedProps<{ item: MenuItem }>) => {\n if (isSeparator(item)) {\n return <NaturalToolbar.Separator variant={item.properties.variant} />;\n }\n\n if (isMenuGroup(item)) {\n if (item.properties.variant === 'dropdownMenu') {\n return (\n <DropdownMenuToolbarItem\n __menuScope={__menuScope}\n group={item as MenuItemGroup<DropdownMenuItemGroupProperties>}\n />\n );\n }\n\n return (\n <ToggleGroupToolbarItem\n __menuScope={__menuScope}\n group={item as MenuItemGroup<ToggleGroupMenuItemGroupProperties>}\n />\n );\n }\n\n const action = item as MenuAction;\n if (action.properties?.variant === 'switch') {\n return <SwitchToolbarItem __menuScope={__menuScope} action={action} />;\n }\n\n // The contributor owns the rendered element (interactions the action model cannot express).\n if (action.properties?.variant === 'custom' && action.properties.render) {\n return <>{action.properties.render()}</>;\n }\n\n return <ActionToolbarItem __menuScope={__menuScope} action={action} />;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { RegistryContext } from '@effect/atom-react/RegistryContext';\nimport { useControllableState } from '@radix-ui/react-use-controllable-state';\nimport * as Atom from 'effect/unstable/reactivity/Atom';\nimport React, { type MouseEvent, type PropsWithChildren, useCallback, useContext, useMemo } from 'react';\n\nimport { type DropdownMenuRootProps, Icon, DropdownMenu as NaturalDropdownMenu } from '@dxos/react-ui';\nimport { mx } from '@dxos/ui-theme';\n\nimport {\n type AddMenuItemsProps,\n type MenuAction,\n type MenuContextValue,\n type MenuGroupContext,\n type MenuItem,\n type MenuItemsMap,\n isSeparator,\n} from '../types';\nimport { executeMenuAction } from '../util';\nimport { ActionLabel } from './ActionLabel';\nimport {\n MenuContextProvider,\n MenuDropdownContext,\n type MenuScopedProps,\n menuContextDefaults,\n useMenuItems,\n useMenuScope,\n useMenuScoped,\n} from './MenuContext';\nimport { ToolbarMenu, ToolbarMenuItems } from './ToolbarMenu';\n\n//\n// MenuProvider (internal) — the context provider used by Menu.Root.\n//\n\nconst DEFAULT_PRIORITY = 100;\n\ntype MenuProviderProps = PropsWithChildren<Partial<MenuContextValue>>;\n\nconst MenuProvider = ({\n children,\n items = menuContextDefaults.items,\n iconSize = menuContextDefaults.iconSize,\n attendableId,\n alwaysActive,\n onAction,\n}: MenuProviderProps) => {\n const { scope } = useMenuScope(undefined);\n const registry = useContext(RegistryContext);\n const menuItemsAtom = useMemo(() => Atom.make<MenuItemsMap>(new Map()).pipe(Atom.keepAlive), []);\n\n const addMenuItems = useCallback(\n (props: AddMenuItemsProps) => {\n const priority = props.priority ?? DEFAULT_PRIORITY;\n const prev = registry.get(menuItemsAtom);\n const next = new Map(prev);\n next.set(props.id, { ...props, priority });\n registry.set(menuItemsAtom, next);\n },\n [registry, menuItemsAtom],\n );\n\n const removeMenuItems = useCallback(\n (id: string) => {\n const prev = registry.get(menuItemsAtom);\n const next = new Map(prev);\n next.delete(id);\n registry.set(menuItemsAtom, next);\n },\n [registry, menuItemsAtom],\n );\n\n return (\n <MenuContextProvider\n items={items}\n iconSize={iconSize}\n attendableId={attendableId}\n alwaysActive={alwaysActive}\n menuItemsAtom={menuItemsAtom}\n addMenuItems={addMenuItems}\n removeMenuItems={removeMenuItems}\n onAction={onAction}\n scope={scope}\n >\n {children}\n </MenuContextProvider>\n );\n};\n\n//\n// Menu.Root\n//\n\ntype MenuRootProps = MenuProviderProps &\n Pick<DropdownMenuRootProps, 'children' | 'open' | 'defaultOpen' | 'onOpenChange'> & {\n /** Identifies the component that owns this menu (passed to action handlers). */\n caller?: string;\n };\n\n/**\n * Menu context boundary.\n *\n * NOTE: This component is headless since it's root div has `contents`.\n *\n * Provides the menu context (action dispatch, contribution registry, icon size, etc.)\n * and an optional dropdown root for use with `Menu.Trigger` + `Menu.Content`.\n */\nconst MenuRoot = ({ children, open, defaultOpen, onOpenChange, caller, ...props }: MenuRootProps) => {\n const [menuOpen, setMenuOpen] = useControllableState({\n prop: open,\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n });\n\n const closeMenu = useCallback(() => setMenuOpen(false), [setMenuOpen]);\n\n return (\n <MenuProvider {...props}>\n <MenuDropdownContext.Provider value={{ closeMenu, caller }}>\n <NaturalDropdownMenu.Root open={menuOpen} onOpenChange={setMenuOpen}>\n {children}\n </NaturalDropdownMenu.Root>\n </MenuDropdownContext.Provider>\n </MenuProvider>\n );\n};\n\n//\n// Menu.Content\n//\n\ntype MenuContentProps = {\n group?: MenuGroupContext;\n items?: MenuItem[];\n caller?: string;\n};\n\nconst MenuContentItem = ({\n item,\n onClick,\n __menuScope,\n}: MenuScopedProps<{\n item: MenuItem;\n onClick: (action: MenuAction, event: MouseEvent) => void;\n}>) => {\n const action = item as MenuAction;\n const handleClick = useCallback((event: MouseEvent) => onClick(action, event), [action, onClick]);\n const { iconSize } = useMenuScoped('MenuContentItem', __menuScope);\n return (\n <NaturalDropdownMenu.Item\n onClick={handleClick}\n classNames='gap-2'\n disabled={action.properties?.disabled}\n {...(action.properties?.testId && { 'data-testid': action.properties.testId })}\n >\n {action.properties?.icon && (\n <Icon\n icon={action.properties.icon}\n size={iconSize}\n classNames={mx(action.properties.spin && 'animate-spin', action.properties.iconClassNames)}\n />\n )}\n <ActionLabel action={action} />\n </NaturalDropdownMenu.Item>\n );\n};\n\n/**\n * Renders the dropdown menu portal, content, and graph-backed items.\n *\n * Must be a descendant of `Menu.Root`. Reads items via `useMenuItems` from the\n * nearest menu context, with optional `group`/`items` prop overrides.\n */\nconst MenuContent = ({\n group,\n items: propsItems,\n caller: callerOverride,\n __menuScope,\n}: MenuScopedProps<MenuContentProps>) => {\n const { closeMenu, caller: contextCaller } = useContext(MenuDropdownContext);\n const caller = callerOverride ?? contextCaller;\n const { onAction } = useMenuScoped('MenuContent', __menuScope);\n const resolvedItems = useMenuItems(group, propsItems, 'MenuContent', __menuScope);\n\n const handleActionClick = useCallback(\n (action: MenuAction, event: MouseEvent) => {\n if (action.properties?.disabled) {\n return;\n }\n event.stopPropagation();\n closeMenu();\n const params = { parent: group, caller, modifiers: { shift: event.shiftKey } };\n if (onAction) {\n onAction(action, params);\n } else {\n void executeMenuAction(action, params);\n }\n },\n [group, caller, onAction, closeMenu],\n );\n\n return (\n <NaturalDropdownMenu.Portal>\n <NaturalDropdownMenu.Content>\n <NaturalDropdownMenu.Viewport>\n {resolvedItems?.map((item) =>\n isSeparator(item) ? (\n <NaturalDropdownMenu.Separator key={item.id} />\n ) : (\n <MenuContentItem key={item.id} item={item} onClick={handleActionClick} />\n ),\n )}\n </NaturalDropdownMenu.Viewport>\n <NaturalDropdownMenu.Arrow />\n </NaturalDropdownMenu.Content>\n </NaturalDropdownMenu.Portal>\n );\n};\n\n//\n// Namespace.\n//\n\n/**\n * Primary namespace export for the menu system.\n *\n * - `Menu.Root` — context boundary (replaces `MenuProvider`); also provides a dropdown root.\n * - `Menu.Trigger` / `Menu.VirtualTrigger` — dropdown trigger (use with `Menu.Content`).\n * - `Menu.Content` — renders graph-backed dropdown items inside a portal.\n * - `Menu.Toolbar` — attention-gated toolbar container; renders only its children.\n * - `Menu.Items` — the graph-backed toolbar items; place it among `Menu.Toolbar`'s children,\n * whose JSX order controls where the items sit.\n */\nconst Menu = {\n Root: MenuRoot,\n Trigger: NaturalDropdownMenu.Trigger,\n Content: MenuContent,\n VirtualTrigger: NaturalDropdownMenu.VirtualTrigger,\n Toolbar: ToolbarMenu,\n Items: ToolbarMenuItems,\n};\n\nexport { Menu };\n\nexport type { MenuContentProps, MenuRootProps };\n\nexport type {\n ToolbarMenuActionGroupProperties,\n ToolbarMenuActionGroupProps,\n ToolbarMenuActionProps,\n ToolbarMenuDropdownMenuActionGroup,\n ToolbarMenuProps,\n ToolbarMenuToggleGroupActionGroup,\n} from './ToolbarMenu';\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport { type MenuActionProperties } from '@dxos/ui-types';\n\n/**\n * Merges an action's `presentation[surface]` chrome override (if any) into its properties, letting a\n * single action declare multiple dispositions yet render appropriately in each surface — e.g. a\n * primary toolbar button and a plain nav-tree row from the same definition.\n */\nexport const applyPresentation = <A extends AppGraphNode.ActionLike>(action: A, surface: string): A => {\n const presentation = (action.properties as MenuActionProperties).presentation?.[surface];\n if (!presentation) {\n return action;\n }\n return { ...action, properties: { ...action.properties, ...presentation } };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport { RegistryContext } from '@effect/atom-react/RegistryContext';\nimport * as Atom from 'effect/unstable/reactivity/Atom';\nimport { type DependencyList, useCallback, useContext, useMemo } from 'react';\n\nimport * as AppGraph from '@dxos/app-graph/AppGraph';\nimport * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport * as GraphNode from '@dxos/graph/GraphNode';\n\nimport { type MenuItem, type MenuItemGroup, type MenuItemsAccessor } from '../types';\n\nexport type ActionGraphNodes = AppGraphNode.NodeArg<any>[];\nexport type ActionGraphEdges = AppGraph.Edge[];\nexport type ActionGraphProps = {\n nodes: ActionGraphNodes;\n edges: ActionGraphEdges;\n};\n\nexport type MenuActions = {\n items: MenuItemsAccessor;\n};\n\nexport const useMenuActions = (props: Atom.Atom<ActionGraphProps>): MenuActions => {\n const registry = useContext(RegistryContext);\n const menuGraphProps = useAtomValue(props);\n\n // Create a new graph whenever props change to preserve correct order.\n // (AppGraph.addEdges appends rather than replaces, which breaks ordering on updates.)\n // NOTE: Using useMemo rather than a ref-mutation pattern to avoid calling registry.set during render,\n // which would trigger atom state updates in other components (setState-in-render React warning).\n const graph = useMemo(() => {\n const newGraph = AppGraph.make({ registry });\n AppGraph.addNodes(newGraph, menuGraphProps.nodes as AppGraphNode.NodeArg<any>[]);\n AppGraph.addEdges(newGraph, menuGraphProps.edges);\n return newGraph;\n }, [registry, menuGraphProps]);\n\n const items: MenuItemsAccessor = useCallback(\n (group?: MenuItemGroup) => {\n // TODO(wittjosiah): Migrate to using action relation instead of child.\n return graph.connections(group?.id || GraphNode.RootId, 'child') as Atom.Atom<MenuItem[] | null>;\n },\n [graph],\n );\n\n return { items };\n};\n\n/**\n * Convenience wrapper around `useMenuActions` that creates the backing atom inline.\n * Pass a builder thunk and a dependency list — the hook memoizes `Atom.make(build)` and threads\n * it through `useMenuActions`. Saves the `useMemo(() => Atom.make(...), deps)` boilerplate when\n * the action graph is composed from local state (e.g. a toolbar driven by component state).\n *\n * Read reactive state via `get` inside the builder; `deps` should hold only stable references.\n */\nexport const useMenuBuilder = (\n build: (get: Atom.AtomContext) => ActionGraphProps,\n deps: DependencyList,\n): MenuActions => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const atom = useMemo(() => Atom.make(build), deps);\n return useMenuActions(atom);\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Atom from 'effect/unstable/reactivity/Atom';\nimport { useMemo } from 'react';\n\nimport type * as AppGraph from '@dxos/app-graph/AppGraph';\nimport * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport * as GraphNode from '@dxos/graph/GraphNode';\n\nimport { applyPresentation } from '../presentation';\nimport {\n type ActionGraphEdges,\n type ActionGraphNodes,\n type ActionGraphProps,\n type MenuActions,\n useMenuActions,\n} from './useMenuActions';\n\nexport type GraphMenuOptions = {\n /** Group the actions descend from. Defaults to the menu root (top-level toolbar items). */\n rootId?: string;\n /** Keep only the actions the predicate accepts (e.g. by `disposition`). */\n filter?: (action: AppGraphNode.ActionLike) => boolean;\n /**\n * Disposition key this surface renders — when set, each action's `presentation[surface]` chrome\n * override (if any) is merged into its properties, letting one action multi-target dispositions\n * (e.g. `['toolbar', 'list-item']`) with per-surface chrome.\n */\n surface?: string;\n};\n\n/**\n * Flatten a node's contributed actions (and nested {@link AppGraphNode.ActionGroup}s) into the\n * `{ nodes, edges }` an {@link useMenuActions} / `Menu` consumes. The action nodes are spliced\n * verbatim, so their Effect `data` (and captured `_actionContext`) execute via `executeMenuAction`\n * without any `onAction` wiring.\n *\n * Pure over a `resolve` function so it is testable without a live graph; `resolve(id)` returns the\n * direct actions for `id` (a node id at the top level, a group id when expanding a group).\n */\nexport const buildGraphMenu = (\n resolve: (id: string) => AppGraphNode.ActionLike[],\n nodeId: string,\n options: GraphMenuOptions = {},\n): ActionGraphProps => {\n const { rootId = GraphNode.RootId, filter, surface } = options;\n const nodes: ActionGraphNodes = [];\n const edges: ActionGraphEdges = [];\n const seen = new Set<string>();\n\n const visit = (parentId: string, sourceId: string) => {\n for (const action of resolve(sourceId)) {\n if (filter && !filter(action)) {\n continue;\n }\n // Always record the parent→child edge (a shared action may sit under multiple groups).\n edges.push({ source: parentId, target: action.id, relation: 'child' });\n if (seen.has(action.id)) {\n continue;\n }\n seen.add(action.id);\n nodes.push((surface ? applyPresentation(action, surface) : action) as ActionGraphNodes[number]);\n // A group's children are the actions contributed for the group's own id.\n if (AppGraphNode.isActionGroup(action)) {\n visit(action.id, action.id);\n }\n }\n };\n\n visit(rootId, nodeId);\n return { nodes, edges };\n};\n\n/**\n * Read a node's contributed actions from the app-graph as menu `ActionGraphProps`, for splicing into a\n * hand-built toolbar via `MenuBuilder.subgraph(graphActions(graph, get, nodeId))`. Reactive: reads the\n * graph's action atoms through `get`, so contributed actions appear/disappear as extensions update.\n *\n * @idiom org.dxos.react-ui-menu.graphActionsToolbar\n * applies: Splicing a node's app-graph-contributed toolbar actions into a hand-built object toolbar\n * instead-of: Reading `graph.actions(nodeId)` and filtering/wiring `nodes`/`edges` by hand in every toolbar\n * uses: {@link graphActions}, {@link MenuBuilder}, {@link useMenuBuilder}\n * related: org.dxos.react-ui-menu.toolbarMenu\n */\nexport const graphActions = (\n graph: AppGraph.ReadableGraph | undefined,\n get: Atom.AtomContext,\n nodeId: string | undefined,\n options?: GraphMenuOptions,\n): ActionGraphProps =>\n graph && nodeId ? buildGraphMenu((id) => get(graph.actions(id)), nodeId, options) : { nodes: [], edges: [] };\n\n/**\n * Menu actions sourced entirely from a node's app-graph contributions — the common \"show every action\n * the graph offers for this object\" toolbar/menu. Compose with hand-built items via {@link graphActions}.\n */\nexport const useGraphMenuActions = (\n graph: AppGraph.ReadableGraph | undefined,\n nodeId: string | undefined,\n options?: GraphMenuOptions,\n): MenuActions => {\n const atom = useMemo(\n () => Atom.make((get) => graphActions(graph, get, nodeId, options)),\n // `options` is expected to be stable; `rootId` participates since it reshapes the graph.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [graph, nodeId, options?.rootId],\n );\n return useMenuActions(atom);\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\nimport type { DropdownMenuItemGroupProperties, MenuActionProperties, MenuItemGroupProperties } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport type { ActionGraphProps } from './hooks';\nimport { MenuSeparatorType } from './types';\nimport { createMenuAction, createMenuItemGroup } from './util';\n\n/** Default trigger for toolbar overflow (⋮) dropdown menus. */\nconst overflowMenuProperties = {\n label: ['toolbar-overflow.menu', { ns: translationKey }],\n icon: 'ph--dots-three-vertical--regular',\n iconOnly: true,\n caretDown: false,\n variant: 'dropdownMenu',\n} as const satisfies DropdownMenuItemGroupProperties;\n\n/** Callback that populates an action group builder. */\nexport type ActionGroupBuilderFn = (builder: ActionGroupBuilder) => void;\n\n/**\n * Fluent builder for composing menu action graphs.\n * Subgraph functions return a callback that receives the builder, enabling composition via partial application:\n * ```ts\n * const addHeadings = (state): ActionGroupBuilderFn => (builder) => {\n * builder.group('headings', { ... }, (group) => { ... });\n * };\n *\n * MenuBuilder.make()\n * .subgraph(showHeadings && addHeadings(state))\n * .subgraph(showFormatting && addFormatting(state))\n * .build();\n * ```\n */\nexport interface ActionGroupBuilder {\n /** Add an action node as a child of the current group. */\n action<P extends {} = {}>(id: string, props: P & MenuActionProperties, invoke: () => void): this;\n\n /** Add a switch action rendered as a labeled Input.Switch. */\n switch(id: string, props: Omit<MenuActionProperties, 'variant'> & { checked: boolean }, invoke: () => void): this;\n\n /** Add a nested action group. */\n group(id: string, props: MenuItemGroupProperties, cb: ActionGroupBuilderFn): this;\n\n /** Merge pre-built nodes and edges, or build a subgraph via callback scoped to the current group. Falsy values are ignored. */\n subgraph(subgraphOrCb: ActionGraphProps | ActionGroupBuilderFn | false | null | undefined): this;\n\n /** Add a separator. */\n separator(variant?: 'gap' | 'line'): this;\n}\n\n/** Top-level builder that creates the root group and produces the final action graph. */\nexport interface MenuBuilder extends ActionGroupBuilder {\n /**\n * Set properties on the root menu group. May only be called once.\n */\n root(props: MenuItemGroupProperties): this;\n\n /**\n * Add a root-level toolbar overflow dropdown (⋮) with built-in trigger styling.\n */\n menu(id: string, cb: ActionGroupBuilderFn, testId?: string): this;\n\n /**\n * Return the assembled action graph.\n */\n build(): ActionGraphProps;\n}\n\nclass MenuBuilderImpl implements MenuBuilder {\n private _separatorCount = 0;\n\n constructor(\n private readonly _data: ActionGraphProps,\n private readonly _rootId: string,\n ) {}\n\n root(props: MenuItemGroupProperties): this {\n invariant(this._rootId === 'root', 'Root group can only be at the top level');\n invariant(this._data.nodes.find((node) => node.id === 'root') === undefined, 'Root group can only be created once');\n this._data.nodes.push(createMenuItemGroup('root', props));\n return this;\n }\n\n build(): ActionGraphProps {\n return this._data;\n }\n\n action<P extends {} = {}>(id: string, props: P & MenuActionProperties, invoke: () => void): this {\n this._data.nodes.push(createMenuAction(id, invoke, props));\n this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });\n return this;\n }\n\n switch(id: string, props: Omit<MenuActionProperties, 'variant'> & { checked: boolean }, invoke: () => void): this {\n return this.action(id, { ...props, variant: 'switch' }, invoke);\n }\n\n group(id: string, props: MenuItemGroupProperties, cb: ActionGroupBuilderFn): this {\n this._data.nodes.push(createMenuItemGroup(id, props));\n this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });\n cb(new MenuBuilderImpl(this._data, id));\n return this;\n }\n\n menu(id: string, cb: ActionGroupBuilderFn, testId?: string): this {\n return this.group(id, testId === undefined ? overflowMenuProperties : { ...overflowMenuProperties, testId }, cb);\n }\n\n subgraph(subgraphOrCb: ActionGraphProps | ActionGroupBuilderFn | false | null | undefined): this {\n if (!subgraphOrCb) {\n return this;\n }\n if (typeof subgraphOrCb === 'function') {\n subgraphOrCb(new MenuBuilderImpl(this._data, this._rootId));\n } else {\n this._data.nodes.push(...subgraphOrCb.nodes);\n this._data.edges.push(...subgraphOrCb.edges);\n }\n return this;\n }\n\n separator(variant: 'gap' | 'line' = 'gap'): this {\n const id = `separator-${++this._separatorCount}`;\n this._data.nodes.push({\n id,\n type: MenuSeparatorType,\n properties: { variant },\n data: undefined as never,\n });\n this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });\n return this;\n }\n}\n\nexport const MenuBuilder = Object.freeze({\n make: (): MenuBuilder => new MenuBuilderImpl({ nodes: [], edges: [] }, 'root'),\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\n\n/** `disposition` value an action opts into to appear in an object toolbar (vs context-menu-only). */\nexport const TOOLBAR_DISPOSITION = 'toolbar';\n\n/** Filter for {@link graphActions}: keeps only actions a producer opted into the toolbar. */\nexport const isToolbarAction = (action: AppGraphNode.ActionLike): boolean =>\n AppGraphNode.hasDisposition(action, TOOLBAR_DISPOSITION);\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
- import { type Node } from '@dxos/app-graph';
2
+ import type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';
3
3
  import { type MenuActionProperties, type MenuItemChrome } from '@dxos/ui-types';
4
- type Action = Node.Action<MenuActionProperties> | Node.ActionGroup<MenuItemChrome>;
4
+ type Action = AppGraphNode.Action<MenuActionProperties> | AppGraphNode.ActionGroup<MenuItemChrome>;
5
5
  export declare const ActionLabel: ({ action }: {
6
6
  action: Action;
7
7
  }) => React.JSX.Element;