@dxos/plugin-navtree 0.8.2-main.f11618f → 0.8.2-main.fbd8ed0

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 (139) hide show
  1. package/dist/lib/browser/{app-graph-builder-26D3MUCA.mjs → app-graph-builder-P7AY3K72.mjs} +7 -6
  2. package/dist/lib/browser/app-graph-builder-P7AY3K72.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-FHOAKMRS.mjs → chunk-UM2C5XK3.mjs} +352 -360
  4. package/dist/lib/browser/chunk-UM2C5XK3.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +4 -10
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/{intent-resolver-OBGGMBVJ.mjs → intent-resolver-PW7B76HO.mjs} +6 -6
  8. package/dist/lib/browser/intent-resolver-PW7B76HO.mjs.map +7 -0
  9. package/dist/lib/browser/keyboard-DDFATRUG.mjs +51 -0
  10. package/dist/lib/browser/keyboard-DDFATRUG.mjs.map +7 -0
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/{react-surface-DWMOF2TM.mjs → react-surface-M3CAS7ZP.mjs} +2 -2
  13. package/dist/lib/browser/{state-EW62QYEN.mjs → state-3UBYOTQT.mjs} +2 -2
  14. package/dist/lib/browser/state-3UBYOTQT.mjs.map +7 -0
  15. package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
  16. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  17. package/dist/types/src/capabilities/index.d.ts +4 -180
  18. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  19. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  20. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  21. package/dist/types/src/capabilities/keyboard.d.ts +2 -2
  22. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  23. package/dist/types/src/capabilities/state.d.ts +2 -2
  24. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  25. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -0
  26. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -0
  27. package/dist/types/src/components/CommandsDialog/index.d.ts +3 -0
  28. package/dist/types/src/components/CommandsDialog/index.d.ts.map +1 -0
  29. package/dist/types/src/components/NavTree/NavTree.d.ts +8 -0
  30. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -0
  31. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +8 -0
  32. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/NavTree/index.d.ts +2 -0
  34. package/dist/types/src/components/NavTree/index.d.ts.map +1 -0
  35. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  36. package/dist/types/src/components/NavTreeContext.d.ts.map +1 -1
  37. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
  38. package/dist/types/src/components/{NavTreeItemAction.d.ts → NavTreeItem/NavTreeItemAction.d.ts} +2 -3
  39. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -0
  40. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +8 -0
  41. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/{NavTreeItemColumns.d.ts → NavTreeItem/NavTreeItemColumns.d.ts} +1 -1
  43. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -0
  44. package/dist/types/src/components/NavTreeItem/index.d.ts +3 -0
  45. package/dist/types/src/components/NavTreeItem/index.d.ts.map +1 -0
  46. package/dist/types/src/components/Sidebar/L0Menu.d.ts +13 -0
  47. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -0
  48. package/dist/types/src/components/Sidebar/L1Panel.d.ts +15 -0
  49. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -0
  50. package/dist/types/src/components/Sidebar/index.d.ts +3 -0
  51. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -0
  52. package/dist/types/src/components/{UserAccountAvatar.d.ts → UserAccountAvatar/UserAccountAvatar.d.ts} +1 -1
  53. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -0
  54. package/dist/types/src/components/{UserAccountAvatar.stories.d.ts → UserAccountAvatar/UserAccountAvatar.stories.d.ts} +1 -2
  55. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -0
  56. package/dist/types/src/components/UserAccountAvatar/index.d.ts +2 -0
  57. package/dist/types/src/components/UserAccountAvatar/index.d.ts.map +1 -0
  58. package/dist/types/src/components/index.d.ts +0 -1
  59. package/dist/types/src/components/index.d.ts.map +1 -1
  60. package/dist/types/src/components/types.d.ts +17 -15
  61. package/dist/types/src/components/types.d.ts.map +1 -1
  62. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  63. package/dist/types/src/hooks.d.ts.map +1 -1
  64. package/dist/types/src/testing/app-graph-builder.d.ts +3 -0
  65. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -0
  66. package/dist/types/src/testing/generator.d.ts +5 -4
  67. package/dist/types/src/testing/generator.d.ts.map +1 -1
  68. package/dist/types/src/testing/index.d.ts +1 -0
  69. package/dist/types/src/testing/index.d.ts.map +1 -1
  70. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  71. package/dist/types/src/translations.d.ts +1 -1
  72. package/dist/types/src/util.d.ts +7 -10
  73. package/dist/types/src/util.d.ts.map +1 -1
  74. package/dist/types/tsconfig.tsbuildinfo +1 -1
  75. package/package.json +40 -31
  76. package/src/NavTreePlugin.tsx +2 -2
  77. package/src/capabilities/app-graph-builder.ts +38 -28
  78. package/src/capabilities/intent-resolver.ts +8 -7
  79. package/src/capabilities/keyboard.ts +37 -32
  80. package/src/capabilities/state.ts +3 -3
  81. package/src/components/{CommandsDialog.tsx → CommandsDialog/CommandsDialog.tsx} +8 -7
  82. package/src/components/{CommandsTrigger.tsx → CommandsDialog/CommandsTrigger.tsx} +1 -1
  83. package/src/components/CommandsDialog/index.ts +6 -0
  84. package/src/components/NavTree/NavTree.stories.tsx +159 -0
  85. package/src/components/NavTree/NavTree.tsx +62 -0
  86. package/src/components/NavTree/index.ts +5 -0
  87. package/src/components/NavTreeContainer.tsx +85 -79
  88. package/src/components/NavTreeContext.tsx +1 -1
  89. package/src/components/{NavTreeItemAction.stories.tsx → NavTreeItem/NavTreeItemAction.stories.tsx} +12 -6
  90. package/src/components/{NavTreeItemAction.tsx → NavTreeItem/NavTreeItemAction.tsx} +13 -14
  91. package/src/components/{NavTreeItemColumns.tsx → NavTreeItem/NavTreeItemColumns.tsx} +5 -5
  92. package/src/components/NavTreeItem/index.ts +6 -0
  93. package/src/components/{L0Menu.tsx → Sidebar/L0Menu.tsx} +188 -131
  94. package/src/components/{L1Panels.tsx → Sidebar/L1Panel.tsx} +40 -65
  95. package/src/components/Sidebar/index.ts +6 -0
  96. package/src/components/{UserAccountAvatar.stories.tsx → UserAccountAvatar/UserAccountAvatar.stories.tsx} +5 -5
  97. package/src/components/{UserAccountAvatar.tsx → UserAccountAvatar/UserAccountAvatar.tsx} +6 -6
  98. package/src/components/UserAccountAvatar/index.ts +5 -0
  99. package/src/components/index.ts +0 -1
  100. package/src/components/types.ts +17 -16
  101. package/src/hooks.ts +12 -11
  102. package/src/testing/app-graph-builder.ts +224 -0
  103. package/src/testing/generator.ts +12 -9
  104. package/src/testing/index.ts +1 -0
  105. package/src/translations.ts +1 -1
  106. package/src/util.ts +10 -32
  107. package/dist/lib/browser/app-graph-builder-26D3MUCA.mjs.map +0 -7
  108. package/dist/lib/browser/chunk-FHOAKMRS.mjs.map +0 -7
  109. package/dist/lib/browser/intent-resolver-OBGGMBVJ.mjs.map +0 -7
  110. package/dist/lib/browser/keyboard-IQXHKUWM.mjs +0 -46
  111. package/dist/lib/browser/keyboard-IQXHKUWM.mjs.map +0 -7
  112. package/dist/lib/browser/state-EW62QYEN.mjs.map +0 -7
  113. package/dist/types/src/components/CommandsDialog.d.ts.map +0 -1
  114. package/dist/types/src/components/CommandsTrigger.d.ts.map +0 -1
  115. package/dist/types/src/components/L0Menu.d.ts +0 -10
  116. package/dist/types/src/components/L0Menu.d.ts.map +0 -1
  117. package/dist/types/src/components/L1Panels.d.ts +0 -14
  118. package/dist/types/src/components/L1Panels.d.ts.map +0 -1
  119. package/dist/types/src/components/NavTree.d.ts +0 -5
  120. package/dist/types/src/components/NavTree.d.ts.map +0 -1
  121. package/dist/types/src/components/NavTree.stories.d.ts +0 -7
  122. package/dist/types/src/components/NavTree.stories.d.ts.map +0 -1
  123. package/dist/types/src/components/NavTreeItemAction.d.ts.map +0 -1
  124. package/dist/types/src/components/NavTreeItemAction.stories.d.ts +0 -41
  125. package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +0 -1
  126. package/dist/types/src/components/NavTreeItemColumns.d.ts.map +0 -1
  127. package/dist/types/src/components/UserAccountAvatar.d.ts.map +0 -1
  128. package/dist/types/src/components/UserAccountAvatar.stories.d.ts.map +0 -1
  129. package/dist/types/src/stories/Graph.stories.d.ts +0 -7
  130. package/dist/types/src/stories/Graph.stories.d.ts.map +0 -1
  131. package/dist/types/src/stories/StorybookNavTree.d.ts +0 -4
  132. package/dist/types/src/stories/StorybookNavTree.d.ts.map +0 -1
  133. package/src/components/NavTree.stories.tsx +0 -107
  134. package/src/components/NavTree.tsx +0 -51
  135. package/src/stories/Graph.stories.tsx +0 -130
  136. package/src/stories/StorybookNavTree.tsx +0 -63
  137. /package/dist/lib/browser/{react-surface-DWMOF2TM.mjs.map → react-surface-M3CAS7ZP.mjs.map} +0 -0
  138. /package/dist/types/src/components/{CommandsDialog.d.ts → CommandsDialog/CommandsDialog.d.ts} +0 -0
  139. /package/dist/types/src/components/{CommandsTrigger.d.ts → CommandsDialog/CommandsTrigger.d.ts} +0 -0
