@dxos/react-ui-menu 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.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.
Files changed (91) hide show
  1. package/dist/lib/index.mjs +766 -476
  2. package/dist/lib/index.mjs.map +1 -1
  3. package/dist/lib/translations.mjs.map +1 -1
  4. package/dist/types/src/builder.d.ts +2 -2
  5. package/dist/types/src/builder.d.ts.map +1 -1
  6. package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
  7. package/dist/types/src/components/ActionMenu.d.ts +32 -0
  8. package/dist/types/src/components/ActionMenu.d.ts.map +1 -0
  9. package/dist/types/src/components/{Menu.stories.d.ts → ActionMenu.stories.d.ts} +5 -4
  10. package/dist/types/src/components/ActionMenu.stories.d.ts.map +1 -0
  11. package/dist/types/src/components/ActionToolbar.d.ts +20 -0
  12. package/dist/types/src/components/ActionToolbar.d.ts.map +1 -0
  13. package/dist/types/src/components/ActionToolbar.stories.d.ts +39 -0
  14. package/dist/types/src/components/ActionToolbar.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/ViewOptionsMenu.d.ts +44 -0
  16. package/dist/types/src/components/ViewOptionsMenu.d.ts.map +1 -0
  17. package/dist/types/src/components/ViewOptionsMenu.stories.d.ts +25 -0
  18. package/dist/types/src/components/ViewOptionsMenu.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/action-label.d.ts +2 -2
  20. package/dist/types/src/components/action-label.d.ts.map +1 -1
  21. package/dist/types/src/components/index.d.ts +3 -3
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/hooks/index.d.ts +2 -2
  24. package/dist/types/src/hooks/index.d.ts.map +1 -1
  25. package/dist/types/src/hooks/useGraphMenuActions.d.ts +4 -3
  26. package/dist/types/src/hooks/useGraphMenuActions.d.ts.map +1 -1
  27. package/dist/types/src/hooks/useMenuActions.browser.test.d.ts +2 -0
  28. package/dist/types/src/hooks/useMenuActions.browser.test.d.ts.map +1 -0
  29. package/dist/types/src/hooks/useMenuActions.d.ts +27 -6
  30. package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
  31. package/dist/types/src/index.d.ts +7 -7
  32. package/dist/types/src/index.d.ts.map +1 -1
  33. package/dist/types/src/testing/generator.d.ts +4 -7
  34. package/dist/types/src/testing/generator.d.ts.map +1 -1
  35. package/dist/types/src/testing/index.d.ts +1 -1
  36. package/dist/types/src/testing/index.d.ts.map +1 -1
  37. package/dist/types/src/toolbar.d.ts +12 -0
  38. package/dist/types/src/toolbar.d.ts.map +1 -1
  39. package/dist/types/src/toolbar.test.d.ts +2 -0
  40. package/dist/types/src/toolbar.test.d.ts.map +1 -0
  41. package/dist/types/src/translations.d.ts.map +1 -1
  42. package/dist/types/src/types.d.ts +22 -18
  43. package/dist/types/src/types.d.ts.map +1 -1
  44. package/dist/types/src/util.d.ts +2 -2
  45. package/dist/types/src/util.d.ts.map +1 -1
  46. package/dist/types/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +26 -25
  48. package/src/builder.test.ts +4 -4
  49. package/src/builder.ts +4 -4
  50. package/src/components/ActionLabel.tsx +7 -6
  51. package/src/components/ActionMenu.stories.tsx +221 -0
  52. package/src/components/ActionMenu.tsx +369 -0
  53. package/src/components/ActionToolbar.stories.tsx +346 -0
  54. package/src/components/ActionToolbar.tsx +405 -0
  55. package/src/components/ViewOptionsMenu.stories.tsx +92 -0
  56. package/src/components/ViewOptionsMenu.tsx +172 -0
  57. package/src/components/action-label.ts +5 -5
  58. package/src/components/index.ts +3 -4
  59. package/src/hooks/index.ts +2 -2
  60. package/src/hooks/useGraphMenuActions.test.ts +1 -1
  61. package/src/hooks/useGraphMenuActions.ts +6 -6
  62. package/src/hooks/useMenuActions.browser.test.tsx +251 -0
  63. package/src/hooks/useMenuActions.ts +141 -9
  64. package/src/index.ts +14 -7
  65. package/src/testing/generator.ts +5 -5
  66. package/src/testing/index.ts +1 -1
  67. package/src/toolbar.test.ts +52 -0
  68. package/src/toolbar.ts +15 -0
  69. package/src/translations.ts +2 -2
  70. package/src/types.ts +28 -18
  71. package/src/util.ts +4 -6
  72. package/dist/types/src/components/DropdownMenu.d.ts +0 -22
  73. package/dist/types/src/components/DropdownMenu.d.ts.map +0 -1
  74. package/dist/types/src/components/Menu.d.ts +0 -60
  75. package/dist/types/src/components/Menu.d.ts.map +0 -1
  76. package/dist/types/src/components/Menu.stories.d.ts.map +0 -1
  77. package/dist/types/src/components/Menu.test.d.ts +0 -2
  78. package/dist/types/src/components/Menu.test.d.ts.map +0 -1
  79. package/dist/types/src/components/MenuContext.d.ts +0 -28
  80. package/dist/types/src/components/MenuContext.d.ts.map +0 -1
  81. package/dist/types/src/components/ToolbarMenu.d.ts +0 -35
  82. package/dist/types/src/components/ToolbarMenu.d.ts.map +0 -1
  83. package/dist/types/src/components/ToolbarMenu.stories.d.ts +0 -45
  84. package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +0 -1
  85. package/src/components/DropdownMenu.tsx +0 -134
  86. package/src/components/Menu.stories.tsx +0 -266
  87. package/src/components/Menu.test.tsx +0 -448
  88. package/src/components/Menu.tsx +0 -257
  89. package/src/components/MenuContext.ts +0 -130
  90. package/src/components/ToolbarMenu.stories.tsx +0 -153
  91. package/src/components/ToolbarMenu.tsx +0 -353
