@dxos/react-ui-menu 0.8.4-main.b97322e → 0.8.4-main.bc674ce

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 +309 -313
  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 +309 -313
  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 +11 -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 +16 -4
  37. package/src/components/ToolbarMenu.stories.tsx +81 -0
  38. package/src/components/ToolbarMenu.tsx +68 -34
  39. package/src/hooks/useMenuActions.ts +32 -18
  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 +12 -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,26 @@ 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, onAction }) => {
116
+ const { scope } = useMenuScope(void 0);
117
+ return /* @__PURE__ */ React2.createElement(MenuContextProvider, {
118
+ useGroupItems,
119
+ iconSize,
120
+ attendableId,
121
+ onAction,
122
+ scope
123
+ }, children);
129
124
  };
130
125
  var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __menuScope) => {
131
126
  const { useGroupItems } = useMenu(consumerName, __menuScope);
@@ -138,66 +133,65 @@ var useMenuItems = (group, propsItems, consumerName = "useMenuItemConsumer", __m
138
133
 
139
134
  // src/components/DropdownMenu.tsx
140
135
  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
- }
136
+ const action = item;
137
+ const handleClick = useCallback((event) => onClick(action, event), [
138
+ action,
139
+ onClick
140
+ ]);
141
+ const { iconSize } = useMenu("DropdownMenuItem", __menuScope);
142
+ return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Item, {
143
+ onClick: handleClick,
144
+ classNames: "gap-2",
145
+ disabled: action.properties?.disabled,
146
+ ...action.properties?.testId && {
147
+ "data-testid": action.properties.testId
148
+ }
149
+ }, action.properties?.icon && /* @__PURE__ */ React3.createElement(Icon, {
150
+ icon: action.properties.icon,
151
+ size: iconSize
152
+ }), /* @__PURE__ */ React3.createElement(ActionLabel, {
153
+ action
154
+ }));
165
155
  };
166
156
  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?.({
157
+ const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
158
+ prop: open,
159
+ defaultProp: defaultOpen,
160
+ onChange: onOpenChange
161
+ });
162
+ const { onAction } = useMenu("DropdownMenuRoot", __menuScope);
163
+ const handleActionClick = useCallback((action, event) => {
164
+ if (action.properties?.disabled) {
165
+ return;
166
+ }
167
+ event.stopPropagation();
168
+ setOptionsMenuOpen(false);
169
+ if (onAction) {
170
+ onAction(action, {
181
171
  parent: group,
182
172
  caller
183
173
  });
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
- }
174
+ } else {
175
+ void executeMenuAction(action, {
176
+ parent: group,
177
+ caller
178
+ });
179
+ }
180
+ }, [
181
+ group,
182
+ caller,
183
+ onAction
184
+ ]);
185
+ const items = useMenuItems(group, propsItems);
186
+ return /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Root, {
187
+ open: optionsMenuOpen,
188
+ onOpenChange: setOptionsMenuOpen,
189
+ ...naturalProps
190
+ }, 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, {
191
+ key: item.id,
192
+ item,
193
+ onClick: handleActionClick
194
+ }))), /* @__PURE__ */ React3.createElement(NaturalDropdownMenu.Arrow, null))));
201
195
  };
