@dxos/react-ui-menu 0.8.3-staging.0fa589b → 0.8.4-main.1068cf700f

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 (50) hide show
  1. package/dist/lib/browser/index.mjs +364 -339
  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 +364 -339
  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 +14 -0
  8. package/dist/types/src/builder.d.ts.map +1 -0
  9. package/dist/types/src/components/ActionLabel.d.ts +3 -3
  10. package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
  11. package/dist/types/src/components/DropdownMenu.d.ts +1 -1
  12. package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
  13. package/dist/types/src/components/MenuContext.d.ts +3 -1
  14. package/dist/types/src/components/MenuContext.d.ts.map +1 -1
  15. package/dist/types/src/components/ToolbarMenu.d.ts +3 -2
  16. package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
  17. package/dist/types/src/components/ToolbarMenu.stories.d.ts +20 -0
  18. package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -0
  19. package/dist/types/src/hooks/useMenuActions.d.ts +6 -5
  20. package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
  21. package/dist/types/src/index.d.ts +1 -1
  22. package/dist/types/src/index.d.ts.map +1 -1
  23. package/dist/types/src/testing/generator.d.ts +13 -11
  24. package/dist/types/src/testing/generator.d.ts.map +1 -1
  25. package/dist/types/src/translations.d.ts +3 -4
  26. package/dist/types/src/translations.d.ts.map +1 -1
  27. package/dist/types/src/types.d.ts +13 -24
  28. package/dist/types/src/types.d.ts.map +1 -1
  29. package/dist/types/src/util.d.ts +12 -5
  30. package/dist/types/src/util.d.ts.map +1 -1
  31. package/dist/types/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +33 -28
  33. package/src/builder.ts +62 -0
  34. package/src/components/ActionLabel.tsx +6 -7
  35. package/src/components/DropdownMenu.tsx +16 -6
  36. package/src/components/MenuContext.tsx +18 -4
  37. package/src/components/ToolbarMenu.stories.tsx +81 -0
  38. package/src/components/ToolbarMenu.tsx +70 -35
  39. package/src/hooks/useMenuActions.ts +23 -45
  40. package/src/index.ts +1 -2
  41. package/src/testing/generator.ts +63 -58
  42. package/src/translations.ts +5 -3
  43. package/src/types.ts +14 -26
  44. package/src/util.ts +32 -14
  45. package/dist/lib/node/index.cjs +0 -506
  46. package/dist/lib/node/index.cjs.map +0 -7
  47. package/dist/lib/node/meta.json +0 -1
  48. package/dist/types/src/components/Menus.stories.d.ts +0 -25
  49. package/dist/types/src/components/Menus.stories.d.ts.map +0 -1
  50. package/src/components/Menus.stories.tsx +0 -67
@@ -1,46 +1,34 @@
1
- // packages/ui/react-ui-menu/src/index.ts
2
- import { rxFromSignal } from "@dxos/app-graph";
3
-
4
- // packages/ui/react-ui-menu/src/components/DropdownMenu.tsx
5
- import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
1
+ // src/components/DropdownMenu.tsx
6
2
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
7
3
  import React3, { useCallback } from "react";
8
- import { DropdownMenu as NaturalDropdownMenu, Icon } from "@dxos/react-ui";
9
-
10
- // packages/ui/react-ui-menu/src/components/ActionLabel.tsx
11
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
12
- import React from "react";
13
- import { keySymbols } from "@dxos/keyboard";
14
- import { toLocalizedString, useTranslation } from "@dxos/react-ui";
15
- import { mx, descriptionText } from "@dxos/react-ui-theme";
16
-
17
- // packages/ui/react-ui-menu/src/translations.ts
18
- var translationKey = "menus";
19
- var translations_default = [
20
- {
21
- "en-US": {
22
- [translationKey]: {}
23
- }
24
- }
25
- ];
4
+ import { Icon, DropdownMenu as NaturalDropdownMenu } from "@dxos/react-ui";
26
5
 
27
- // packages/ui/react-ui-menu/src/util.ts
28
- import { ACTION_GROUP_TYPE, ACTION_TYPE, actionGroupSymbol } from "@dxos/app-graph";
6
+ // src/util.ts
7
+ import * as Effect from "effect/Effect";
8
+ import { Node } from "@dxos/app-graph";
9
+ import { runAndForwardErrors } from "@dxos/effect";
29
10
  import { getHostPlatform } from "@dxos/util";