@@ -1,40 +1,147 @@
1
+ import { cloneElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { keySymbols } from "@dxos/react-focus";
3
+ import * as Hooks from "@dxos/react-ui/Hooks";
4
+ import * as Icon from "@dxos/react-ui/Icon";
5
+ import * as Menu from "@dxos/react-ui/Menu";
6
+ import * as Theme from "@dxos/react-ui/Theme";
7
+ import * as VirtualAnchor from "@dxos/react-ui/VirtualAnchor";
8
+ import { resolveKeyBinding } from "@dxos/util";
9
+ import { translationKey } from "#translations";
10
+ import * as Atom from "effect/reactivity/Atom";
11
+ import * as AppGraphNode from "@dxos/app-graph/AppGraphNode";
12
+ import * as GraphNode from "@dxos/graph/GraphNode";
1
13
  import { useAtomValue } from "@effect/atom-react/Hooks";
2
- import { createContextScope } from "@radix-ui/react-context";
3
- import * as Atom from "effect/unstable/reactivity/Atom";
4
- import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
14
+ import { RegistryContext } from "@effect/atom-react/RegistryContext";
15
+ import * as AppGraph from "@dxos/app-graph/AppGraph";
5
16
  import { log } from "@dxos/log";
6
- import { keySymbols } from "@dxos/keyboard";
7
- import { DropdownMenu, Icon, Input, Toolbar, Tooltip, composable, composableProps, toLocalizedString, useTranslation } from "@dxos/react-ui";
8
17
  import * as Effect from "effect/Effect";
9
- import * as AppGraphNode from "@dxos/app-graph/AppGraphNode";
10
- import { EffectEx } from "@dxos/effect";
11
- import { getHostPlatform } from "@dxos/util";
12
- import { RegistryContext } from "@effect/atom-react/RegistryContext";
13
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
14
- import { mx } from "@dxos/ui-theme";
15
- import { translationKey } from "#translations";
18
+ import * as EffectEx from "@dxos/effect/EffectEx";
16
19
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
20
  import { useAttention } from "@dxos/react-ui-attention";
18
- import * as GraphNode from "@dxos/graph/GraphNode";
19
- import * as AppGraph from "@dxos/app-graph/AppGraph";
21
+ import * as Button from "@dxos/react-ui/Button";
22
+ import * as Input from "@dxos/react-ui/Input";
23
+ import * as ToggleGroup from "@dxos/react-ui/ToggleGroup";
24
+ import * as Toolbar from "@dxos/react-ui/Toolbar";
25
+ import * as Tooltip from "@dxos/react-ui/Tooltip";
26
+ import * as Util from "@dxos/react-ui/Util";
27
+ import { mx } from "@dxos/ui-theme";
20
28
  import { invariant } from "@dxos/invariant";
21
- //#region src/components/MenuContext.ts
22
- var __dxlog_file$1 = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/components/MenuContext.ts";
23
- var MENU_NAME = "Menu";
24
- var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
25
- var nullItemsAtom = Atom.make(null);
26
- var defaultItemsAccessor = () => nullItemsAtom;
27
- var menuContextDefaults = {
28
- iconSize: 5,
29
- items: defaultItemsAccessor,
30
- onAction: void 0,
31
- menuItemsAtom: Atom.make(/* @__PURE__ */ new Map()),
32
- addMenuItems: () => {},
33
- removeMenuItems: () => {}
34
- };
35
- var [MenuContextProvider, useMenuScoped] = createMenuContext(MENU_NAME, menuContextDefaults);
36
- var useMenuScope = createMenuScope();
37
- var MenuDropdownContext = createContext({ closeMenu: () => {} });
29
+ //#region src/presentation.ts
30
+ /**
31
+ * Merges an action's `presentation[surface]` chrome override (if any) into its properties, letting a
32
+ * single action declare multiple dispositions yet render appropriately in each surface — e.g. a
33
+ * primary toolbar button and a plain nav-tree row from the same definition.
34
+ */
35
+ var applyPresentation = (action, surface) => {
36
+ const presentation = action.properties.presentation?.[surface];
37
+ if (!presentation) return action;
38
+ return {
39
+ ...action,
40
+ properties: {
41
+ ...action.properties,
42
+ ...presentation
43
+ }
44
+ };
45
+ };
46
+ //#endregion
47
+ //#region src/hooks/useMenuActions.ts
48
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/hooks/useMenuActions.ts";
49
+ var DEFAULT_PRIORITY = 100;
50
+ var EMPTY_GRAPH = Atom.make({
51
+ nodes: [],
52
+ edges: []
53
+ });
54
+ /**
55
+ * The graph behind a menu, rebuilt when `deps` change. Its nodes stay pinned while the menu is mounted,
56
+ * taken on commit so a render React discards leaves nothing behind.
57
+ */
58
+ var useMenuGraph = (build, deps) => {
59
+ const graph = useMemo(build, deps);
60
+ useLayoutEffect(() => graph ? AppGraph.retain(graph) : void 0, [graph]);
61
+ return graph;
62
+ };
63
+ /** A `MenuActions` over a given accessor, for sources that are not an action graph (tests, fixtures). */
64
+ var makeMenuActions = ({ items, ...options }) => ({
65
+ items,
66
+ contributions: Atom.make(/* @__PURE__ */ new Map()),
67
+ ...options
68
+ });
69
+ /**
70
+ * The menu over an action graph. Omit `props` for a menu with no items of its own, filled entirely by
71
+ * contributions (a card whose actions its children register).
72
+ */
73
+ var useMenuActions = (props = EMPTY_GRAPH, options = {}) => {
74
+ const registry = useContext(RegistryContext);
75
+ const menuGraphProps = useAtomValue(props);
76
+ const graph = useMenuGraph(() => {
77
+ const newGraph = AppGraph.make({ registry });
78
+ AppGraph.addNodes(newGraph, menuGraphProps.nodes);
79
+ AppGraph.addEdges(newGraph, menuGraphProps.edges);
80
+ return newGraph;
81
+ }, [registry, menuGraphProps]);
82
+ const items = useCallback((group) => {
83
+ return graph.connections(group?.id || GraphNode.RootId, "child");
84
+ }, [graph]);
85
+ const contributions = useMemo(() => Atom.make(/* @__PURE__ */ new Map()), []);
86
+ useLayoutEffect(() => registry.mount(contributions), [registry, contributions]);
87
+ const { onAction, caller, iconSize } = options;
88
+ return useMemo(() => ({
89
+ items,
90
+ contributions,
91
+ onAction,
92
+ caller,
93
+ iconSize
94
+ }), [
95
+ items,
96
+ contributions,
97
+ onAction,
98
+ caller,
99
+ iconSize
100
+ ]);
101
+ };
102
+ /**
103
+ * Convenience wrapper around `useMenuActions` that creates the backing atom inline.
104
+ * Pass a builder thunk and a dependency list — the hook memoizes `Atom.make(build)` and threads
105
+ * it through `useMenuActions`. Saves the `useMemo(() => Atom.make(...), deps)` boilerplate when
106
+ * the action graph is composed from local state (e.g. a toolbar driven by component state).
107
+ *
108
+ * Read reactive state via `get` inside the builder; `deps` should hold only stable references.
109
+ */
110
+ var useMenuBuilder = (build, deps, options) => {
111
+ return useMenuActions(useMemo(() => Atom.make(build), deps), options);
112
+ };
113
+ /**
114
+ * Registers items with a menu another component owns, for as long as the caller is mounted. The
115
+ * owner hands its `MenuActions` down (props or its own context); there is no menu provider.
116
+ */
117
+ var useMenuContribution = (menu, props) => {
118
+ const registry = useContext(RegistryContext);
119
+ const { id, mode, priority = DEFAULT_PRIORITY, items, groupFilter } = props;
120
+ useEffect(() => {
121
+ if (!menu) return;
122
+ const update = (change) => registry.set(menu.contributions, change(registry.get(menu.contributions)));
123
+ update((previous) => new Map(previous).set(id, {
124
+ id,
125
+ mode,
126
+ priority,
127
+ items,
128
+ groupFilter
129
+ }));
130
+ return () => update((previous) => {
131
+ const next = new Map(previous);
132
+ next.delete(id);
133
+ return next;
134
+ });
135
+ }, [
136
+ registry,
137
+ menu,
138
+ id,
139
+ mode,
140
+ priority,
141
+ items,
142
+ groupFilter
143
+ ]);
144
+ };
38
145
  var sortMenuItems = (items) => [...items].sort((a, b) => a.priority !== b.priority ? a.priority - b.priority : a.id.localeCompare(b.id));
39
146
  var resolveItems = (baseItems, group, entries) => {
40
147
  const applicable = [...entries.values()].filter((entry) => !entry.groupFilter || entry.groupFilter(group));
@@ -48,7 +155,7 @@ var resolveItems = (baseItems, group, entries) => {
48
155
  }, {
49
156
  "~LogMeta": "~LogMeta",
50
157
  F: __dxlog_file$1,
51
- L: 85,
158
+ L: 168,
52
159
  S: void 0
53
160
  });
54
161
  return replacements[0].items;
@@ -57,10 +164,13 @@ var resolveItems = (baseItems, group, entries) => {
57
164
  if (!baseItems || baseItems.length === 0) return additiveItems.length > 0 ? additiveItems : null;
58
165
  return [...baseItems, ...additiveItems];
59
166
  };
60
- var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
61
- const { items, menuItemsAtom } = useMenuScoped(consumerName, __menuScope);
62
- const groupItems = useAtomValue(items(group));
63
- const entries = useAtomValue(menuItemsAtom) ?? /* @__PURE__ */ new Map();
167
+ /**
168
+ * A group's items (the root's when `group` is undefined), with the menu's contributions applied;
169
+ * `propsItems` stands in for the group's own items when given.
170
+ */
171
+ var useMenuItems = (menu, group, propsItems) => {
172
+ const groupItems = useAtomValue(menu.items(group));
173
+ const entries = useAtomValue(menu.contributions);
64
174
  const baseItems = useMemo(() => propsItems ?? groupItems ?? null, [propsItems, groupItems]);
65
175
  return useMemo(() => resolveItems(baseItems, group, entries), [
66
176
  baseItems,
@@ -68,10 +178,73 @@ var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __m
68
178
  entries
69
179
  ]) ?? void 0;
70
180
  };
71
- /** Returns the menu context without Radix scope. */
72
- var useMenu = (consumerName) => {
73
- return useMenuScoped(consumerName, void 0);
181
+ //#endregion
182
+ //#region src/hooks/useGraphMenuActions.ts
183
+ /**
184
+ * Flatten a node's contributed actions (and nested {@link AppGraphNode.ActionGroup}s) into the
185
+ * `{ nodes, edges }` an {@link useMenuActions} / `Menu` consumes. The action nodes are spliced
186
+ * verbatim, so their Effect `data` (and captured `_actionContext`) execute via `executeMenuAction`
187
+ * without any `onAction` wiring.
188
+ *
189
+ * Pure over a `resolve` function so it is testable without a live graph; `resolve(id)` returns the
190
+ * direct actions for `id` (a node id at the top level, a group id when expanding a group).
191
+ */
192
+ var buildGraphMenu = (resolve, nodeId, options = {}) => {
193
+ const { rootId = GraphNode.RootId, filter, surface } = options;
194
+ const nodes = [];
195
+ const edges = [];
196
+ const seen = /* @__PURE__ */ new Set();
197
+ const visit = (parentId, sourceId) => {
198
+ for (const action of resolve(sourceId)) {
199
+ if (filter && !filter(action)) continue;
200
+ edges.push({
201
+ source: parentId,
202
+ target: action.id,
203
+ relation: "child"
204
+ });
205
+ if (seen.has(action.id)) continue;
206
+ seen.add(action.id);
207
+ nodes.push(surface ? applyPresentation(action, surface) : action);
208
+ if (AppGraphNode.isActionGroup(action)) visit(action.id, action.id);
209
+ }
210
+ };
211
+ visit(rootId, nodeId);
212
+ return {
213
+ nodes,
214
+ edges
215
+ };
74
216
  };
217
+ /**
218
+ * Read a node's contributed actions from the app-graph as menu `ActionGraphProps`, for splicing into a
219
+ * hand-built toolbar via `MenuBuilder.subgraph(graphActions(graph, get, nodeId))`. Reactive: reads the
220
+ * graph's action atoms through `get`, so contributed actions appear/disappear as extensions update.
221
+ *
222
+ * @idiom org.dxos.react-ui-menu.graphActionsToolbar
223
+ * applies: Splicing a node's app-graph-contributed toolbar actions into a hand-built object toolbar
224
+ * instead-of: Reading `graph.actions(nodeId)` and filtering/wiring `nodes`/`edges` by hand in every toolbar
225
+ * uses: {@link graphActions}, {@link MenuBuilder}, {@link useMenuBuilder}
226
+ * related: org.dxos.react-ui-menu.toolbarMenu
227
+ */
228
+ var graphActions = (graph, get, nodeId, options) => graph && nodeId ? buildGraphMenu((id) => get(graph.actions(id)), nodeId, options) : {
229
+ nodes: [],
230
+ edges: []
231
+ };
232
+ /**
233
+ * Menu actions sourced entirely from a node's app-graph contributions — the common "show every action
234
+ * the graph offers for this object" toolbar/menu. Compose with hand-built items via {@link graphActions}.
235
+ */
236
+ var useGraphMenuActions = (graph, nodeId, options) => {
237
+ return useMenuActions(useMemo(() => Atom.make((get) => graphActions(graph, get, nodeId, options)), [
238
+ graph,
239
+ nodeId,
240
+ options?.rootId
241
+ ]), options);
242
+ };
243
+ //#endregion
244
+ //#region src/types.ts
245
+ var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
246
+ var isSeparator = (node) => node.type === MenuSeparatorType;
247
+ var isMenuGroup = (node) => node.type === AppGraphNode.ActionGroupType;
75
248
  //#endregion
76
249
  //#region src/util.ts
77
250
  /**
@@ -84,8 +257,9 @@ var executeMenuAction = async (action, params = {}) => {
84
257
  await EffectEx.runAndForwardErrors(effect);
85
258
  };
86
259
  var getShortcut = (action) => {
87
- return typeof action.properties?.keyBinding === "string" ? action.properties.keyBinding : action.properties?.keyBinding?.[getHostPlatform()];
260
+ return resolveKeyBinding(action.properties?.keyBinding);
88
261
  };
262
+ var fallbackIcon = "ph--circle-dashed--regular";
89
263
  var createMenuAction = (id, invoke, properties) => ({
90
264
  id,
91
265
  type: AppGraphNode.ActionType,
@@ -125,75 +299,28 @@ var createLineSeparator = (id = "line", source = "root") => ({
125
299
  }]
126
300
  });
127
301
  //#endregion
128
- //#region src/components/action-label.ts
129
- var actionLabel = (action, t) => {
130
- const shortcut = getShortcut(action);
131
- return `${toLocalizedString(action.properties.label, t)}${shortcut ? ` (${keySymbols(shortcut).join("")})` : ""}`;
132
- };
133
- //#endregion
134
- //#region src/types.ts
135
- var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
136
- var isSeparator = (node) => node.type === MenuSeparatorType;
137
- var isMenuGroup = (node) => node.type === AppGraphNode.ActionGroupType;
138
- //#endregion
139
- //#region src/components/ActionLabel.tsx
140
- var ActionLabel = ({ action }) => {
141
- const { t } = useTranslation(translationKey);
142
- const shortcut = getShortcut(action);
143
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
144
- className: "grow truncate",
145
- children: toLocalizedString(action.properties.label, t)
146
- }), shortcut && /* @__PURE__ */ jsx("span", {
147
- className: mx("shrink-0", "text-description"),
148
- children: keySymbols(shortcut).join("")
149
- })] });
150
- };
151
- //#endregion
152
- //#region src/components/DropdownMenu.tsx
153
- var DropdownMenuItem = ({ item, group, onClick, __menuScope }) => {
154
- const action = item;
155
- const handleClick = useCallback((event) => onClick(action, event), [action, onClick]);
156
- const { iconSize } = useMenuScoped("DropdownMenuItem", __menuScope);
157
- const checkable = typeof action.properties?.checked === "boolean";
158
- const multiple = group?.properties?.selectCardinality === "multiple";
159
- const role = multiple ? "menuitemcheckbox" : "menuitemradio";
160
- const handleSelect = useCallback((event) => multiple && event.preventDefault(), [multiple]);
161
- return /* @__PURE__ */ jsxs(DropdownMenu.Item, {
162
- onClick: handleClick,
163
- onSelect: handleSelect,
164
- classNames: "gap-2",
165
- disabled: action.properties?.disabled,
166
- ...checkable && {
167
- role,
168
- "aria-checked": !!action.properties?.checked
169
- },
170
- ...action.properties?.testId && { "data-testid": action.properties.testId },
171
- children: [
172
- action.properties?.icon && /* @__PURE__ */ jsx(Icon, {
173
- icon: action.properties.icon,
174
- size: iconSize,
175
- classNames: mx(action.properties.spin && "animate-spin", action.properties.iconClassNames)
176
- }),
177
- /* @__PURE__ */ jsx(ActionLabel, { action }),
178
- action.properties?.checked && /* @__PURE__ */ jsx(Icon, {
179
- icon: "ph--check--regular",
180
- size: iconSize,
181
- classNames: "ms-auto"
182
- })
183
- ]
184
- });
302
+ //#region src/components/ActionMenu.tsx
303
+ var isMultiSelect = (group) => group?.properties?.selectCardinality === "multiple";
304
+ /** An action that declares `checked` is a select-group member (radio, or checkbox in a multi-select group). */
305
+ var isCheckable = (item) => !isSeparator(item) && !isMenuGroup(item) && typeof item.properties?.checked === "boolean";
306
+ /** The row data Next's item parts render: the localized label and the platform's shortcut glyphs. */
307
+ var useItemData = (id, properties) => {
308
+ const { t } = Hooks.useTranslation(translationKey);
309
+ const shortcut = resolveKeyBinding(properties.keyBinding);
310
+ return {
311
+ value: id,
312
+ label: Theme.toLocalizedString(properties.label, t),
313
+ icon: properties.icon,
314
+ shortcut: shortcut ? keySymbols(shortcut).join("") : void 0,
315
+ disabled: properties.disabled
316
+ };
185
317
  };
186
- var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenChange, caller, children, __menuScope, ...naturalProps }) => {
187
- const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
188
- prop: open,
189
- defaultProp: defaultOpen,
190
- onChange: onOpenChange
191
- });
192
- const { onAction } = useMenuScoped("DropdownMenuRoot", __menuScope);
193
- const handleActionClick = useCallback((action, event) => {
318
+ /** Runs the action on click, which zag also dispatches for Enter, so keyboard and pointer share one path. */
319
+ var useInvoke = (menu, action, group) => {
320
+ const { onAction, caller } = menu;
321
+ return useCallback((event) => {
194
322
  if (action.properties?.disabled) return;
195
323
  event.stopPropagation();
196
- if (group?.properties?.selectCardinality !== "multiple") setOptionsMenuOpen(false);
197
324
  const params = {
198
325
  parent: group,
199
326
  caller,
@@ -202,36 +329,257 @@ var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenCha
202
329
  if (onAction) onAction(action, params);
203
330
  else executeMenuAction(action, params);
204
331
  }, [
332
+ action,
205
333
  group,
206
334
  caller,
207
335
  onAction
208
336
  ]);
209
- const items = useMenuItems(group, propsItems);
210
- return /* @__PURE__ */ jsxs(DropdownMenu.Root, {
211
- open: optionsMenuOpen,
212
- onOpenChange: setOptionsMenuOpen,
213
- ...naturalProps,
214
- 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, {
215
- item,
216
- group,
217
- onClick: handleActionClick
218
- }, item.id)) }), /* @__PURE__ */ jsx(DropdownMenu.Arrow, {})] }) })]
337
+ };
338
+ var ItemIcon = ({ menu, action }) => action.properties?.icon ? /* @__PURE__ */ jsx(Menu.ItemIcon, {
339
+ spin: action.properties.spin,
340
+ size: menu.iconSize,
341
+ classNames: action.properties.iconClassNames
342
+ }) : null;
343
+ var ActionMenuItem = ({ menu, action, group }) => {
344
+ const item = useItemData(action.id, action.properties);
345
+ const handleClick = useInvoke(menu, action, group);
346
+ return /* @__PURE__ */ jsxs(Menu.Item, {
347
+ item,
348
+ onClick: handleClick,
349
+ closeOnSelect: !isMultiSelect(group),
350
+ ...action.properties?.testId && { "data-testid": action.properties.testId },
351
+ children: [
352
+ /* @__PURE__ */ jsx(ItemIcon, {
353
+ menu,
354
+ action
355
+ }),
356
+ /* @__PURE__ */ jsx(Menu.ItemText, {}),
357
+ item.shortcut && /* @__PURE__ */ jsx(Menu.ItemShortcut, {})
358
+ ]
219
359
  });
220
360
  };
