@dxos/react-ui-menu 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7
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/browser/index.mjs +320 -145
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +320 -145
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/builder.d.ts +6 -0
- package/dist/types/src/builder.d.ts.map +1 -1
- package/dist/types/src/components/DropdownMenu.d.ts +1 -1
- package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Menu.d.ts +46 -0
- package/dist/types/src/components/Menu.d.ts.map +1 -0
- package/dist/types/src/components/Menu.test.d.ts +2 -0
- package/dist/types/src/components/Menu.test.d.ts.map +1 -0
- package/dist/types/src/components/MenuItems.stories.d.ts +21 -0
- package/dist/types/src/components/MenuItems.stories.d.ts.map +1 -0
- package/dist/types/src/components/ToolbarMenu.d.ts +1 -1
- package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -3
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useMenuActions.d.ts +2 -2
- package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +3 -3
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +25 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +2 -0
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +24 -21
- package/src/builder.ts +35 -8
- package/src/components/ActionLabel.tsx +2 -2
- package/src/components/DropdownMenu.tsx +3 -3
- package/src/components/Menu.test.tsx +448 -0
- package/src/components/Menu.tsx +349 -0
- package/src/components/MenuItems.stories.tsx +265 -0
- package/src/components/ToolbarMenu.stories.tsx +17 -18
- package/src/components/ToolbarMenu.tsx +12 -11
- package/src/components/index.ts +1 -3
- package/src/hooks/useMenuActions.ts +15 -50
- package/src/testing/generator.ts +9 -9
- package/src/types.ts +27 -2
- package/src/util.ts +2 -1
- package/dist/types/src/components/MenuContext.d.ts +0 -15
- package/dist/types/src/components/MenuContext.d.ts.map +0 -1
- package/src/components/MenuContext.tsx +0 -62
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
|
-
// src/components/
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
3
|
+
// src/components/Menu.tsx
|
|
4
|
+
import { Atom, RegistryContext, useAtomValue } from "@effect-atom/atom-react";
|
|
5
|
+
import { createContextScope } from "@radix-ui/react-context";
|
|
6
|
+
import { useControllableState as useControllableState2 } from "@radix-ui/react-use-controllable-state";
|
|
7
|
+
import React4, { useCallback as useCallback3, useContext, useMemo } from "react";
|
|
8
|
+
import { log } from "@dxos/log";
|
|
9
|
+
import { Icon as Icon3, DropdownMenu as NaturalDropdownMenu2 } from "@dxos/react-ui";
|
|
7
10
|
|
|
8
11
|
// src/util.ts
|
|
9
12
|
import * as Effect from "effect/Effect";
|
|
@@ -46,7 +49,8 @@ var createGapSeparator = (id = "gap", source = "root") => ({
|
|
|
46
49
|
edges: [
|
|
47
50
|
{
|
|
48
51
|
source,
|
|
49
|
-
target: id
|
|
52
|
+
target: id,
|
|
53
|
+
relation: "child"
|
|
50
54
|
}
|
|
51
55
|
]
|
|
52
56
|
});
|
|
@@ -64,7 +68,8 @@ var createLineSeparator = (id = "line", source = "root") => ({
|
|
|
64
68
|
edges: [
|
|
65
69
|
{
|
|
66
70
|
source,
|
|
67
|
-
target: id
|
|
71
|
+
target: id,
|
|
72
|
+
relation: "child"
|
|
68
73
|
}
|
|
69
74
|
]
|
|
70
75
|
});
|
|
@@ -73,7 +78,7 @@ var createLineSeparator = (id = "line", source = "root") => ({
|
|
|
73
78
|
import React from "react";
|
|
74
79
|
import { keySymbols } from "@dxos/keyboard";
|
|
75
80
|
import { toLocalizedString, useTranslation } from "@dxos/react-ui";
|
|
76
|
-
import {
|
|
81
|
+
import { mx } from "@dxos/ui-theme";
|
|
77
82
|
|
|
78
83
|
// src/translations.ts
|
|
79
84
|
var translationKey = "@dxos/react-ui-menu";
|
|
@@ -92,7 +97,7 @@ var ActionLabel = ({ action }) => {
|
|
|
92
97
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", {
|
|
93
98
|
className: "grow truncate"
|
|
94
99
|
}, toLocalizedString(action.properties.label, t)), shortcut && /* @__PURE__ */ React.createElement("span", {
|
|
95
|
-
className: mx("shrink-0",
|
|
100
|
+
className: mx("shrink-0", "text-description")
|
|
96
101
|
}, keySymbols(shortcut).join("")));
|
|
97
102
|
};
|
|
98
103
|
var actionLabel = (action, t) => {
|
|
@@ -100,56 +105,40 @@ var actionLabel = (action, t) => {
|
|
|
100
105
|
return `${toLocalizedString(action.properties.label, t)}${shortcut ? ` (${keySymbols(shortcut).join("")})` : ""}`;
|
|
101
106
|
};
|
|
102
107
|
|
|
103
|
-
// src/components/
|
|
104
|
-
import {
|
|
105
|
-
import
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
var useMenuScope = createMenuScope();
|
|
115
|
-
var MenuProvider = ({ children, useGroupItems = menuContextDefaults.useGroupItems, iconSize = menuContextDefaults.iconSize, attendableId, onAction }) => {
|
|
116
|
-
const { scope } = useMenuScope(void 0);
|
|
117
|
-
return /* @__PURE__ */ React2.createElement(MenuContextProvider, {
|
|
118
|
-
useGroupItems,
|
|
119
|
-
iconSize,
|
|
120
|
-
attendableId,
|
|
121
|
-
onAction,
|
|
122
|
-
scope
|
|
123
|
-
}, children);
|
|
124
|
-
};
|
|
125
|
-
var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
|
|
126
|
-
const { useGroupItems } = useMenu(consumerName, __menuScope);
|
|
127
|
-
const groupItems = useGroupItems(group);
|
|
128
|
-
return useMemo(() => propsItems ?? groupItems ?? void 0, [
|
|
129
|
-
propsItems,
|
|
130
|
-
groupItems
|
|
131
|
-
]);
|
|
132
|
-
};
|
|
108
|
+
// src/components/ToolbarMenu.tsx
|
|
109
|
+
import React3, { Fragment, useCallback as useCallback2 } from "react";
|
|
110
|
+
import { Icon as Icon2, Toolbar as NaturalToolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
|
|
111
|
+
import { useAttention } from "@dxos/react-ui-attention";
|
|
112
|
+
import { mx as mx2, textBlockWidth, toolbarLayout } from "@dxos/ui-theme";
|
|
113
|
+
|
|
114
|
+
// src/types.ts
|
|
115
|
+
import { Node as Node2 } from "@dxos/app-graph";
|
|
116
|
+
var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
|
|
117
|
+
var isSeparator = (node) => node.type === MenuSeparatorType;
|
|
118
|
+
var isMenuGroup = (node) => node.type === Node2.ActionGroupType;
|
|
133
119
|
|
|
134
120
|
// src/components/DropdownMenu.tsx
|
|
121
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
122
|
+
import React2, { useCallback } from "react";
|
|
123
|
+
import { Icon, DropdownMenu as NaturalDropdownMenu } from "@dxos/react-ui";
|
|
135
124
|
var DropdownMenuItem = ({ item, onClick, __menuScope }) => {
|
|
136
125
|
const action = item;
|
|
137
126
|
const handleClick = useCallback((event) => onClick(action, event), [
|
|
138
127
|
action,
|
|
139
128
|
onClick
|
|
140
129
|
]);
|
|
141
|
-
const { iconSize } =
|
|
142
|
-
return /* @__PURE__ */
|
|
130
|
+
const { iconSize } = useMenuScoped("DropdownMenuItem", __menuScope);
|
|
131
|
+
return /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Item, {
|
|
143
132
|
onClick: handleClick,
|
|
144
133
|
classNames: "gap-2",
|
|
145
134
|
disabled: action.properties?.disabled,
|
|
146
135
|
...action.properties?.testId && {
|
|
147
136
|
"data-testid": action.properties.testId
|
|
148
137
|
}
|
|
149
|
-
}, action.properties?.icon && /* @__PURE__ */
|
|
138
|
+
}, action.properties?.icon && /* @__PURE__ */ React2.createElement(Icon, {
|
|
150
139
|
icon: action.properties.icon,
|
|
151
140
|
size: iconSize
|
|
152
|
-
}), /* @__PURE__ */
|
|
141
|
+
}), /* @__PURE__ */ React2.createElement(ActionLabel, {
|
|
153
142
|
action
|
|
154
143
|
}));
|
|
155
144
|
};
|
|
@@ -159,7 +148,7 @@ var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenCha
|
|
|
159
148
|
defaultProp: defaultOpen,
|
|
160
149
|
onChange: onOpenChange
|
|
161
150
|
});
|
|
162
|
-
const { onAction } =
|
|
151
|
+
const { onAction } = useMenuScoped("DropdownMenuRoot", __menuScope);
|
|
163
152
|
const handleActionClick = useCallback((action, event) => {
|
|
164
153
|
if (action.properties?.disabled) {
|
|
165
154
|
return;
|
|
@@ -183,15 +172,15 @@ var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenCha
|
|
|
183
172
|
onAction
|
|
184
173
|
]);
|
|
185
174
|
const items = useMenuItems(group, propsItems);
|
|
186
|
-
return /* @__PURE__ */
|
|
175
|
+
return /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Root, {
|
|
187
176
|
open: optionsMenuOpen,
|
|
188
177
|
onOpenChange: setOptionsMenuOpen,
|
|
189
178
|
...naturalProps
|
|
190
|
-
}, children, /* @__PURE__ */
|
|
179
|
+
}, children, /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Portal, null, /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Content, null, /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Viewport, null, items?.map((item) => /* @__PURE__ */ React2.createElement(DropdownMenuItem, {
|
|
191
180
|
key: item.id,
|
|
192
181
|
item,
|
|
193
182
|
onClick: handleActionClick
|
|
194
|
-
}))), /* @__PURE__ */
|
|
183
|
+
}))), /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Arrow, null))));
|
|
195
184
|
};
|
|
196
185
|
var DropdownMenu = {
|
|
197
186
|
Root: DropdownMenuRoot,
|
|
@@ -199,23 +188,11 @@ var DropdownMenu = {
|
|
|
199
188
|
VirtualTrigger: NaturalDropdownMenu.VirtualTrigger
|
|
200
189
|
};
|
|
201
190
|
|
|
202
|
-
// src/components/ToolbarMenu.tsx
|
|
203
|
-
import React4, { Fragment, useCallback as useCallback2 } from "react";
|
|
204
|
-
import { Icon as Icon2, Toolbar as NaturalToolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
|
|
205
|
-
import { useAttention } from "@dxos/react-ui-attention";
|
|
206
|
-
import { mx as mx2, textBlockWidth, toolbarInactive, toolbarLayout } from "@dxos/ui-theme";
|
|
207
|
-
|
|
208
|
-
// src/types.ts
|
|
209
|
-
import { Node as Node2 } from "@dxos/app-graph";
|
|
210
|
-
var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
|
|
211
|
-
var isSeparator = (node) => node.type === MenuSeparatorType;
|
|
212
|
-
var isMenuGroup = (node) => node.type === Node2.ActionGroupType;
|
|
213
|
-
|
|
214
191
|
// src/components/ToolbarMenu.tsx
|
|
215
192
|
var ActionToolbarItem = ({ action, __menuScope }) => {
|
|
216
|
-
const { iconSize, onAction } =
|
|
193
|
+
const { iconSize, onAction } = useMenuScoped("ActionToolbarItem", __menuScope);
|
|
217
194
|
const { t } = useTranslation2(translationKey);
|
|
218
|
-
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
195
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames, variant } = action.properties;
|
|
219
196
|
const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
|
|
220
197
|
const rootProps = icon ? {
|
|
221
198
|
icon,
|
|
@@ -223,7 +200,7 @@ var ActionToolbarItem = ({ action, __menuScope }) => {
|
|
|
223
200
|
iconOnly,
|
|
224
201
|
label: actionLabel(action, t)
|
|
225
202
|
} : {
|
|
226
|
-
children: /* @__PURE__ */
|
|
203
|
+
children: /* @__PURE__ */ React3.createElement(ActionLabel, {
|
|
227
204
|
action
|
|
228
205
|
})
|
|
229
206
|
};
|
|
@@ -237,11 +214,11 @@ var ActionToolbarItem = ({ action, __menuScope }) => {
|
|
|
237
214
|
action,
|
|
238
215
|
onAction
|
|
239
216
|
]);
|
|
240
|
-
return hidden ? null : /* @__PURE__ */
|
|
217
|
+
return hidden ? null : /* @__PURE__ */ React3.createElement(Root, {
|
|
241
218
|
key: action.id,
|
|
242
219
|
disabled,
|
|
243
220
|
onClick: handleClick,
|
|
244
|
-
variant: "ghost",
|
|
221
|
+
variant: variant ?? "ghost",
|
|
245
222
|
...testId && {
|
|
246
223
|
"data-testid": testId
|
|
247
224
|
},
|
|
@@ -252,7 +229,7 @@ var ActionToolbarItem = ({ action, __menuScope }) => {
|
|
|
252
229
|
var DropdownMenuToolbarItem = ({ group, items: propsItems, __menuScope }) => {
|
|
253
230
|
const { iconOnly, disabled, testId } = group.properties;
|
|
254
231
|
const { t } = useTranslation2(translationKey);
|
|
255
|
-
const { iconSize } =
|
|
232
|
+
const { iconSize } = useMenuScoped("DropdownMenuToolbarItem", __menuScope);
|
|
256
233
|
const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
|
|
257
234
|
const activeItem = items?.find((item) => !!item.properties.checked);
|
|
258
235
|
const icon = group.properties.applyActive && // TODO(thure): Handle other menu item types.
|
|
@@ -266,20 +243,20 @@ var DropdownMenuToolbarItem = ({ group, items: propsItems, __menuScope }) => {
|
|
|
266
243
|
label: actionLabel(labelAction, t),
|
|
267
244
|
caretDown: true
|
|
268
245
|
} : {
|
|
269
|
-
children: /* @__PURE__ */
|
|
246
|
+
children: /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(ActionLabel, {
|
|
270
247
|
action: labelAction
|
|
271
|
-
}), /* @__PURE__ */
|
|
248
|
+
}), /* @__PURE__ */ React3.createElement(Icon2, {
|
|
272
249
|
size: 3,
|
|
273
250
|
icon: "ph--caret-down--bold",
|
|
274
|
-
classNames: "
|
|
251
|
+
classNames: "ms-1"
|
|
275
252
|
}))
|
|
276
253
|
};
|
|
277
|
-
return /* @__PURE__ */
|
|
254
|
+
return /* @__PURE__ */ React3.createElement(DropdownMenu.Root, {
|
|
278
255
|
group,
|
|
279
256
|
items
|
|
280
|
-
}, /* @__PURE__ */
|
|
257
|
+
}, /* @__PURE__ */ React3.createElement(DropdownMenu.Trigger, {
|
|
281
258
|
asChild: true
|
|
282
|
-
}, /* @__PURE__ */
|
|
259
|
+
}, /* @__PURE__ */ React3.createElement(Root, {
|
|
283
260
|
variant: "ghost",
|
|
284
261
|
disabled,
|
|
285
262
|
...rootProps,
|
|
@@ -289,7 +266,7 @@ var DropdownMenuToolbarItem = ({ group, items: propsItems, __menuScope }) => {
|
|
|
289
266
|
})));
|
|
290
267
|
};
|
|
291
268
|
var ToggleGroupItem = ({ group, action, __menuScope }) => {
|
|
292
|
-
const { iconSize, onAction } =
|
|
269
|
+
const { iconSize, onAction } = useMenuScoped("ToggleGroupItem", __menuScope);
|
|
293
270
|
const { t } = useTranslation2(translationKey);
|
|
294
271
|
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
295
272
|
const handleClick = useCallback2(() => {
|
|
@@ -314,11 +291,11 @@ var ToggleGroupItem = ({ group, action, __menuScope }) => {
|
|
|
314
291
|
iconOnly,
|
|
315
292
|
label: actionLabel(action, t)
|
|
316
293
|
} : {
|
|
317
|
-
children: /* @__PURE__ */
|
|
294
|
+
children: /* @__PURE__ */ React3.createElement(ActionLabel, {
|
|
318
295
|
action
|
|
319
296
|
})
|
|
320
297
|
};
|
|
321
|
-
return hidden ? null : /* @__PURE__ */
|
|
298
|
+
return hidden ? null : /* @__PURE__ */ React3.createElement(Root, {
|
|
322
299
|
key: action.id,
|
|
323
300
|
value: action.id,
|
|
324
301
|
disabled,
|
|
@@ -337,14 +314,14 @@ var ToggleGroupToolbarItem = ({ group, items: propsItems, __menuScope }) => {
|
|
|
337
314
|
return (
|
|
338
315
|
// TODO(thure): Fix.
|
|
339
316
|
// @ts-ignore
|
|
340
|
-
/* @__PURE__ */
|
|
317
|
+
/* @__PURE__ */ React3.createElement(
|
|
341
318
|
NaturalToolbar.ToggleGroup,
|
|
342
319
|
{
|
|
343
320
|
type: selectCardinality,
|
|
344
321
|
value
|
|
345
322
|
},
|
|
346
323
|
// TODO(thure): Handle other menu item types.
|
|
347
|
-
items.map((action) => /* @__PURE__ */
|
|
324
|
+
items.map((action) => /* @__PURE__ */ React3.createElement(ToggleGroupItem, {
|
|
348
325
|
key: action.id,
|
|
349
326
|
group,
|
|
350
327
|
action
|
|
@@ -354,124 +331,294 @@ var ToggleGroupToolbarItem = ({ group, items: propsItems, __menuScope }) => {
|
|
|
354
331
|
};
|
|
355
332
|
var ToolbarMenu = ({ __menuScope, classNames, textBlockWidth: textBlockWidthProp, ...props }) => {
|
|
356
333
|
const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
|
|
357
|
-
const { attendableId } =
|
|
334
|
+
const { attendableId, alwaysActive } = useMenuScoped("ToolbarMenu", __menuScope);
|
|
358
335
|
const { hasAttention } = useAttention(attendableId);
|
|
359
336
|
const InnerRoot = textBlockWidthProp ? "div" : Fragment;
|
|
360
337
|
const innerRootProps = textBlockWidthProp ? {
|
|
361
338
|
role: "none",
|
|
362
|
-
className: mx2(textBlockWidth, toolbarLayout, "
|
|
339
|
+
className: mx2(textBlockWidth, toolbarLayout, "h-full")
|
|
363
340
|
} : {};
|
|
364
|
-
return /* @__PURE__ */
|
|
341
|
+
return /* @__PURE__ */ React3.createElement(NaturalToolbar.Root, {
|
|
365
342
|
...props,
|
|
366
343
|
classNames: [
|
|
367
|
-
attendableId
|
|
344
|
+
attendableId,
|
|
368
345
|
classNames
|
|
369
346
|
],
|
|
347
|
+
disabled: !alwaysActive && !hasAttention,
|
|
370
348
|
layoutManaged: textBlockWidthProp
|
|
371
|
-
}, /* @__PURE__ */
|
|
349
|
+
}, /* @__PURE__ */ React3.createElement(InnerRoot, innerRootProps, items?.map((item, i) => /* @__PURE__ */ React3.createElement(ToolbarMenuItem, {
|
|
372
350
|
key: item.id ?? i,
|
|
373
351
|
item
|
|
374
352
|
}))));
|
|
375
353
|
};
|
|
376
354
|
var ToolbarMenuItem = ({ item }) => {
|
|
377
355
|
if (isSeparator(item)) {
|
|
378
|
-
return /* @__PURE__ */
|
|
356
|
+
return /* @__PURE__ */ React3.createElement(NaturalToolbar.Separator, {
|
|
379
357
|
variant: item.properties.variant
|
|
380
358
|
});
|
|
381
359
|
}
|
|
382
360
|
if (isMenuGroup(item)) {
|
|
383
361
|
if (item.properties.variant === "dropdownMenu") {
|
|
384
|
-
return /* @__PURE__ */
|
|
362
|
+
return /* @__PURE__ */ React3.createElement(DropdownMenuToolbarItem, {
|
|
385
363
|
group: item
|
|
386
364
|
});
|
|
387
365
|
} else {
|
|
388
|
-
return /* @__PURE__ */
|
|
366
|
+
return /* @__PURE__ */ React3.createElement(ToggleGroupToolbarItem, {
|
|
389
367
|
group: item
|
|
390
368
|
});
|
|
391
369
|
}
|
|
392
370
|
}
|
|
393
|
-
return /* @__PURE__ */
|
|
371
|
+
return /* @__PURE__ */ React3.createElement(ActionToolbarItem, {
|
|
394
372
|
action: item
|
|
395
373
|
});
|
|
396
374
|
};
|
|
397
375
|
|
|
398
|
-
// src/
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
var
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
376
|
+
// src/components/Menu.tsx
|
|
377
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/components/Menu.tsx";
|
|
378
|
+
var MENU_NAME = "Menu";
|
|
379
|
+
var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
|
|
380
|
+
var nullItemsAtom = Atom.make(null);
|
|
381
|
+
var defaultItemsAccessor = () => nullItemsAtom;
|
|
382
|
+
var menuContextDefaults = {
|
|
383
|
+
iconSize: 5,
|
|
384
|
+
items: defaultItemsAccessor,
|
|
385
|
+
onAction: void 0,
|
|
386
|
+
menuItemsAtom: Atom.make(/* @__PURE__ */ new Map()),
|
|
387
|
+
addMenuItems: () => {
|
|
388
|
+
},
|
|
389
|
+
removeMenuItems: () => {
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
var [MenuContextProvider, useMenuScoped] = createMenuContext(MENU_NAME, menuContextDefaults);
|
|
393
|
+
var useMenuScope = createMenuScope();
|
|
394
|
+
var MenuDropdownContext = /* @__PURE__ */ React4.createContext({
|
|
395
|
+
closeMenu: () => {
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
var DEFAULT_PRIORITY = 100;
|
|
399
|
+
var sortMenuItems = (items) => [
|
|
400
|
+
...items
|
|
401
|
+
].sort((a, b) => {
|
|
402
|
+
if (a.priority !== b.priority) {
|
|
403
|
+
return a.priority - b.priority;
|
|
404
|
+
}
|
|
405
|
+
return a.id.localeCompare(b.id);
|
|
406
|
+
});
|
|
407
|
+
var MenuProvider = ({ children, items = menuContextDefaults.items, iconSize = menuContextDefaults.iconSize, attendableId, alwaysActive, onAction }) => {
|
|
408
|
+
const { scope } = useMenuScope(void 0);
|
|
409
|
+
const registry = useContext(RegistryContext);
|
|
410
|
+
const menuItemsAtom = useMemo(() => Atom.make(/* @__PURE__ */ new Map()).pipe(Atom.keepAlive), []);
|
|
411
|
+
const addMenuItems = useCallback3((props) => {
|
|
412
|
+
const priority = props.priority ?? DEFAULT_PRIORITY;
|
|
413
|
+
const prev = registry.get(menuItemsAtom);
|
|
414
|
+
const next = new Map(prev);
|
|
415
|
+
next.set(props.id, {
|
|
416
|
+
...props,
|
|
417
|
+
priority
|
|
418
|
+
});
|
|
419
|
+
registry.set(menuItemsAtom, next);
|
|
420
|
+
}, [
|
|
421
|
+
registry,
|
|
422
|
+
menuItemsAtom
|
|
423
|
+
]);
|
|
424
|
+
const removeMenuItems = useCallback3((id) => {
|
|
425
|
+
const prev = registry.get(menuItemsAtom);
|
|
426
|
+
const next = new Map(prev);
|
|
427
|
+
next.delete(id);
|
|
428
|
+
registry.set(menuItemsAtom, next);
|
|
429
|
+
}, [
|
|
430
|
+
registry,
|
|
431
|
+
menuItemsAtom
|
|
432
|
+
]);
|
|
433
|
+
return /* @__PURE__ */ React4.createElement(MenuContextProvider, {
|
|
434
|
+
items,
|
|
435
|
+
iconSize,
|
|
436
|
+
attendableId,
|
|
437
|
+
alwaysActive,
|
|
438
|
+
menuItemsAtom,
|
|
439
|
+
addMenuItems,
|
|
440
|
+
removeMenuItems,
|
|
441
|
+
onAction,
|
|
442
|
+
scope
|
|
443
|
+
}, children);
|
|
444
|
+
};
|
|
445
|
+
var resolveItems = (baseItems, group, entries) => {
|
|
446
|
+
const applicable = [
|
|
447
|
+
...entries.values()
|
|
448
|
+
].filter((entry) => !entry.groupFilter || entry.groupFilter(group));
|
|
449
|
+
if (applicable.length === 0) {
|
|
450
|
+
return baseItems;
|
|
451
|
+
}
|
|
452
|
+
const sorted = sortMenuItems(applicable);
|
|
453
|
+
const replacements = sorted.filter((entry) => entry.mode === "replacement");
|
|
454
|
+
if (replacements.length > 0) {
|
|
455
|
+
if (replacements.length > 1) {
|
|
456
|
+
log.warn("multiple replacement entries found", {
|
|
457
|
+
ids: replacements.map((r) => r.id).join(", "),
|
|
458
|
+
using: replacements[0].id
|
|
459
|
+
}, {
|
|
460
|
+
F: __dxlog_file,
|
|
461
|
+
L: 152,
|
|
462
|
+
S: void 0,
|
|
463
|
+
C: (f, a) => f(...a)
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return replacements[0].items;
|
|
467
|
+
}
|
|
468
|
+
const additive = sorted.filter((entry) => entry.mode === "additive");
|
|
469
|
+
const additiveItems = additive.flatMap((entry) => entry.items);
|
|
470
|
+
if (!baseItems || baseItems.length === 0) {
|
|
471
|
+
return additiveItems.length > 0 ? additiveItems : null;
|
|
472
|
+
}
|
|
473
|
+
return [
|
|
474
|
+
...baseItems,
|
|
475
|
+
...additiveItems
|
|
476
|
+
];
|
|
477
|
+
};
|
|
478
|
+
var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
|
|
479
|
+
const { items, menuItemsAtom } = useMenuScoped(consumerName, __menuScope);
|
|
480
|
+
const groupItems = useAtomValue(items(group));
|
|
481
|
+
const entries = useAtomValue(menuItemsAtom) ?? /* @__PURE__ */ new Map();
|
|
482
|
+
const baseItems = useMemo(() => propsItems ?? groupItems ?? null, [
|
|
483
|
+
propsItems,
|
|
484
|
+
groupItems
|
|
485
|
+
]);
|
|
486
|
+
const resolved = useMemo(() => resolveItems(baseItems, group, entries), [
|
|
487
|
+
baseItems,
|
|
488
|
+
group,
|
|
489
|
+
entries
|
|
490
|
+
]);
|
|
491
|
+
return resolved ?? void 0;
|
|
492
|
+
};
|
|
493
|
+
var useMenu = (consumerName) => {
|
|
494
|
+
return useMenuScoped(consumerName, void 0);
|
|
495
|
+
};
|
|
496
|
+
var MenuRoot = ({ children, open, defaultOpen, onOpenChange, caller, ...props }) => {
|
|
497
|
+
const [menuOpen, setMenuOpen] = useControllableState2({
|
|
498
|
+
prop: open,
|
|
499
|
+
defaultProp: defaultOpen,
|
|
500
|
+
onChange: onOpenChange
|
|
501
|
+
});
|
|
502
|
+
const closeMenu = useCallback3(() => setMenuOpen(false), [
|
|
503
|
+
setMenuOpen
|
|
504
|
+
]);
|
|
505
|
+
return /* @__PURE__ */ React4.createElement(MenuProvider, props, /* @__PURE__ */ React4.createElement(MenuDropdownContext.Provider, {
|
|
506
|
+
value: {
|
|
507
|
+
closeMenu,
|
|
508
|
+
caller
|
|
409
509
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
510
|
+
}, /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Root, {
|
|
511
|
+
open: menuOpen,
|
|
512
|
+
onOpenChange: setMenuOpen
|
|
513
|
+
}, children)));
|
|
514
|
+
};
|
|
515
|
+
var MenuContentItem = ({ item, onClick, __menuScope }) => {
|
|
516
|
+
const action = item;
|
|
517
|
+
const handleClick = useCallback3((event) => onClick(action, event), [
|
|
518
|
+
action,
|
|
519
|
+
onClick
|
|
520
|
+
]);
|
|
521
|
+
const { iconSize } = useMenuScoped("MenuContentItem", __menuScope);
|
|
522
|
+
return /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Item, {
|
|
523
|
+
onClick: handleClick,
|
|
524
|
+
classNames: "gap-2",
|
|
525
|
+
disabled: action.properties?.disabled,
|
|
526
|
+
...action.properties?.testId && {
|
|
527
|
+
"data-testid": action.properties.testId
|
|
415
528
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
529
|
+
}, action.properties?.icon && /* @__PURE__ */ React4.createElement(Icon3, {
|
|
530
|
+
icon: action.properties.icon,
|
|
531
|
+
size: iconSize
|
|
532
|
+
}), /* @__PURE__ */ React4.createElement(ActionLabel, {
|
|
533
|
+
action
|
|
534
|
+
}));
|
|
535
|
+
};
|
|
536
|
+
var MenuContent = ({ group, items: propsItems, caller: callerOverride, __menuScope }) => {
|
|
537
|
+
const { closeMenu, caller: contextCaller } = useContext(MenuDropdownContext);
|
|
538
|
+
const caller = callerOverride ?? contextCaller;
|
|
539
|
+
const { onAction } = useMenuScoped("MenuContent", __menuScope);
|
|
540
|
+
const resolvedItems = useMenuItems(group, propsItems, "MenuContent", __menuScope);
|
|
541
|
+
const handleActionClick = useCallback3((action, event) => {
|
|
542
|
+
if (action.properties?.disabled) {
|
|
543
|
+
return;
|
|
419
544
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
545
|
+
event.stopPropagation();
|
|
546
|
+
closeMenu();
|
|
547
|
+
if (onAction) {
|
|
548
|
+
onAction(action, {
|
|
549
|
+
parent: group,
|
|
550
|
+
caller
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
void executeMenuAction(action, {
|
|
554
|
+
parent: group,
|
|
555
|
+
caller
|
|
556
|
+
});
|
|
423
557
|
}
|
|
424
|
-
|
|
425
|
-
|
|
558
|
+
}, [
|
|
559
|
+
group,
|
|
560
|
+
caller,
|
|
561
|
+
onAction,
|
|
562
|
+
closeMenu
|
|
563
|
+
]);
|
|
564
|
+
return /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Portal, null, /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Content, null, /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Viewport, null, resolvedItems?.map((item) => /* @__PURE__ */ React4.createElement(MenuContentItem, {
|
|
565
|
+
key: item.id,
|
|
566
|
+
item,
|
|
567
|
+
onClick: handleActionClick
|
|
568
|
+
}))), /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Arrow, null)));
|
|
426
569
|
};
|
|
570
|
+
var Menu = {
|
|
571
|
+
Root: MenuRoot,
|
|
572
|
+
Trigger: NaturalDropdownMenu2.Trigger,
|
|
573
|
+
Content: MenuContent,
|
|
574
|
+
VirtualTrigger: NaturalDropdownMenu2.VirtualTrigger,
|
|
575
|
+
Toolbar: ToolbarMenu
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// src/hooks/useMenuActions.ts
|
|
579
|
+
import { RegistryContext as RegistryContext2, useAtomValue as useAtomValue2 } from "@effect-atom/atom-react";
|
|
580
|
+
import { useCallback as useCallback4, useContext as useContext2, useRef } from "react";
|
|
581
|
+
import { Graph, Node as Node3 } from "@dxos/app-graph";
|
|
427
582
|
var useMenuActions = (props) => {
|
|
428
|
-
const registry =
|
|
429
|
-
const menuGraphProps =
|
|
583
|
+
const registry = useContext2(RegistryContext2);
|
|
584
|
+
const menuGraphProps = useAtomValue2(props);
|
|
430
585
|
const graphRef = useRef(null);
|
|
431
|
-
if (!graphRef.current) {
|
|
432
|
-
|
|
433
|
-
registry
|
|
434
|
-
nodes: menuGraphProps.nodes,
|
|
435
|
-
edges: edgesArrayToRecord(menuGraphProps.edges)
|
|
586
|
+
if (!graphRef.current || graphRef.current.props !== menuGraphProps) {
|
|
587
|
+
const newGraph = Graph.make({
|
|
588
|
+
registry
|
|
436
589
|
});
|
|
590
|
+
newGraph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
|
|
591
|
+
graphRef.current = {
|
|
592
|
+
graph: newGraph,
|
|
593
|
+
props: menuGraphProps
|
|
594
|
+
};
|
|
437
595
|
}
|
|
438
|
-
const graph = graphRef.current;
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
prevPropsRef.current = menuGraphProps;
|
|
442
|
-
graph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
|
|
443
|
-
}
|
|
444
|
-
const useGroupItems = useCallback3((sourceNode) => {
|
|
445
|
-
const items = useAtomValue(graph.connections(sourceNode?.id || Node3.RootId));
|
|
446
|
-
return items;
|
|
596
|
+
const graph = graphRef.current.graph;
|
|
597
|
+
const items = useCallback4((group) => {
|
|
598
|
+
return graph.connections(group?.id || Node3.RootId, "child");
|
|
447
599
|
}, [
|
|
448
600
|
graph
|
|
449
601
|
]);
|
|
450
602
|
return {
|
|
451
|
-
|
|
603
|
+
items
|
|
452
604
|
};
|
|
453
605
|
};
|
|
454
606
|
|
|
455
607
|
// src/builder.ts
|
|
456
608
|
import { invariant } from "@dxos/invariant";
|
|
457
|
-
var
|
|
458
|
-
var MenuBuilder = Object.freeze({
|
|
459
|
-
make: () => new MenuBuilderImpl({
|
|
460
|
-
nodes: [],
|
|
461
|
-
edges: []
|
|
462
|
-
}, "root")
|
|
463
|
-
});
|
|
609
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/builder.ts";
|
|
464
610
|
var MenuBuilderImpl = class _MenuBuilderImpl {
|
|
465
611
|
_data;
|
|
466
612
|
_rootId;
|
|
613
|
+
_separatorCount = 0;
|
|
467
614
|
constructor(_data, _rootId) {
|
|
468
615
|
this._data = _data;
|
|
469
616
|
this._rootId = _rootId;
|
|
470
617
|
}
|
|
471
618
|
root(props) {
|
|
472
619
|
invariant(this._rootId === "root", "Root group can only be at the top level", {
|
|
473
|
-
F:
|
|
474
|
-
L:
|
|
620
|
+
F: __dxlog_file2,
|
|
621
|
+
L: 41,
|
|
475
622
|
S: this,
|
|
476
623
|
A: [
|
|
477
624
|
"this._rootId === 'root'",
|
|
@@ -479,8 +626,8 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
|
|
|
479
626
|
]
|
|
480
627
|
});
|
|
481
628
|
invariant(this._data.nodes.find((node) => node.id === "root") === void 0, "Root group can only be created once", {
|
|
482
|
-
F:
|
|
483
|
-
L:
|
|
629
|
+
F: __dxlog_file2,
|
|
630
|
+
L: 42,
|
|
484
631
|
S: this,
|
|
485
632
|
A: [
|
|
486
633
|
"this._data.nodes.find((node) => node.id === 'root') === undefined",
|
|
@@ -490,11 +637,15 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
|
|
|
490
637
|
this._data.nodes.push(createMenuItemGroup("root", props));
|
|
491
638
|
return this;
|
|
492
639
|
}
|
|
640
|
+
build() {
|
|
641
|
+
return this._data;
|
|
642
|
+
}
|
|
493
643
|
action(id, props, invoke) {
|
|
494
644
|
this._data.nodes.push(createMenuAction(id, invoke, props));
|
|
495
645
|
this._data.edges.push({
|
|
496
646
|
source: this._rootId,
|
|
497
|
-
target: id
|
|
647
|
+
target: id,
|
|
648
|
+
relation: "child"
|
|
498
649
|
});
|
|
499
650
|
return this;
|
|
500
651
|
}
|
|
@@ -502,31 +653,55 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
|
|
|
502
653
|
this._data.nodes.push(createMenuItemGroup(id, props));
|
|
503
654
|
this._data.edges.push({
|
|
504
655
|
source: this._rootId,
|
|
505
|
-
target: id
|
|
656
|
+
target: id,
|
|
657
|
+
relation: "child"
|
|
506
658
|
});
|
|
507
659
|
cb(new _MenuBuilderImpl(this._data, id));
|
|
508
660
|
return this;
|
|
509
661
|
}
|
|
510
|
-
|
|
511
|
-
|
|
662
|
+
subgraph(subgraph) {
|
|
663
|
+
this._data.nodes.push(...subgraph.nodes);
|
|
664
|
+
this._data.edges.push(...subgraph.edges);
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
separator(id, variant = "gap") {
|
|
668
|
+
id ??= `separator-${++this._separatorCount}`;
|
|
669
|
+
this._data.nodes.push({
|
|
670
|
+
id,
|
|
671
|
+
type: MenuSeparatorType,
|
|
672
|
+
properties: {
|
|
673
|
+
variant
|
|
674
|
+
},
|
|
675
|
+
data: void 0
|
|
676
|
+
});
|
|
677
|
+
this._data.edges.push({
|
|
678
|
+
source: this._rootId,
|
|
679
|
+
target: id,
|
|
680
|
+
relation: "child"
|
|
681
|
+
});
|
|
682
|
+
return this;
|
|
512
683
|
}
|
|
513
684
|
};
|
|
685
|
+
var MenuBuilder = Object.freeze({
|
|
686
|
+
make: () => new MenuBuilderImpl({
|
|
687
|
+
nodes: [],
|
|
688
|
+
edges: []
|
|
689
|
+
}, "root")
|
|
690
|
+
});
|
|
514
691
|
export {
|
|
515
|
-
|
|
692
|
+
Menu,
|
|
516
693
|
MenuBuilder,
|
|
517
|
-
MenuProvider,
|
|
518
694
|
MenuSeparatorType,
|
|
519
|
-
ToolbarMenu,
|
|
520
695
|
createGapSeparator,
|
|
521
696
|
createLineSeparator,
|
|
522
697
|
createMenuAction,
|
|
523
698
|
createMenuItemGroup,
|
|
524
|
-
createMenuScope,
|
|
525
699
|
isMenuGroup,
|
|
526
700
|
isSeparator,
|
|
527
701
|
menuContextDefaults,
|
|
528
702
|
useMenu,
|
|
529
703
|
useMenuActions,
|
|
530
|
-
useMenuItems
|
|
704
|
+
useMenuItems,
|
|
705
|
+
useMenuScoped
|
|
531
706
|
};
|
|
532
707
|
//# sourceMappingURL=index.mjs.map
|