@dxos/react-ui-menu 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/lib/index.mjs +747 -0
  2. package/dist/lib/index.mjs.map +1 -0
  3. package/dist/lib/translations.mjs +7 -0
  4. package/dist/lib/translations.mjs.map +1 -0
  5. package/dist/types/src/builder.d.ts +1 -1
  6. package/dist/types/src/builder.d.ts.map +1 -1
  7. package/dist/types/src/components/ActionLabel.d.ts +0 -2
  8. package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
  9. package/dist/types/src/components/DropdownMenu.d.ts +2 -4
  10. package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
  11. package/dist/types/src/components/Menu.d.ts +5 -15
  12. package/dist/types/src/components/Menu.d.ts.map +1 -1
  13. package/dist/types/src/components/MenuContext.d.ts +28 -0
  14. package/dist/types/src/components/MenuContext.d.ts.map +1 -0
  15. package/dist/types/src/components/ToolbarMenu.d.ts +2 -2
  16. package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
  17. package/dist/types/src/components/ToolbarMenu.stories.d.ts +2 -2
  18. package/dist/types/src/components/action-label.d.ts +7 -0
  19. package/dist/types/src/components/action-label.d.ts.map +1 -0
  20. package/dist/types/src/components/index.d.ts +2 -0
  21. package/dist/types/src/components/index.d.ts.map +1 -1
  22. package/dist/types/src/hooks/useGraphMenuActions.d.ts +13 -1
  23. package/dist/types/src/hooks/useGraphMenuActions.d.ts.map +1 -1
  24. package/dist/types/src/index.d.ts +2 -0
  25. package/dist/types/src/index.d.ts.map +1 -1
  26. package/dist/types/src/presentation.d.ts +8 -0
  27. package/dist/types/src/presentation.d.ts.map +1 -0
  28. package/dist/types/src/toolbar.d.ts +6 -0
  29. package/dist/types/src/toolbar.d.ts.map +1 -0
  30. package/dist/types/src/types.d.ts +1 -1
  31. package/dist/types/src/types.d.ts.map +1 -1
  32. package/dist/types/src/util.d.ts +5 -3
  33. package/dist/types/src/util.d.ts.map +1 -1
  34. package/dist/types/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +19 -20
  36. package/src/builder.ts +3 -3
  37. package/src/components/ActionLabel.tsx +1 -6
  38. package/src/components/DropdownMenu.tsx +30 -8
  39. package/src/components/Menu.stories.tsx +1 -1
  40. package/src/components/Menu.test.tsx +3 -2
  41. package/src/components/Menu.tsx +32 -122
  42. package/src/components/MenuContext.ts +129 -0
  43. package/src/components/ToolbarMenu.tsx +27 -13
  44. package/src/components/action-label.ts +20 -0
  45. package/src/components/index.ts +3 -0
  46. package/src/hooks/useGraphMenuActions.ts +16 -3
  47. package/src/index.ts +2 -0
  48. package/src/presentation.ts +19 -0
  49. package/src/toolbar.ts +11 -0
  50. package/src/types.ts +1 -1
  51. package/src/util.ts +2 -2
  52. package/dist/lib/browser/index.mjs +0 -797
  53. package/dist/lib/browser/index.mjs.map +0 -7
  54. package/dist/lib/browser/meta.json +0 -1
  55. package/dist/lib/browser/translations.mjs +0 -16
  56. package/dist/lib/browser/translations.mjs.map +0 -7
  57. package/dist/lib/node-esm/index.mjs +0 -799
  58. package/dist/lib/node-esm/index.mjs.map +0 -7
  59. package/dist/lib/node-esm/meta.json +0 -1
  60. package/dist/lib/node-esm/translations.mjs +0 -18
  61. package/dist/lib/node-esm/translations.mjs.map +0 -7
