@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.
- package/dist/lib/index.mjs +766 -476
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/builder.d.ts +2 -2
- package/dist/types/src/builder.d.ts.map +1 -1
- package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
- package/dist/types/src/components/ActionMenu.d.ts +32 -0
- package/dist/types/src/components/ActionMenu.d.ts.map +1 -0
- package/dist/types/src/components/{Menu.stories.d.ts → ActionMenu.stories.d.ts} +5 -4
- package/dist/types/src/components/ActionMenu.stories.d.ts.map +1 -0
- package/dist/types/src/components/ActionToolbar.d.ts +20 -0
- package/dist/types/src/components/ActionToolbar.d.ts.map +1 -0
- package/dist/types/src/components/ActionToolbar.stories.d.ts +39 -0
- package/dist/types/src/components/ActionToolbar.stories.d.ts.map +1 -0
- package/dist/types/src/components/ViewOptionsMenu.d.ts +44 -0
- package/dist/types/src/components/ViewOptionsMenu.d.ts.map +1 -0
- package/dist/types/src/components/ViewOptionsMenu.stories.d.ts +25 -0
- package/dist/types/src/components/ViewOptionsMenu.stories.d.ts.map +1 -0
- package/dist/types/src/components/action-label.d.ts +2 -2
- package/dist/types/src/components/action-label.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +3 -3
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useGraphMenuActions.d.ts +4 -3
- package/dist/types/src/hooks/useGraphMenuActions.d.ts.map +1 -1
- package/dist/types/src/hooks/useMenuActions.browser.test.d.ts +2 -0
- package/dist/types/src/hooks/useMenuActions.browser.test.d.ts.map +1 -0
- package/dist/types/src/hooks/useMenuActions.d.ts +27 -6
- package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +7 -7
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +4 -7
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -1
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/toolbar.d.ts +12 -0
- package/dist/types/src/toolbar.d.ts.map +1 -1
- package/dist/types/src/toolbar.test.d.ts +2 -0
- package/dist/types/src/toolbar.test.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +22 -18
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +2 -2
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -25
- package/src/builder.test.ts +4 -4
- package/src/builder.ts +4 -4
- package/src/components/ActionLabel.tsx +7 -6
- package/src/components/ActionMenu.stories.tsx +221 -0
- package/src/components/ActionMenu.tsx +369 -0
- package/src/components/ActionToolbar.stories.tsx +346 -0
- package/src/components/ActionToolbar.tsx +405 -0
- package/src/components/ViewOptionsMenu.stories.tsx +92 -0
- package/src/components/ViewOptionsMenu.tsx +172 -0
- package/src/components/action-label.ts +5 -5
- package/src/components/index.ts +3 -4
- package/src/hooks/index.ts +2 -2
- package/src/hooks/useGraphMenuActions.test.ts +1 -1
- package/src/hooks/useGraphMenuActions.ts +6 -6
- package/src/hooks/useMenuActions.browser.test.tsx +251 -0
- package/src/hooks/useMenuActions.ts +141 -9
- package/src/index.ts +14 -7
- package/src/testing/generator.ts +5 -5
- package/src/testing/index.ts +1 -1
- package/src/toolbar.test.ts +52 -0
- package/src/toolbar.ts +15 -0
- package/src/translations.ts +2 -2
- package/src/types.ts +28 -18
- package/src/util.ts +4 -6
- package/dist/types/src/components/DropdownMenu.d.ts +0 -22
- package/dist/types/src/components/DropdownMenu.d.ts.map +0 -1
- package/dist/types/src/components/Menu.d.ts +0 -60
- package/dist/types/src/components/Menu.d.ts.map +0 -1
- package/dist/types/src/components/Menu.stories.d.ts.map +0 -1
- package/dist/types/src/components/Menu.test.d.ts +0 -2
- package/dist/types/src/components/Menu.test.d.ts.map +0 -1
- package/dist/types/src/components/MenuContext.d.ts +0 -28
- package/dist/types/src/components/MenuContext.d.ts.map +0 -1
- package/dist/types/src/components/ToolbarMenu.d.ts +0 -35
- package/dist/types/src/components/ToolbarMenu.d.ts.map +0 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts +0 -45
- package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +0 -1
- package/src/components/DropdownMenu.tsx +0 -134
- package/src/components/Menu.stories.tsx +0 -266
- package/src/components/Menu.test.tsx +0 -448
- package/src/components/Menu.tsx +0 -257
- package/src/components/MenuContext.ts +0 -130
- package/src/components/ToolbarMenu.stories.tsx +0 -153
- package/src/components/ToolbarMenu.tsx +0 -353
package/dist/lib/index.mjs
CHANGED
|
@@ -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 {
|
|
3
|
-
import * as
|
|
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
|
|
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
|
|
19
|
-
import * as
|
|
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/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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:
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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/
|
|
129
|
-
var
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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/
|
|
228
|
-
var
|
|
229
|
-
const
|
|
230
|
-
|
|
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 {
|
|
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
|
-
}, [
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
}
|
|
642
|
+
});
|
|
265
643
|
};
|
|
266
|
-
var SwitchToolbarItem = ({
|
|
267
|
-
const { onAction } =
|
|
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
|
-
}, [
|
|
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
|
|
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(
|
|
665
|
+
return iconOnly ? /* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, {
|
|
284
666
|
asChild: true,
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const {
|
|
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
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
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
|
-
|
|
314
|
-
children: /* @__PURE__ */ jsx(ActionLabel, { action: labelAction })
|
|
699
|
+
testId
|
|
315
700
|
});
|
|
316
701
|
if (disabled) return trigger;
|
|
317
|
-
return /* @__PURE__ */ jsx(
|
|
702
|
+
return /* @__PURE__ */ jsx(ActionMenu, {
|
|
703
|
+
...menu,
|
|
318
704
|
group,
|
|
319
|
-
items,
|
|
320
|
-
children:
|
|
321
|
-
asChild: true,
|
|
322
|
-
children: trigger
|
|
323
|
-
})
|
|
705
|
+
actions: items,
|
|
706
|
+
children: trigger
|
|
324
707
|
});
|
|
325
708
|
};
|
|
326
|
-
var ToggleGroupItem = ({
|
|
327
|
-
const {
|
|
328
|
-
const { t } = useTranslation(translationKey);
|
|
329
|
-
const { icon, iconOnly = true, disabled, testId, hidden, classNames,
|
|
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, {
|
|
332
|
-
|
|
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
|
-
|
|
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
|
|
347
|
-
...
|
|
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 = ({
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
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
|
-
},
|
|
365
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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(
|
|
406
|
-
|
|
407
|
-
|
|
786
|
+
case "toggle": return /* @__PURE__ */ jsx(ToggleToolbarItem, {
|
|
787
|
+
menu,
|
|
788
|
+
action
|
|
408
789
|
});
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
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
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
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
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
821
|
+
caller,
|
|
822
|
+
iconSize,
|
|
823
|
+
standalone
|
|
530
824
|
]);
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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/
|
|
839
|
+
//#region src/components/ViewOptionsMenu.tsx
|
|
600
840
|
/**
|
|
601
|
-
*
|
|
602
|
-
*
|
|
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
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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 {
|
|
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
|