@dxos/react-ui-menu 0.8.4-main.8342792842 → 0.8.4-main.8360d9e660

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