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