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