@@ -0,0 +1,62 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { type TreeProps } from '@dxos/react-ui-list';
8
+ import { Tabs } from '@dxos/react-ui-tabs';
9
+
10
+ import { useLoadDescendents } from '../../hooks';
11
+ import { type NavTreeItemGraphNode } from '../../types';
12
+ import { useNavTreeContext } from '../NavTreeContext';
13
+ import { L0Menu, L1Panels, type L1PanelsProps } from '../Sidebar';
14
+
15
+ export const NAV_TREE_ITEM = 'NavTreeItem';
16
+
17
+ export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<L1PanelsProps, 'open'>;
18
+
19
+ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
20
+ const { tab, useItems, onBack } = useNavTreeContext();
21
+ const topLevelActions = useItems(root, { disposition: 'menu', sort: true });
22
+ const topLevelCollections = useItems(root, { disposition: 'collection' });
23
+ const topLevelWorkspaces = useItems(root, { disposition: 'workspace' });
24
+ const topLevelNavigation = useItems(root, { disposition: 'navigation' });
25
+ const l0Items = useMemo(
26
+ () => [
27
+ // prettier-ignore
28
+ ...topLevelCollections,
29
+ ...topLevelWorkspaces,
30
+ ...topLevelNavigation,
31
+ ],
32
+ [topLevelCollections, topLevelWorkspaces, topLevelNavigation],
33
+ );
34
+ const pinnedItems = useItems(root, { disposition: 'pin-end', sort: true });
35
+ const userAccountItem = useItems(root, { disposition: 'user-account' })[0];
36
+ const topLevelItems = useMemo(
37
+ () => [
38
+ // prettier-ignore
39
+ ...l0Items,
40
+ ...pinnedItems,
41
+ ...(userAccountItem ? [userAccountItem] : []),
42
+ ],
43
+ [l0Items, pinnedItems, userAccountItem],
44
+ );
45
+
46
+ useLoadDescendents(root);
47
+ const path = useMemo(() => [id], [id]);
48
+
49
+ return (
50
+ <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
51
+ <L0Menu
52
+ menuActions={topLevelActions}
53
+ topLevelItems={l0Items}
54
+ pinnedItems={pinnedItems}
55
+ userAccountItem={userAccountItem}
56
+ path={path}
57
+ parent={root}
58
+ />
59
+ <L1Panels topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
60
+ </Tabs.Root>
61
+ );
62
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './NavTree';
@@ -16,28 +16,22 @@ import {
16
16
  useIntentDispatcher,
17
17
  useLayout,
18
18
  } from '@dxos/app-framework';