11
+ var executeMenuAction = async (action, params = {}) => {
12
+ let effect = action.data(params);
13
+ if (action._actionContext) {
14
+ effect = effect.pipe(Effect.provide(action._actionContext));
15
+ }
16
+ await runAndForwardErrors(effect);
17
+ };
30
18
  var getShortcut = (action) => {
31
19
  return typeof action.properties?.keyBinding === "string" ? action.properties.keyBinding : action.properties?.keyBinding?.[getHostPlatform()];
32
20
  };
33
21
  var createMenuAction = (id, invoke, properties) => ({
34
22
  id,
35
- type: ACTION_TYPE,
23
+ type: Node.ActionType,
36
24
  properties,
37
- data: invoke
25
+ data: () => Effect.sync(invoke)
38
26
  });
39
27
  var createMenuItemGroup = (id, properties) => ({
40
28
  id,
41
- type: ACTION_GROUP_TYPE,
29
+ type: Node.ActionGroupType,
42
30
  properties,
43
- data: actionGroupSymbol
31
+ data: Node.actionGroupSymbol
44
32
  });
45
33
  var createGapSeparator = (id = "gap", source = "root") => ({
46
34
  nodes: [
@@ -79,51 +67,59 @@ var createLineSeparator = (id = "line", source = "root") => ({
79
67
  ]
80
68
  });
81
69
 
82
- // packages/ui/react-ui-menu/src/components/ActionLabel.tsx
83
- var ActionLabel = ({ action }) => {
84
- var _effect = _useSignals();
85
- try {
86
- const shortcut = getShortcut(action);
87
- const { t } = useTranslation(translationKey);
88
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", {
89
- className: "grow truncate"
90
- }, toLocalizedString(action.properties.label, t)), shortcut && /* @__PURE__ */ React.createElement("span", {
91
- className: mx("shrink-0", descriptionText)
92
- }, keySymbols(shortcut).join("")));
93
- } finally {
94
- _effect.f();
70
+ // src/components/ActionLabel.tsx
71
+ import React from "react";
72
+ import { keySymbols } from "@dxos/keyboard";
73
+ import { toLocalizedString, useTranslation } from "@dxos/react-ui";
74
+ import { descriptionText, mx } from "@dxos/ui-theme";
75
+
76
+ // src/translations.ts
77
+ var translationKey = "@dxos/react-ui-menu";
78
+ var translations = [
79
+ {
80
+ "en-US": {
81
+ [translationKey]: {}
82
+ }
95
83
  }
84
+ ];
85
+
86
+ // src/components/ActionLabel.tsx
87
+ var ActionLabel = ({ action }) => {
88
+ const { t } = useTranslation(translationKey);
89
+ const shortcut = getShortcut(action);
90
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", {
91
+ className: "grow truncate"
92
+ }, toLocalizedString(action.properties.label, t)), shortcut && /* @__PURE__ */ React.createElement("span", {
93
+ className: mx("shrink-0", descriptionText)
94
+ }, keySymbols(shortcut).join("")));
96
95
  };
97
96
  var actionLabel = (action, t) => {
98
97
  const shortcut = getShortcut(action);
99
98
  return `${toLocalizedString(action.properties.label, t)}${shortcut ? ` (${keySymbols(shortcut).join("")})` : ""}`;
100
99
  };
101
100
 
102
- // packages/ui/react-ui-menu/src/components/MenuContext.tsx
103
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
101
+ // src/components/MenuContext.tsx
104
102
  import { createContextScope } from "@radix-ui/react-context";
105
103
  import React2, { useMemo } from "react";
106
- var MENU_NAME = "GraphMenu";
104
+ var MENU_NAME = "Menu";
107
105
  var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
108
106
  var [MenuContextProvider, useMenu] = createMenuContext(MENU_NAME);
109
107
  var menuContextDefaults = {
110
108
  iconSize: 5,
111
- useGroupItems: () => null
109
+ useGroupItems: () => null,
110
+ onAction: void 0
112
111
  };
113
112
  var useMenuScope = createMenuScope();
