@dxos/react-ui-menu 0.8.4-main.1068cf700f → 0.8.4-main.1c7ec43d41

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