221
- var DropdownMenu$1 = {
222
- Root: DropdownMenuRoot,
223
- Trigger: DropdownMenu.Trigger,
224
- VirtualTrigger: DropdownMenu.VirtualTrigger
361
+ var ActionCheckboxItem = ({ menu, action, group }) => {
362
+ const item = useItemData(action.id, action.properties);
363
+ const handleClick = useInvoke(menu, action, group);
364
+ return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
365
+ item,
366
+ checked: !!action.properties.checked,
367
+ onClick: handleClick,
368
+ closeOnSelect: false,
369
+ ...action.properties?.testId && { "data-testid": action.properties.testId },
370
+ children: [
371
+ /* @__PURE__ */ jsx(ItemIcon, {
372
+ menu,
373
+ action
374
+ }),
375
+ /* @__PURE__ */ jsx(Menu.ItemText, {}),
376
+ /* @__PURE__ */ jsx(Menu.ItemIndicator, {})
377
+ ]
378
+ });
379
+ };
380
+ var ActionRadioItem = ({ menu, action, group }) => {
381
+ const item = useItemData(action.id, action.properties);
382
+ const handleClick = useInvoke(menu, action, group);
383
+ return /* @__PURE__ */ jsxs(Menu.RadioItem, {
384
+ item,
385
+ onClick: handleClick,
386
+ ...action.properties?.testId && { "data-testid": action.properties.testId },
387
+ children: [
388
+ /* @__PURE__ */ jsx(ItemIcon, {
389
+ menu,
390
+ action
391
+ }),
392
+ /* @__PURE__ */ jsx(Menu.ItemText, {}),
393
+ /* @__PURE__ */ jsx(Menu.ItemIndicator, {})
394
+ ]
395
+ });
396
+ };
397
+ /** A group inside a menu is a submenu; its items resolve when it opens. */
398
+ var ActionSubMenu = ({ menu, group }) => {
399
+ const item = useItemData(group.id, group.properties);
400
+ return /* @__PURE__ */ jsxs(Menu.Sub, { children: [/* @__PURE__ */ jsxs(Menu.TriggerItem, {
401
+ item,
402
+ disabled: group.properties.disabled,
403
+ ...group.properties.testId && { "data-testid": group.properties.testId },
404
+ children: [
405
+ /* @__PURE__ */ jsx(ItemIcon, {
406
+ menu,
407
+ action: group
408
+ }),
409
+ /* @__PURE__ */ jsx(Menu.ItemText, {}),
410
+ /* @__PURE__ */ jsx(Icon.Icon, { icon: "ph--caret-right--regular" })
411
+ ]
412
+ }), /* @__PURE__ */ jsx(Menu.Content, { children: /* @__PURE__ */ jsx(ActionMenuItems, {
413
+ menu,
414
+ group
415
+ }) })] });
416
+ };
417
+ /**
418
+ * Runs of checkable items in a single-select group become one radio group (Ark's radio items need one), whose value is
419
+ * the member marked `checked`.
420
+ */
421
+ var segment = (items, multiple) => items.reduce((segments, item) => {
422
+ const last = segments.at(-1);
423
+ if (multiple || !isCheckable(item)) segments.push({
424
+ kind: "item",
425
+ item
426
+ });
427
+ else if (last?.kind === "radio") last.actions.push(item);
428
+ else segments.push({
429
+ kind: "radio",
430
+ id: item.id,
431
+ actions: [item]
432
+ });
433
+ return segments;
434
+ }, []);
435
+ var ActionMenuEntry = ({ menu, item, group }) => {
436
+ if (isSeparator(item)) return /* @__PURE__ */ jsx(Menu.Separator, {});
437
+ if (isMenuGroup(item)) return /* @__PURE__ */ jsx(ActionSubMenu, {
438
+ menu,
439
+ group: item
440
+ });
441
+ const action = item;
442
+ if (action.properties?.hidden) return null;
443
+ return isCheckable(action) ? /* @__PURE__ */ jsx(ActionCheckboxItem, {
444
+ menu,
445
+ action,
446
+ group
447
+ }) : /* @__PURE__ */ jsx(ActionMenuItem, {
448
+ menu,
449
+ action,
450
+ group
451
+ });
452
+ };
453
+ var ActionMenuItems = ({ menu, group, actions }) => {
454
+ const items = useMenuItems(menu, group, actions);
455
+ const segments = useMemo(() => segment(items ?? [], isMultiSelect(group)), [items, group]);
456
+ return /* @__PURE__ */ jsx(Fragment, { children: segments.map((entry) => entry.kind === "radio" ? /* @__PURE__ */ jsx(Menu.RadioItemGroup, {
457
+ value: entry.actions.find((action) => action.properties.checked)?.id ?? "",
458
+ children: entry.actions.filter((action) => !action.properties.hidden).map((action) => /* @__PURE__ */ jsx(ActionRadioItem, {
459
+ menu,
460
+ action,
461
+ group
462
+ }, action.id))
463
+ }, entry.id) : /* @__PURE__ */ jsx(ActionMenuEntry, {
464
+ menu,
465
+ item: entry.item,
466
+ group
467
+ }, entry.item.id)) });
468
+ };
469
+ /**
470
+ * A whole `Menu.Root` driven from a `MenuActions`: the child is the trigger (`asChild`), the items come from the
471
+ * graph; groups become `Sub` menus, `checked` members radio or checkbox items. Without a `MenuActions` it is a menu of
472
+ * the explicit `actions` alone.
473
+ */
474
+ var ActionMenu = (props) => {
475
+ const { virtualRef, open, defaultOpen, onOpenChange, deferUntilOpen, children } = props;
476
+ const [built, setBuilt] = useState(false);
477
+ const deferred = !!deferUntilOpen && !virtualRef && open === void 0 && defaultOpen === void 0;
478
+ const trigger = isValidElement(children) ? children : void 0;
479
+ const handleTriggerClick = useCallback((event) => {
480
+ trigger?.props.onClick?.(event);
481
+ setBuilt(true);
482
+ onOpenChange?.(true);
483
+ }, [trigger, onOpenChange]);
484
+ if (deferred && !built && trigger) return cloneElement(trigger, {
485
+ "onClick": handleTriggerClick,
486
+ "aria-haspopup": "menu",
487
+ "aria-expanded": false
488
+ });
489
+ return /* @__PURE__ */ jsx(ActionMenuRoot, {
490
+ ...props,
491
+ deferred
492
+ });
493
+ };
494
+ var ActionMenuRoot = ({ items, contributions, onAction, caller, iconSize, group, actions, virtualRef, disabled, container, open, defaultOpen, onOpenChange, deferred, children }) => {
495
+ const standalone = useMenuActions();
496
+ const menu = useMemo(() => ({
497
+ items: items ?? standalone.items,
498
+ contributions: contributions ?? standalone.contributions,
499
+ onAction,
500
+ caller,
501
+ iconSize
502
+ }), [
503
+ items,
504
+ contributions,
505
+ onAction,
506
+ caller,
507
+ iconSize,
508
+ standalone
509
+ ]);
510
+ const containerRef = useMemo(() => container ? { current: container } : void 0, [container]);
511
+ const positioning = VirtualAnchor.useVirtualAnchor(virtualRef);
512
+ return /* @__PURE__ */ jsxs(Menu.Root, {
513
+ open: deferred ? void 0 : open,
514
+ defaultOpen: deferred ? true : defaultOpen,
515
+ onOpenChange: ({ open }) => onOpenChange?.(open),
516
+ positioning,
517
+ children: [children && /* @__PURE__ */ jsx(Menu.Trigger, {
518
+ asChild: true,
519
+ disabled,
520
+ children
521
+ }), /* @__PURE__ */ jsx(Menu.Content, {
522
+ container: containerRef,
523
+ children: /* @__PURE__ */ jsx(ActionMenuItems, {
524
+ menu,
525
+ group,
526
+ actions: typeof actions === "function" ? actions() : actions
527
+ })
528
+ })]
529
+ });
225
530
  };
