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