114
- var MenuProvider = ({ children, useGroupItems = menuContextDefaults.useGroupItems, iconSize = menuContextDefaults.iconSize, attendableId }) => {
115
- var _effect = _useSignals2();
116
- try {
117
- const { scope } = useMenuScope(void 0);
118
- return /* @__PURE__ */ React2.createElement(MenuContextProvider, {
119
- useGroupItems,
120
- iconSize,
121
- attendableId,
122
- scope
123
- }, children);
124
- } finally {
125
- _effect.f();
126
- }
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);
127
123
  };
128
124
  var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
129
125
  const { useGroupItems } = useMenu(consumerName, __menuScope);
@@ -134,68 +130,67 @@ var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __m
134
130
  ]);
135
131
  };
136
132
 
137
- // packages/ui/react-ui-menu/src/components/DropdownMenu.tsx
133
+ // src/components/DropdownMenu.tsx
138
134
  var DropdownMenuItem = ({ item, onClick, __menuScope }) => {
139
- var _effect = _useSignals3();
140
- try {
141
- const action = item;
142
- const handleClick = useCallback((event) => onClick(action, event), [
143
- action,
144
- onClick
145
- ]);
146
- const { iconSize } = useMenu("DropdownMenuItem", __menuScope);
147
- return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Item, {
148
- onClick: handleClick,
149
- classNames: "gap-2",
150
- disabled: action.properties?.disabled,
151
- ...action.properties?.testId && {
152
- "data-testid": action.properties.testId
153
- }
154
- }, action.properties?.icon && /* @__PURE__ */ React3.createElement(Icon, {
155
- icon: action.properties.icon,
156
- size: iconSize
157
- }), /* @__PURE__ */ React3.createElement(ActionLabel, {
158
- action
159
- }));
160
- } finally {
161
- _effect.f();
162
- }
135
+ const action = item;
136
+ const handleClick = useCallback((event) => onClick(action, event), [
137
+ action,
138
+ onClick
139
+ ]);
140
+ const { iconSize } = useMenu("DropdownMenuItem", __menuScope);
141
+ return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Item, {
142
+ onClick: handleClick,
143
+ classNames: "gap-2",
144
+ disabled: action.properties?.disabled,
145
+ ...action.properties?.testId && {
146
+ "data-testid": action.properties.testId
147
+ }
148
+ }, action.properties?.icon && /* @__PURE__ */ React3.createElement(Icon, {
149
+ icon: action.properties.icon,
150
+ size: iconSize
151
+ }), /* @__PURE__ */ React3.createElement(ActionLabel, {
152
+ action
153
+ }));
163
154
  };
164
155
  var DropdownMenuRoot = ({ items: propsItems, group, open, defaultOpen, onOpenChange, caller, children, __menuScope, ...naturalProps }) => {
165
- var _effect = _useSignals3();
166
- try {
167
- const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
168
- prop: open,
169
- defaultProp: defaultOpen,
170
- onChange: onOpenChange
171
- });
172
- const handleActionClick = useCallback((action, event) => {
173
- if (action.properties?.disabled) {
174
- return;
175
- }
176
- event.stopPropagation();
177
- setOptionsMenuOpen(false);
178
- void action.data?.({
156
+ const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
157
+ prop: open,
158
+ defaultProp: defaultOpen,
159
+ onChange: onOpenChange
160
+ });
161
+ const { onAction } = useMenu("DropdownMenuRoot", __menuScope);
162
+ const handleActionClick = useCallback((action, event) => {
163
+ if (action.properties?.disabled) {
164
+ return;
165
+ }
166
+ event.stopPropagation();
167
+ setOptionsMenuOpen(false);
168
+ if (onAction) {
169
+ onAction(action, {
179
170
  parent: group,
180
171
  caller
181
172
  });
182
- }, [
183
- group,
184
- caller
185
- ]);
186
- const items = useMenuItems(group, propsItems);
187
- return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Root, {
188
- open: optionsMenuOpen,
189
- onOpenChange: setOptionsMenuOpen,
190
- ...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, {
192
- key: item.id,
193
- item,
194
- onClick: handleActionClick
195
- }))), /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Arrow, null))));
196
- } finally {
197
- _effect.f();
198
- }
173
+ } else {
174
+ void executeMenuAction(action, {
175
+ parent: group,
176
+ caller
177
+ });
178
+ }
179
+ }, [
180
+ group,
181
+ caller,
182
+ onAction
183
+ ]);
184
+ const items = useMenuItems(group, propsItems);
185
+ return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Root, {
186
+ open: optionsMenuOpen,
187
+ onOpenChange: setOptionsMenuOpen,
188
+ ...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, {
190
+ key: item.id,
191
+ item,
192
+ onClick: handleActionClick
193
+ }))), /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Arrow, null))));
199
194
  };
