@dxos/react-ui-menu 0.10.0 → 0.11.0
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 +747 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/translations.mjs +7 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/types/src/builder.d.ts +1 -1
- package/dist/types/src/builder.d.ts.map +1 -1
- package/dist/types/src/components/ActionLabel.d.ts +0 -2
- package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
- package/dist/types/src/components/DropdownMenu.d.ts +2 -4
- package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Menu.d.ts +5 -15
- package/dist/types/src/components/Menu.d.ts.map +1 -1
- package/dist/types/src/components/MenuContext.d.ts +28 -0
- package/dist/types/src/components/MenuContext.d.ts.map +1 -0
- package/dist/types/src/components/ToolbarMenu.d.ts +2 -2
- package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts +2 -2
- package/dist/types/src/components/action-label.d.ts +7 -0
- package/dist/types/src/components/action-label.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useGraphMenuActions.d.ts +13 -1
- package/dist/types/src/hooks/useGraphMenuActions.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/presentation.d.ts +8 -0
- package/dist/types/src/presentation.d.ts.map +1 -0
- package/dist/types/src/toolbar.d.ts +6 -0
- package/dist/types/src/toolbar.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +5 -3
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -20
- package/src/builder.ts +3 -3
- package/src/components/ActionLabel.tsx +1 -6
- package/src/components/DropdownMenu.tsx +30 -8
- package/src/components/Menu.stories.tsx +1 -1
- package/src/components/Menu.test.tsx +3 -2
- package/src/components/Menu.tsx +32 -122
- package/src/components/MenuContext.ts +129 -0
- package/src/components/ToolbarMenu.tsx +27 -13
- package/src/components/action-label.ts +20 -0
- package/src/components/index.ts +3 -0
- package/src/hooks/useGraphMenuActions.ts +16 -3
- package/src/index.ts +2 -0
- package/src/presentation.ts +19 -0
- package/src/toolbar.ts +11 -0
- package/src/types.ts +1 -1
- package/src/util.ts +2 -2
- package/dist/lib/browser/index.mjs +0 -797
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/translations.mjs +0 -16
- package/dist/lib/browser/translations.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -799
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/translations.mjs +0 -18
- package/dist/lib/node-esm/translations.mjs.map +0 -7
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import { Atom, RegistryContext, useAtomValue } from "@effect-atom/atom-react";
|
|
2
|
+
import { createContextScope } from "@radix-ui/react-context";
|
|
3
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { log } from "@dxos/log";
|
|
5
|
+
import { keySymbols } from "@dxos/keyboard";
|
|
6
|
+
import { DropdownMenu, Icon, Input, Toolbar, Tooltip, composable, composableProps, toLocalizedString, useTranslation } from "@dxos/react-ui";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import { Graph, Node } from "@dxos/app-graph";
|
|
9
|
+
import { EffectEx } from "@dxos/effect";
|
|
10
|
+
import { getHostPlatform } from "@dxos/util";
|
|
11
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
12
|
+
import { mx } from "@dxos/ui-theme";
|
|
13
|
+
import { translationKey } from "#translations";
|
|
14
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
import { useAttention } from "@dxos/react-ui-attention";
|
|
16
|
+
import { Atom as Atom$1 } from "@effect-atom/atom";
|
|
17
|
+
import { invariant } from "@dxos/invariant";
|
|
18
|
+
//#region src/components/MenuContext.ts
|
|
19
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/components/MenuContext.ts";
|
|
20
|
+
var MENU_NAME = "Menu";
|
|
21
|
+
var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
|
|
22
|
+
var nullItemsAtom = Atom.make(null);
|
|
23
|
+
var defaultItemsAccessor = () => nullItemsAtom;
|
|
24
|
+
var menuContextDefaults = {
|
|
25
|
+
iconSize: 5,
|
|
26
|
+
items: defaultItemsAccessor,
|
|
27
|
+
onAction: void 0,
|
|
28
|
+
menuItemsAtom: Atom.make(/* @__PURE__ */ new Map()),
|
|
29
|
+
addMenuItems: () => {},
|
|
30
|
+
removeMenuItems: () => {}
|
|
31
|
+
};
|
|
32
|
+
var [MenuContextProvider, useMenuScoped] = createMenuContext(MENU_NAME, menuContextDefaults);
|
|
33
|
+
var useMenuScope = createMenuScope();
|
|
34
|
+
var MenuDropdownContext = createContext({ closeMenu: () => {} });
|
|
35
|
+
var sortMenuItems = (items) => [...items].sort((a, b) => a.priority !== b.priority ? a.priority - b.priority : a.id.localeCompare(b.id));
|
|
36
|
+
var resolveItems = (baseItems, group, entries) => {
|
|
37
|
+
const applicable = [...entries.values()].filter((entry) => !entry.groupFilter || entry.groupFilter(group));
|
|
38
|
+
if (applicable.length === 0) return baseItems;
|
|
39
|
+
const sorted = sortMenuItems(applicable);
|
|
40
|
+
const replacements = sorted.filter((entry) => entry.mode === "replacement");
|
|
41
|
+
if (replacements.length > 0) {
|
|
42
|
+
if (replacements.length > 1) log.warn("multiple replacement entries found", {
|
|
43
|
+
ids: replacements.map((r) => r.id).join(", "),
|
|
44
|
+
using: replacements[0].id
|
|
45
|
+
}, {
|
|
46
|
+
"~LogMeta": "~LogMeta",
|
|
47
|
+
F: __dxlog_file$1,
|
|
48
|
+
L: 84,
|
|
49
|
+
S: void 0
|
|
50
|
+
});
|
|
51
|
+
return replacements[0].items;
|
|
52
|
+
}
|
|
53
|
+
const additiveItems = sorted.filter((entry) => entry.mode === "additive").flatMap((entry) => entry.items);
|
|
54
|
+
if (!baseItems || baseItems.length === 0) return additiveItems.length > 0 ? additiveItems : null;
|
|
55
|
+
return [...baseItems, ...additiveItems];
|
|
56
|
+
};
|
|
57
|
+
var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
|
|
58
|
+
const { items, menuItemsAtom } = useMenuScoped(consumerName, __menuScope);
|
|
59
|
+
const groupItems = useAtomValue(items(group));
|
|
60
|
+
const entries = useAtomValue(menuItemsAtom) ?? /* @__PURE__ */ new Map();
|
|
61
|
+
const baseItems = useMemo(() => propsItems ?? groupItems ?? null, [propsItems, groupItems]);
|
|
62
|
+
return useMemo(() => resolveItems(baseItems, group, entries), [
|
|
63
|
+
baseItems,
|
|
64
|
+
group,
|
|
65
|
+
entries
|
|
66
|
+
]) ?? void 0;
|
|
67
|
+
};
|
|
68
|
+
/** Returns the menu context without Radix scope. */
|
|
69
|
+
var useMenu = (consumerName) => {
|
|
70
|
+
return useMenuScoped(consumerName, void 0);
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/util.ts
|
|
74
|
+
/**
|
|
75
|
+
* Execute a menu action's Effect with its captured context.
|
|
76
|
+
* This provides the `_actionContext` layer if available.
|
|
77
|
+
*/
|
|
78
|
+
var executeMenuAction = async (action, params = {}) => {
|
|
79
|
+
let effect = action.data(params);
|
|
80
|
+
if (action._actionContext) effect = effect.pipe(Effect.provide(action._actionContext));
|
|
81
|
+
await EffectEx.runAndForwardErrors(effect);
|
|
82
|
+
};
|
|
83
|
+
var getShortcut = (action) => {
|
|
84
|
+
return typeof action.properties?.keyBinding === "string" ? action.properties.keyBinding : action.properties?.keyBinding?.[getHostPlatform()];
|
|
85
|
+
};
|
|
86
|
+
var createMenuAction = (id, invoke, properties) => ({
|
|
87
|
+
id,
|
|
88
|
+
type: Node.ActionType,
|
|
89
|
+
properties,
|
|
90
|
+
data: (params) => Effect.sync(() => invoke(params))
|
|
91
|
+
});
|
|
92
|
+
var createMenuItemGroup = (id, properties) => ({
|
|
93
|
+
id,
|
|
94
|
+
type: Node.ActionGroupType,
|
|
95
|
+
properties,
|
|
96
|
+
data: Node.actionGroupSymbol
|
|
97
|
+
});
|
|
98
|
+
var createGapSeparator = (id = "gap", source = "root") => ({
|
|
99
|
+
nodes: [{
|
|
100
|
+
id,
|
|
101
|
+
type: "@dxos/react-ui-toolbar/separator",
|
|
102
|
+
properties: { variant: "gap" },
|
|
103
|
+
data: void 0
|
|
104
|
+
}],
|
|
105
|
+
edges: [{
|
|
106
|
+
source,
|
|
107
|
+
target: id,
|
|
108
|
+
relation: "child"
|
|
109
|
+
}]
|
|
110
|
+
});
|
|
111
|
+
var createLineSeparator = (id = "line", source = "root") => ({
|
|
112
|
+
nodes: [{
|
|
113
|
+
id,
|
|
114
|
+
type: "@dxos/react-ui-toolbar/separator",
|
|
115
|
+
properties: { variant: "line" },
|
|
116
|
+
data: void 0
|
|
117
|
+
}],
|
|
118
|
+
edges: [{
|
|
119
|
+
source,
|
|
120
|
+
target: id,
|
|
121
|
+
relation: "child"
|
|
122
|
+
}]
|
|
123
|
+
});
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/components/action-label.ts
|
|
126
|
+
var actionLabel = (action, t) => {
|
|
127
|
+
const shortcut = getShortcut(action);
|
|
128
|
+
return `${toLocalizedString(action.properties.label, t)}${shortcut ? ` (${keySymbols(shortcut).join("")})` : ""}`;
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/types.ts
|
|
132
|
+
var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
|
|
133
|
+
var isSeparator = (node) => node.type === MenuSeparatorType;
|
|
134
|
+
var isMenuGroup = (node) => node.type === Node.ActionGroupType;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/components/ActionLabel.tsx
|
|
137
|
+
var ActionLabel = ({ action }) => {
|
|
138
|
+
const { t } = useTranslation(translationKey);
|
|
139
|
+
const shortcut = getShortcut(action);
|
|
140
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
141
|
+
className: "grow truncate",
|
|
142
|
+
children: toLocalizedString(action.properties.label, t)
|
|
143
|
+
}), shortcut && /* @__PURE__ */ jsx("span", {
|
|
144
|
+
className: mx("shrink-0", "text-description"),
|
|
145
|
+
children: keySymbols(shortcut).join("")
|
|
146
|
+
})] });
|
|
147
|
+
};
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/components/DropdownMenu.tsx
|
|
150
|
+
var DropdownMenuItem = ({ item, group, onClick, __menuScope }) => {
|
|
151
|
+
const action = item;
|
|
152
|
+
const handleClick = useCallback((event) => onClick(action, event), [action, onClick]);
|
|
153
|
+
const { iconSize } = useMenuScoped("DropdownMenuItem", __menuScope);
|
|
154
|
+
const checkable = typeof action.properties?.checked === "boolean";
|
|
155
|
+
const role = group?.properties?.selectCardinality === "multiple" ? "menuitemcheckbox" : "menuitemradio";
|
|
156
|
+
return /* @__PURE__ */ jsxs(DropdownMenu.Item, {
|
|
157
|
+
onClick: handleClick,
|
|
158
|
+
classNames: "gap-2",
|
|
159
|
+
disabled: action.properties?.disabled,
|
|
160
|
+
...checkable && {
|
|
161
|
+
role,
|
|
162
|
+
"aria-checked": !!action.properties?.checked
|
|
163
|
+
},
|
|
164
|
+
...action.properties?.testId && { "data-testid": action.properties.testId },
|
|
165
|
+
children: [
|
|
166
|
+
action.properties?.icon && /* @__PURE__ */ jsx(Icon, {
|
|
167
|
+
icon: action.properties.icon,
|
|
168
|
+
size: iconSize,
|
|
169
|
+
classNames: mx(action.properties.spin && "animate-spin", action.properties.iconClassNames)
|
|
170
|
+
}),
|
|
171
|
+
/* @__PURE__ */ jsx(ActionLabel, { action }),
|
|
172
|
+
action.properties?.checked && /* @__PURE__ */ jsx(Icon, {
|
|
173
|
+
icon: "ph--check--regular",
|
|
174
|
+
size: iconSize,
|
|
175
|
+
classNames: "mis-auto"
|
|
176
|
+
})
|
|
177
|
+
]
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenChange, caller, children, __menuScope, ...naturalProps }) => {
|
|
181
|
+
const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
|
|
182
|
+
prop: open,
|
|
183
|
+
defaultProp: defaultOpen,
|
|
184
|
+
onChange: onOpenChange
|
|
185
|
+
});
|
|
186
|
+
const { onAction } = useMenuScoped("DropdownMenuRoot", __menuScope);
|
|
187
|
+
const handleActionClick = useCallback((action, event) => {
|
|
188
|
+
if (action.properties?.disabled) return;
|
|
189
|
+
event.stopPropagation();
|
|
190
|
+
setOptionsMenuOpen(false);
|
|
191
|
+
const params = {
|
|
192
|
+
parent: group,
|
|
193
|
+
caller,
|
|
194
|
+
modifiers: { shift: event.shiftKey }
|
|
195
|
+
};
|
|
196
|
+
if (onAction) onAction(action, params);
|
|
197
|
+
else executeMenuAction(action, params);
|
|
198
|
+
}, [
|
|
199
|
+
group,
|
|
200
|
+
caller,
|
|
201
|
+
onAction
|
|
202
|
+
]);
|
|
203
|
+
const items = useMenuItems(group, propsItems);
|
|
204
|
+
return /* @__PURE__ */ jsxs(DropdownMenu.Root, {
|
|
205
|
+
open: optionsMenuOpen,
|
|
206
|
+
onOpenChange: setOptionsMenuOpen,
|
|
207
|
+
...naturalProps,
|
|
208
|
+
children: [children, /* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsxs(DropdownMenu.Content, { children: [/* @__PURE__ */ jsx(DropdownMenu.Viewport, { children: items?.map((item) => isSeparator(item) ? /* @__PURE__ */ jsx(DropdownMenu.Separator, {}, item.id) : /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
209
|
+
item,
|
|
210
|
+
group,
|
|
211
|
+
onClick: handleActionClick
|
|
212
|
+
}, item.id)) }), /* @__PURE__ */ jsx(DropdownMenu.Arrow, {})] }) })]
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
var DropdownMenu$1 = {
|
|
216
|
+
Root: DropdownMenuRoot,
|
|
217
|
+
Trigger: DropdownMenu.Trigger,
|
|
218
|
+
VirtualTrigger: DropdownMenu.VirtualTrigger
|
|
219
|
+
};
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/components/ToolbarMenu.tsx
|
|
222
|
+
var ActionToolbarItem = ({ __menuScope, action }) => {
|
|
223
|
+
const { iconSize, onAction } = useMenuScoped("ActionToolbarItem", __menuScope);
|
|
224
|
+
const { t } = useTranslation(translationKey);
|
|
225
|
+
const [pending, setPending] = useState(false);
|
|
226
|
+
const pendingRef = useRef(false);
|
|
227
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;
|
|
228
|
+
const buttonVariant = action.properties.variant === "primary" ? "primary" : "ghost";
|
|
229
|
+
const handleClick = useCallback(() => {
|
|
230
|
+
if (pendingRef.current) return;
|
|
231
|
+
pendingRef.current = true;
|
|
232
|
+
setPending(true);
|
|
233
|
+
const done = () => {
|
|
234
|
+
pendingRef.current = false;
|
|
235
|
+
setPending(false);
|
|
236
|
+
};
|
|
237
|
+
if (onAction) Promise.resolve(onAction(action, {})).then(done, done);
|
|
238
|
+
else executeMenuAction(action).then(done, done);
|
|
239
|
+
}, [action, onAction]);
|
|
240
|
+
const commonProps = {
|
|
241
|
+
variant: buttonVariant,
|
|
242
|
+
disabled: disabled || pending,
|
|
243
|
+
classNames,
|
|
244
|
+
onClick: handleClick,
|
|
245
|
+
...testId && { "data-testid": testId }
|
|
246
|
+
};
|
|
247
|
+
if (hidden) return null;
|
|
248
|
+
return icon ? /* @__PURE__ */ jsx(Toolbar.IconButton, {
|
|
249
|
+
...commonProps,
|
|
250
|
+
icon,
|
|
251
|
+
size: iconSize,
|
|
252
|
+
iconOnly,
|
|
253
|
+
iconClassNames: mx(spin && "animate-spin", iconClassNames),
|
|
254
|
+
label: actionLabel(action, t)
|
|
255
|
+
}, action.id) : /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
256
|
+
...commonProps,
|
|
257
|
+
children: /* @__PURE__ */ jsx(ActionLabel, { action })
|
|
258
|
+
}, action.id);
|
|
259
|
+
};
|
|
260
|
+
var SwitchToolbarItem = ({ __menuScope, action }) => {
|
|
261
|
+
const { onAction } = useMenuScoped("SwitchToolbarItem", __menuScope);
|
|
262
|
+
const { t } = useTranslation(translationKey);
|
|
263
|
+
const { label, iconOnly, disabled, testId, hidden, checked } = action.properties;
|
|
264
|
+
const labelStr = toLocalizedString(label, t);
|
|
265
|
+
const handleCheckedChange = useCallback(() => {
|
|
266
|
+
if (onAction) onAction(action, {});
|
|
267
|
+
else executeMenuAction(action);
|
|
268
|
+
}, [action, onAction]);
|
|
269
|
+
if (hidden) return null;
|
|
270
|
+
const switchInput = /* @__PURE__ */ jsx(Input.Switch, {
|
|
271
|
+
checked,
|
|
272
|
+
disabled,
|
|
273
|
+
"aria-label": iconOnly ? labelStr : void 0,
|
|
274
|
+
onCheckedChange: handleCheckedChange,
|
|
275
|
+
...testId && { "data-testid": testId }
|
|
276
|
+
});
|
|
277
|
+
return /* @__PURE__ */ jsxs(Input.Root, { children: [iconOnly ? /* @__PURE__ */ jsx(Tooltip.Trigger, {
|
|
278
|
+
asChild: true,
|
|
279
|
+
content: labelStr,
|
|
280
|
+
children: /* @__PURE__ */ jsx(Input.Block, { children: switchInput })
|
|
281
|
+
}) : /* @__PURE__ */ jsx(Input.Block, { children: switchInput }), !iconOnly && /* @__PURE__ */ jsx(Input.Label, { children: labelStr })] });
|
|
282
|
+
};
|
|
283
|
+
var DropdownMenuToolbarItem = ({ __menuScope, group, items: propsItems }) => {
|
|
284
|
+
const { t } = useTranslation(translationKey);
|
|
285
|
+
const { iconSize } = useMenuScoped("DropdownMenuToolbarItem", __menuScope);
|
|
286
|
+
const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
|
|
287
|
+
const { iconOnly, disabled, testId, applyActive, caretDown = true, icon: groupIcon, iconClassNames: groupIconClassNames, spin: groupSpin } = group.properties;
|
|
288
|
+
const activeItem = items?.find((item) => !!item.properties.checked);
|
|
289
|
+
const icon = applyActive && activeItem?.properties.icon || groupIcon;
|
|
290
|
+
const iconClassNames = applyActive && activeItem?.properties.iconClassNames || groupIconClassNames;
|
|
291
|
+
const spin = applyActive && activeItem?.properties.spin || groupSpin;
|
|
292
|
+
const labelAction = applyActive && activeItem ? activeItem : group;
|
|
293
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Root, {
|
|
294
|
+
group,
|
|
295
|
+
items,
|
|
296
|
+
children: /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, {
|
|
297
|
+
asChild: true,
|
|
298
|
+
children: icon ? /* @__PURE__ */ jsx(Toolbar.IconButton, {
|
|
299
|
+
variant: "ghost",
|
|
300
|
+
disabled,
|
|
301
|
+
icon,
|
|
302
|
+
size: iconSize,
|
|
303
|
+
iconOnly,
|
|
304
|
+
iconClassNames: mx(spin && "animate-spin", iconClassNames),
|
|
305
|
+
label: actionLabel(labelAction, t),
|
|
306
|
+
caretDown,
|
|
307
|
+
...testId && { "data-testid": testId }
|
|
308
|
+
}) : /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
309
|
+
variant: "ghost",
|
|
310
|
+
disabled,
|
|
311
|
+
caretDown,
|
|
312
|
+
...testId && { "data-testid": testId },
|
|
313
|
+
children: /* @__PURE__ */ jsx(ActionLabel, { action: labelAction })
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
var ToggleGroupItem = ({ __menuScope, group, action }) => {
|
|
319
|
+
const { iconSize, onAction } = useMenuScoped("ToggleGroupItem", __menuScope);
|
|
320
|
+
const { t } = useTranslation(translationKey);
|
|
321
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;
|
|
322
|
+
const handleClick = useCallback(() => {
|
|
323
|
+
if (onAction) onAction(action, { parent: group });
|
|
324
|
+
else executeMenuAction(action, { parent: group });
|
|
325
|
+
}, [
|
|
326
|
+
action,
|
|
327
|
+
group,
|
|
328
|
+
onAction
|
|
329
|
+
]);
|
|
330
|
+
const commonProps = {
|
|
331
|
+
value: action.id,
|
|
332
|
+
disabled,
|
|
333
|
+
variant: "ghost",
|
|
334
|
+
classNames,
|
|
335
|
+
onClick: handleClick,
|
|
336
|
+
...testId && { "data-testid": testId }
|
|
337
|
+
};
|
|
338
|
+
return hidden ? null : icon ? /* @__PURE__ */ jsx(Toolbar.ToggleGroupIconItem, {
|
|
339
|
+
...commonProps,
|
|
340
|
+
icon,
|
|
341
|
+
size: iconSize,
|
|
342
|
+
iconOnly,
|
|
343
|
+
iconClassNames: mx(spin && "animate-spin", iconClassNames),
|
|
344
|
+
label: actionLabel(action, t)
|
|
345
|
+
}) : /* @__PURE__ */ jsx(Toolbar.ToggleGroupItem, {
|
|
346
|
+
...commonProps,
|
|
347
|
+
children: /* @__PURE__ */ jsx(ActionLabel, { action })
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
var ToggleGroupToolbarItem = ({ __menuScope, group, items: itemsProp }) => {
|
|
351
|
+
const items = useMenuItems(group, itemsProp, "ToggleGroupToolbarItem", __menuScope);
|
|
352
|
+
const { selectCardinality } = group.properties;
|
|
353
|
+
const children = items.map((action) => /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
354
|
+
group,
|
|
355
|
+
action
|
|
356
|
+
}, action.id));
|
|
357
|
+
if (selectCardinality === "multiple") return /* @__PURE__ */ jsx(Toolbar.ToggleGroup, {
|
|
358
|
+
type: "multiple",
|
|
359
|
+
value: group.properties.value,
|
|
360
|
+
children
|
|
361
|
+
});
|
|
362
|
+
else return /* @__PURE__ */ jsx(Toolbar.ToggleGroup, {
|
|
363
|
+
type: "single",
|
|
364
|
+
value: group.properties.value,
|
|
365
|
+
children
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
var ToolbarMenu = composable(({ __menuScope, children, ...props }, forwardedRef) => {
|
|
369
|
+
const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
|
|
370
|
+
const { attendableId, alwaysActive } = useMenuScoped("ToolbarMenu", __menuScope);
|
|
371
|
+
const { hasAttention } = useAttention(attendableId);
|
|
372
|
+
return /* @__PURE__ */ jsxs(Toolbar.Root, {
|
|
373
|
+
...composableProps(props, { classNames: attendableId }),
|
|
374
|
+
disabled: !alwaysActive && !hasAttention,
|
|
375
|
+
ref: forwardedRef,
|
|
376
|
+
children: [items?.map((item) => /* @__PURE__ */ jsx(ToolbarMenuItem, {
|
|
377
|
+
__menuScope,
|
|
378
|
+
item
|
|
379
|
+
}, item.id)), children]
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
var ToolbarMenuItem = ({ __menuScope, item }) => {
|
|
383
|
+
if (isSeparator(item)) return /* @__PURE__ */ jsx(Toolbar.Separator, { variant: item.properties.variant });
|
|
384
|
+
if (isMenuGroup(item)) {
|
|
385
|
+
if (item.properties.variant === "dropdownMenu") return /* @__PURE__ */ jsx(DropdownMenuToolbarItem, {
|
|
386
|
+
__menuScope,
|
|
387
|
+
group: item
|
|
388
|
+
});
|
|
389
|
+
return /* @__PURE__ */ jsx(ToggleGroupToolbarItem, {
|
|
390
|
+
__menuScope,
|
|
391
|
+
group: item
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const action = item;
|
|
395
|
+
if (action.properties?.variant === "switch") return /* @__PURE__ */ jsx(SwitchToolbarItem, {
|
|
396
|
+
__menuScope,
|
|
397
|
+
action
|
|
398
|
+
});
|
|
399
|
+
if (action.properties?.variant === "custom" && action.properties.render) return /* @__PURE__ */ jsx(Fragment, { children: action.properties.render() });
|
|
400
|
+
return /* @__PURE__ */ jsx(ActionToolbarItem, {
|
|
401
|
+
__menuScope,
|
|
402
|
+
action
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/components/Menu.tsx
|
|
407
|
+
var DEFAULT_PRIORITY = 100;
|
|
408
|
+
var MenuProvider = ({ children, items = menuContextDefaults.items, iconSize = menuContextDefaults.iconSize, attendableId, alwaysActive, onAction }) => {
|
|
409
|
+
const { scope } = useMenuScope(void 0);
|
|
410
|
+
const registry = useContext(RegistryContext);
|
|
411
|
+
const menuItemsAtom = useMemo(() => Atom.make(/* @__PURE__ */ new Map()).pipe(Atom.keepAlive), []);
|
|
412
|
+
return /* @__PURE__ */ jsx(MenuContextProvider, {
|
|
413
|
+
items,
|
|
414
|
+
iconSize,
|
|
415
|
+
attendableId,
|
|
416
|
+
alwaysActive,
|
|
417
|
+
menuItemsAtom,
|
|
418
|
+
addMenuItems: useCallback((props) => {
|
|
419
|
+
const priority = props.priority ?? DEFAULT_PRIORITY;
|
|
420
|
+
const prev = registry.get(menuItemsAtom);
|
|
421
|
+
const next = new Map(prev);
|
|
422
|
+
next.set(props.id, {
|
|
423
|
+
...props,
|
|
424
|
+
priority
|
|
425
|
+
});
|
|
426
|
+
registry.set(menuItemsAtom, next);
|
|
427
|
+
}, [registry, menuItemsAtom]),
|
|
428
|
+
removeMenuItems: useCallback((id) => {
|
|
429
|
+
const prev = registry.get(menuItemsAtom);
|
|
430
|
+
const next = new Map(prev);
|
|
431
|
+
next.delete(id);
|
|
432
|
+
registry.set(menuItemsAtom, next);
|
|
433
|
+
}, [registry, menuItemsAtom]),
|
|
434
|
+
onAction,
|
|
435
|
+
scope,
|
|
436
|
+
children
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* Menu context boundary.
|
|
441
|
+
*
|
|
442
|
+
* NOTE: This component is headless since it's root div has `contents`.
|
|
443
|
+
*
|
|
444
|
+
* Provides the menu context (action dispatch, contribution registry, icon size, etc.)
|
|
445
|
+
* and an optional dropdown root for use with `Menu.Trigger` + `Menu.Content`.
|
|
446
|
+
*/
|
|
447
|
+
var MenuRoot = ({ children, open, defaultOpen, onOpenChange, caller, ...props }) => {
|
|
448
|
+
const [menuOpen, setMenuOpen] = useControllableState({
|
|
449
|
+
prop: open,
|
|
450
|
+
defaultProp: defaultOpen,
|
|
451
|
+
onChange: onOpenChange
|
|
452
|
+
});
|
|
453
|
+
const closeMenu = useCallback(() => setMenuOpen(false), [setMenuOpen]);
|
|
454
|
+
return /* @__PURE__ */ jsx(MenuProvider, {
|
|
455
|
+
...props,
|
|
456
|
+
children: /* @__PURE__ */ jsx(MenuDropdownContext.Provider, {
|
|
457
|
+
value: {
|
|
458
|
+
closeMenu,
|
|
459
|
+
caller
|
|
460
|
+
},
|
|
461
|
+
children: /* @__PURE__ */ jsx(DropdownMenu.Root, {
|
|
462
|
+
open: menuOpen,
|
|
463
|
+
onOpenChange: setMenuOpen,
|
|
464
|
+
children
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
var MenuContentItem = ({ item, onClick, __menuScope }) => {
|
|
470
|
+
const action = item;
|
|
471
|
+
const handleClick = useCallback((event) => onClick(action, event), [action, onClick]);
|
|
472
|
+
const { iconSize } = useMenuScoped("MenuContentItem", __menuScope);
|
|
473
|
+
return /* @__PURE__ */ jsxs(DropdownMenu.Item, {
|
|
474
|
+
onClick: handleClick,
|
|
475
|
+
classNames: "gap-2",
|
|
476
|
+
disabled: action.properties?.disabled,
|
|
477
|
+
...action.properties?.testId && { "data-testid": action.properties.testId },
|
|
478
|
+
children: [action.properties?.icon && /* @__PURE__ */ jsx(Icon, {
|
|
479
|
+
icon: action.properties.icon,
|
|
480
|
+
size: iconSize,
|
|
481
|
+
classNames: mx(action.properties.spin && "animate-spin", action.properties.iconClassNames)
|
|
482
|
+
}), /* @__PURE__ */ jsx(ActionLabel, { action })]
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* Renders the dropdown menu portal, content, and graph-backed items.
|
|
487
|
+
*
|
|
488
|
+
* Must be a descendant of `Menu.Root`. Reads items via `useMenuItems` from the
|
|
489
|
+
* nearest menu context, with optional `group`/`items` prop overrides.
|
|
490
|
+
*/
|
|
491
|
+
var MenuContent = ({ group, items: propsItems, caller: callerOverride, __menuScope }) => {
|
|
492
|
+
const { closeMenu, caller: contextCaller } = useContext(MenuDropdownContext);
|
|
493
|
+
const caller = callerOverride ?? contextCaller;
|
|
494
|
+
const { onAction } = useMenuScoped("MenuContent", __menuScope);
|
|
495
|
+
const resolvedItems = useMenuItems(group, propsItems, "MenuContent", __menuScope);
|
|
496
|
+
const handleActionClick = useCallback((action, event) => {
|
|
497
|
+
if (action.properties?.disabled) return;
|
|
498
|
+
event.stopPropagation();
|
|
499
|
+
closeMenu();
|
|
500
|
+
const params = {
|
|
501
|
+
parent: group,
|
|
502
|
+
caller,
|
|
503
|
+
modifiers: { shift: event.shiftKey }
|
|
504
|
+
};
|
|
505
|
+
if (onAction) onAction(action, params);
|
|
506
|
+
else executeMenuAction(action, params);
|
|
507
|
+
}, [
|
|
508
|
+
group,
|
|
509
|
+
caller,
|
|
510
|
+
onAction,
|
|
511
|
+
closeMenu
|
|
512
|
+
]);
|
|
513
|
+
return /* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsxs(DropdownMenu.Content, { children: [/* @__PURE__ */ jsx(DropdownMenu.Viewport, { children: resolvedItems?.map((item) => isSeparator(item) ? /* @__PURE__ */ jsx(DropdownMenu.Separator, {}, item.id) : /* @__PURE__ */ jsx(MenuContentItem, {
|
|
514
|
+
item,
|
|
515
|
+
onClick: handleActionClick
|
|
516
|
+
}, item.id)) }), /* @__PURE__ */ jsx(DropdownMenu.Arrow, {})] }) });
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Primary namespace export for the menu system.
|
|
520
|
+
*
|
|
521
|
+
* - `Menu.Root` — context boundary (replaces `MenuProvider`); also provides a dropdown root.
|
|
522
|
+
* - `Menu.Trigger` / `Menu.VirtualTrigger` — dropdown trigger (use with `Menu.Content`).
|
|
523
|
+
* - `Menu.Content` — renders graph-backed dropdown items inside a portal.
|
|
524
|
+
* - `Menu.Toolbar` — flat toolbar component with graph-backed item rendering.
|
|
525
|
+
*/
|
|
526
|
+
var Menu = {
|
|
527
|
+
Root: MenuRoot,
|
|
528
|
+
Trigger: DropdownMenu.Trigger,
|
|
529
|
+
Content: MenuContent,
|
|
530
|
+
VirtualTrigger: DropdownMenu.VirtualTrigger,
|
|
531
|
+
Toolbar: ToolbarMenu
|
|
532
|
+
};
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region src/presentation.ts
|
|
535
|
+
/**
|
|
536
|
+
* Merges an action's `presentation[surface]` chrome override (if any) into its properties, letting a
|
|
537
|
+
* single action declare multiple dispositions yet render appropriately in each surface — e.g. a
|
|
538
|
+
* primary toolbar button and a plain nav-tree row from the same definition.
|
|
539
|
+
*/
|
|
540
|
+
var applyPresentation = (action, surface) => {
|
|
541
|
+
const presentation = action.properties.presentation?.[surface];
|
|
542
|
+
if (!presentation) return action;
|
|
543
|
+
return {
|
|
544
|
+
...action,
|
|
545
|
+
properties: {
|
|
546
|
+
...action.properties,
|
|
547
|
+
...presentation
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/hooks/useMenuActions.ts
|
|
553
|
+
var useMenuActions = (props) => {
|
|
554
|
+
const registry = useContext(RegistryContext);
|
|
555
|
+
const menuGraphProps = useAtomValue(props);
|
|
556
|
+
const graph = useMemo(() => {
|
|
557
|
+
const newGraph = Graph.make({ registry });
|
|
558
|
+
newGraph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
|
|
559
|
+
return newGraph;
|
|
560
|
+
}, [registry, menuGraphProps]);
|
|
561
|
+
return { items: useCallback((group) => {
|
|
562
|
+
return graph.connections(group?.id || Node.RootId, "child");
|
|
563
|
+
}, [graph]) };
|
|
564
|
+
};
|
|
565
|
+
/**
|
|
566
|
+
* Convenience wrapper around `useMenuActions` that creates the backing atom inline.
|
|
567
|
+
* Pass a builder thunk and a dependency list — the hook memoizes `Atom.make(build)` and threads
|
|
568
|
+
* it through `useMenuActions`. Saves the `useMemo(() => Atom.make(...), deps)` boilerplate when
|
|
569
|
+
* the action graph is composed from local state (e.g. a toolbar driven by component state).
|
|
570
|
+
*
|
|
571
|
+
* Read reactive state via `get` inside the builder; `deps` should hold only stable references.
|
|
572
|
+
*/
|
|
573
|
+
var useMenuBuilder = (build, deps) => {
|
|
574
|
+
return useMenuActions(useMemo(() => Atom.make(build), deps));
|
|
575
|
+
};
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/hooks/useGraphMenuActions.ts
|
|
578
|
+
/**
|
|
579
|
+
* Flatten a node's contributed actions (and nested {@link Node.ActionGroup}s) into the
|
|
580
|
+
* `{ nodes, edges }` an {@link useMenuActions} / `Menu` consumes. The action nodes are spliced
|
|
581
|
+
* verbatim, so their Effect `data` (and captured `_actionContext`) execute via `executeMenuAction`
|
|
582
|
+
* without any `onAction` wiring.
|
|
583
|
+
*
|
|
584
|
+
* Pure over a `resolve` function so it is testable without a live graph; `resolve(id)` returns the
|
|
585
|
+
* direct actions for `id` (a node id at the top level, a group id when expanding a group).
|
|
586
|
+
*/
|
|
587
|
+
var buildGraphMenu = (resolve, nodeId, options = {}) => {
|
|
588
|
+
const { rootId = Node.RootId, filter, surface } = options;
|
|
589
|
+
const nodes = [];
|
|
590
|
+
const edges = [];
|
|
591
|
+
const seen = /* @__PURE__ */ new Set();
|
|
592
|
+
const visit = (parentId, sourceId) => {
|
|
593
|
+
for (const action of resolve(sourceId)) {
|
|
594
|
+
if (filter && !filter(action)) continue;
|
|
595
|
+
edges.push({
|
|
596
|
+
source: parentId,
|
|
597
|
+
target: action.id,
|
|
598
|
+
relation: "child"
|
|
599
|
+
});
|
|
600
|
+
if (seen.has(action.id)) continue;
|
|
601
|
+
seen.add(action.id);
|
|
602
|
+
nodes.push(surface ? applyPresentation(action, surface) : action);
|
|
603
|
+
if (Node.isActionGroup(action)) visit(action.id, action.id);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
visit(rootId, nodeId);
|
|
607
|
+
return {
|
|
608
|
+
nodes,
|
|
609
|
+
edges
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
/**
|
|
613
|
+
* Read a node's contributed actions from the app-graph as menu `ActionGraphProps`, for splicing into a
|
|
614
|
+
* hand-built toolbar via `MenuBuilder.subgraph(graphActions(graph, get, nodeId))`. Reactive: reads the
|
|
615
|
+
* graph's action atoms through `get`, so contributed actions appear/disappear as extensions update.
|
|
616
|
+
*
|
|
617
|
+
* @idiom org.dxos.react-ui-menu.graphActionsToolbar
|
|
618
|
+
* applies: Splicing a node's app-graph-contributed toolbar actions into a hand-built object toolbar
|
|
619
|
+
* instead-of: Reading `graph.actions(nodeId)` and filtering/wiring `nodes`/`edges` by hand in every toolbar
|
|
620
|
+
* uses: {@link graphActions}, {@link MenuBuilder}, {@link useMenuBuilder}
|
|
621
|
+
* related: org.dxos.react-ui-menu.toolbarMenu
|
|
622
|
+
*/
|
|
623
|
+
var graphActions = (graph, get, nodeId, options) => graph && nodeId ? buildGraphMenu((id) => get(graph.actions(id)), nodeId, options) : {
|
|
624
|
+
nodes: [],
|
|
625
|
+
edges: []
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* Menu actions sourced entirely from a node's app-graph contributions — the common "show every action
|
|
629
|
+
* the graph offers for this object" toolbar/menu. Compose with hand-built items via {@link graphActions}.
|
|
630
|
+
*/
|
|
631
|
+
var useGraphMenuActions = (graph, nodeId, options) => {
|
|
632
|
+
return useMenuActions(useMemo(() => Atom$1.make((get) => graphActions(graph, get, nodeId, options)), [
|
|
633
|
+
graph,
|
|
634
|
+
nodeId,
|
|
635
|
+
options?.rootId
|
|
636
|
+
]));
|
|
637
|
+
};
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region src/builder.ts
|
|
640
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/builder.ts";
|
|
641
|
+
/** Default trigger for toolbar overflow (⋮) dropdown menus. */
|
|
642
|
+
var overflowMenuProperties = {
|
|
643
|
+
label: ["toolbar-overflow.menu", { ns: translationKey }],
|
|
644
|
+
icon: "ph--dots-three-vertical--regular",
|
|
645
|
+
iconOnly: true,
|
|
646
|
+
caretDown: false,
|
|
647
|
+
variant: "dropdownMenu"
|
|
648
|
+
};
|
|
649
|
+
var MenuBuilderImpl = class MenuBuilderImpl {
|
|
650
|
+
_data;
|
|
651
|
+
_rootId;
|
|
652
|
+
_separatorCount = 0;
|
|
653
|
+
constructor(_data, _rootId) {
|
|
654
|
+
this._data = _data;
|
|
655
|
+
this._rootId = _rootId;
|
|
656
|
+
}
|
|
657
|
+
root(props) {
|
|
658
|
+
invariant(this._rootId === "root", "Root group can only be at the top level", {
|
|
659
|
+
"~LogMeta": "~LogMeta",
|
|
660
|
+
F: __dxlog_file,
|
|
661
|
+
L: 84,
|
|
662
|
+
S: this,
|
|
663
|
+
A: ["this._rootId === 'root'", "'Root group can only be at the top level'"]
|
|
664
|
+
});
|
|
665
|
+
invariant(this._data.nodes.find((node) => node.id === "root") === void 0, "Root group can only be created once", {
|
|
666
|
+
"~LogMeta": "~LogMeta",
|
|
667
|
+
F: __dxlog_file,
|
|
668
|
+
L: 85,
|
|
669
|
+
S: this,
|
|
670
|
+
A: ["this._data.nodes.find((node) => node.id === 'root') === undefined", "'Root group can only be created once'"]
|
|
671
|
+
});
|
|
672
|
+
this._data.nodes.push(createMenuItemGroup("root", props));
|
|
673
|
+
return this;
|
|
674
|
+
}
|
|
675
|
+
build() {
|
|
676
|
+
return this._data;
|
|
677
|
+
}
|
|
678
|
+
action(id, props, invoke) {
|
|
679
|
+
this._data.nodes.push(createMenuAction(id, invoke, props));
|
|
680
|
+
this._data.edges.push({
|
|
681
|
+
source: this._rootId,
|
|
682
|
+
target: id,
|
|
683
|
+
relation: "child"
|
|
684
|
+
});
|
|
685
|
+
return this;
|
|
686
|
+
}
|
|
687
|
+
switch(id, props, invoke) {
|
|
688
|
+
return this.action(id, {
|
|
689
|
+
...props,
|
|
690
|
+
variant: "switch"
|
|
691
|
+
}, invoke);
|
|
692
|
+
}
|
|
693
|
+
group(id, props, cb) {
|
|
694
|
+
this._data.nodes.push(createMenuItemGroup(id, props));
|
|
695
|
+
this._data.edges.push({
|
|
696
|
+
source: this._rootId,
|
|
697
|
+
target: id,
|
|
698
|
+
relation: "child"
|
|
699
|
+
});
|
|
700
|
+
cb(new MenuBuilderImpl(this._data, id));
|
|
701
|
+
return this;
|
|
702
|
+
}
|
|
703
|
+
menu(id, cb, testId) {
|
|
704
|
+
return this.group(id, testId === void 0 ? overflowMenuProperties : {
|
|
705
|
+
...overflowMenuProperties,
|
|
706
|
+
testId
|
|
707
|
+
}, cb);
|
|
708
|
+
}
|
|
709
|
+
subgraph(subgraphOrCb) {
|
|
710
|
+
if (!subgraphOrCb) return this;
|
|
711
|
+
if (typeof subgraphOrCb === "function") subgraphOrCb(new MenuBuilderImpl(this._data, this._rootId));
|
|
712
|
+
else {
|
|
713
|
+
this._data.nodes.push(...subgraphOrCb.nodes);
|
|
714
|
+
this._data.edges.push(...subgraphOrCb.edges);
|
|
715
|
+
}
|
|
716
|
+
return this;
|
|
717
|
+
}
|
|
718
|
+
separator(variant = "gap") {
|
|
719
|
+
const id = `separator-${++this._separatorCount}`;
|
|
720
|
+
this._data.nodes.push({
|
|
721
|
+
id,
|
|
722
|
+
type: MenuSeparatorType,
|
|
723
|
+
properties: { variant },
|
|
724
|
+
data: void 0
|
|
725
|
+
});
|
|
726
|
+
this._data.edges.push({
|
|
727
|
+
source: this._rootId,
|
|
728
|
+
target: id,
|
|
729
|
+
relation: "child"
|
|
730
|
+
});
|
|
731
|
+
return this;
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
var MenuBuilder = Object.freeze({ make: () => new MenuBuilderImpl({
|
|
735
|
+
nodes: [],
|
|
736
|
+
edges: []
|
|
737
|
+
}, "root") });
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/toolbar.ts
|
|
740
|
+
/** `disposition` value an action opts into to appear in an object toolbar (vs context-menu-only). */
|
|
741
|
+
var TOOLBAR_DISPOSITION = "toolbar";
|
|
742
|
+
/** Filter for {@link graphActions}: keeps only actions a producer opted into the toolbar. */
|
|
743
|
+
var isToolbarAction = (action) => Node.hasDisposition(action, TOOLBAR_DISPOSITION);
|
|
744
|
+
//#endregion
|
|
745
|
+
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
|
+
|
|
747
|
+
//# sourceMappingURL=index.mjs.map
|