19
- import { isAction, isActionLike, type Node } from '@dxos/app-graph';
19
+ import { isAction, isActionLike, type ReadableGraph, ROOT_ID, type Node } from '@dxos/app-graph';
20
20
  import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
21
+ import { useConnections } from '@dxos/plugin-graph';
21
22
  import { isEchoObject, isSpace } from '@dxos/react-client/echo';
22
- import { useMediaQuery } from '@dxos/react-ui';
23
+ import { useMediaQuery, useSidebars } from '@dxos/react-ui';
23
24
  import { isTreeData, type TreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
24
25
  import { mx } from '@dxos/react-ui-theme';
25
- import { arrayMove } from '@dxos/util';
26
+ import { arrayMove, byPosition } from '@dxos/util';
26
27
 
27
28
  import { NAV_TREE_ITEM, NavTree } from './NavTree';
28
29
  import { NavTreeContext } from './NavTreeContext';
29
30
  import { type NavTreeContextValue } from './types';
30
31
  import { NavTreeCapabilities } from '../capabilities';
32
+ import { NAVTREE_PLUGIN } from '../meta';
31
33
  import { type NavTreeItemGraphNode } from '../types';
32
- import {
33
- expandActions,
34
- expandChildren,
35
- getActions as naturalGetActions,
36
- getChildren,
37
- getParent,
38
- resolveMigrationOperation,
39
- expandChildrenAndActions,
40
- } from '../util';
34
+ import { getActions as naturalGetActions, getChildren, getParent, resolveMigrationOperation } from '../util';
41
35
 
42
36
  // TODO(thure): Is NavTree truly authoritative in this regard?
43
37
  export const NODE_TYPE = 'dxos/app-graph/node';
@@ -49,6 +43,29 @@ const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
49
43
  const getChildrenFilter = (node: Node): node is Node =>
50
44
  untracked(() => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE);
51
45
 
46
+ const filterItems = (node: Node, disposition?: string) => {
47
+ if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
48
+ return false;
49
+ } else if (!disposition) {
50
+ const action = isAction(node);
51
+ return !action || node.properties.disposition === 'item';
52
+ } else {
53
+ return node.properties.disposition === disposition;
54
+ }
55
+ };
56
+
57
+ const getItems = (graph: ReadableGraph, node?: Node, disposition?: string) => {
58
+ return graph.getConnections(node?.id ?? ROOT_ID, 'outbound').filter((node) => filterItems(node, disposition));
59
+ };
60
+
61
+ const useItems = (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
62
+ const { graph } = useAppGraph();
63
+ const connections = useConnections(graph, node?.id ?? ROOT_ID).filter((node) =>
64
+ filterItems(node, options?.disposition),
65
+ );
66
+ return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
67
+ };
68
+
52
69
  export type NavTreeContainerProps = {
53
70
  popoverAnchorId?: string;
54
71
  topbar?: boolean;
@@ -58,37 +75,15 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
58
75
  const [isLg] = useMediaQuery('lg', { ssr: false });
59
76
  const { dispatchPromise: dispatch } = useIntentDispatcher();
60
77
  const { graph } = useAppGraph();
61
- const layout = useLayout();
62
78
  const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
79
+ const layout = useLayout();
80
+ const { navigationSidebarState } = useSidebars(NAVTREE_PLUGIN);
63
81
 
64
82
  const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
65
83
 
66
- const getItems = useCallback(
67
- (node?: Node, disposition?: string) => {
68
- return graph.nodes(node ?? graph.root, {
69
- filter: (node): node is Node => {
70
- return untracked(() => {
71
- if (
72
- !disposition &&
73
- (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')
74
- ) {
75
- return false;
76
- } else if (!disposition) {
77
- const action = isAction(node);
78
- return !action || node.properties.disposition === 'item';
79
- } else {
80
- return node.properties.disposition === disposition;
81
- }
82
- });
83
- },
84
- });
85
- },
86
- [graph],
87
- );
88
-
89
84
  const getProps = useCallback(
90
85
  (node: Node, path: string[]): PropsFromTreeItem => {
91
- const children = getChildren(graph, node, getChildrenFilter, path);
86
+ const children = getChildren(graph, node, path).filter(getChildrenFilter);
92
87
  const parentOf =
93
88
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
94
89
  return {
@@ -105,51 +100,63 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
105
100
  [graph],
106
101
  );
107
102
 
108
- const loadDescendents = useCallback(
109
- (node: Node) => {
110
- void expandActions(graph, node);
111
- if (!isActionLike(node)) {
112
- void expandChildren(graph, node).then(() =>
113
- // Load one level deeper, which resolves some juddering observed on open/close.
114
- Promise.all(
115
- getChildren(graph, node).flatMap((child) => [expandActions(graph, child), expandChildren(graph, child)]),
116
- ),
117
- );
118
- }
119
- },
120
- [graph],
121
- );
103
+ // TODO(wittjosiah): Graph is currently expanded eagerly. See graph plugin.
104
+ // const loadDescendents = useCallback(
105
+ // (node: Node) => {
106
+ // graph.expand(node.id, 'outbound');
107
+ // // Load one level deeper, which resolves some juddering observed on open/close.
108
+ // graph.getConnections(node.id, 'outbound').forEach((child) => {
109
+ // graph.expand(child.id, 'outbound');
110
+ // });
111
+ // },
112
+ // [graph],
113
+ // );
122
114
 
123
- const onOpenChange = useCallback(
115
+ const handleOpenChange = useCallback(
124
116
  ({ item: { id }, path, open }: { item: Node; path: string[]; open: boolean }) => {
125
117
  // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
126
118
  setItem(path, 'open', open);
127
-
128
- if (graph) {
129
- const node = graph.findNode(id);
130
- return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
131
- }
119
+ // TODO(wittjosiah): Graph is currently expanded eagerly. See graph plugin.
120
+ // graph.expand(id, 'outbound');
132
121
  },
133
122
  [graph],
134
123
  );
135
124
 
136
- const onTabChange = useCallback(
125
+ const handleTabChange = useCallback(
137
126
  async (node: NavTreeItemGraphNode) => {
127
+ await dispatch(
128
+ createIntent(LayoutAction.UpdateSidebar, {
129
+ part: 'sidebar',
130
+ options: {
131
+ state:
132
+ node.id === tab
133
+ ? navigationSidebarState === 'expanded'
134
+ ? isLg
135
+ ? 'collapsed'
136
+ : 'closed'
137
+ : 'expanded'
138
+ : 'expanded',
139
+ },
140
+ }),
141
+ );
142
+
138
143
  await dispatch(createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: node.id }));
144
+
139
145
  // Open the first item if the workspace is empty.
140
146
  if (layout.active.length === 0) {
141
- const [item] = getItems(node).filter((node) => !isActionLike(node));
147
+ const [item] = getItems(graph, node).filter((node) => !isActionLike(node));
142
148
  if (item && item.data) {
143
149
  await dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [item.id] }));
144
150
  }
145
151
  }
146
152
  },
147
- [dispatch, layout],
153
+ [dispatch, layout.active, tab, navigationSidebarState, isLg],
148
154
  );
149
155
 
150
156
  const canDrop = useCallback((source: TreeData, target: TreeData) => {
151
157
  const sourceNode = source.item as Node;
152
158
  const targetNode = target.item as Node;
159
+ // TODO(wittjosiah): This shouldn't depend directly on client, there should be a graph-level abstraction.
153
160
  if (isSpace(targetNode.data)) {
154
161
  // TODO(wittjosiah): Find a way to only allow space as source for rearranging.
155
162
  return isEchoObject(sourceNode.data) || isSpace(sourceNode.data);
@@ -167,8 +174,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
167
174
  }
168
175
 
169
176
  if (isAction(node)) {
170
- const [parent] = graph.nodes(node, { relation: 'inbound' });
171
- void (parent && node.data({ node: parent, caller: NAV_TREE_ITEM }));
177
+ const [parent] = graph.getConnections(node.id, 'inbound');
178
+ void (parent && node.data({ parent, caller: NAV_TREE_ITEM }));
172
179
  return;
173
180
  }
174
181
 
@@ -189,7 +196,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
189
196
  void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: node.id }));