200
195
  var DropdownMenu = {
201
196
  Root: DropdownMenuRoot,
@@ -203,254 +198,224 @@ var DropdownMenu = {
203
198
  VirtualTrigger: NaturalDropdownMenu.VirtualTrigger
204
199
  };
205
200
 
206
- // packages/ui/react-ui-menu/src/components/ToolbarMenu.tsx
207
- import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
201
+ // src/components/ToolbarMenu.tsx
208
202
  import React4, { Fragment, useCallback as useCallback2 } from "react";
209
203
  import { Icon as Icon2, Toolbar as NaturalToolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
210
204
  import { useAttention } from "@dxos/react-ui-attention";
211
- import { mx as mx2, textBlockWidth, toolbarLayout } from "@dxos/react-ui-theme";
205
+ import { mx as mx2, textBlockWidth, toolbarLayout } from "@dxos/ui-theme";
212
206
 
213
- // packages/ui/react-ui-menu/src/types.ts
214
- import { ACTION_GROUP_TYPE as ACTION_GROUP_TYPE2 } from "@dxos/app-graph";
207
+ // src/types.ts
208
+ import { Node as Node2 } from "@dxos/app-graph";
215
209
  var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
216
210
  var isSeparator = (node) => node.type === MenuSeparatorType;
217
- var isMenuGroup = (node) => node.type === ACTION_GROUP_TYPE2;
211
+ var isMenuGroup = (node) => node.type === Node2.ActionGroupType;
218
212
 
219
- // packages/ui/react-ui-menu/src/components/ToolbarMenu.tsx
213
+ // src/components/ToolbarMenu.tsx
220
214
  var ActionToolbarItem = ({ action, __menuScope }) => {
221
- var _effect = _useSignals4();
222
- try {
223
- const { iconSize } = useMenu("ActionToolbarItem", __menuScope);
224
- const { t } = useTranslation2(translationKey);
225
- const handleClick = useCallback2(() => action.data?.(), [
215
+ const { iconSize, onAction } = useMenu("ActionToolbarItem", __menuScope);
216
+ const { t } = useTranslation2(translationKey);
217
+ 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
226
  action
227
- ]);
228
- const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
229
- const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
230
- const rootProps = icon ? {
231
- icon,
232
- size: iconSize,
233
- iconOnly,
234
- label: actionLabel(action, t)
235
- } : {
236
- children: /* @__PURE__ */ React4.createElement(ActionLabel, {
237
- action
238
- })
239
- };
240
- return hidden ? null : /* @__PURE__ */ React4.createElement(Root, {
241
- key: action.id,
242
- disabled,
243
- onClick: handleClick,
244
- variant: "ghost",
245
- ...testId && {
246
- "data-testid": testId
247
- },
248
- ...rootProps,
249
- classNames
250
- });
251
- } finally {
252
- _effect.f();
253
- }
227
+ })
228
+ };
229
+ const handleClick = useCallback2(() => {
230
+ if (onAction) {
231
+ onAction(action, {});
232
+ } else {
233
+ void executeMenuAction(action);
234
+ }
235
+ }, [
236
+ action,
237
+ onAction
238
+ ]);
239
+ return hidden ? null : /* @__PURE__ */ React4.createElement(Root, {
240
+ key: action.id,
241
+ disabled,
242
+ onClick: handleClick,
243
+ variant: "ghost",
244
+ ...testId && {
245
+ "data-testid": testId
246
+ },
247
+ ...rootProps,
248
+ classNames
249
+ });
254
250
  };
