@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.
- package/dist/lib/index.mjs +82 -60
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/components/ActionLabel.d.ts +2 -2
- package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
- package/dist/types/src/components/DropdownMenu.d.ts +3 -1
- package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Menu.d.ts +23 -3
- package/dist/types/src/components/Menu.d.ts.map +1 -1
- package/dist/types/src/components/Menu.stories.d.ts.map +1 -1
- package/dist/types/src/components/MenuContext.d.ts +2 -2
- package/dist/types/src/components/MenuContext.d.ts.map +1 -1
- package/dist/types/src/components/ToolbarMenu.d.ts +6 -0
- package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts +5 -0
- package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
- package/dist/types/src/components/action-label.d.ts +2 -2
- package/dist/types/src/components/action-label.d.ts.map +1 -1
- package/dist/types/src/hooks/useGraphMenuActions.d.ts +8 -7
- package/dist/types/src/hooks/useGraphMenuActions.d.ts.map +1 -1
- package/dist/types/src/hooks/useMenuActions.d.ts +6 -5
- package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
- package/dist/types/src/presentation.d.ts +2 -2
- package/dist/types/src/presentation.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +5 -4
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/toolbar.d.ts +2 -2
- package/dist/types/src/toolbar.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +10 -10
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +6 -6
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -24
- package/src/builder.test.ts +4 -4
- package/src/components/ActionLabel.tsx +2 -2
- package/src/components/DropdownMenu.tsx +10 -3
- package/src/components/Menu.stories.tsx +1 -1
- package/src/components/Menu.test.tsx +1 -1
- package/src/components/Menu.tsx +7 -3
- package/src/components/MenuContext.ts +2 -1
- package/src/components/ToolbarMenu.stories.tsx +34 -5
- package/src/components/ToolbarMenu.tsx +47 -28
- package/src/components/action-label.ts +2 -2
- package/src/hooks/useGraphMenuActions.test.ts +14 -13
- package/src/hooks/useGraphMenuActions.ts +12 -10
- package/src/hooks/useMenuActions.ts +17 -9
- package/src/presentation.ts +2 -2
- package/src/testing/generator.ts +21 -19
- package/src/toolbar.ts +3 -2
- package/src/types.ts +11 -10
- package/src/util.ts +8 -8
package/dist/lib/index.mjs
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
97
|
+
type: AppGraphNode.ActionGroupType,
|
|
95
98
|
properties,
|
|
96
|
-
data:
|
|
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 ===
|
|
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
|
|
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: "
|
|
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:
|
|
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__ */
|
|
383
|
+
return /* @__PURE__ */ jsx(Toolbar.Root, {
|
|
373
384
|
...composableProps(props, { classNames: attendableId }),
|
|
374
385
|
disabled: !alwaysActive && !hasAttention,
|
|
375
386
|
ref: forwardedRef,
|
|
376
|
-
children
|
|
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
|
|
419
|
-
|
|
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` —
|
|
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 =
|
|
558
|
-
|
|
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 ||
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
|
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) =>
|
|
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
|
|
package/dist/lib/index.mjs.map
CHANGED
|
@@ -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
|
|
2
|
+
import type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';
|
|
3
3
|
import { type MenuActionProperties, type MenuItemChrome } from '@dxos/ui-types';
|
|
4
|
-
type Action =
|
|
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;
|