531
+ ActionMenu.displayName = "ActionMenu";
226
532
  //#endregion
227
- //#region src/components/ToolbarMenu.tsx
228
- var ActionToolbarItem = ({ __menuScope, action }) => {
229
- const { iconSize, onAction } = useMenuScoped("ActionToolbarItem", __menuScope);
230
- const { t } = useTranslation(translationKey);
533
+ //#region src/components/action-label.ts
534
+ var actionLabel = (action, t) => {
535
+ const shortcut = getShortcut(action);
536
+ return `${Theme.toLocalizedString(action.properties.label, t)}${shortcut ? ` (${keySymbols(shortcut).join("")})` : ""}`;
537
+ };
538
+ //#endregion
539
+ //#region src/components/ActionLabel.tsx
540
+ var ActionLabel = ({ action }) => {
541
+ const { t } = Hooks.useTranslation(translationKey);
542
+ const shortcut = getShortcut(action);
543
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
544
+ className: "grow truncate",
545
+ children: Theme.toLocalizedString(action.properties.label, t)
546
+ }), shortcut && /* @__PURE__ */ jsx("span", {
547
+ className: mx("shrink-0", "text-fg-muted"),
548
+ children: keySymbols(shortcut).join("")
549
+ })] });
550
+ };
551
+ //#endregion
552
+ //#region src/components/ActionToolbar.tsx
553
+ /**
554
+ * An action as a Next Button: icon-only with its label (and shortcut) in a tooltip, or text with a trailing shortcut.
555
+ * Forwards its ref and remaining props, since a `Menu.Trigger asChild` merges the trigger's onto it.
556
+ */
557
+ var ActionButton = forwardRef(({ action, classNames, testId, ...props }, forwardedRef) => {
558
+ const { t } = Hooks.useTranslation(translationKey);
559
+ const { icon, iconOnly = true, spin } = action.properties;
560
+ const common = {
561
+ ...props,
562
+ spin,
563
+ classNames,
564
+ ...testId && { "data-testid": testId },
565
+ ref: forwardedRef
566
+ };
567
+ return icon && iconOnly ? /* @__PURE__ */ jsx(Button.Root, {
568
+ ...common,
569
+ icon,
570
+ label: actionLabel(action, t),
571
+ iconOnly: true
572
+ }) : /* @__PURE__ */ jsx(Button.Root, {
573
+ ...common,
574
+ icon,
575
+ children: /* @__PURE__ */ jsx(ActionLabel, { action })
576
+ });
577
+ });
578
+ var ActionToolbarItem = ({ menu, action }) => {
579
+ const { onAction, caller, iconSize } = menu;
231
580
  const [pending, setPending] = useState(false);
232
581
  const pendingRef = useRef(false);
233
- const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;
234
- const buttonVariant = action.properties.variant === "primary" ? "primary" : "ghost";
582
+ const { disabled, testId, hidden, classNames } = action.properties;
235
583
  const handleClick = useCallback(() => {
236
584
  if (pendingRef.current) return;
237
585
  pendingRef.current = true;
@@ -240,423 +588,353 @@ var ActionToolbarItem = ({ __menuScope, action }) => {
240
588
  pendingRef.current = false;
241
589
  setPending(false);
242
590
  };
243
- if (onAction) Promise.resolve(onAction(action, {})).then(done, done);
244
- else executeMenuAction(action).then(done, done);
245
- }, [action, onAction]);
246
- const commonProps = {
247
- variant: buttonVariant,
591
+ if (onAction) Promise.resolve(onAction(action, { caller })).then(done, done);
592
+ else executeMenuAction(action, { caller }).then(done, done);
593
+ }, [
594
+ action,
595
+ caller,
596
+ onAction
597
+ ]);
598
+ if (hidden) return null;
599
+ return /* @__PURE__ */ jsx(ActionButton, {
600
+ action,
601
+ variant: action.properties.variant === "primary" ? "primary" : "ghost",
248
602
  disabled: disabled || pending,
603
+ iconSize,
249
604
  classNames,
250
605
  onClick: handleClick,
606
+ testId
607
+ });
608
+ };
609
+ /** A `toggle` action is a pressed button whose state is the action's `checked`. */
610
+ var ToggleToolbarItem = ({ menu, action }) => {
611
+ const { onAction, caller, iconSize } = menu;
612
+ const { t } = Hooks.useTranslation(translationKey);
613
+ const { icon, iconOnly = true, disabled, testId, hidden, checked, classNames, spin } = action.properties;
614
+ const handlePressedChange = useCallback(() => {
615
+ if (onAction) onAction(action, { caller });
616
+ else executeMenuAction(action, { caller });
617
+ }, [
618
+ action,
619
+ caller,
620
+ onAction
621
+ ]);
622
+ if (hidden) return null;
623
+ const common = {
624
+ variant: "ghost",
625
+ pressed: !!checked,
626
+ disabled,
627
+ spin,
628
+ iconSize,
629
+ classNames,
630
+ onPressedChange: handlePressedChange,
251
631
  ...testId && { "data-testid": testId }
252
632
  };
253
- if (hidden) return null;
254
- return icon ? /* @__PURE__ */ jsx(Toolbar.IconButton, {
255
- ...commonProps,
633
+ return icon && iconOnly ? /* @__PURE__ */ jsx(Button.Toggle, {
634
+ ...common,
635
+ icon,
636
+ label: actionLabel(action, t),
637
+ iconOnly: true
638
+ }) : /* @__PURE__ */ jsx(Button.Toggle, {
639
+ ...common,
256
640
  icon,
257
- size: iconSize,
258
- iconOnly,
259
- iconClassNames: mx(spin && "animate-spin", iconClassNames),
260
- label: actionLabel(action, t)
261
- }, action.id) : /* @__PURE__ */ jsx(Toolbar.Button, {
262
- ...commonProps,
263
641
  children: /* @__PURE__ */ jsx(ActionLabel, { action })
264
- }, action.id);
642
+ });
265
643
  };