@@ -1,797 +0,0 @@
1
- // src/components/Menu.tsx
2
- import { Atom, RegistryContext, useAtomValue } from "@effect-atom/atom-react";
3
- import { createContextScope } from "@radix-ui/react-context";
4
- import { useControllableState as useControllableState2 } from "@radix-ui/react-use-controllable-state";
5
- import React4, { createContext, useCallback as useCallback3, useContext, useMemo } from "react";
6
- import { log } from "@dxos/log";
7
- import { Icon as Icon2, DropdownMenu as NaturalDropdownMenu2 } from "@dxos/react-ui";
8
-
9
- // src/util.ts
10
- import * as Effect from "effect/Effect";
11
- import { Node } from "@dxos/app-graph";
12
- import { EffectEx } from "@dxos/effect";
13
- import { getHostPlatform } from "@dxos/util";
14
- var executeMenuAction = async (action, params = {}) => {
15
- let effect = action.data(params);
16
- if (action._actionContext) {
17
- effect = effect.pipe(Effect.provide(action._actionContext));
18
- }
19
- await EffectEx.runAndForwardErrors(effect);
20
- };
21
- var getShortcut = (action) => {
22
- return typeof action.properties?.keyBinding === "string" ? action.properties.keyBinding : action.properties?.keyBinding?.[getHostPlatform()];
23
- };
24
- var createMenuAction = (id, invoke, properties) => ({
25
- id,
26
- type: Node.ActionType,
27
- properties,
28
- data: () => Effect.sync(invoke)
29
- });
30
- var createMenuItemGroup = (id, properties) => ({
31
- id,
32
- type: Node.ActionGroupType,
33
- properties,
34
- data: Node.actionGroupSymbol
35
- });
36
- var createGapSeparator = (id = "gap", source = "root") => ({
37
- nodes: [
38
- {
39
- id,
40
- type: "@dxos/react-ui-toolbar/separator",
41
- properties: {
42
- variant: "gap"
43
- },
44
- data: void 0
45
- }
46
- ],
47
- edges: [
48
- {
49
- source,
50
- target: id,
51
- relation: "child"
52
- }
53
- ]
54
- });
55
- var createLineSeparator = (id = "line", source = "root") => ({
56
- nodes: [
57
- {
58
- id,
59
- type: "@dxos/react-ui-toolbar/separator",
60
- properties: {
61
- variant: "line"
62
- },
63
- data: void 0
64
- }
65
- ],
66
- edges: [
67
- {
68
- source,
69
- target: id,
70
- relation: "child"
71
- }
72
- ]
73
- });
74
-
75
- // src/components/ActionLabel.tsx
76
- import React from "react";
77
- import { keySymbols } from "@dxos/keyboard";
78
- import { toLocalizedString, useTranslation } from "@dxos/react-ui";
79
- import { mx } from "@dxos/ui-theme";
80
- import { translationKey } from "#translations";
81
- var ActionLabel = ({ action }) => {
82
- const { t } = useTranslation(translationKey);
83
- const shortcut = getShortcut(action);
84
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", {
85
- className: "grow truncate"
86
- }, toLocalizedString(action.properties.label, t)), shortcut && /* @__PURE__ */ React.createElement("span", {
87
- className: mx("shrink-0", "text-description")
88
- }, keySymbols(shortcut).join("")));
89
- };
90
- var actionLabel = (action, t) => {
91
- const shortcut = getShortcut(action);
92
- return `${toLocalizedString(action.properties.label, t)}${shortcut ? ` (${keySymbols(shortcut).join("")})` : ""}`;
93
- };
94
-
95
- // src/components/ToolbarMenu.tsx
96
- import React3, { useCallback as useCallback2 } from "react";
97
- import { Input, Toolbar as NaturalToolbar, Tooltip, toLocalizedString as toLocalizedString2, useTranslation as useTranslation2 } from "@dxos/react-ui";
98
- import { composable, composableProps } from "@dxos/react-ui";
99
- import { useAttention } from "@dxos/react-ui-attention";
100
- import { translationKey as translationKey2 } from "#translations";
101
-
102
- // src/types.ts
103
- import { Node as Node2 } from "@dxos/app-graph";
104
- var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
105
- var isSeparator = (node) => node.type === MenuSeparatorType;
106
- var isMenuGroup = (node) => node.type === Node2.ActionGroupType;
107
-
108
- // src/components/DropdownMenu.tsx
109
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
110
- import React2, { useCallback } from "react";
111
- import { Icon, DropdownMenu as NaturalDropdownMenu } from "@dxos/react-ui";
112
- var DropdownMenuItem = ({ item, onClick, __menuScope }) => {
113
- const action = item;
114
- const handleClick = useCallback((event) => onClick(action, event), [
115
- action,
116
- onClick
117
- ]);
118
- const { iconSize } = useMenuScoped("DropdownMenuItem", __menuScope);
119
- return /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Item, {
120
- onClick: handleClick,
121
- classNames: "gap-2",
122
- disabled: action.properties?.disabled,
123
- ...action.properties?.testId && {
124
- "data-testid": action.properties.testId
125
- }
126
- }, action.properties?.icon && /* @__PURE__ */ React2.createElement(Icon, {
127
- icon: action.properties.icon,
128
- size: iconSize
129
- }), /* @__PURE__ */ React2.createElement(ActionLabel, {
130
- action
131
- }));
132
- };
133
- var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenChange, caller, children, __menuScope, ...naturalProps }) => {
134
- const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
135
- prop: open,
136
- defaultProp: defaultOpen,
137
- onChange: onOpenChange
138
- });
139
- const { onAction } = useMenuScoped("DropdownMenuRoot", __menuScope);
140
- const handleActionClick = useCallback((action, event) => {
141
- if (action.properties?.disabled) {
142
- return;
143
- }
144
- event.stopPropagation();
145
- setOptionsMenuOpen(false);
146
- if (onAction) {
147
- onAction(action, {
148
- parent: group,
149
- caller
150
- });
151
- } else {
152
- void executeMenuAction(action, {
153
- parent: group,
154
- caller
155
- });
156
- }
157
- }, [
158
- group,
159
- caller,
160
- onAction
161
- ]);
162
- const items = useMenuItems(group, propsItems);
163
- return /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Root, {
164
- open: optionsMenuOpen,
165
- onOpenChange: setOptionsMenuOpen,
166
- ...naturalProps
167
- }, 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, {
168
- key: item.id,
169
- item,
170
- onClick: handleActionClick
171
- }))), /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Arrow, null))));
172
- };
173
- var DropdownMenu = {
174
- Root: DropdownMenuRoot,
175
- Trigger: NaturalDropdownMenu.Trigger,
176
- VirtualTrigger: NaturalDropdownMenu.VirtualTrigger
177
- };
178
-
179
- // src/components/ToolbarMenu.tsx
180
- var ActionToolbarItem = ({ __menuScope, action }) => {
181
- const { iconSize, onAction } = useMenuScoped("ActionToolbarItem", __menuScope);
182
- const { t } = useTranslation2(translationKey2);
183
- const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames } = action.properties;
184
- const buttonVariant = action.properties.variant === "primary" ? "primary" : "ghost";
185
- const handleClick = useCallback2(() => {
186
- if (onAction) {
187
- onAction(action, {});
188
- } else {
189
- void executeMenuAction(action);
190
- }
191
- }, [
192
- action,
193
- onAction
194
- ]);
195
- const commonProps = {
196
- variant: buttonVariant,
197
- disabled,
198
- classNames,
199
- onClick: handleClick,
200
- ...testId && {
201
- "data-testid": testId
202
- }
203
- };
204
- if (hidden) {
205
- return null;
206
- }
207
- return icon ? /* @__PURE__ */ React3.createElement(NaturalToolbar.IconButton, {
208
- key: action.id,
209
- ...commonProps,
210
- icon,
211
- size: iconSize,
212
- iconOnly,
213
- iconClassNames,
214
- label: actionLabel(action, t)
215
- }) : /* @__PURE__ */ React3.createElement(NaturalToolbar.Button, {
216
- key: action.id,
217
- ...commonProps
218
- }, /* @__PURE__ */ React3.createElement(ActionLabel, {
219
- action
220
- }));
221
- };
222
- var SwitchToolbarItem = ({ __menuScope, action }) => {
223
- const { onAction } = useMenuScoped("SwitchToolbarItem", __menuScope);
224
- const { t } = useTranslation2(translationKey2);
225
- const { label, iconOnly, disabled, testId, hidden, checked } = action.properties;
226
- const labelStr = toLocalizedString2(label, t);
227
- const handleCheckedChange = useCallback2(() => {
228
- if (onAction) {
229
- onAction(action, {});
230
- } else {
231
- void executeMenuAction(action);
232
- }
233
- }, [
234
- action,
235
- onAction
236
- ]);
237
- if (hidden) {
238
- return null;
239
- }
240
- const switchInput = /* @__PURE__ */ React3.createElement(Input.Switch, {
241
- checked,
242
- disabled,
243
- "aria-label": iconOnly ? labelStr : void 0,
244
- onCheckedChange: handleCheckedChange,
245
- ...testId && {
246
- "data-testid": testId
247
- }
248
- });
249
- return /* @__PURE__ */ React3.createElement(Input.Root, null, iconOnly ? /* @__PURE__ */ React3.createElement(Tooltip.Trigger, {
250
- asChild: true,
251
- content: labelStr
252
- }, /* @__PURE__ */ React3.createElement(Input.Block, null, switchInput)) : /* @__PURE__ */ React3.createElement(Input.Block, null, switchInput), !iconOnly && /* @__PURE__ */ React3.createElement(Input.Label, null, labelStr));
253
- };
254
- var DropdownMenuToolbarItem = ({ __menuScope, group, items: propsItems }) => {
255
- const { t } = useTranslation2(translationKey2);
256
- const { iconSize } = useMenuScoped("DropdownMenuToolbarItem", __menuScope);
257
- const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
258
- const { iconOnly, disabled, testId, applyActive, caretDown = true, icon: groupIcon, iconClassNames: groupIconClassNames } = group.properties;
259
- const activeItem = items?.find((item) => !!item.properties.checked);
260
- const icon = applyActive && // TODO(thure): Handle other menu item types.
261
- activeItem?.properties.icon || groupIcon;
262
- const iconClassNames = applyActive && activeItem?.properties.iconClassNames || groupIconClassNames;
263
- const labelAction = applyActive && activeItem ? activeItem : group;
264
- return /* @__PURE__ */ React3.createElement(DropdownMenu.Root, {
265
- group,
266
- items
267
- }, /* @__PURE__ */ React3.createElement(DropdownMenu.Trigger, {
268
- asChild: true
269
- }, icon ? /* @__PURE__ */ React3.createElement(NaturalToolbar.IconButton, {
270
- variant: "ghost",
271
- disabled,
272
- icon,
273
- size: iconSize,
274
- iconOnly,
275
- iconClassNames,
276
- label: actionLabel(labelAction, t),
277
- caretDown,
278
- ...testId && {
279
- "data-testid": testId
280
- }
281
- }) : /* @__PURE__ */ React3.createElement(NaturalToolbar.Button, {
282
- variant: "ghost",
283
- disabled,
284
- caretDown,
285
- ...testId && {
286
- "data-testid": testId
287
- }
288
- }, /* @__PURE__ */ React3.createElement(ActionLabel, {
289
- action: labelAction
290
- }))));
291
- };
292
- var ToggleGroupItem = ({ __menuScope, group, action }) => {
293
- const { iconSize, onAction } = useMenuScoped("ToggleGroupItem", __menuScope);
294
- const { t } = useTranslation2(translationKey2);
295
- const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames } = action.properties;
296
- const handleClick = useCallback2(() => {
297
- if (onAction) {
298
- onAction(action, {
299
- parent: group
300
- });
301
- } else {
302
- void executeMenuAction(action, {
303
- parent: group
304
- });
305
- }
306
- }, [
307
- action,
308
- group,
309
- onAction
310
- ]);
311
- const commonProps = {
312
- value: action.id,
313
- disabled,
314
- variant: "ghost",
315
- classNames,
316
- onClick: handleClick,
317
- ...testId && {
318
- "data-testid": testId
319
- }
320
- };
321
- return hidden ? null : icon ? /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroupIconItem, {
322
- ...commonProps,
323
- icon,
324
- size: iconSize,
325
- iconOnly,
326
- iconClassNames,
327
- label: actionLabel(action, t)
328
- }) : /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroupItem, commonProps, /* @__PURE__ */ React3.createElement(ActionLabel, {
329
- action
330
- }));
331
- };
332
- var ToggleGroupToolbarItem = ({ __menuScope, group, items: itemsProp }) => {
333
- const items = useMenuItems(group, itemsProp, "ToggleGroupToolbarItem", __menuScope);
334
- const { selectCardinality } = group.properties;
335
- const children = items.map((action) => /* @__PURE__ */ React3.createElement(ToggleGroupItem, {
336
- key: action.id,
337
- group,
338
- action
339
- }));
340
- if (selectCardinality === "multiple") {
341
- return /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroup, {
342
- type: "multiple",
343
- value: group.properties.value
344
- }, children);
345
- } else {
346
- return /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroup, {
347
- type: "single",
348
- value: group.properties.value
349
- }, children);
350
- }
351
- };
352
- var ToolbarMenu = composable(({ __menuScope, children, ...props }, forwardedRef) => {
353
- const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
354
- const { attendableId, alwaysActive } = useMenuScoped("ToolbarMenu", __menuScope);
355
- const { hasAttention } = useAttention(attendableId);
356
- return /* @__PURE__ */ React3.createElement(NaturalToolbar.Root, {
357
- ...composableProps(props, {
358
- classNames: attendableId
359
- }),
360
- disabled: !alwaysActive && !hasAttention,
361
- ref: forwardedRef
362
- }, items?.map((item) => /* @__PURE__ */ React3.createElement(ToolbarMenuItem, {
363
- key: item.id,
364
- __menuScope,
365
- item
366
- })), children);
367
- });
368
- var ToolbarMenuItem = ({ __menuScope, item }) => {
369
- if (isSeparator(item)) {
370
- return /* @__PURE__ */ React3.createElement(NaturalToolbar.Separator, {
371
- variant: item.properties.variant
372
- });
373
- }
374
- if (isMenuGroup(item)) {
375
- if (item.properties.variant === "dropdownMenu") {
376
- return /* @__PURE__ */ React3.createElement(DropdownMenuToolbarItem, {
377
- __menuScope,
378
- group: item
379
- });
380
- }
381
- return /* @__PURE__ */ React3.createElement(ToggleGroupToolbarItem, {
382
- __menuScope,
383
- group: item
384
- });
385
- }
386
- const action = item;
387
- if (action.properties?.variant === "switch") {
388
- return /* @__PURE__ */ React3.createElement(SwitchToolbarItem, {
389
- __menuScope,
390
- action
391
- });
392
- }
393
- if (action.properties?.variant === "custom" && action.properties.render) {
394
- return /* @__PURE__ */ React3.createElement(React3.Fragment, null, action.properties.render());
395
- }
396
- return /* @__PURE__ */ React3.createElement(ActionToolbarItem, {
397
- __menuScope,
398
- action
399
- });
400
- };
401
-
402
- // src/components/Menu.tsx
403
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/components/Menu.tsx";
404
- var MENU_NAME = "Menu";
405
- var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
406
- var nullItemsAtom = Atom.make(null);
407
- var defaultItemsAccessor = () => nullItemsAtom;
408
- var menuContextDefaults = {
409
- iconSize: 5,
410
- items: defaultItemsAccessor,
411
- onAction: void 0,
412
- menuItemsAtom: Atom.make(/* @__PURE__ */ new Map()),
413
- addMenuItems: () => {
414
- },
415
- removeMenuItems: () => {
416
- }
417
- };
418
- var [MenuContextProvider, useMenuScoped] = createMenuContext(MENU_NAME, menuContextDefaults);
419
- var useMenuScope = createMenuScope();
420
- var MenuDropdownContext = /* @__PURE__ */ createContext({
421
- closeMenu: () => {
422
- }
423
- });
424
- var DEFAULT_PRIORITY = 100;
425
- var sortMenuItems = (items) => [
426
- ...items
427
- ].sort((a, b) => a.priority !== b.priority ? a.priority - b.priority : a.id.localeCompare(b.id));
428
- var MenuProvider = ({ children, items = menuContextDefaults.items, iconSize = menuContextDefaults.iconSize, attendableId, alwaysActive, onAction }) => {
429
- const { scope } = useMenuScope(void 0);
430
- const registry = useContext(RegistryContext);
431
- const menuItemsAtom = useMemo(() => Atom.make(/* @__PURE__ */ new Map()).pipe(Atom.keepAlive), []);
432
- const addMenuItems = useCallback3((props) => {
433
- const priority = props.priority ?? DEFAULT_PRIORITY;
434
- const prev = registry.get(menuItemsAtom);
435
- const next = new Map(prev);
436
- next.set(props.id, {
437
- ...props,
438
- priority
439
- });
440
- registry.set(menuItemsAtom, next);
441
- }, [
442
- registry,
443
- menuItemsAtom
444
- ]);
445
- const removeMenuItems = useCallback3((id) => {
446
- const prev = registry.get(menuItemsAtom);
447
- const next = new Map(prev);
448
- next.delete(id);
449
- registry.set(menuItemsAtom, next);
450
- }, [
451
- registry,
452
- menuItemsAtom
453
- ]);
454
- return /* @__PURE__ */ React4.createElement(MenuContextProvider, {
455
- items,
456
- iconSize,
457
- attendableId,
458
- alwaysActive,
459
- menuItemsAtom,
460
- addMenuItems,
461
- removeMenuItems,
462
- onAction,
463
- scope
464
- }, children);
465
- };
466
- var resolveItems = (baseItems, group, entries) => {
467
- const applicable = [
468
- ...entries.values()
469
- ].filter((entry) => !entry.groupFilter || entry.groupFilter(group));
470
- if (applicable.length === 0) {
471
- return baseItems;
472
- }
473
- const sorted = sortMenuItems(applicable);
474
- const replacements = sorted.filter((entry) => entry.mode === "replacement");
475
- if (replacements.length > 0) {
476
- if (replacements.length > 1) {
477
- log.warn("multiple replacement entries found", {
478
- ids: replacements.map((r) => r.id).join(", "),
479
- using: replacements[0].id
480
- }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 89, S: void 0 });
481
- }
482
- return replacements[0].items;
483
- }
484
- const additive = sorted.filter((entry) => entry.mode === "additive");
485
- const additiveItems = additive.flatMap((entry) => entry.items);
486
- if (!baseItems || baseItems.length === 0) {
487
- return additiveItems.length > 0 ? additiveItems : null;
488
- }
489
- return [
490
- ...baseItems,
491
- ...additiveItems
492
- ];
493
- };
494
- var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
495
- const { items, menuItemsAtom } = useMenuScoped(consumerName, __menuScope);
496
- const groupItems = useAtomValue(items(group));
497
- const entries = useAtomValue(menuItemsAtom) ?? /* @__PURE__ */ new Map();
498
- const baseItems = useMemo(() => propsItems ?? groupItems ?? null, [
499
- propsItems,
500
- groupItems
501
- ]);
502
- const resolved = useMemo(() => resolveItems(baseItems, group, entries), [
503
- baseItems,
504
- group,
505
- entries
506
- ]);
507
- return resolved ?? void 0;
508
- };
509
- var useMenu = (consumerName) => {
510
- return useMenuScoped(consumerName, void 0);
511
- };
512
- var MenuRoot = ({ children, open, defaultOpen, onOpenChange, caller, ...props }) => {
513
- const [menuOpen, setMenuOpen] = useControllableState2({
514
- prop: open,
515
- defaultProp: defaultOpen,
516
- onChange: onOpenChange
517
- });
518
- const closeMenu = useCallback3(() => setMenuOpen(false), [
519
- setMenuOpen
520
- ]);
521
- return /* @__PURE__ */ React4.createElement(MenuProvider, props, /* @__PURE__ */ React4.createElement(MenuDropdownContext.Provider, {
522
- value: {
523
- closeMenu,
524
- caller
525
- }
526
- }, /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Root, {
527
- open: menuOpen,
528
- onOpenChange: setMenuOpen
529
- }, children)));
530
- };
531
- var MenuContentItem = ({ item, onClick, __menuScope }) => {
532
- const action = item;
533
- const handleClick = useCallback3((event) => onClick(action, event), [
534
- action,
535
- onClick
536
- ]);
537
- const { iconSize } = useMenuScoped("MenuContentItem", __menuScope);
538
- return /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Item, {
539
- onClick: handleClick,
540
- classNames: "gap-2",
541
- disabled: action.properties?.disabled,
542
- ...action.properties?.testId && {
543
- "data-testid": action.properties.testId
544
- }
545
- }, action.properties?.icon && /* @__PURE__ */ React4.createElement(Icon2, {
546
- icon: action.properties.icon,
547
- size: iconSize
548
- }), /* @__PURE__ */ React4.createElement(ActionLabel, {
549
- action
550
- }));
551
- };
552
- var MenuContent = ({ group, items: propsItems, caller: callerOverride, __menuScope }) => {
553
- const { closeMenu, caller: contextCaller } = useContext(MenuDropdownContext);
554
- const caller = callerOverride ?? contextCaller;
555
- const { onAction } = useMenuScoped("MenuContent", __menuScope);
556
- const resolvedItems = useMenuItems(group, propsItems, "MenuContent", __menuScope);
557
- const handleActionClick = useCallback3((action, event) => {
558
- if (action.properties?.disabled) {
559
- return;
560
- }
561
- event.stopPropagation();
562
- closeMenu();
563
- if (onAction) {
564
- onAction(action, {
565
- parent: group,
566
- caller
567
- });
568
- } else {
569
- void executeMenuAction(action, {
570
- parent: group,
571
- caller
572
- });
573
- }
574
- }, [
575
- group,
576
- caller,
577
- onAction,
578
- closeMenu
579
- ]);
580
- 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, {
581
- key: item.id,
582
- item,
583
- onClick: handleActionClick
584
- }))), /* @__PURE__ */ React4.createElement(NaturalDropdownMenu2.Arrow, null)));
585
- };
586
- var Menu = {
587
- Root: MenuRoot,
588
- Trigger: NaturalDropdownMenu2.Trigger,
589
- Content: MenuContent,
590
- VirtualTrigger: NaturalDropdownMenu2.VirtualTrigger,
591
- Toolbar: ToolbarMenu
592
- };
593
-
594
- // src/hooks/useGraphMenuActions.ts
595
- import { Atom as Atom3 } from "@effect-atom/atom-react";
596
- import { useMemo as useMemo3 } from "react";
597
- import { Node as Node4 } from "@dxos/app-graph";
598
-
599
- // src/hooks/useMenuActions.ts
600
- import { Atom as Atom2, RegistryContext as RegistryContext2, useAtomValue as useAtomValue2 } from "@effect-atom/atom-react";
601
- import { useCallback as useCallback4, useContext as useContext2, useMemo as useMemo2 } from "react";
602
- import { Graph, Node as Node3 } from "@dxos/app-graph";
603
- var useMenuActions = (props) => {
604
- const registry = useContext2(RegistryContext2);
605
- const menuGraphProps = useAtomValue2(props);
606
- const graph = useMemo2(() => {
607
- const newGraph = Graph.make({
608
- registry
609
- });
610
- newGraph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
611
- return newGraph;
612
- }, [
613
- registry,
614
- menuGraphProps
615
- ]);
616
- const items = useCallback4((group) => {
617
- return graph.connections(group?.id || Node3.RootId, "child");
618
- }, [
619
- graph
620
- ]);
621
- return {
622
- items
623
- };
624
- };
625
- var useMenuBuilder = (build, deps) => {
626
- const atom = useMemo2(() => Atom2.make(build), deps);
627
- return useMenuActions(atom);
628
- };
629
-
630
- // src/hooks/useGraphMenuActions.ts
631
- var buildGraphMenu = (resolve, nodeId, options = {}) => {
632
- const { rootId = Node4.RootId, filter } = options;
633
- const nodes = [];
634
- const edges = [];
635
- const seen = /* @__PURE__ */ new Set();
636
- const visit = (parentId, sourceId) => {
637
- for (const action of resolve(sourceId)) {
638
- if (filter && !filter(action)) {
639
- continue;
640
- }
641
- edges.push({
642
- source: parentId,
643
- target: action.id,
644
- relation: "child"
645
- });
646
- if (seen.has(action.id)) {
647
- continue;
648
- }
649
- seen.add(action.id);
650
- nodes.push(action);
651
- if (Node4.isActionGroup(action)) {
652
- visit(action.id, action.id);
653
- }
654
- }
655
- };
656
- visit(rootId, nodeId);
657
- return {
658
- nodes,
659
- edges
660
- };
661
- };
662
- var graphActions = (graph, get, nodeId, options) => graph && nodeId ? buildGraphMenu((id) => get(graph.actions(id)), nodeId, options) : {
663
- nodes: [],
664
- edges: []
665
- };
666
- var useGraphMenuActions = (graph, nodeId, options) => {
667
- const atom = useMemo3(
668
- () => Atom3.make((get) => graphActions(graph, get, nodeId, options)),
669
- // `options` is expected to be stable; `rootId` participates since it reshapes the graph.
670
- // eslint-disable-next-line react-hooks/exhaustive-deps
671
- [
672
- graph,
673
- nodeId,
674
- options?.rootId
675
- ]
676
- );
677
- return useMenuActions(atom);
678
- };
679
-
680
- // src/builder.ts
681
- import { invariant } from "@dxos/invariant";
682
- import { translationKey as translationKey3 } from "#translations";
683
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/builder.ts";
684
- var overflowMenuProperties = {
685
- label: [
686
- "toolbar-overflow.menu",
687
- {
688
- ns: translationKey3
689
- }
690
- ],
691
- icon: "ph--dots-three-vertical--regular",
692
- iconOnly: true,
693
- caretDown: false,
694
- variant: "dropdownMenu"
695
- };
696
- var MenuBuilderImpl = class _MenuBuilderImpl {
697
- _data;
698
- _rootId;
699
- _separatorCount = 0;
700
- constructor(_data, _rootId) {
701
- this._data = _data;
702
- this._rootId = _rootId;
703
- }
704
- root(props) {
705
- invariant(this._rootId === "root", "Root group can only be at the top level", { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 29, S: this, A: ["this._rootId === 'root'", "'Root group can only be at the top level'"] });
706
- invariant(this._data.nodes.find((node) => node.id === "root") === void 0, "Root group can only be created once", { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 30, S: this, A: ["this._data.nodes.find((node)=>node.id === 'root') === undefined", "'Root group can only be created once'"] });
707
- this._data.nodes.push(createMenuItemGroup("root", props));
708
- return this;
709
- }
710
- build() {
711
- return this._data;
712
- }
713
- action(id, props, invoke) {
714
- this._data.nodes.push(createMenuAction(id, invoke, props));
715
- this._data.edges.push({
716
- source: this._rootId,
717
- target: id,
718
- relation: "child"
719
- });
720
- return this;
721
- }
722
- switch(id, props, invoke) {
723
- return this.action(id, {
724
- ...props,
725
- variant: "switch"
726
- }, invoke);
727
- }
728
- group(id, props, cb) {
729
- this._data.nodes.push(createMenuItemGroup(id, props));
730
- this._data.edges.push({
731
- source: this._rootId,
732
- target: id,
733
- relation: "child"
734
- });
735
- cb(new _MenuBuilderImpl(this._data, id));
736
- return this;
737
- }
738
- menu(id, cb) {
739
- return this.group(id, overflowMenuProperties, cb);
740
- }
741
- subgraph(subgraphOrCb) {
742
- if (!subgraphOrCb) {
743
- return this;
744
- }
745
- if (typeof subgraphOrCb === "function") {
746
- subgraphOrCb(new _MenuBuilderImpl(this._data, this._rootId));
747
- } else {
748
- this._data.nodes.push(...subgraphOrCb.nodes);
749
- this._data.edges.push(...subgraphOrCb.edges);
750
- }
751
- return this;
752
- }
753
- separator(variant = "gap") {
754
- const id = `separator-${++this._separatorCount}`;
755
- this._data.nodes.push({
756
- id,
757
- type: MenuSeparatorType,
758
- properties: {
759
- variant
760
- },
761
- data: void 0
762
- });
763
- this._data.edges.push({
764
- source: this._rootId,
765
- target: id,
766
- relation: "child"
767
- });
768
- return this;
769
- }
770
- };
771
- var MenuBuilder = Object.freeze({
772
- make: () => new MenuBuilderImpl({
773
- nodes: [],
774
- edges: []
775
- }, "root")
776
- });
777
- export {
778
- Menu,
779
- MenuBuilder,
780
- MenuSeparatorType,
781
- buildGraphMenu,
782
- createGapSeparator,
783
- createLineSeparator,
784
- createMenuAction,
785
- createMenuItemGroup,
786
- graphActions,
787
- isMenuGroup,
788
- isSeparator,
789
- menuContextDefaults,
790
- useGraphMenuActions,
791
- useMenu,
792
- useMenuActions,
793
- useMenuBuilder,
794
- useMenuItems,
795
- useMenuScoped
796
- };
797
- //# sourceMappingURL=index.mjs.map