@dxos/plugin-navtree 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6

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 (187) hide show
  1. package/dist/lib/browser/index.mjs +137 -72
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  5. package/dist/types/src/capabilities/app-graph-builder.d.ts +6 -0
  6. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  7. package/dist/types/src/capabilities/index.d.ts +7 -5
  8. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  9. package/dist/types/src/capabilities/keyboard.d.ts +5 -0
  10. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -0
  11. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  12. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  13. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  14. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  15. package/dist/types/src/capabilities/state.d.ts +6 -0
  16. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  17. package/dist/types/src/components/NavTree/NavTree.d.ts +10 -32
  18. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  19. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +10 -14
  20. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  22. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  23. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  24. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  25. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +11 -3
  26. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  27. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
  28. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  30. package/dist/types/src/components/Sidebar/L0Menu.d.ts +4 -1
  31. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  32. package/dist/types/src/components/Sidebar/L1Panel.d.ts +3 -6
  33. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  34. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +3 -2
  35. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  36. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/index.d.ts +0 -3
  38. package/dist/types/src/components/index.d.ts.map +1 -1
  39. package/dist/types/src/components/types.d.ts +5 -13
  40. package/dist/types/src/components/types.d.ts.map +1 -1
  41. package/dist/types/src/{components/CommandsDialog/CommandsDialog.d.ts → containers/CommandsDialogContent/CommandsDialogContent.d.ts} +1 -1
  42. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -0
  43. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts +29 -0
  44. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts.map +1 -0
  45. package/dist/types/src/containers/CommandsDialogContent/index.d.ts +3 -0
  46. package/dist/types/src/containers/CommandsDialogContent/index.d.ts.map +1 -0
  47. package/dist/types/src/containers/CommandsTrigger/CommandsTrigger.d.ts.map +1 -0
  48. package/dist/types/src/containers/CommandsTrigger/index.d.ts +3 -0
  49. package/dist/types/src/containers/CommandsTrigger/index.d.ts.map +1 -0
  50. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts +9 -0
  51. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -0
  52. package/dist/types/src/containers/NavTreeContainer/index.d.ts +3 -0
  53. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -0
  54. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -0
  55. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts +3 -0
  56. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts.map +1 -0
  57. package/dist/types/src/containers/index.d.ts +7 -0
  58. package/dist/types/src/containers/index.d.ts.map +1 -0
  59. package/dist/types/src/experimental/Tree.stories.d.ts +18 -4
  60. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  61. package/dist/types/src/hooks/index.d.ts +5 -0
  62. package/dist/types/src/hooks/index.d.ts.map +1 -0
  63. package/dist/types/src/hooks/useActions.d.ts +5 -0
  64. package/dist/types/src/hooks/useActions.d.ts.map +1 -0
  65. package/dist/types/src/hooks/useLoadDescendents.d.ts +6 -0
  66. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -0
  67. package/dist/types/src/hooks/useNavTreeModel.d.ts +7 -0
  68. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -0
  69. package/dist/types/src/{hooks.d.ts → hooks/useNavTreeState.d.ts} +2 -14
  70. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -0
  71. package/dist/types/src/meta.d.ts.map +1 -1
  72. package/dist/types/src/operations/expose.d.ts +5 -0
  73. package/dist/types/src/operations/expose.d.ts.map +1 -0
  74. package/dist/types/src/operations/index.d.ts +3 -0
  75. package/dist/types/src/operations/index.d.ts.map +1 -0
  76. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  77. package/dist/types/src/testing/generator.d.ts +3 -3
  78. package/dist/types/src/testing/generator.d.ts.map +1 -1
  79. package/dist/types/src/translations.d.ts +9 -10
  80. package/dist/types/src/translations.d.ts.map +1 -1
  81. package/dist/types/src/types/events.d.ts.map +1 -1
  82. package/dist/types/src/util.d.ts +6 -2
  83. package/dist/types/src/util.d.ts.map +1 -1
  84. package/dist/types/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +54 -43
  86. package/src/NavTreePlugin.tsx +26 -27
  87. package/src/capabilities/{app-graph-builder/app-graph-builder.ts → app-graph-builder.ts} +6 -5
  88. package/src/capabilities/index.ts +11 -5
  89. package/src/capabilities/{keyboard/keyboard.ts → keyboard.ts} +7 -6
  90. package/src/capabilities/operation-handler.ts +16 -0
  91. package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +13 -14
  92. package/src/capabilities/{state/state.ts → state.ts} +43 -15
  93. package/src/components/NavTree/NavTree.stories.tsx +26 -24
  94. package/src/components/NavTree/NavTree.tsx +63 -22
  95. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  96. package/src/components/NavTreeContext/index.ts +5 -0
  97. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +1 -3
  98. package/src/components/NavTreeItem/NavTreeItemAction.tsx +56 -39
  99. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +42 -16
  100. package/src/components/Sidebar/L0Menu.tsx +99 -109
  101. package/src/components/Sidebar/L1Panel.tsx +252 -94
  102. package/src/components/Sidebar/L1Tabs.tsx +13 -3
  103. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +2 -3
  104. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +1 -1
  105. package/src/components/index.ts +0 -3
  106. package/src/components/types.ts +12 -17
  107. package/src/containers/CommandsDialogContent/CommandsDialogContent.stories.tsx +46 -0
  108. package/src/{components/CommandsDialog/CommandsDialog.tsx → containers/CommandsDialogContent/CommandsDialogContent.tsx} +23 -21
  109. package/src/containers/CommandsDialogContent/index.ts +7 -0
  110. package/src/containers/CommandsTrigger/CommandsTrigger.tsx +28 -0
  111. package/src/containers/CommandsTrigger/index.ts +7 -0
  112. package/src/{components → containers/NavTreeContainer}/NavTreeContainer.tsx +76 -142
  113. package/src/containers/NavTreeContainer/index.ts +6 -0
  114. package/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.tsx +2 -2
  115. package/src/containers/NavTreeDocumentTitle/index.ts +7 -0
  116. package/src/containers/index.ts +12 -0
  117. package/src/experimental/Tree.stories.tsx +54 -57
  118. package/src/experimental/Tree.tsx +3 -3
  119. package/src/hooks/index.ts +8 -0
  120. package/src/hooks/useActions.ts +34 -0
  121. package/src/hooks/useLoadDescendents.ts +23 -0
  122. package/src/hooks/useNavTreeModel.ts +118 -0
  123. package/src/{hooks.ts → hooks/useNavTreeState.ts} +3 -34
  124. package/src/meta.ts +2 -2
  125. package/src/operations/expose.ts +42 -0
  126. package/src/operations/index.ts +7 -0
  127. package/src/testing/app-graph-builder.ts +34 -30
  128. package/src/testing/generator.ts +4 -4
  129. package/src/translations.ts +9 -10
  130. package/src/types/capabilities.ts +2 -2
  131. package/src/types/events.ts +4 -3
  132. package/src/util.ts +19 -5
  133. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs +0 -43
  134. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs.map +0 -7
  135. package/dist/lib/browser/chunk-6YPQ625I.mjs +0 -1293
  136. package/dist/lib/browser/chunk-6YPQ625I.mjs.map +0 -7
  137. package/dist/lib/browser/chunk-AGQDZBPL.mjs +0 -23
  138. package/dist/lib/browser/chunk-AGQDZBPL.mjs.map +0 -7
  139. package/dist/lib/browser/chunk-YKTYPINZ.mjs +0 -19
  140. package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +0 -7
  141. package/dist/lib/browser/keyboard-32EH3N4K.mjs +0 -52
  142. package/dist/lib/browser/keyboard-32EH3N4K.mjs.map +0 -7
  143. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs +0 -52
  144. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs.map +0 -7
  145. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs +0 -58
  146. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs.map +0 -7
  147. package/dist/lib/browser/state-EVYQOUI3.mjs +0 -120
  148. package/dist/lib/browser/state-EVYQOUI3.mjs.map +0 -7
  149. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +0 -6
  150. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
  151. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
  152. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
  153. package/dist/types/src/capabilities/keyboard/index.d.ts +0 -3
  154. package/dist/types/src/capabilities/keyboard/index.d.ts.map +0 -1
  155. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +0 -5
  156. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +0 -1
  157. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  158. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  159. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  160. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  161. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  162. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  163. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
  164. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  165. package/dist/types/src/capabilities/state/index.d.ts +0 -3
  166. package/dist/types/src/capabilities/state/index.d.ts.map +0 -1
  167. package/dist/types/src/capabilities/state/state.d.ts +0 -6
  168. package/dist/types/src/capabilities/state/state.d.ts.map +0 -1
  169. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  170. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  171. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  172. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  173. package/dist/types/src/components/NavTreeContainer.d.ts +0 -16
  174. package/dist/types/src/components/NavTreeContainer.d.ts.map +0 -1
  175. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  176. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +0 -1
  177. package/dist/types/src/hooks.d.ts.map +0 -1
  178. package/src/capabilities/app-graph-builder/index.ts +0 -7
  179. package/src/capabilities/keyboard/index.ts +0 -7
  180. package/src/capabilities/operation-resolver/index.ts +0 -7
  181. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -41
  182. package/src/capabilities/react-surface/index.ts +0 -7
  183. package/src/capabilities/state/index.ts +0 -7
  184. package/src/components/CommandsDialog/CommandsTrigger.tsx +0 -26
  185. package/src/components/CommandsDialog/index.ts +0 -6
  186. /package/dist/types/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.d.ts +0 -0
  187. /package/dist/types/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.d.ts +0 -0