255
251
  var DropdownMenuToolbarItem = ({ group, items: propsItems, __menuScope }) => {
256
- var _effect = _useSignals4();
257
- try {
258
- const { iconOnly, disabled, testId } = group.properties;
259
- const { t } = useTranslation2(translationKey);
260
- const { iconSize } = useMenu("DropdownMenuToolbarItem", __menuScope);
261
- const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
262
- const activeItem = items?.find((item) => !!item.properties.checked);
263
- const icon = group.properties.applyActive && // TODO(thure): Handle other menu item types.
264
- activeItem?.properties.icon || group.properties.icon;
265
- const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
266
- const labelAction = group.properties.applyActive && activeItem ? activeItem : group;
267
- const rootProps = icon ? {
268
- icon,
269
- size: iconSize,
270
- iconOnly,
271
- label: actionLabel(labelAction, t),
272
- caretDown: true
273
- } : {
274
- children: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(ActionLabel, {
275
- action: labelAction
276
- }), /* @__PURE__ */ React4.createElement(Icon2, {
277
- size: 3,
278
- icon: "ph--caret-down--bold",
279
- classNames: "mis-1"
280
- }))
281
- };
282
- return /* @__PURE__ */ React4.createElement(DropdownMenu.Root, {
283
- group,
284
- items
285
- }, /* @__PURE__ */ React4.createElement(DropdownMenu.Trigger, {
286
- asChild: true
287
- }, /* @__PURE__ */ React4.createElement(Root, {
288
- variant: "ghost",
289
- disabled,
290
- ...rootProps,
291
- ...testId && {
292
- "data-testid": testId
293
- }
294
- })));
295
- } finally {
296
- _effect.f();
297
- }
252
+ const { iconOnly, disabled, testId } = group.properties;
253
+ const { t } = useTranslation2(translationKey);
254
+ const { iconSize } = useMenu("DropdownMenuToolbarItem", __menuScope);
255
+ const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
256
+ const activeItem = items?.find((item) => !!item.properties.checked);
257
+ const icon = group.properties.applyActive && // TODO(thure): Handle other menu item types.
258
+ activeItem?.properties.icon || group.properties.icon;
259
+ const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
260
+ const labelAction = group.properties.applyActive && activeItem ? activeItem : group;
261
+ const rootProps = icon ? {
262
+ icon,
263
+ size: iconSize,
264
+ iconOnly,
265
+ label: actionLabel(labelAction, t),
266
+ caretDown: true
267
+ } : {
268
+ children: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(ActionLabel, {
269
+ action: labelAction
270
+ }), /* @__PURE__ */ React4.createElement(Icon2, {
271
+ size: 3,
272
+ icon: "ph--caret-down--bold",
273
+ classNames: "mis-1"
274
+ }))
275
+ };
276
+ return /* @__PURE__ */ React4.createElement(DropdownMenu.Root, {
277
+ group,
278
+ items
279
+ }, /* @__PURE__ */ React4.createElement(DropdownMenu.Trigger, {
280
+ asChild: true
281
+ }, /* @__PURE__ */ React4.createElement(Root, {
282
+ variant: "ghost",
283
+ disabled,
284
+ ...rootProps,
285
+ ...testId && {
286
+ "data-testid": testId
287
+ }
288
+ })));
298
289
  };
299
290
  var ToggleGroupItem = ({ group, action, __menuScope }) => {
300
- var _effect = _useSignals4();
301
- try {
302
- const { iconSize } = useMenu("ToggleGroupItem", __menuScope);
303
- const { t } = useTranslation2(translationKey);
304
- const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
305
- const handleClick = useCallback2(() => action.data?.({
306
- parent: group
307
- }), [
308
- action,
309
- group
310
- ]);
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,
324
- value: action.id,
325
- disabled,
326
- onClick: handleClick,
327
- variant: "ghost",
328
- ...testId && {
329
- "data-testid": testId
330
- },
331
- ...rootProps,
332
- classNames
333
- });
334
- } finally {
335
- _effect.f();
336
- }
291
+ const { iconSize, onAction } = useMenu("ToggleGroupItem", __menuScope);
292
+ const { t } = useTranslation2(translationKey);
293
+ const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
294
+ const handleClick = useCallback2(() => {
295
+ if (onAction) {
296
+ onAction(action, {
297
+ parent: group
298
+ });
299
+ } else {
300
+ void executeMenuAction(action, {
301
+ parent: group
302
+ });
303
+ }
304
+ }, [
305
+ action,
306
+ group,
307
+ onAction
308
+ ]);
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,
322
+ value: action.id,
323
+ disabled,
324
+ onClick: handleClick,
325
+ variant: "ghost",
326
+ ...testId && {
327
+ "data-testid": testId
328
+ },
329
+ ...rootProps,
330
+ classNames
331
+ });
337
332
  };