266
- var SwitchToolbarItem = ({ __menuScope, action }) => {
267
- const { onAction } = useMenuScoped("SwitchToolbarItem", __menuScope);
268
- const { t } = useTranslation(translationKey);
644
+ var SwitchToolbarItem = ({ menu, action }) => {
645
+ const { onAction, caller } = menu;
646
+ const { t } = Hooks.useTranslation(translationKey);
269
647
  const { label, iconOnly, disabled, testId, hidden, checked } = action.properties;
270
- const labelStr = toLocalizedString(label, t);
648
+ const labelStr = Theme.toLocalizedString(label, t);
271
649
  const handleCheckedChange = useCallback(() => {
272
- if (onAction) onAction(action, {});
273
- else executeMenuAction(action);
274
- }, [action, onAction]);
650
+ if (onAction) onAction(action, { caller });
651
+ else executeMenuAction(action, { caller });
652
+ }, [
653
+ action,
654
+ caller,
655
+ onAction
656
+ ]);
275
657
  if (hidden) return null;
276
- const switchInput = /* @__PURE__ */ jsx(Input.Switch, {
658
+ const control = /* @__PURE__ */ jsx(Input.Switch, {
277
659
  checked,
278
660
  disabled,
279
- "aria-label": iconOnly ? labelStr : void 0,
280
661
  onCheckedChange: handleCheckedChange,
662
+ ...iconOnly ? { "aria-label": labelStr } : { label: labelStr },
281
663
  ...testId && { "data-testid": testId }
282
664
  });
283
- return /* @__PURE__ */ jsxs(Input.Root, { children: [iconOnly ? /* @__PURE__ */ jsx(Tooltip.Trigger, {
665
+ return iconOnly ? /* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, {
284
666
  asChild: true,
285
- content: labelStr,
286
- children: /* @__PURE__ */ jsx(Input.Block, { children: switchInput })
287
- }) : /* @__PURE__ */ jsx(Input.Block, { children: switchInput }), !iconOnly && /* @__PURE__ */ jsx(Input.Label, { children: labelStr })] });
288
- };
289
- var DropdownMenuToolbarItem = ({ __menuScope, group, items: propsItems }) => {
290
- const { t } = useTranslation(translationKey);
291
- const { iconSize } = useMenuScoped("DropdownMenuToolbarItem", __menuScope);
292
- const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
293
- const { iconOnly, disabled, testId, applyActive, caretDown = true, icon: groupIcon, iconClassNames: groupIconClassNames, spin: groupSpin } = group.properties;
667
+ children: control
668
+ }), /* @__PURE__ */ jsx(Tooltip.Content, { children: labelStr })] }) : control;
669
+ };
670
+ var DropdownToolbarItem = ({ menu, group }) => {
671
+ const items = useMenuItems(menu, group);
672
+ const { disabled, testId, applyActive, caretDown = true } = group.properties;
294
673
  const activeItem = items?.find((item) => !!item.properties.checked);
295
- const icon = applyActive && activeItem?.properties.icon || groupIcon;
296
- const iconClassNames = applyActive && activeItem?.properties.iconClassNames || groupIconClassNames;
297
- const spin = applyActive && activeItem?.properties.spin || groupSpin;
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, {
674
+ const display = useMemo(() => {
675
+ if (!applyActive || !activeItem) return group;
676
+ const { icon, iconClassNames, spin, label, keyBinding } = activeItem.properties;
677
+ return {
678
+ ...group,
679
+ properties: {
680
+ ...group.properties,
681
+ icon: icon || group.properties.icon,
682
+ iconClassNames: iconClassNames || group.properties.iconClassNames,
683
+ spin: spin || group.properties.spin,
684
+ label,
685
+ keyBinding
686
+ }
687
+ };
688
+ }, [
689
+ group,
690
+ applyActive,
691
+ activeItem
692
+ ]);
693
+ const trigger = /* @__PURE__ */ jsx(ActionButton, {
694
+ action: display,
310
695
  variant: "ghost",
311
696
  disabled,
697
+ iconSize: menu.iconSize,
312
698
  caretDown: caretDown && !disabled,
313
- ...testId && { "data-testid": testId },
314
- children: /* @__PURE__ */ jsx(ActionLabel, { action: labelAction })
699
+ testId
315
700
  });
316
701
  if (disabled) return trigger;
317
- return /* @__PURE__ */ jsx(DropdownMenu$1.Root, {
702
+ return /* @__PURE__ */ jsx(ActionMenu, {
703
+ ...menu,
318
704
  group,
319
- items,
320
- children: /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, {
321
- asChild: true,
322
- children: trigger
323
- })
705
+ actions: items,
706
+ children: trigger
324
707
  });
325
708
  };
326
- var ToggleGroupItem = ({ __menuScope, group, action }) => {
327
- const { iconSize, onAction } = useMenuScoped("ToggleGroupItem", __menuScope);
328
- const { t } = useTranslation(translationKey);
329
- const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames, spin } = action.properties;
709
+ var ToggleGroupItem = ({ menu, group, action }) => {
710
+ const { onAction, caller, iconSize } = menu;
711
+ const { t } = Hooks.useTranslation(translationKey);
712
+ const { icon, iconOnly = true, disabled, testId, hidden, classNames, spin } = action.properties;
330
713
  const handleClick = useCallback(() => {
331
- if (onAction) onAction(action, { parent: group });
332
- else executeMenuAction(action, { parent: group });
714
+ if (onAction) onAction(action, {
715
+ parent: group,
716
+ caller
717
+ });
718
+ else executeMenuAction(action, {
719
+ parent: group,
720
+ caller
721
+ });
333
722
  }, [
334
723
  action,
335
724
  group,
725
+ caller,
336
726
  onAction
337
727
  ]);
338
- const commonProps = {
728
+ if (hidden) return null;
729
+ const common = {
339
730
  value: action.id,
340
731
  disabled,
341
732
  variant: "ghost",
733
+ spin,
734
+ iconSize,
342
735
  classNames,
343
736
  onClick: handleClick,
344
737
  ...testId && { "data-testid": testId }
345
738
  };
346
- return hidden ? null : icon ? /* @__PURE__ */ jsx(Toolbar.ToggleGroupIconItem, {
347
- ...commonProps,
739
+ return icon && iconOnly ? /* @__PURE__ */ jsx(ToggleGroup.Item, {
740
+ ...common,
741
+ icon,
742
+ label: actionLabel(action, t),
743
+ iconOnly: true
744
+ }) : /* @__PURE__ */ jsx(ToggleGroup.Item, {
745
+ ...common,
348
746
  icon,
349
- size: iconSize,
350
- iconOnly,
351
- iconClassNames: mx(spin && "animate-spin", iconClassNames),
352
- label: actionLabel(action, t)
353
- }) : /* @__PURE__ */ jsx(Toolbar.ToggleGroupItem, {
354
- ...commonProps,
355
747
  children: /* @__PURE__ */ jsx(ActionLabel, { action })
356
748
  });
357
749
  };
358
- var ToggleGroupToolbarItem = ({ __menuScope, group, items: itemsProp }) => {
359
- const items = useMenuItems(group, itemsProp, "ToggleGroupToolbarItem", __menuScope);
360
- const { selectCardinality } = group.properties;
361
- const children = items.map((action) => /* @__PURE__ */ jsx(ToggleGroupItem, {
750
+ var ToggleGroupToolbarItem = ({ menu, group }) => {
751
+ const { t } = Hooks.useTranslation(translationKey);
752
+ const items = useMenuItems(menu, group);
753
+ const label = Theme.toLocalizedString(group.properties.label, t);
754
+ const children = items?.filter((item) => !isSeparator(item) && !isMenuGroup(item)).map((item) => /* @__PURE__ */ jsx(ToggleGroupItem, {
755
+ menu,
362
756
  group,
363
- action
364
- }, action.id));
365
- if (selectCardinality === "multiple") return /* @__PURE__ */ jsx(Toolbar.ToggleGroup, {
757
+ action: item
758
+ }, item.id));
759
+ return group.properties.selectCardinality === "multiple" ? /* @__PURE__ */ jsx(Toolbar.ToggleGroup, {
366
760
  type: "multiple",
367
761
  value: group.properties.value,
762
+ "aria-label": label,
368
763
  children
369
- });
370
- else return /* @__PURE__ */ jsx(Toolbar.ToggleGroup, {
764
+ }) : /* @__PURE__ */ jsx(Toolbar.ToggleGroup, {
371
765
  type: "single",
372
766
  value: group.properties.value,
767
+ "aria-label": label,
373
768
  children
374
769
  });
375
770
  };
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
- */
380
- var ToolbarMenu = composable(({ __menuScope, children, ...props }, forwardedRef) => {
381
- const { attendableId, alwaysActive } = useMenuScoped("ToolbarMenu", __menuScope);
382
- const { hasAttention } = useAttention(attendableId);
383
- return /* @__PURE__ */ jsx(Toolbar.Root, {
384
- ...composableProps(props, { classNames: attendableId }),
385
- disabled: !alwaysActive && !hasAttention,
386
- ref: forwardedRef,
387
- children
771
+ var ToolbarItem = ({ menu, item }) => {
772
+ if (isSeparator(item)) return /* @__PURE__ */ jsx(Toolbar.Separator, { variant: item.properties.variant === "line" ? "line" : "gap" });
773
+ if (isMenuGroup(item)) return item.properties.variant === "dropdownMenu" ? /* @__PURE__ */ jsx(DropdownToolbarItem, {
774
+ menu,
775
+ group: item
776
+ }) : /* @__PURE__ */ jsx(ToggleGroupToolbarItem, {
777
+ menu,
778
+ group: item
388
779
  });
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
- };
398
- var ToolbarMenuItem = ({ __menuScope, item }) => {
399
- if (isSeparator(item)) return /* @__PURE__ */ jsx(Toolbar.Separator, { variant: item.properties.variant });
400
- if (isMenuGroup(item)) {
401
- if (item.properties.variant === "dropdownMenu") return /* @__PURE__ */ jsx(DropdownMenuToolbarItem, {
402
- __menuScope,
403
- group: item
780
+ const action = item;
781
+ switch (action.properties?.variant) {
782
+ case "switch": return /* @__PURE__ */ jsx(SwitchToolbarItem, {
783
+ menu,
784
+ action
404
785
  });
405
- return /* @__PURE__ */ jsx(ToggleGroupToolbarItem, {
406
- __menuScope,
407
- group: item
786
+ case "toggle": return /* @__PURE__ */ jsx(ToggleToolbarItem, {
787
+ menu,
788
+ action
408
789
  });
409
- }
410
- const action = item;
411
- if (action.properties?.variant === "switch") return /* @__PURE__ */ jsx(SwitchToolbarItem, {
412
- __menuScope,
413
- action
414
- });
415
- if (action.properties?.variant === "custom" && action.properties.render) return /* @__PURE__ */ jsx(Fragment, { children: action.properties.render() });
416
- return /* @__PURE__ */ jsx(ActionToolbarItem, {
417
- __menuScope,
418
- action
419
- });
420
- };
421
- //#endregion
422
- //#region src/components/Menu.tsx
423
- var DEFAULT_PRIORITY = 100;
424
- var MenuProvider = ({ children, items = menuContextDefaults.items, iconSize = menuContextDefaults.iconSize, attendableId, alwaysActive, onAction }) => {
425
- const { scope } = useMenuScope(void 0);
426
- const registry = useContext(RegistryContext);
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
790
+ case "custom": return action.properties.render ? /* @__PURE__ */ jsx(Fragment, { children: action.properties.render() }) : null;
791
+ default: return /* @__PURE__ */ jsx(ActionToolbarItem, {
792
+ menu,
793
+ action
435
794
  });
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]);
444
- return /* @__PURE__ */ jsx(MenuContextProvider, {
445
- items,
446
- iconSize,
447
- attendableId,
448
- alwaysActive,
449
- menuItemsAtom,
450
- addMenuItems,
451
- removeMenuItems,
452
- onAction,
453
- scope,
454
- children
455
- });
456
- };
457
- /**
458
- * Menu context boundary.
459
- *
460
- * NOTE: This component is headless since it's root div has `contents`.
461
- *
462
- * Provides the menu context (action dispatch, contribution registry, icon size, etc.)
463
- * and an optional dropdown root for use with `Menu.Trigger` + `Menu.Content`.
464
- */
465
- var MenuRoot = ({ children, open, defaultOpen, onOpenChange, caller, ...props }) => {
466
- const [menuOpen, setMenuOpen] = useControllableState({
467
- prop: open,
468
- defaultProp: defaultOpen,
469
- onChange: onOpenChange
470
- });
471
- const closeMenu = useCallback(() => setMenuOpen(false), [setMenuOpen]);
472
- return /* @__PURE__ */ jsx(MenuProvider, {
473
- ...props,
474
- children: /* @__PURE__ */ jsx(MenuDropdownContext.Provider, {
475
- value: {
476
- closeMenu,
477
- caller
478
- },
479
- children: /* @__PURE__ */ jsx(DropdownMenu.Root, {
480
- open: menuOpen,
481
- onOpenChange: setMenuOpen,
482
- children
483
- })
484
- })
485
- });
795
+ }
486
796
  };
487
- var MenuContentItem = ({ item, onClick, __menuScope }) => {
488
- const action = item;
489
- const handleClick = useCallback((event) => onClick(action, event), [action, onClick]);
490
- const { iconSize } = useMenuScoped("MenuContentItem", __menuScope);
491
- return /* @__PURE__ */ jsxs(DropdownMenu.Item, {
492
- onClick: handleClick,
493
- classNames: "gap-2",
494
- disabled: action.properties?.disabled,
495
- ...action.properties?.testId && { "data-testid": action.properties.testId },
496
- children: [action.properties?.icon && /* @__PURE__ */ jsx(Icon, {
497
- icon: action.properties.icon,
498
- size: iconSize,
499
- classNames: mx(action.properties.spin && "animate-spin", action.properties.iconClassNames)
500
- }), /* @__PURE__ */ jsx(ActionLabel, { action })]
501
- });
797
+ var ActionToolbarItems = ({ menu }) => {
798
+ const items = useMenuItems(menu);
799
+ return /* @__PURE__ */ jsx(Fragment, { children: items?.map((item) => /* @__PURE__ */ jsx(ToolbarItem, {
800
+ menu,
801
+ item
802
+ }, item.id)) });
502
803
  };
503
804
  /**
504
- * Renders the dropdown menu portal, content, and graph-backed items.
505
- *
506
- * Must be a descendant of `Menu.Root`. Reads items via `useMenuItems` from the
507
- * nearest menu context, with optional `group`/`items` prop overrides.
805
+ * A whole `Toolbar.Root` driven from a `MenuActions`: the graph's root items render first, then the toolbar's own
806
+ * children. Mix graph and hand-written controls the other way round by dropping an `ActionMenu` into a plain
807
+ * `Toolbar.Root`. Without a `MenuActions` it is an empty toolbar until one arrives.
508
808
  */
509
- var MenuContent = ({ group, items: propsItems, caller: callerOverride, __menuScope }) => {
510
- const { closeMenu, caller: contextCaller } = useContext(MenuDropdownContext);
511
- const caller = callerOverride ?? contextCaller;
512
- const { onAction } = useMenuScoped("MenuContent", __menuScope);
513
- const resolvedItems = useMenuItems(group, propsItems, "MenuContent", __menuScope);
514
- const handleActionClick = useCallback((action, event) => {
515
- if (action.properties?.disabled) return;
516
- event.stopPropagation();
517
- closeMenu();
518
- const params = {
519
- parent: group,
520
- caller,
521
- modifiers: { shift: event.shiftKey }
522
- };
523
- if (onAction) onAction(action, params);
524
- else executeMenuAction(action, params);
525
- }, [
526
- group,
809
+ var ActionToolbar = Util.composable(({ items, contributions, onAction, caller, iconSize, attendableId, alwaysActive, start, children, ...props }, forwardedRef) => {
810
+ const standalone = useMenuActions();
811
+ const menu = useMemo(() => ({
812
+ items: items ?? standalone.items,
813
+ contributions: contributions ?? standalone.contributions,
814
+ onAction,
527
815
  caller,
816
+ iconSize
817
+ }), [
818
+ items,
819
+ contributions,
528
820
  onAction,
529
- closeMenu
821
+ caller,
822
+ iconSize,
823
+ standalone
530
824
  ]);
531
- 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, {
532
- item,
533
- onClick: handleActionClick
534
- }, item.id)) }), /* @__PURE__ */ jsx(DropdownMenu.Arrow, {})] }) });
535
- };
536
- /**
537
- * Primary namespace export for the menu system.
538
- *
539
- * - `Menu.Root` — context boundary (replaces `MenuProvider`); also provides a dropdown root.
540
- * - `Menu.Trigger` / `Menu.VirtualTrigger` — dropdown trigger (use with `Menu.Content`).
541
- * - `Menu.Content` — renders graph-backed dropdown items inside a portal.
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.
545
- */
546
- var Menu = {
547
- Root: MenuRoot,
548
- Trigger: DropdownMenu.Trigger,
549
- Content: MenuContent,
550
- VirtualTrigger: DropdownMenu.VirtualTrigger,
551
- Toolbar: ToolbarMenu,
552
- Items: ToolbarMenuItems
553
- };
554
- //#endregion
555
- //#region src/presentation.ts
556
- /**
557
- * Merges an action's `presentation[surface]` chrome override (if any) into its properties, letting a
558
- * single action declare multiple dispositions yet render appropriately in each surface — e.g. a
559
- * primary toolbar button and a plain nav-tree row from the same definition.
560
- */
561
- var applyPresentation = (action, surface) => {
562
- const presentation = action.properties.presentation?.[surface];
563
- if (!presentation) return action;
564
- return {
565
- ...action,
566
- properties: {
567
- ...action.properties,
568
- ...presentation
569
- }
570
- };
571
- };
572
- //#endregion
573
- //#region src/hooks/useMenuActions.ts
574
- var useMenuActions = (props) => {
575
- const registry = useContext(RegistryContext);
576
- const menuGraphProps = useAtomValue(props);
577
- const graph = useMemo(() => {
578
- const newGraph = AppGraph.make({ registry });
579
- AppGraph.addNodes(newGraph, menuGraphProps.nodes);
580
- AppGraph.addEdges(newGraph, menuGraphProps.edges);
581
- return newGraph;
582
- }, [registry, menuGraphProps]);
583
- return { items: useCallback((group) => {
584
- return graph.connections(group?.id || GraphNode.RootId, "child");
585
- }, [graph]) };
586
- };
587
- /**
588
- * Convenience wrapper around `useMenuActions` that creates the backing atom inline.
589
- * Pass a builder thunk and a dependency list — the hook memoizes `Atom.make(build)` and threads
590
- * it through `useMenuActions`. Saves the `useMemo(() => Atom.make(...), deps)` boilerplate when
591
- * the action graph is composed from local state (e.g. a toolbar driven by component state).
592
- *
593
- * Read reactive state via `get` inside the builder; `deps` should hold only stable references.
594
- */
595
- var useMenuBuilder = (build, deps) => {
596
- return useMenuActions(useMemo(() => Atom.make(build), deps));
597
- };
825
+ const { hasAttention } = useAttention(attendableId);
826
+ return /* @__PURE__ */ jsxs(Toolbar.Root, {
827
+ ...Util.composableProps(props, { classNames: attendableId }),
828
+ inactive: !alwaysActive && !hasAttention,
829
+ ref: forwardedRef,
830
+ children: [
831
+ start,
832
+ /* @__PURE__ */ jsx(ActionToolbarItems, { menu }),
833
+ children
834
+ ]
835
+ });
836
+ });
837
+ ActionToolbar.displayName = "ActionToolbar";
598
838
  //#endregion
599
- //#region src/hooks/useGraphMenuActions.ts
839
+ //#region src/components/ViewOptionsMenu.tsx
600
840
  /**
601
- * Flatten a node's contributed actions (and nested {@link AppGraphNode.ActionGroup}s) into the
602
- * `{ nodes, edges }` an {@link useMenuActions} / `Menu` consumes. The action nodes are spliced
603
- * verbatim, so their Effect `data` (and captured `_actionContext`) execute via `executeMenuAction`
604
- * without any `onAction` wiring.
605
- *
606
- * Pure over a `resolve` function so it is testable without a live graph; `resolve(id)` returns the
607
- * direct actions for `id` (a node id at the top level, a group id when expanding a group).
841
+ * The order-by field and its direction, as one menu beside a list's filter. The value is the host's to
842
+ * persist — typically in a `ViewState` aspect beside the filter query.
608
843
  */
609
- var buildGraphMenu = (resolve, nodeId, options = {}) => {
610
- const { rootId = GraphNode.RootId, filter, surface } = options;
611
- const nodes = [];
612
- const edges = [];
613
- const seen = /* @__PURE__ */ new Set();
614
- const visit = (parentId, sourceId) => {
615
- for (const action of resolve(sourceId)) {
616
- if (filter && !filter(action)) continue;
617
- edges.push({
618
- source: parentId,
619
- target: action.id,
620
- relation: "child"
621
- });
622
- if (seen.has(action.id)) continue;
623
- seen.add(action.id);
624
- nodes.push(surface ? applyPresentation(action, surface) : action);
625
- if (AppGraphNode.isActionGroup(action)) visit(action.id, action.id);
626
- }
627
- };
628
- visit(rootId, nodeId);
629
- return {
630
- nodes,
631
- edges
632
- };
633
- };
634
- /**
635
- * Read a node's contributed actions from the app-graph as menu `ActionGraphProps`, for splicing into a
636
- * hand-built toolbar via `MenuBuilder.subgraph(graphActions(graph, get, nodeId))`. Reactive: reads the
637
- * graph's action atoms through `get`, so contributed actions appear/disappear as extensions update.
638
- *
639
- * @idiom org.dxos.react-ui-menu.graphActionsToolbar
640
- * applies: Splicing a node's app-graph-contributed toolbar actions into a hand-built object toolbar
641
- * instead-of: Reading `graph.actions(nodeId)` and filtering/wiring `nodes`/`edges` by hand in every toolbar
642
- * uses: {@link graphActions}, {@link MenuBuilder}, {@link useMenuBuilder}
643
- * related: org.dxos.react-ui-menu.toolbarMenu
644
- */
645
- var graphActions = (graph, get, nodeId, options) => graph && nodeId ? buildGraphMenu((id) => get(graph.actions(id)), nodeId, options) : {
646
- nodes: [],
647
- edges: []
844
+ var SortMenu = ({ fields, value, onChange, label, directionLabels, unsorted, testId = "sort" }) => {
845
+ const sorted = value.field !== unsorted;
846
+ const current = fields.find((field) => field.id === value.field);
847
+ const group = useMemo(() => createMenuItemGroup(testId, {
848
+ label,
849
+ icon: "ph--sort-ascending--regular",
850
+ variant: "dropdownMenu",
851
+ selectCardinality: "single",
852
+ value: value.field
853
+ }), [
854
+ testId,
855
+ label,
856
+ value.field
857
+ ]);
858
+ const actions = useCallback(() => [
859
+ ...fields.map((field) => createMenuAction(`sort-${field.id}`, () => onChange({
860
+ ...value,
861
+ field: field.id
862
+ }), {
863
+ label: field.label,
864
+ icon: field.icon,
865
+ checked: value.field === field.id,
866
+ testId: `${testId}.${field.id}`
867
+ })),
868
+ createLineSeparator(`${testId}Separator`).nodes[0],
869
+ ...["asc", "desc"].map((direction) => createMenuAction(`sort-${direction}`, () => onChange({
870
+ ...value,
871
+ direction
872
+ }), {
873
+ label: directionLabels[direction],
874
+ icon: direction === "asc" ? "ph--sort-ascending--regular" : "ph--sort-descending--regular",
875
+ checked: sorted && value.direction === direction,
876
+ disabled: !sorted,
877
+ testId: `${testId}.${direction}`
878
+ }))
879
+ ], [
880
+ fields,
881
+ value,
882
+ sorted,
883
+ onChange,
884
+ directionLabels,
885
+ testId
886
+ ]);
887
+ return /* @__PURE__ */ jsx(ActionMenu, {
888
+ deferUntilOpen: true,
889
+ group,
890
+ actions,
891
+ children: /* @__PURE__ */ jsx(Button.Root, {
892
+ icon: !sorted ? "ph--arrows-down-up--regular" : value.direction === "asc" ? "ph--sort-ascending--regular" : "ph--sort-descending--regular",
893
+ iconOnly: !sorted,
894
+ label: sorted && current ? current.label : label,
895
+ "data-testid": testId
896
+ })
897
+ });
648
898
  };
649
- /**
650
- * Menu actions sourced entirely from a node's app-graph contributions — the common "show every action
651
- * the graph offers for this object" toolbar/menu. Compose with hand-built items via {@link graphActions}.
652
- */
653
- var useGraphMenuActions = (graph, nodeId, options) => {
654
- return useMenuActions(useMemo(() => Atom.make((get) => graphActions(graph, get, nodeId, options)), [
655
- graph,
656
- nodeId,
657
- options?.rootId
658
- ]));
899
+ SortMenu.displayName = "SortMenu";
900
+ /** What a list is grouped by, as a single-select menu beside its sort. */
901
+ var GroupMenu = ({ fields, value, onChange, label, none, testId = "group" }) => {
902
+ const current = value !== none ? fields.find((field) => field.id === value) : void 0;
903
+ const group = useMemo(() => createMenuItemGroup(testId, {
904
+ label,
905
+ icon: "ph--rows--regular",
906
+ variant: "dropdownMenu",
907
+ selectCardinality: "single",
908
+ value
909
+ }), [
910
+ testId,
911
+ label,
912
+ value
913
+ ]);
914
+ const actions = useCallback(() => fields.map((field) => createMenuAction(`group-${field.id}`, () => onChange(field.id), {
915
+ label: field.label,
916
+ icon: field.icon,
917
+ checked: value === field.id,
918
+ testId: `${testId}.${field.id}`
919
+ })), [
920
+ fields,
921
+ value,
922
+ onChange,
923
+ testId
924
+ ]);
925
+ return /* @__PURE__ */ jsx(ActionMenu, {
926
+ deferUntilOpen: true,
927
+ group,
928
+ actions,
929
+ children: /* @__PURE__ */ jsx(Button.Root, {
930
+ icon: current ? current.icon : "ph--rows--regular",
931
+ iconOnly: !current,
932
+ label: current ? current.label : label,
933
+ "data-testid": testId
934
+ })
935
+ });
659
936
  };
937
+ GroupMenu.displayName = "GroupMenu";
660
938
  //#endregion
661
939
  //#region src/builder.ts
662
940
  var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/builder.ts";
@@ -763,7 +1041,19 @@ var MenuBuilder = Object.freeze({ make: () => new MenuBuilderImpl({
763
1041
  var TOOLBAR_DISPOSITION = "toolbar";
764
1042
  /** Filter for {@link graphActions}: keeps only actions a producer opted into the toolbar. */
765
1043
  var isToolbarAction = (action) => AppGraphNode.hasDisposition(action, TOOLBAR_DISPOSITION);
1044
+ /**
1045
+ * `disposition` value an action opts into to appear in the row beside a text input — a chat prompt
1046
+ * today.
1047
+ *
1048
+ * Separate from {@link TOOLBAR_DISPOSITION} because the two surfaces act on different things: an
1049
+ * object toolbar acts on the object, a prompt row acts on the text being composed. Commenting on a
1050
+ * document belongs in the first and means nothing in the second; dictation belongs in both, and says
1051
+ * so by listing both (`disposition` takes an array).
1052
+ */
1053
+ var PROMPT_DISPOSITION = "prompt";
1054
+ /** Filter for {@link graphActions}: keeps only actions a producer opted into a prompt row. */
1055
+ var isPromptAction = (action) => AppGraphNode.hasDisposition(action, PROMPT_DISPOSITION);
766
1056
  //#endregion
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 };
1057
+ export { ActionMenu, ActionToolbar, GroupMenu, MenuBuilder, MenuSeparatorType, PROMPT_DISPOSITION, SortMenu, TOOLBAR_DISPOSITION, applyPresentation, buildGraphMenu, createGapSeparator, createLineSeparator, createMenuAction, createMenuItemGroup, executeMenuAction, fallbackIcon, graphActions, isMenuGroup, isPromptAction, isSeparator, isToolbarAction, makeMenuActions, resolveItems, useGraphMenuActions, useMenuActions, useMenuBuilder, useMenuContribution, useMenuGraph, useMenuItems };
768
1058
 
769
1059
  //# sourceMappingURL=index.mjs.map