202
196
  var DropdownMenu = {
203
197
  Root: DropdownMenuRoot,
@@ -206,212 +200,205 @@ var DropdownMenu = {
206
200
  };
207
201
 
208
202
  // src/components/ToolbarMenu.tsx
209
- import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
210
203
  import React4, { Fragment, useCallback as useCallback2 } from "react";
211
204
  import { Icon as Icon2, Toolbar as NaturalToolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
212
205
  import { useAttention } from "@dxos/react-ui-attention";
213
- import { mx as mx2, textBlockWidth, toolbarLayout } from "@dxos/react-ui-theme";
206
+ import { mx as mx2, textBlockWidth, toolbarInactive, toolbarLayout } from "@dxos/ui-theme";
214
207
 
215
208
  // src/types.ts
216
- import { ACTION_GROUP_TYPE as ACTION_GROUP_TYPE2 } from "@dxos/app-graph";
209
+ import { Node as Node2 } from "@dxos/app-graph";
217
210
  var MenuSeparatorType = "@dxos/react-ui-toolbar/separator";
218
211
  var isSeparator = (node) => node.type === MenuSeparatorType;
219
- var isMenuGroup = (node) => node.type === ACTION_GROUP_TYPE2;
212
+ var isMenuGroup = (node) => node.type === Node2.ActionGroupType;
220
213
 
221
214
  // src/components/ToolbarMenu.tsx
222
215
  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?.(), [
216
+ const { iconSize, onAction } = useMenu("ActionToolbarItem", __menuScope);
217
+ const { t } = useTranslation2(translationKey);
218
+ const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
219
+ const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
220
+ const rootProps = icon ? {
221
+ icon,
222
+ size: iconSize,
223
+ iconOnly,
224
+ label: actionLabel(action, t)
225
+ } : {
226
+ children: /* @__PURE__ */ React4.createElement(ActionLabel, {
228
227
  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
- }
228
+ })
229
+ };
230
+ const handleClick = useCallback2(() => {
231
+ if (onAction) {
232
+ onAction(action, {});
233
+ } else {
234
+ void executeMenuAction(action);
235
+ }
236
+ }, [
237
+ action,
238
+ onAction
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
+ });
256
251
  };
257
252
  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
- }
253
+ const { iconOnly, disabled, testId } = group.properties;
254
+ const { t } = useTranslation2(translationKey);
255
+ const { iconSize } = useMenu("DropdownMenuToolbarItem", __menuScope);
256
+ const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
257
+ const activeItem = items?.find((item) => !!item.properties.checked);
258
+ const icon = group.properties.applyActive && // TODO(thure): Handle other menu item types.
259
+ activeItem?.properties.icon || group.properties.icon;
260
+ const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
261
+ const labelAction = group.properties.applyActive && activeItem ? activeItem : group;
262
+ const rootProps = icon ? {
263
+ icon,
264
+ size: iconSize,
265
+ iconOnly,
266
+ label: actionLabel(labelAction, t),
267
+ caretDown: true
268
+ } : {
269
+ children: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(ActionLabel, {
270
+ action: labelAction
271
+ }), /* @__PURE__ */ React4.createElement(Icon2, {
272
+ size: 3,
273
+ icon: "ph--caret-down--bold",
274
+ classNames: "mis-1"
275
+ }))
276
+ };
277
+ return /* @__PURE__ */ React4.createElement(DropdownMenu.Root, {
278
+ group,
279
+ items
280
+ }, /* @__PURE__ */ React4.createElement(DropdownMenu.Trigger, {
281
+ asChild: true
282
+ }, /* @__PURE__ */ React4.createElement(Root, {
283
+ variant: "ghost",
284
+ disabled,
285
+ ...rootProps,
286
+ ...testId && {
287
+ "data-testid": testId
288
+ }
289
+ })));
300
290
  };
301
291
  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
- }
292
+ const { iconSize, onAction } = useMenu("ToggleGroupItem", __menuScope);
293
+ const { t } = useTranslation2(translationKey);
294
+ const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
295
+ const handleClick = useCallback2(() => {
296
+ if (onAction) {
297
+ onAction(action, {
298
+ parent: group
299
+ });
300
+ } else {
301
+ void executeMenuAction(action, {
302
+ parent: group
303
+ });
304
+ }
305
+ }, [
306
+ action,
307
+ group,
308
+ onAction
309
+ ]);
310
+ const Root = icon ? NaturalToolbar.ToggleGroupIconItem : NaturalToolbar.ToggleGroupItem;
311
+ const rootProps = icon ? {
312
+ icon,
313
+ size: iconSize,
314
+ iconOnly,
315
+ label: actionLabel(action, t)
316
+ } : {
317
+ children: /* @__PURE__ */ React4.createElement(ActionLabel, {
318
+ action
319
+ })
320
+ };
321
+ return hidden ? null : /* @__PURE__ */ React4.createElement(Root, {
322
+ key: action.id,
323
+ value: action.id,
324
+ disabled,
325
+ onClick: handleClick,
326
+ variant: "ghost",
327
+ ...testId && {
328
+ "data-testid": testId
329
+ },
330
+ ...rootProps,
331
+ classNames
332
+ });
339
333
  };
340
334
  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
- }
335
+ const items = useMenuItems(group, propsItems, "ToggleGroupToolbarItem", __menuScope);
336
+ const { selectCardinality, value } = group.properties;
337
+ return (
338
+ // TODO(thure): Fix.
339
+ // @ts-ignore
340
+ /* @__PURE__ */ React4.createElement(
341
+ NaturalToolbar.ToggleGroup,
342
+ {
343
+ type: selectCardinality,
344
+ value
345
+ },
346
+ // TODO(thure): Handle other menu item types.
347
+ items.map((action) => /* @__PURE__ */ React4.createElement(ToggleGroupItem, {
348
+ key: action.id,
349
+ group,
350
+ action
351
+ }))
352
+ )
353
+ );
365
354
  };
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,
355
+ var ToolbarMenu = ({ __menuScope, classNames, textBlockWidth: textBlockWidthProp, ...props }) => {
356
+ const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
357
+ const { attendableId } = useMenu("ToolbarMenu", __menuScope);
358
+ const { hasAttention } = useAttention(attendableId);
359
+ const InnerRoot = textBlockWidthProp ? "div" : Fragment;
360
+ const innerRootProps = textBlockWidthProp ? {
361
+ role: "none",
362
+ className: mx2(textBlockWidth, toolbarLayout, "bs-full")
363
+ } : {};
364
+ return /* @__PURE__ */ React4.createElement(NaturalToolbar.Root, {
365
+ ...props,
366
+ classNames: [
367
+ attendableId && !hasAttention && toolbarInactive,
368
+ classNames
369
+ ],
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, {
386
379
  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,
380
+ });
381
+ }
382
+ if (isMenuGroup(item)) {
383
+ if (item.properties.variant === "dropdownMenu") {
384
+ return /* @__PURE__ */ React4.createElement(DropdownMenuToolbarItem, {
391
385
  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,
386
+ });
387
+ } else {
388
+ return /* @__PURE__ */ React4.createElement(ToggleGroupToolbarItem, {
397
389
  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();
390
+ });
391
+ }
408
392
  }
393
+ return /* @__PURE__ */ React4.createElement(ActionToolbarItem, {
394
+ action: item
395
+ });
409
396
  };