@@ -4,44 +4,33 @@
4
4
 
5
5
  import React, { forwardRef, useMemo } from 'react';
6
6
 
7
- import { type TreeProps } from '@dxos/react-ui-list';
7
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
8
+ import { Node, useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
8
9
  import { Tabs } from '@dxos/react-ui-tabs';
10
+ import { byPosition } from '@dxos/util';
9
11
 
10
- import { useLoadDescendents } from '../../hooks';
11
- import { type NavTreeItemGraphNode } from '../../types';
12
+ import { useLoadDescendents } from '#hooks';
12
13
  import { useNavTreeContext } from '../NavTreeContext';
13
14
  import { L0Menu, L1Tabs, type L1TabsProps } from '../Sidebar';
14
15
 
15
16
  export const NAV_TREE_ITEM = 'NavTreeItem';
16
17
 
17
- export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<L1TabsProps, 'open'>;
18
+ export type NavTreeProps = { id: string; root?: Node.Node; tab: string } & Pick<L1TabsProps, 'open'>;
18
19
 
19
- export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, ...props }, forwardedRef) => {
20
- const { tab, useItems, onBack } = useNavTreeContext();
21
- const topLevelActions = useItems(root, { disposition: 'menu', sort: true });
22
- const topLevelWorkspaces = useItems(root, { disposition: 'workspace' });
23
- const l0Items = topLevelWorkspaces;
24
- const pinnedItems = useItems(root, { disposition: 'pin-end', sort: true });
25
- const userAccountItem = useItems(root, { disposition: 'user-account' })[0];
26
- const topLevelItems = useMemo(
27
- () => [
28
- // prettier-ignore
29
- ...l0Items,
30
- ...pinnedItems,
31
- ...(userAccountItem ? [userAccountItem] : []),
32
- ],
33
- [l0Items, pinnedItems, userAccountItem],
34
- );
20
+ // TODO(wittjosiah): Refactor to Radix-style.
21
+ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, tab, ...props }, forwardedRef) => {
22
+ const { onBack, onItemHover } = useNavTreeContext();
23
+ const { topLevelActions, l0Items, pinnedItems, userAccountItem, topLevelItems } = useTopLevelNavItems(root);
35
24
 
36
25
  useLoadDescendents(root);
37
26
  const path = useMemo(() => [id], [id]);
38
27
 
39
28
  return (
40
29
  // TODO(thure): `Tabs.Root` forces all items that should be able to receive focus to use `Tabs.Tab(Primitive)` since
41
- // it uses RovingFocus and doesn’t support moving focus to an item that is not a tab. Assess whether this situation
30
+ // it uses RovingFocus and doesn't support moving focus to an item that is not a tab. Assess whether this situation
42
31
  // should change including whether it should motivate a change in the design/taxonomy, or if this means this should
43
32
  // not use `react-ui-tabs` at all.
44
- <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative' ref={forwardedRef}>
33
+ <Tabs.Root value={tab} orientation='vertical' classNames='relative' ref={forwardedRef}>
45
34
  <L0Menu
46
35
  menuActions={topLevelActions}
47
36
  topLevelItems={l0Items}
@@ -49,8 +38,60 @@ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, ...
49
38
  userAccountItem={userAccountItem}
50
39
  path={path}
51
40
  parent={root}
41
+ onItemHover={onItemHover}
52
42
  />
53
43
  <L1Tabs topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
54
44
  </Tabs.Root>
55
45
  );
56
46
  });