190
197
  }
191
198
 
192
- const defaultAction = graph.actions(node).find((action) => action.properties?.disposition === 'default');
199
+ const defaultAction = graph.getActions(node.id).find((action) => action.properties?.disposition === 'default');
193
200
  if (isAction(defaultAction)) {
194
201
  void (defaultAction.data as () => void)();
195
202
  }
@@ -201,7 +208,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
201
208
  [graph, dispatch, isCurrent, isLg],
202
209
  );
203
210
 
204
- const onBack = useCallback(
211
+ const handleBack = useCallback(
205
212
  () => dispatch(createIntent(LayoutAction.RevertWorkspace, { part: 'workspace', options: { revert: true } })),
206
213
  [dispatch],
207
214
  );
@@ -228,8 +235,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
228
235
  : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
229
236
  const sourceParent = getParent(graph, sourceNode, sourcePath);
230
237
  const targetParent = getParent(graph, targetNode, targetPath);
231
- const sourceItems = getItems(sourceParent);
232
- const targetItems = getItems(targetParent);
238
+ const sourceItems = getItems(graph, sourceParent);
239
+ const targetItems = getItems(graph, targetParent);
233
240
  const sourceIndex = sourceItems.findIndex(({ id }) => id === sourceNode.id);
234
241
  const targetIndex = targetItems.findIndex(({ id }) => id === targetNode.id);
235
242
  const migrationIndex =
@@ -274,48 +281,47 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
274
281
 
275
282
  const navTreeContextValue = useMemo(
276
283
  () => ({
284
+ useItems,
277
285
  tab,
278
- onTabChange,
279
- onBack,
280
286
  getActions,
281
- loadDescendents,
287
+ // loadDescendents,
282
288
  renderItemEnd,
283
289
  popoverAnchorId,
284
290
  topbar,
285
- getItems,
286
291
  getProps,
287
292
  isCurrent,
288
293
  isOpen,
289
- onOpenChange,
290
294
  canDrop,
291
- onSelect: handleSelect,
292
295
  isAlternateTree,
293
296
  setAlternateTree,
297
+ onTabChange: handleTabChange,
298
+ onOpenChange: handleOpenChange,
299
+ onSelect: handleSelect,
300
+ onBack: handleBack,
294
301
  }),
295
302
  [
296
303
  tab,
297
- onTabChange,
298
- onBack,
299
304
  getActions,
300
- loadDescendents,
305
+ // loadDescendents,
301
306
  renderItemEnd,
302
307
  popoverAnchorId,
303
308
  topbar,
304
- getItems,
305
309
  getProps,
306
310
  isCurrent,
307
311
  isOpen,
308
- onOpenChange,
309
312
  canDrop,
310
- handleSelect,
311
313
  isAlternateTree,
312
314
  setAlternateTree,
315
+ handleTabChange,
316
+ handleOpenChange,
317
+ handleSelect,
318
+ handleBack,
313
319
  ],
314
320
  );
315
321
 
316
322
  return (
317
323
  <NavTreeContext.Provider value={navTreeContextValue}>
318
- <NavTree root={graph.root} id={graph.root.id} />
324
+ <NavTree id={ROOT_ID} root={graph.root} open={layout.sidebarOpen} />
319
325
  </NavTreeContext.Provider>
320
326
  );
321
327
  });