338
333
  var ToggleGroupToolbarItem = ({ group, items: propsItems, __menuScope }) => {
339
- var _effect = _useSignals4();
340
- try {
341
- const items = useMenuItems(group, propsItems, "ToggleGroupToolbarItem", __menuScope);
342
- const { selectCardinality, value } = group.properties;
343
- return (
344
- // TODO(thure): The type here is difficult to manage, what do?
345
- // @ts-ignore
346
- /* @__PURE__ */ React4.createElement(
347
- NaturalToolbar.ToggleGroup,
348
- {
349
- type: selectCardinality,
350
- value
351
- },
352
- // TODO(thure): Handle other menu item types.
353
- items.map((action) => /* @__PURE__ */ React4.createElement(ToggleGroupItem, {
354
- key: action.id,
355
- group,
356
- action
357
- }))
358
- )
359
- );
360
- } finally {
361
- _effect.f();
362
- }
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
+ );
363
353
  };
364
- var ToolbarMenu = ({ __menuScope, classNames, textBlockWidth: wrapContents, ...props }) => {
365
- var _effect = _useSignals4();
366
- try {
367
- const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
368
- const { attendableId } = useMenu("ToolbarMenu", __menuScope);
369
- const { hasAttention } = useAttention(attendableId);
370
- const InnerRoot = wrapContents ? "div" : Fragment;
371
- const innerRootProps = wrapContents ? {
372
- role: "none",
373
- className: mx2(textBlockWidth, toolbarLayout)
374
- } : {};
375
- return /* @__PURE__ */ React4.createElement(NaturalToolbar.Root, {
376
- ...props,
377
- layoutManaged: wrapContents,
378
- classNames: [
379
- attendableId && !hasAttention && "*:opacity-20 !bg-transparent",
380
- classNames
381
- ]
382
- }, /* @__PURE__ */ React4.createElement(InnerRoot, innerRootProps, items?.map((item, i) => isSeparator(item) ? /* @__PURE__ */ React4.createElement(NaturalToolbar.Separator, {
383
- key: i,
354
+ var ToolbarMenu = ({ __menuScope, classNames, textBlockWidth: textBlockWidthProp, ...props }) => {
355
+ const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
356
+ const { attendableId, alwaysActive } = useMenu("ToolbarMenu", __menuScope);
357
+ 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, {
364
+ ...props,
365
+ classNames: [
366
+ attendableId,
367
+ classNames
368
+ ],
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,
373
+ item
374
+ }))));
375
+ };
376
+ var ToolbarMenuItem = ({ item }) => {
377
+ if (isSeparator(item)) {
378
+ return /* @__PURE__ */ React4.createElement(NaturalToolbar.Separator, {
384
379
  variant: item.properties.variant
385
- }) : isMenuGroup(item) ? item.properties.variant === "dropdownMenu" ? (
386
- // TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
387
- /* @__PURE__ */ React4.createElement(DropdownMenuToolbarItem, {
388
- key: item.id,
380
+ });
381
+ }
382
+ if (isMenuGroup(item)) {
383
+ if (item.properties.variant === "dropdownMenu") {
384
+ return /* @__PURE__ */ React4.createElement(DropdownMenuToolbarItem, {
389
385
  group: item
390
- })
391
- ) : (
392
- // TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
393
- /* @__PURE__ */ React4.createElement(ToggleGroupToolbarItem, {
394
- key: item.id,
386
+ });
387
+ } else {
388
+ return /* @__PURE__ */ React4.createElement(ToggleGroupToolbarItem, {
395
389
  group: item
396
- })
397
- ) : (
398
- // TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
399
- /* @__PURE__ */ React4.createElement(ActionToolbarItem, {
400
- key: item.id,
401
- action: item
402
- })
403
- ))));
404
- } finally {
405
- _effect.f();
390
+ });
391
+ }
406
392
  }
393
+ return /* @__PURE__ */ React4.createElement(ActionToolbarItem, {
394
+ action: item
395
+ });
407
396
  };
408
397
 