47
+
48
+ /**
49
+ * Partitions root children into workspaces, pinned items, user-account, and top-level actions.
50
+ */
51
+ const useTopLevelNavItems = (root?: Node.Node) => {
52
+ const { graph } = useAppGraph();
53
+ const rootId = root?.id ?? Node.RootId;
54
+ const rootOutboundItems = useConnections(graph, rootId, 'child');
55
+ const rootActions = useGraphActions(graph, rootId);
56
+
57
+ const { topLevelActions, l0Items, pinnedItems, userAccountItem } = useMemo(() => {
58
+ const topLevelWorkspaces: Node.Node[] = [];
59
+ const outboundPinnedItems: Node.Node[] = [];
60
+ let userAccountItem: Node.Node | undefined;
61
+ for (const node of rootOutboundItems) {
62
+ switch (node.properties.disposition) {
63
+ case 'workspace':
64
+ topLevelWorkspaces.push(node);
65
+ break;
66
+ case 'pin-end':
67
+ outboundPinnedItems.push(node);
68
+ break;
69
+ case 'user-account':
70
+ userAccountItem ??= node;
71
+ break;
72
+ }
73
+ }
74
+
75
+ const topLevelActions = rootActions
76
+ .filter((action) => action.properties.disposition === 'menu')
77
+ .toSorted((a, b) => byPosition(a.properties, b.properties));
78
+ const pinnedItems = [
79
+ ...outboundPinnedItems,
80
+ ...rootActions.filter((action) => action.properties.disposition === 'pin-end'),
81
+ ].toSorted((a, b) => byPosition(a.properties, b.properties));
82
+
83
+ return {
84
+ topLevelActions,
85
+ l0Items: topLevelWorkspaces,
86
+ pinnedItems,
87
+ userAccountItem,
88
+ };
89
+ }, [rootActions, rootOutboundItems]);
90
+
91
+ const topLevelItems = useMemo(
92
+ () => [...l0Items, ...pinnedItems, ...(userAccountItem ? [userAccountItem] : [])],
93
+ [l0Items, pinnedItems, userAccountItem],
94
+ );
95
+
96
+ return { topLevelActions, l0Items, pinnedItems, userAccountItem, topLevelItems };
97
+ };
@@ -6,7 +6,7 @@ import { createContext, useContext } from 'react';
6
6
 
