@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.
Files changed (48) hide show
  1. package/dist/lib/browser/index.mjs +320 -145
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +320 -145
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/builder.d.ts +6 -0
  8. package/dist/types/src/builder.d.ts.map +1 -1
  9. package/dist/types/src/components/DropdownMenu.d.ts +1 -1
  10. package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
  11. package/dist/types/src/components/Menu.d.ts +46 -0
  12. package/dist/types/src/components/Menu.d.ts.map +1 -0
  13. package/dist/types/src/components/Menu.test.d.ts +2 -0
  14. package/dist/types/src/components/Menu.test.d.ts.map +1 -0
  15. package/dist/types/src/components/MenuItems.stories.d.ts +21 -0
  16. package/dist/types/src/components/MenuItems.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/ToolbarMenu.d.ts +1 -1
  18. package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
  19. package/dist/types/src/components/ToolbarMenu.stories.d.ts +1 -1
  20. package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/index.d.ts +1 -3
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/hooks/useMenuActions.d.ts +2 -2
  24. package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
  25. package/dist/types/src/testing/generator.d.ts +3 -3
  26. package/dist/types/src/testing/generator.d.ts.map +1 -1
  27. package/dist/types/src/types.d.ts +25 -2
  28. package/dist/types/src/types.d.ts.map +1 -1
  29. package/dist/types/src/util.d.ts +2 -0
  30. package/dist/types/src/util.d.ts.map +1 -1
  31. package/dist/types/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +24 -21
  33. package/src/builder.ts +35 -8
  34. package/src/components/ActionLabel.tsx +2 -2
  35. package/src/components/DropdownMenu.tsx +3 -3
  36. package/src/components/Menu.test.tsx +448 -0
  37. package/src/components/Menu.tsx +349 -0
  38. package/src/components/MenuItems.stories.tsx +265 -0
  39. package/src/components/ToolbarMenu.stories.tsx +17 -18
  40. package/src/components/ToolbarMenu.tsx +12 -11
  41. package/src/components/index.ts +1 -3
  42. package/src/hooks/useMenuActions.ts +15 -50
  43. package/src/testing/generator.ts +9 -9
  44. package/src/types.ts +27 -2
  45. package/src/util.ts +2 -1
  46. package/dist/types/src/components/MenuContext.d.ts +0 -15
  47. package/dist/types/src/components/MenuContext.d.ts.map +0 -1
  48. 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/DropdownMenu.tsx