@@ -10,4 +10,4 @@ import { type NavTreeContextValue } from './types';
10
10
 
11
11
  export const NavTreeContext = createContext<NavTreeContextValue | null>(null);
12
12
 
13
- export const useNavTreeContext = () => useContext(NavTreeContext) ?? raise(new Error('NavTreeContext not found'));
13
+ export const useNavTreeContext = () => useContext(NavTreeContext) ?? raise(new Error('Missing NavTreeContext'));
@@ -4,6 +4,8 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
+ import { type Meta, type StoryObj } from '@storybook/react';
8
+
7
9
  import { type NodeArg } from '@dxos/app-graph';
8
10
  import { faker } from '@dxos/random';
9
11
  import { withLayout, withTheme } from '@dxos/storybook-utils';
@@ -37,19 +39,23 @@ const menuActions = faker.helpers.multiple(
37
39
  { count: 20 },
38
40
  );
39
41
 
40
- export default {
42
+ const meta: Meta<typeof NavTreeItemAction> = {
41
43
  title: 'plugins/plugin-navtree/NavTreeItemAction',
42
44
  component: NavTreeItemAction,
43
- decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
45
+ decorators: [withTheme, withLayout()],
44
46
  args: {
45
47
  icon: 'ph--list--regular',
46
48
  parent,
47
49
  menuActions,
48
50
  label: 'Select action',
49
- onAction: (action) => {
50
- console.log(action);
51
- },
52
51
  } satisfies Partial<NavTreeItemActionMenuProps>,
52
+ parameters: {
53
+ layout: 'centered',
54
+ },
53
55
  };
54
56
 
55
- export const Default = {};
57
+ export default meta;
58
+
59
+ type Story = StoryObj<typeof meta>;
60
+
61
+ export const Default: Story = {};
@@ -9,8 +9,8 @@ import { useTranslation, toLocalizedString, IconButton, useDensityContext } from
9
9
  import { DropdownMenu, MenuProvider, type MenuItem } from '@dxos/react-ui-menu';
10
10
  import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
11
11
 
12
- import { NAVTREE_PLUGIN } from '../meta';
13
- import { type ActionProperties } from '../types';
12
+ import { NAVTREE_PLUGIN } from '../../meta';
13
+ import { type ActionProperties } from '../../types';
14
14
 
15
15
  export type NavTreeItemActionMenuProps = ActionProperties & {
16
16
  parent: Node;
@@ -18,7 +18,6 @@ export type NavTreeItemActionMenuProps = ActionProperties & {
18
18
  monolithic?: boolean;
19
19
  menuActions?: Action[];
20
20
  suppressNextTooltip?: MutableRefObject<boolean>;
21
- onAction?: (action: Action) => void;
22
21
  };
23
22
 
24
23
  const fallbackIcon = 'ph--placeholder--regular';
@@ -33,18 +32,24 @@ const coarseActionButtonProps = {
33
32
  };
34
33
 
35
34
  export const NavTreeItemActionDropdownMenu = ({
35
+ parent,
36
36
  label,
37
37
  icon,
38
38
  testId,
39
39
  menuActions,
40
- onAction,
40
+ caller,
41
41
  }: NavTreeItemActionMenuProps) => {
42
42
  const { t } = useTranslation(NAVTREE_PLUGIN);
43
43
  const suppressNextTooltip = useRef<boolean>(false);
44
44
  const density = useDensityContext();
45
45
  return (
46
- <MenuProvider onAction={onAction}>
47
- <DropdownMenu.Root items={menuActions as MenuItem[]} suppressNextTooltip={suppressNextTooltip}>
46
+ <MenuProvider>
47
+ <DropdownMenu.Root
48
+ group={parent}
49
+ items={menuActions as MenuItem[]}
50
+ caller={caller}
51
+ suppressNextTooltip={suppressNextTooltip}
52
+ >
48
53
  <DropdownMenu.Trigger asChild>
49
54
  <IconButton
50
55
  {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
@@ -88,7 +93,7 @@ export const NavTreeItemMonolithicAction = ({
88
93
  return;
89
94
  }
90
95
  event.stopPropagation();
91
- void invoke?.(caller ? { node: parent, caller } : { node: parent });
96
+ void invoke?.(caller ? { parent, caller } : { parent });
92
97
  }}
93
98
  data-testid={testId}
94
99
  />
@@ -104,12 +109,6 @@ export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...pr
104
109
  return monolithic && menuActions?.length === 1 ? (
105
110
  <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
106
111
  ) : (
107
- <NavTreeItemActionDropdownMenu
108
- {...props}
109
- label={baseLabel}
110
- parent={node}
111
- menuActions={menuActions}
112
- onAction={(action) => action.data?.(props.caller ? { node, caller: props.caller } : { node })}
113
- />
112
+ <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} menuActions={menuActions} />
114
113
  );
115
114
  };
@@ -7,12 +7,12 @@ import React, { Fragment, memo } from 'react';
7
7
  import { isAction } from '@dxos/app-graph';
8
8
  import { DensityProvider, Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { NAV_TREE_ITEM } from './NavTree';
11
- import { useNavTreeContext } from './NavTreeContext';
12
10
  import { NavTreeItemAction } from './NavTreeItemAction';
13
- import type { NavTreeItemColumnsProps } from './types';
14
- import { useLoadDescendents } from '../hooks';
15
- import { NAVTREE_PLUGIN } from '../meta';
11
+ import { useLoadDescendents } from '../../hooks';
12
+ import { NAVTREE_PLUGIN } from '../../meta';
13
+ import { NAV_TREE_ITEM } from '../NavTree';
14
+ import { useNavTreeContext } from '../NavTreeContext';
15
+ import type { NavTreeItemColumnsProps } from '../types';
16
16
 
17
17
  export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }: NavTreeItemColumnsProps) => {
18
18
  const { t } = useTranslation(NAVTREE_PLUGIN);
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './NavTreeItemAction';
6
+ export * from './NavTreeItemColumns';