7
7
  import { raise } from '@dxos/debug';
8
8
 
9
- import { type NavTreeContextValue } from './types';
9
+ import { type NavTreeContextValue } from '../types';
10
10
 
11
11
  export const NavTreeContext = createContext<NavTreeContextValue | null>(null);
12
12
 
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './NavTreeContext';
@@ -10,7 +10,6 @@ import { withPluginManager } from '@dxos/app-framework/testing';
10
10
  import { type Node } from '@dxos/app-graph';
11
11
  import { corePlugins } from '@dxos/plugin-testing';
12
12
  import { faker } from '@dxos/random';
13
- import { withTheme } from '@dxos/react-ui/testing';
14
13
 
15
14
  import { NavTreeItemAction, type NavTreeItemActionMenuProps } from './NavTreeItemAction';
16
15
 
@@ -43,7 +42,7 @@ const menuActions = faker.helpers.multiple(
43
42
  );
44
43
 
45
44
  const meta = {
46
- title: 'plugins/plugin-navtree/NavTreeItemAction',
45
+ title: 'plugins/plugin-navtree/components/NavTreeItemAction',
47
46
  component: NavTreeItemAction,
48
47
  args: {
49
48
  icon: 'ph--list--regular',
@@ -52,7 +51,6 @@ const meta = {
52
51
  label: 'Select action',
53
52
  } satisfies Partial<NavTreeItemActionMenuProps>,
54
53
  decorators: [
55
- withTheme,
56
54
  withPluginManager({
57
55
  plugins: [...corePlugins(), RuntimePlugin()],
58
56
  }),
@@ -2,23 +2,16 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback } from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
8
  import { useActionRunner } from '@dxos/plugin-graph';
9
9
  import { IconButton, toLocalizedString, useDensityContext, useTranslation } from '@dxos/react-ui';
10
- import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
11
- import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
10
+ import { Menu, type MenuItem } from '@dxos/react-ui-menu';
11
+ import { composable, composableProps, hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
12
12
 
13
- import { meta } from '../../meta';
14
- import { type ActionProperties } from '../../types';
15
-
16
- export type NavTreeItemActionMenuProps = ActionProperties & {
17
- parent: Node.Node;
18
- caller?: string;
19
- monolithic?: boolean;
20
- menuActions?: Node.Action[];
21
- };
13
+ import { meta } from '#meta';
14
+ import { type ActionProperties } from '#types';
22
15
 
23
16
  const fallbackIcon = 'ph--placeholder--regular';
24
17
 
@@ -26,47 +19,64 @@ const fineActionButtonProps = {
26
19
  size: 4 as const,
27
20
  density: 'fine' as const,
28
21
  };
22
+
29
23
  const coarseActionButtonProps = {
30
24
  size: 5 as const,
31
25
  density: 'coarse' as const,
32
26
  };
33
27
 
34
- export const NavTreeItemActionDropdownMenu = ({
35
- parent,
36
- label,
37
- icon,
38
- testId,
39
- menuActions,
40
- caller,
41
- }: NavTreeItemActionMenuProps) => {
42
- const { t } = useTranslation(meta.id);
43
- const density = useDensityContext();
44
- const runAction = useActionRunner();
28
+ export type NavTreeItemActionMenuProps = ActionProperties & {
29
+ parent: Node.Node;
30
+ path?: string[];
31
+ caller?: string;
32
+ monolithic?: boolean;
33
+ menuActions?: Node.Action[];
34
+ };
45
35
 
46
- return (
47
- <MenuProvider onAction={runAction}>
48
- <DropdownMenu.Root group={parent} items={menuActions as MenuItem[]} caller={caller}>
49
- <DropdownMenu.Trigger asChild>
36
+ export const NavTreeItemActionDropdownMenu = composable<HTMLButtonElement, NavTreeItemActionMenuProps>(
37
+ ({ parent, path, label, icon, testId, menuActions, caller, ...props }, forwardedRef) => {
38
+ const { t } = useTranslation(meta.id);
39
+ const density = useDensityContext();
40
+ const runAction = useActionRunner();
41
+ const handleAction = useCallback(
42
+ (action: Node.Action, params: Node.InvokeProps = {}) => runAction(action, { ...params, path }),
43
+ [runAction, path],
44
+ );
45
+
46
+ return (
47
+ <Menu.Root caller={caller} onAction={handleAction}>
48
+ <Menu.Trigger asChild>
50
49
  <IconButton
51
50
  {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
52
- classNames={['shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem]}
51
+ {...composableProps(props)}
52
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
53
53
  variant='ghost'
54
54
  icon={icon ?? fallbackIcon}
55
55
  iconOnly
56
56
  label={toLocalizedString(label, t)}
57
57
  data-testid={testId}
58
+ ref={forwardedRef}
58
59
  />
59
- </DropdownMenu.Trigger>
60
- </DropdownMenu.Root>
61
- </MenuProvider>
62
- );
63
- };
60
+ </Menu.Trigger>
61
+ <Menu.Content group={parent} items={menuActions as MenuItem[]} />
62
+ </Menu.Root>
63
+ );
64
+ },
65
+ );
66
+
67
+ NavTreeItemActionDropdownMenu.displayName = 'NavTreeItemActionDropdownMenu';
64
68
 
65
69
  export const NavTreeItemMonolithicAction = (
66
- props: Node.Action & { parent: Node.Node; onAction?: (action: Node.Action) => void; baseLabel: string },
70
+ props: Node.Action & {
71
+ parent: Node.Node;
72
+ path?: string[];
73
+ onAction?: (action: Node.Action) => void;
74
+ baseLabel: string;
75
+ },
67
76
  ) => {
68
77
  const {
69
78
  parent,
79
+ path,
70
80
  properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
71
81
  baseLabel,
72
82
  } = props;
@@ -80,7 +90,7 @@ export const NavTreeItemMonolithicAction = (
80
90
  'shrink-0',
81
91
  hoverableControlItem,
82
92
  hoverableOpenControlItem,
83
- iconOnly ? 'pli-2 pointer-fine:pli-1' : 'p-2 pointer-fine:p-2 mie-1',
93
+ iconOnly ? 'px-2 pointer-fine:px-1' : 'p-2 pointer-fine:p-2 me-1',
84
94
  ]}
85
95
  icon={icon ?? fallbackIcon}
86
96
  iconOnly={iconOnly}
@@ -92,22 +102,29 @@ export const NavTreeItemMonolithicAction = (
92
102
  return;
93
103
  }
94
104
 
95
- void runAction(props, caller ? { parent, caller } : { parent });
105
+ void runAction(props, caller ? { parent, caller, path } : { parent, path });
96
106
  }}
97
107
  data-testid={testId}
98
108
  />
99
109
  );
100
110
  };
101
111
 
102
- export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
112
+ export const NavTreeItemAction = ({
113
+ monolithic,
114
+ menuActions,
115
+ menuType,
116
+ parent: node,
117
+ path,
118
+ ...props
119
+ }: NavTreeItemActionMenuProps) => {
103
120
  const { t } = useTranslation(meta.id);
104
121
 
105
122
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
106
123
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
107
124
 
108
125
  return monolithic && menuActions?.length === 1 ? (
109
- <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
126
+ <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} path={path} {...menuActions[0]} />
110
127
  ) : (
111
- <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} menuActions={menuActions} />
128
+ <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} path={path} menuActions={menuActions} />
112
129
  );
