@dxos/react-ui-menu 0.8.4-main.3f58842 → 0.8.4-main.52d7546f51

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