410
397
 
411
398
  // 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";
399
+ import { RegistryContext, useAtomValue } from "@effect-atom/atom-react";
400
+ import { useCallback as useCallback3, useContext, useEffect, useRef } from "react";
401
+ import { Graph, Node as Node3 } from "@dxos/app-graph";
415
402
  var edgesArrayToRecord = (edges) => {
416
403
  return Object.fromEntries(Object.entries(edges.reduce((acc, { source, target }) => {
417
404
  if (!acc[source]) {
@@ -439,20 +426,28 @@ var edgesArrayToRecord = (edges) => {
439
426
  };
440
427
  var useMenuActions = (props) => {
441
428
  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
- }));
429
+ const menuGraphProps = useAtomValue(props);
430
+ const graphRef = useRef(null);
431
+ if (!graphRef.current) {
432
+ graphRef.current = Graph.make({
433
+ registry,
434
+ nodes: menuGraphProps.nodes,
435
+ edges: edgesArrayToRecord(menuGraphProps.edges)
436
+ });
437
+ }
438
+ const graph = graphRef.current;
439
+ const prevPropsRef = useRef(menuGraphProps);
448
440
  useEffect(() => {
449
- graph.addNodes(menuGraphProps.nodes);
450
- graph.addEdges(menuGraphProps.edges);
441
+ if (prevPropsRef.current !== menuGraphProps) {
442
+ prevPropsRef.current = menuGraphProps;
443
+ graph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
444
+ }
451
445
  }, [
446
+ graph,
452
447
  menuGraphProps
453
448
  ]);
454
449
  const useGroupItems = useCallback3((sourceNode) => {
455
- const items = useRxValue(graph.connections(sourceNode?.id || ROOT_ID));
450
+ const items = useAtomValue(graph.connections(sourceNode?.id || Node3.RootId));
456
451
  return items;
457
452
  }, [
458
453
  graph
@@ -472,22 +467,16 @@ var MenuBuilder = Object.freeze({
472
467
  }, "root")
473
468
  });
474
469
  var MenuBuilderImpl = class _MenuBuilderImpl {
470
+ _data;
471
+ _rootId;
475
472
  constructor(_data, _rootId) {
476
473
  this._data = _data;
477
474
  this._rootId = _rootId;
478
475
  }
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
476
  root(props) {
488
477
  invariant(this._rootId === "root", "Root group can only be at the top level", {
489
478
  F: __dxlog_file,
490
- L: 42,
479
+ L: 36,
491
480
  S: this,
492
481
  A: [
493
482
  "this._rootId === 'root'",
@@ -496,7 +485,7 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
496
485
  });
497
486
  invariant(this._data.nodes.find((node) => node.id === "root") === void 0, "Root group can only be created once", {
498
487
  F: __dxlog_file,
499
- L: 43,
488
+ L: 37,
500
489
  S: this,
501
490
  A: [
502
491
  "this._data.nodes.find((node) => node.id === 'root') === undefined",
@@ -506,8 +495,16 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
506
495
  this._data.nodes.push(createMenuItemGroup("root", props));
507
496
  return this;
508
497
  }
509
- group(id, properties, cb) {
510
- this._data.nodes.push(createMenuItemGroup(id, properties));
498
+ action(id, props, invoke) {
499
+ this._data.nodes.push(createMenuAction(id, invoke, props));
500
+ this._data.edges.push({
501
+ source: this._rootId,
502
+ target: id
503
+ });
504
+ return this;
505
+ }
506
+ group(id, props, cb) {
507
+ this._data.nodes.push(createMenuItemGroup(id, props));
511
508
  this._data.edges.push({
512
509
  source: this._rootId,
513
510
  target: id
@@ -533,7 +530,6 @@ export {
533
530
  isMenuGroup,
534
531
  isSeparator,
535
532
  menuContextDefaults,
536
- rxFromSignal,
537
533
  useMenu,
538
534
  useMenuActions,
539
535
  useMenuItems