113
130
  };
@@ -2,13 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { Fragment, memo } from 'react';
5
+ import React, { Fragment, memo, useMemo } from 'react';
6
6
 
7
7
  import { Node } from '@dxos/app-graph';
8
8
  import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { useLoadDescendents } from '../../hooks';
11
- import { meta } from '../../meta';
10
+ import { useActions } from '#hooks';
11
+ import { meta } from '#meta';
12
12
  import { NAV_TREE_ITEM } from '../NavTree';
13
13
  import { useNavTreeContext } from '../NavTreeContext';
14
14
  import { type NavTreeItemColumnsProps } from '../types';
@@ -17,25 +17,49 @@ import { NavTreeItemAction } from './NavTreeItemAction';
17
17
 
18
18
  export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
19
  const { t } = useTranslation(meta.id);
20
- const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
20
+ const { renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
21
21
 
22
22
  const level = path.length - 2;
23
- const { actions: _actions, groupedActions } = useActions(item);
24
- const [primaryAction, ...secondaryActions] = _actions.toSorted((a, _b) =>
25
- a.properties?.disposition === 'list-item-primary' ? -1 : 1,
23
+ const { actions: actionsProp, groupedActions } = useActions(item);
24
+ const sortedActions = useMemo(
25
+ () =>
26
+ actionsProp.toSorted((actionA, actionB) => {
27
+ const primaryA = actionA.properties?.disposition === 'list-item-primary';
28
+ const primaryB = actionB.properties?.disposition === 'list-item-primary';
29
+ if (primaryA && !primaryB) {
30
+ return -1;
31
+ }
32
+ if (primaryB && !primaryA) {
33
+ return 1;
34
+ }
35
+ return 0;
36
+ }),
37
+ [actionsProp],
26
38
  );
39
+ const [primaryAction, ...secondaryActions] = sortedActions;
27
40
 
28
- const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
29
- .flatMap((action) => (Node.isAction(action) ? [action] : []))
30
- .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
41
+ const actions = useMemo(
42
+ () =>
43
+ (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : sortedActions)
44
+ .flatMap((action) => (Node.isAction(action) ? [action] : []))
45
+ .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition)),
46
+ [sortedActions, primaryAction],
47
+ );
31
48
 