4
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
5
- import React3, { useCallback } from "react";
6
- import { Icon, DropdownMenu as NaturalDropdownMenu } from "@dxos/react-ui";
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 { descriptionText, mx } from "@dxos/ui-theme";
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", descriptionText)
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/MenuContext.tsx
104
- import { createContextScope } from "@radix-ui/react-context";
105
- import React2, { useMemo } from "react";
106
- var MENU_NAME = "Menu";
107
- var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
108
- var [MenuContextProvider, useMenu] = createMenuContext(MENU_NAME);
109
- var menuContextDefaults = {
110
- iconSize: 5,
111
- useGroupItems: () => null,
112
- onAction: void 0
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 } = useMenu("DropdownMenuItem", __menuScope);
142
- return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Item, {
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__ */ React3.createElement(Icon, {
138
+ }, action.properties?.icon && /* @__PURE__ */ React2.createElement(Icon, {
150
139
  icon: action.properties.icon,
151
140
  size: iconSize
152
- }), /* @__PURE__ */ React3.createElement(ActionLabel, {
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 } = useMenu("DropdownMenuRoot", __menuScope);
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__ */ React3.createElement(NaturalDropdownMenu.Root, {
175
+ return /* @__PURE__ */ React2.createElement(NaturalDropdownMenu.Root, {
187
176
  open: optionsMenuOpen,
188
177
  onOpenChange: setOptionsMenuOpen,
189
178
  ...naturalProps
190
- }, children, /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Portal, null, /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Content, null, /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Viewport, null, items?.map((item) => /* @__PURE__ */ React3.createElement(DropdownMenuItem, {
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__ */ React3.createElement(NaturalDropdownMenu.Arrow, null))));
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 } = useMenu("ActionToolbarItem", __menuScope);
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__ */ React4.createElement(ActionLabel, {
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__ */ React4.createElement(Root, {
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 } = useMenu("DropdownMenuToolbarItem", __menuScope);
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__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(ActionLabel, {
246
+ children: /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(ActionLabel, {
270
247
  action: labelAction
271
- }), /* @__PURE__ */ React4.createElement(Icon2, {
248
+ }), /* @__PURE__ */ React3.createElement(Icon2, {
272
249
  size: 3,
273
250
  icon: "ph--caret-down--bold",
274
- classNames: "mis-1"
251
+ classNames: "ms-1"
275
252
  }))
276
253
  };
277
- return /* @__PURE__ */ React4.createElement(DropdownMenu.Root, {
254
+ return /* @__PURE__ */ React3.createElement(DropdownMenu.Root, {
278
255
  group,
279
256
  items
280
- }, /* @__PURE__ */ React4.createElement(DropdownMenu.Trigger, {
257
+ }, /* @__PURE__ */ React3.createElement(DropdownMenu.Trigger, {
281
258
  asChild: true
282
- }, /* @__PURE__ */ React4.createElement(Root, {
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 } = useMenu("ToggleGroupItem", __menuScope);
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__ */ React4.createElement(ActionLabel, {
294
+ children: /* @__PURE__ */ React3.createElement(ActionLabel, {
318
295
  action
319
296
  })
320
297
  };
321
- return hidden ? null : /* @__PURE__ */ React4.createElement(Root, {
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__ */ React4.createElement(
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__ */ React4.createElement(ToggleGroupItem, {
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 } = useMenu("ToolbarMenu", __menuScope);
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, "bs-full")
339
+ className: mx2(textBlockWidth, toolbarLayout, "h-full")
363
340
  } : {};
364
- return /* @__PURE__ */ React4.createElement(NaturalToolbar.Root, {
341
+ return /* @__PURE__ */ React3.createElement(NaturalToolbar.Root, {
365
342
  ...props,
366
343
  classNames: [
367
- attendableId && !hasAttention && toolbarInactive,
344
+ attendableId,
368
345
  classNames
369
346
  ],
347
+ disabled: !alwaysActive && !hasAttention,
370
348
  layoutManaged: textBlockWidthProp
371
- }, /* @__PURE__ */ React4.createElement(InnerRoot, innerRootProps, items?.map((item, i) => /* @__PURE__ */ React4.createElement(ToolbarMenuItem, {
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__ */ React4.createElement(NaturalToolbar.Separator, {
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__ */ React4.createElement(DropdownMenuToolbarItem, {
362
+ return /* @__PURE__ */ React3.createElement(DropdownMenuToolbarItem, {
385
363
  group: item
386
364
  });
387
365
  } else {
388
- return /* @__PURE__ */ React4.createElement(ToggleGroupToolbarItem, {
366
+ return /* @__PURE__ */ React3.createElement(ToggleGroupToolbarItem, {
389
367
  group: item
390
368
  });
391
369
  }
392
370
  }
393
- return /* @__PURE__ */ React4.createElement(ActionToolbarItem, {
371
+ return /* @__PURE__ */ React3.createElement(ActionToolbarItem, {
394
372
  action: item
395
373
  });
396
374
  };
397
375
 
398
- // src/hooks/useMenuActions.ts
399
- import { RegistryContext, useAtomValue } from "@effect-atom/atom-react";
400
- import { useCallback as useCallback3, useContext, useRef } from "react";
401
- import { Graph, Node as Node3 } from "@dxos/app-graph";
402
- var edgesArrayToRecord = (edges) => {
403
- return Object.fromEntries(Object.entries(edges.reduce((acc, { source, target }) => {
404
- if (!acc[source]) {
405
- acc[source] = {
406
- inbound: [],
407
- outbound: []
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
- if (!acc[target]) {
411
- acc[target] = {
412
- inbound: [],
413
- outbound: []
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
- const sourceEdges = acc[source];
417
- if (!sourceEdges.outbound.includes(target)) {
418
- sourceEdges.outbound.push(target);
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
- const targetEdges = acc[target];
421
- if (!targetEdges.inbound.includes(source)) {
422
- targetEdges.inbound.push(source);
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
- return acc;
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 = useContext(RegistryContext);
429
- const menuGraphProps = useAtomValue(props);
583
+ const registry = useContext2(RegistryContext2);
584
+ const menuGraphProps = useAtomValue2(props);
430
585
  const graphRef = useRef(null);
431
- if (!graphRef.current) {
432
- graphRef.current = Graph.make({
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 prevPropsRef = useRef(menuGraphProps);
440
- if (prevPropsRef.current !== menuGraphProps) {
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
- useGroupItems
603
+ items
452
604
  };
453
605
  };
454
606
 
455
607
  // src/builder.ts
456
608
  import { invariant } from "@dxos/invariant";
457
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-menu/src/builder.ts";
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: __dxlog_file,
474
- L: 36,
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: __dxlog_file,
483
- L: 37,
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
- build() {
511
- return this._data;
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
- DropdownMenu,
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