409
- // packages/ui/react-ui-menu/src/hooks/useMenuActions.ts
410
- import { RegistryContext, useRxValue } from "@effect-rx/rx-react";
411
- import { useCallback as useCallback3, useContext, useEffect, useState } from "react";
412
- import { Graph, ROOT_ID } from "@dxos/app-graph";
413
- var edgesArrayToRecord = (edges) => {
414
- return Object.fromEntries(Object.entries(edges.reduce((acc, { source, target }) => {
415
- if (!acc[source]) {
416
- acc[source] = {
417
- inbound: [],
418
- outbound: []
419
- };
420
- }
421
- if (!acc[target]) {
422
- acc[target] = {
423
- inbound: [],
424
- outbound: []
425
- };
426
- }
427
- const sourceEdges = acc[source];
428
- if (!sourceEdges.outbound.includes(target)) {
429
- sourceEdges.outbound.push(target);
430
- }
431
- const targetEdges = acc[target];
432
- if (!targetEdges.inbound.includes(source)) {
433
- targetEdges.inbound.push(source);
434
- }
435
- return acc;
436
- }, {})));
437
- };
398
+ // src/hooks/useMenuActions.ts
399
+ import { RegistryContext, useAtomValue } from "@effect-atom/atom-react";
400
+ import { useCallback as useCallback3, useContext, useRef } from "react";
401
+ import { Graph, Node as Node3 } from "@dxos/app-graph";
438
402
  var useMenuActions = (props) => {
439
403
  const registry = useContext(RegistryContext);
440
- const menuGraphProps = useRxValue(props);
441
- const [graph] = useState(new Graph({
442
- registry,
443
- nodes: menuGraphProps.nodes,
444
- edges: edgesArrayToRecord(menuGraphProps.edges)
445
- }));
446
- useEffect(() => {
447
- graph.addNodes(menuGraphProps.nodes);
448
- graph.addEdges(menuGraphProps.edges);
449
- }, [
450
- menuGraphProps
451
- ]);
404
+ const menuGraphProps = useAtomValue(props);
405
+ const graphRef = useRef(null);
406
+ if (!graphRef.current || graphRef.current.props !== menuGraphProps) {
407
+ const newGraph = Graph.make({
408
+ registry
409
+ });
410
+ 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;
452
417
  const useGroupItems = useCallback3((sourceNode) => {
453
- const items = useRxValue(graph.connections(sourceNode?.id || ROOT_ID));
418
+ const items = useAtomValue(graph.connections(sourceNode?.id || Node3.RootId));
454
419
  return items;
455
420
  }, [
456
421
  graph
@@ -459,8 +424,69 @@ var useMenuActions = (props) => {
459
424
  useGroupItems
460
425
  };
461
426
  };
427
+
428
+ // src/builder.ts
429
+ 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
+ });
437
+ var MenuBuilderImpl = class _MenuBuilderImpl {
438
+ _data;
439
+ _rootId;
440
+ constructor(_data, _rootId) {
441
+ this._data = _data;
442
+ this._rootId = _rootId;
443
+ }
444
+ root(props) {
445
+ invariant(this._rootId === "root", "Root group can only be at the top level", {
446
+ F: __dxlog_file,
447
+ L: 36,
448
+ S: this,
449
+ A: [
450
+ "this._rootId === 'root'",
451
+ "'Root group can only be at the top level'"
452
+ ]
453
+ });
454
+ 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,
457
+ S: this,
458
+ A: [
459
+ "this._data.nodes.find((node) => node.id === 'root') === undefined",
460
+ "'Root group can only be created once'"
461
+ ]
462
+ });
463
+ this._data.nodes.push(createMenuItemGroup("root", props));
464
+ return this;
465
+ }
466
+ action(id, props, invoke) {
467
+ this._data.nodes.push(createMenuAction(id, invoke, props));
468
+ this._data.edges.push({
469
+ source: this._rootId,
470
+ target: id
471
+ });
472
+ return this;
473
+ }
474
+ group(id, props, cb) {
475
+ this._data.nodes.push(createMenuItemGroup(id, props));
476
+ this._data.edges.push({
477
+ source: this._rootId,
478
+ target: id
479
+ });
480
+ cb(new _MenuBuilderImpl(this._data, id));
481
+ return this;
482
+ }
483
+ build() {
484
+ return this._data;
485
+ }
486
+ };
462
487
  export {
463
488
  DropdownMenu,
489
+ MenuBuilder,
464
490
  MenuProvider,
465
491
  MenuSeparatorType,
466
492
  ToolbarMenu,
@@ -472,7 +498,6 @@ export {
472
498
  isMenuGroup,
473
499
  isSeparator,
474
500
  menuContextDefaults,
475
- rxFromSignal,
476
501
  useMenu,
477
502
  useMenuActions,
478
503
  useMenuItems