32
- useLoadDescendents(item);
33
- useLoadDescendents(primaryAction && !Node.isAction(primaryAction) ? (primaryAction as Node.Node) : undefined);
49
+ const primaryMenuActions = useMemo(
50
+ () =>
51
+ primaryAction
52
+ ? Node.isAction(primaryAction)
53
+ ? [primaryAction]
54
+ : groupedActions[primaryAction?.id ?? '']
55
+ : undefined,
56
+ [primaryAction, groupedActions],
57
+ );
34
58
 
35
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
59
+ const ActionRoot = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}` ? Popover.Anchor : Fragment;
36
60
 
37
61
  return (
38
- <div role='none' className='contents app-no-drag'>
62
+ <div role='none' className='contents dx-app-no-drag'>
39
63
  {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
64
  <Treegrid.Cell classNames='contents'>
41
65
  <NavTreeItemAction
@@ -43,8 +67,9 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
43
67
  label={toLocalizedString(primaryAction.properties?.label, t)}
44
68
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
45
69
  parent={item}
70
+ path={path}
46
71
  monolithic={Node.isAction(primaryAction)}
47
- menuActions={Node.isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction?.id ?? '']}
72
+ menuActions={primaryMenuActions}
48
73
  menuType={primaryAction.properties?.menuType}
49
74
  caller={NAV_TREE_ITEM}
50
75
  />
@@ -57,9 +82,10 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
57
82
  <Treegrid.Cell classNames='contents'>
58
83
  <NavTreeItemAction
59
84
  testId={`navtree.treeItem.actionsLevel${level}`}
60
- label={t('tree item actions label')}
85
+ label={t('tree-item-actions.label')}
61
86
  icon='ph--dots-three-vertical--regular'
62
87
  parent={item}
88
+ path={path}
63
89
  menuActions={actions}
64
90
  menuType='dropdown'
65
91
  caller={NAV_TREE_ITEM}