@dxos/plugin-navtree 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7

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 (165) hide show
  1. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs +113 -0
  2. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs.map +7 -0
  3. package/dist/lib/browser/CommandsTrigger-ABODVVMP.mjs +33 -0
  4. package/dist/lib/browser/CommandsTrigger-ABODVVMP.mjs.map +7 -0
  5. package/dist/lib/browser/NavTreeContainer-TLSYS4KK.mjs +12 -0
  6. package/dist/lib/browser/NavTreeContainer-TLSYS4KK.mjs.map +7 -0
  7. package/dist/lib/browser/NavTreeDocumentTitle-L6NLLWLD.mjs +26 -0
  8. package/dist/lib/browser/NavTreeDocumentTitle-L6NLLWLD.mjs.map +7 -0
  9. package/dist/lib/browser/{app-graph-builder-RSMA6I22.mjs → app-graph-builder-Z43OTNI2.mjs} +6 -5
  10. package/dist/lib/browser/app-graph-builder-Z43OTNI2.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-YKTYPINZ.mjs → chunk-A4VDCIJC.mjs} +3 -3
  12. package/dist/lib/browser/{chunk-YKTYPINZ.mjs.map → chunk-A4VDCIJC.mjs.map} +1 -1
  13. package/dist/lib/browser/chunk-AYF2TX65.mjs +14 -0
  14. package/dist/lib/browser/chunk-AYF2TX65.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-JT37R5HS.mjs +1468 -0
  16. package/dist/lib/browser/chunk-JT37R5HS.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-AGQDZBPL.mjs → chunk-KF26WA4O.mjs} +5 -5
  18. package/dist/lib/browser/{chunk-AGQDZBPL.mjs.map → chunk-KF26WA4O.mjs.map} +3 -3
  19. package/dist/lib/browser/index.mjs +35 -31
  20. package/dist/lib/browser/index.mjs.map +3 -3
  21. package/dist/lib/browser/{keyboard-32EH3N4K.mjs → keyboard-HYGFQO7T.mjs} +11 -6
  22. package/dist/lib/browser/keyboard-HYGFQO7T.mjs.map +7 -0
  23. package/dist/lib/browser/meta.json +1 -1
  24. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs +49 -0
  25. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs.map +7 -0
  26. package/dist/lib/browser/{react-surface-F6CVMS3Y.mjs → react-surface-D2RLMV4Z.mjs} +15 -14
  27. package/dist/lib/browser/react-surface-D2RLMV4Z.mjs.map +7 -0
  28. package/dist/lib/browser/{state-EVYQOUI3.mjs → state-CWMNNF7K.mjs} +34 -23
  29. package/dist/lib/browser/state-CWMNNF7K.mjs.map +7 -0
  30. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +1 -1
  32. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -1
  33. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +1 -1
  34. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/operation-resolver/index.d.ts +1 -1
  36. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +2 -2
  38. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/react-surface/index.d.ts +1 -1
  40. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -1
  41. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +2 -2
  42. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
  43. package/dist/types/src/capabilities/state/state.d.ts +1 -1
  44. package/dist/types/src/capabilities/state/state.d.ts.map +1 -1
  45. package/dist/types/src/components/NavTree/NavTree.d.ts +10 -32
  46. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  47. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +2 -5
  48. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  49. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  50. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  51. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  52. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  53. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +4 -2
  54. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
  56. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  57. package/dist/types/src/components/Sidebar/L0Menu.d.ts +4 -1
  58. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  59. package/dist/types/src/components/Sidebar/L1Panel.d.ts +3 -6
  60. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +3 -2
  62. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  63. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  64. package/dist/types/src/components/index.d.ts +0 -3
  65. package/dist/types/src/components/index.d.ts.map +1 -1
  66. package/dist/types/src/components/types.d.ts +5 -13
  67. package/dist/types/src/components/types.d.ts.map +1 -1
  68. package/dist/types/src/{components/CommandsDialog/CommandsDialog.d.ts → containers/CommandsDialogContent/CommandsDialogContent.d.ts} +1 -1
  69. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -0
  70. package/dist/types/src/containers/CommandsDialogContent/index.d.ts +3 -0
  71. package/dist/types/src/containers/CommandsDialogContent/index.d.ts.map +1 -0
  72. package/dist/types/src/containers/CommandsTrigger/CommandsTrigger.d.ts.map +1 -0
  73. package/dist/types/src/containers/CommandsTrigger/index.d.ts +3 -0
  74. package/dist/types/src/containers/CommandsTrigger/index.d.ts.map +1 -0
  75. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts +9 -0
  76. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -0
  77. package/dist/types/src/containers/NavTreeContainer/index.d.ts +3 -0
  78. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -0
  79. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -0
  80. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts +3 -0
  81. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts.map +1 -0
  82. package/dist/types/src/containers/index.d.ts +7 -0
  83. package/dist/types/src/containers/index.d.ts.map +1 -0
  84. package/dist/types/src/experimental/Tree.stories.d.ts +18 -4
  85. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  86. package/dist/types/src/hooks/index.d.ts +5 -0
  87. package/dist/types/src/hooks/index.d.ts.map +1 -0
  88. package/dist/types/src/hooks/useActions.d.ts +5 -0
  89. package/dist/types/src/hooks/useActions.d.ts.map +1 -0
  90. package/dist/types/src/hooks/useLoadDescendents.d.ts +6 -0
  91. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -0
  92. package/dist/types/src/hooks/useNavTreeModel.d.ts +7 -0
  93. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -0
  94. package/dist/types/src/{hooks.d.ts → hooks/useNavTreeState.d.ts} +2 -14
  95. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -0
  96. package/dist/types/src/meta.d.ts.map +1 -1
  97. package/dist/types/src/testing/generator.d.ts +3 -3
  98. package/dist/types/src/testing/generator.d.ts.map +1 -1
  99. package/dist/types/src/types/events.d.ts.map +1 -1
  100. package/dist/types/src/util.d.ts +5 -1
  101. package/dist/types/src/util.d.ts.map +1 -1
  102. package/dist/types/tsconfig.tsbuildinfo +1 -1
  103. package/package.json +43 -42
  104. package/src/NavTreePlugin.tsx +22 -24
  105. package/src/capabilities/app-graph-builder/app-graph-builder.ts +4 -3
  106. package/src/capabilities/keyboard/keyboard.ts +6 -5
  107. package/src/capabilities/operation-resolver/operation-resolver.ts +23 -19
  108. package/src/capabilities/react-surface/react-surface.tsx +12 -13
  109. package/src/capabilities/state/state.ts +41 -13
  110. package/src/components/NavTree/NavTree.stories.tsx +21 -19
  111. package/src/components/NavTree/NavTree.tsx +61 -20
  112. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  113. package/src/components/NavTreeContext/index.ts +5 -0
  114. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +2 -2
  115. package/src/components/NavTreeItem/NavTreeItemAction.tsx +40 -23
  116. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +40 -14
  117. package/src/components/Sidebar/L0Menu.tsx +90 -74
  118. package/src/components/Sidebar/L1Panel.tsx +244 -90
  119. package/src/components/Sidebar/L1Tabs.tsx +13 -3
  120. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +2 -3
  121. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +1 -1
  122. package/src/components/index.ts +0 -3
  123. package/src/components/types.ts +12 -17
  124. package/src/{components/CommandsDialog/CommandsDialog.tsx → containers/CommandsDialogContent/CommandsDialogContent.tsx} +19 -13
  125. package/src/containers/CommandsDialogContent/index.ts +7 -0
  126. package/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.tsx +4 -4
  127. package/src/containers/CommandsTrigger/index.ts +7 -0
  128. package/src/{components → containers/NavTreeContainer}/NavTreeContainer.tsx +75 -141
  129. package/src/containers/NavTreeContainer/index.ts +6 -0
  130. package/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.tsx +1 -1
  131. package/src/containers/NavTreeDocumentTitle/index.ts +7 -0
  132. package/src/containers/index.ts +12 -0
  133. package/src/experimental/Tree.stories.tsx +54 -57
  134. package/src/experimental/Tree.tsx +3 -3
  135. package/src/hooks/index.ts +8 -0
  136. package/src/hooks/useActions.ts +34 -0
  137. package/src/hooks/useLoadDescendents.ts +23 -0
  138. package/src/hooks/useNavTreeModel.ts +118 -0
  139. package/src/{hooks.ts → hooks/useNavTreeState.ts} +3 -34
  140. package/src/meta.ts +2 -2
  141. package/src/testing/app-graph-builder.ts +5 -5
  142. package/src/testing/generator.ts +4 -4
  143. package/src/types/capabilities.ts +1 -1
  144. package/src/types/events.ts +3 -2
  145. package/src/util.ts +18 -4
  146. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs.map +0 -7
  147. package/dist/lib/browser/chunk-6YPQ625I.mjs +0 -1293
  148. package/dist/lib/browser/chunk-6YPQ625I.mjs.map +0 -7
  149. package/dist/lib/browser/keyboard-32EH3N4K.mjs.map +0 -7
  150. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs +0 -52
  151. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs.map +0 -7
  152. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs.map +0 -7
  153. package/dist/lib/browser/state-EVYQOUI3.mjs.map +0 -7
  154. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  155. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  156. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  157. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  158. package/dist/types/src/components/NavTreeContainer.d.ts +0 -16
  159. package/dist/types/src/components/NavTreeContainer.d.ts.map +0 -1
  160. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  161. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +0 -1
  162. package/dist/types/src/hooks.d.ts.map +0 -1
  163. package/src/components/CommandsDialog/index.ts +0 -6
  164. /package/dist/types/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.d.ts +0 -0
  165. /package/dist/types/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.d.ts +0 -0
@@ -4,165 +4,107 @@
4
4
 
5
5
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
6
  import { type Instruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
7
- import React, { forwardRef, memo, useCallback, useEffect, useMemo } from 'react';
7
+ import React, { forwardRef, memo, useCallback, useEffect, useMemo, useRef } from 'react';
8
8
 
9
- import { Common } from '@dxos/app-framework';
10
- import { Surface, useAppGraph, useLayout, useOperationInvoker } from '@dxos/app-framework/react';
11
- import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
9
+ import { Surface, useOperationInvoker } from '@dxos/app-framework/ui';
10
+ import { LayoutOperation } from '@dxos/app-toolkit';
11
+ import { useAppGraph, useLayout } from '@dxos/app-toolkit/ui';
12
12
  import { Graph, Node, useActionRunner } from '@dxos/plugin-graph';
13
- import { useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
14
13
  import { useMediaQuery, useSidebars } from '@dxos/react-ui';
15
- import { type TreeData, type TreeItemDataProps, isTreeData } from '@dxos/react-ui-list';
16
- import { mx } from '@dxos/ui-theme';
17
- import { arrayMove, byPosition } from '@dxos/util';
14
+ import { type TreeData, isTreeData } from '@dxos/react-ui-list';
15
+ import { arrayMove } from '@dxos/util';
18
16
 
19
- import { useIsAlternateTree, useIsCurrent, useIsOpen, useNavTreeState } from '../hooks';
20
- import { meta } from '../meta';
21
- import { type FlattenedActions, type NavTreeItemGraphNode } from '../types';
22
- import { getChildren, getParent, resolveMigrationOperation } from '../util';
23
-
24
- import { NAV_TREE_ITEM, NavTree } from './NavTree';
25
- import { NavTreeContext } from './NavTreeContext';
26
- import { type NavTreeContextValue } from './types';
17
+ import { NAV_TREE_ITEM, NavTree, NavTreeContext } from '../../components';
18
+ import { useNavTreeModel, useNavTreeState } from '../../hooks';
19
+ import { meta } from '../../meta';
20
+ import { type NavTreeItemGraphNode } from '../../types';
21
+ import { filterItems, getParent, resolveMigrationOperation } from '../../util';
27
22
 
28
23
  // TODO(thure): Is NavTree truly authoritative in this regard?
29
24
  export const NODE_TYPE = 'dxos/app-graph/node';
30
25
 
31
- const renderItemEnd = ({ node, open }: { node: Node.Node; open: boolean }) => (
32
- <Surface role='navtree-item-end' data={{ id: node.id, subject: node.data, open }} limit={1} />
33
- );
34
-
35
- const getChildrenFilter = (node: Node.Node): node is Node.Node =>
36
- !Node.isActionLike(node) && node.type !== PLANK_COMPANION_TYPE && node.properties.disposition !== 'hidden';
37
-
38
- const filterItems = (node: Node.Node, disposition?: string) => {
39
- if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
40
- return false;
41
- } else if (!disposition) {
42
- const action = Node.isAction(node);
43
- return !action || node.properties.disposition === 'item';
44
- } else {
45
- return node.properties.disposition === disposition;
46
- }
26
+ // TODO(wittjosiah): Avoid using Surface within the navtree, prefer declarative data flow.
27
+ const NavTreeItemEnd = ({ node, open }: { node: Node.Node; open: boolean }) => {
28
+ const data = useMemo(() => ({ id: node.id, subject: node.data, open }), [node.id, node.data, open]);
29
+ return <Surface.Surface role='navtree-item-end' data={data} limit={1} />;
47
30
  };
48
31
 
49
32
  const getItems = (graph: Graph.ReadableGraph, node?: Node.Node, disposition?: string) => {
50
- return Graph.getConnections(graph, node?.id ?? Node.RootId, 'outbound').filter((node) =>
51
- filterItems(node, disposition),
52
- );
53
- };
54
-
55
- const useItems = (node?: Node.Node, options?: { disposition?: string; sort?: boolean }) => {
56
- const { graph } = useAppGraph();
57
- const connections = useConnections(graph, node?.id ?? Node.RootId);
58
- const filtered = connections.filter((node) => filterItems(node, options?.disposition));
59
- return options?.sort ? filtered.toSorted((a, b) => byPosition(a.properties, b.properties)) : filtered;
60
- };
61
-
62
- const useActions = (node: Node.Node): FlattenedActions => {
63
- const { graph } = useAppGraph();
64
- const actions = useGraphActions(graph, node.id);
65
-
66
- return useMemo(
67
- () =>
68
- actions.reduce(
69
- (acc: FlattenedActions, arg) => {
70
- if (arg.properties.disposition === 'item') {
71
- return acc;
72
- }
73
-
74
- acc.actions.push(arg);
75
- if (!Node.isAction(arg)) {
76
- const actionGroup = Graph.getActions(graph, arg.id);
77
- acc.groupedActions[arg.id] = actionGroup;
78
- }
79
- return acc;
80
- },
81
- { actions: [], groupedActions: {} },
82
- ),
83
- [actions],
84
- );
33
+ return Graph.getConnections(graph, node?.id ?? Node.RootId, 'child').filter((node) => filterItems(node, disposition));
85
34
  };
86
35
 
87
36
  export type NavTreeContainerProps = {
88
37
  popoverAnchorId?: string;
89
- topbar?: boolean;
90
- } & Pick<NavTreeContextValue, 'tab'>;
38
+ tab: string;
39
+ };
91
40
 
92
41
  export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProps>(
93
- ({ tab, popoverAnchorId, topbar }, forwardedRef) => {
42
+ ({ tab, popoverAnchorId }, forwardedRef) => {
94
43
  const [isLg] = useMediaQuery('lg');
95
44
  const { invokeSync, invokePromise } = useOperationInvoker();
96
45
  const runAction = useActionRunner();
97
46
  const { graph } = useAppGraph();
98
47
  const { getItem, setItem } = useNavTreeState();
99
48
  const layout = useLayout();
49
+ const model = useNavTreeModel(Node.RootId);
100
50
  const { navigationSidebarState } = useSidebars(meta.id);
51
+ const latestRef = useRef({
52
+ tab,
53
+ activeItems: layout.active,
54
+ navigationSidebarState,
55
+ isLg,
56
+ });
101
57
 
102
- const getProps = useCallback(
103
- (node: Node.Node, path: string[]): TreeItemDataProps => {
104
- const children = getChildren(graph, node, path).filter(getChildrenFilter);
105
- const parentOf =
106
- children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
107
- return {
108
- id: node.id,
109
- parentOf,
110
- disabled: node.properties.disabled,
111
- label: node.properties.label ?? node.id,
112
- className: mx(node.properties.className, node.properties.modified && 'italic'), // TODO(burdon): Italic?
113
- headingClassName: node.properties.headingClassName,
114
- icon: node.properties.icon,
115
- iconHue: node.properties.iconHue,
116
- testId: node.properties.testId,
117
- };
118
- },
119
- [graph],
120
- );
121
-
122
- const loadDescendents = useCallback(
123
- (node: Node.Node) => {
124
- Graph.expand(graph, node.id, 'outbound');
125
- // Load one level deeper, which resolves some juddering observed on open/close.
126
- Graph.getConnections(graph, node.id, 'outbound').forEach((child) => {
127
- Graph.expand(graph, child.id, 'outbound');
128
- });
129
- },
130
- [graph],
131
- );
58
+ useEffect(() => {
59
+ latestRef.current = {
60
+ tab,
61
+ activeItems: layout.active,
62
+ navigationSidebarState,
63
+ isLg,
64
+ };
65
+ }, [tab, layout.active, navigationSidebarState, isLg]);
132
66
 
133
67
  const handleOpenChange = useCallback(
134
68
  ({ item: { id }, path, open }: { item: Node.Node; path: string[]; open: boolean }) => {
135
69
  // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
136
70
  setItem(path, 'open', open);
137
- Graph.expand(graph, id, 'outbound');
71
+ Graph.expand(graph, id, 'child');
138
72
  },
139
- [graph],
73
+ [graph, setItem],
140
74
  );
141
75
 
142
76
  const handleTabChange = useCallback(
143
77
  (node: NavTreeItemGraphNode) => {
144
- invokeSync(Common.LayoutOperation.UpdateSidebar, {
78
+ Graph.expand(graph, node.id, 'child');
79
+
80
+ const {
81
+ tab: activeTab,
82
+ activeItems,
83
+ navigationSidebarState: currentSidebarState,
84
+ isLg: latestIsLg,
85
+ } = latestRef.current;
86
+ invokeSync(LayoutOperation.UpdateSidebar, {
145
87
  state:
146
- node.id === tab
147
- ? navigationSidebarState === 'expanded'
148
- ? isLg
88
+ node.id === activeTab
89
+ ? currentSidebarState === 'expanded'
90
+ ? latestIsLg
149
91
  ? 'collapsed'
150
92
  : 'closed'
151
93
  : 'expanded'
152
94
  : 'expanded',
153
95
  });
154
96
 
155
- invokeSync(Common.LayoutOperation.SwitchWorkspace, { subject: node.id });
97
+ invokeSync(LayoutOperation.SwitchWorkspace, { subject: node.id });
156
98
 
157
99
  // Open the first item if the workspace is empty.
158
- if (layout.active.length === 0) {
100
+ if (activeItems.length === 0) {
159
101
  const [item] = getItems(graph, node).filter((node) => !Node.isActionLike(node));
160
102
  if (item && item.data) {
161
- invokeSync(Common.LayoutOperation.Open, { subject: [item.id] });
103
+ invokeSync(LayoutOperation.Open, { subject: [item.id] });
162
104
  }
163
105
  }
164
106
  },
165
- [invokeSync, layout.active, tab, navigationSidebarState, isLg],
107
+ [invokeSync, graph],
166
108
  );
167
109
 
168
110
  const blockInstruction = useCallback(
@@ -187,20 +129,20 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
187
129
  }
188
130
 
189
131
  if (Node.isAction(node)) {
190
- const [parent] = Graph.getConnections(graph, node.id, 'inbound');
132
+ const [parent] = Graph.getConnections(graph, node.id, Node.childRelation('inbound'));
191
133
  if (parent) {
192
- void runAction(node, { parent, caller: NAV_TREE_ITEM });
134
+ void runAction(node, { parent, path, caller: NAV_TREE_ITEM });
193
135
  }
194
136
  return;
195
137
  }
196
138
 
197
139
  const current = getItem(path).current;
198
140
  if (!current) {
199
- invokeSync(Common.LayoutOperation.Open, { subject: [node.id], key: node.properties.key });
141
+ invokeSync(LayoutOperation.Open, { subject: [node.id], key: node.properties.key });
200
142
  } else if (option) {
201
- invokeSync(Common.LayoutOperation.Close, { subject: [node.id] });
143
+ invokeSync(LayoutOperation.Close, { subject: [node.id] });
202
144
  } else {
203
- void invokePromise(Common.LayoutOperation.ScrollIntoView, { subject: node.id });
145
+ void invokePromise(LayoutOperation.ScrollIntoView, { subject: node.id });
204
146
  }
205
147
 
206
148
  const defaultAction = Graph.getActions(graph, node.id).find(
@@ -211,13 +153,13 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
211
153
  }
212
154
 
213
155
  if (!isLg) {
214
- invokeSync(Common.LayoutOperation.UpdateSidebar, { state: 'closed' });
156
+ invokeSync(LayoutOperation.UpdateSidebar, { state: 'closed' });
215
157
  }
216
158
  },
217
- [graph, invokeSync, invokePromise, getItem, isLg, runAction],
159
+ [graph, invokeSync, invokePromise, getItem, runAction, isLg],
218
160
  );
219
161
 
220
- const handleBack = useCallback(() => invokeSync(Common.LayoutOperation.RevertWorkspace), [invokeSync]);
162
+ const handleBack = useCallback(() => invokeSync(LayoutOperation.RevertWorkspace), [invokeSync]);
221
163
 
222
164
  // TODO(wittjosiah): Factor out hook.
223
165
  useEffect(() => {
@@ -235,8 +177,9 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
235
177
  const targetNode = target.data.item as NavTreeItemGraphNode;
236
178
  const sourcePath = source.data.path as string[];
237
179
  const targetPath = target.data.path as string[];
180
+ const sameParent = sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join();
238
181
  const operation =
239
- sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join() && instruction.type !== 'make-child'
182
+ sameParent && instruction.type !== 'make-child'
240
183
  ? 'rearrange'
241
184
  : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
242
185
  const sourceParent = getParent(graph, sourceNode, sourcePath);
@@ -265,7 +208,7 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
265
208
  }
266
209
  case 'transfer': {
267
210
  const target = instruction.type === 'make-child' ? targetNode : targetParent;
268
- if (!target?.properties.onTransferStart || !sourceParent?.properties.onTransferEnd) {
211
+ if (!target?.properties.onTransferStart || target?.id === sourceParent?.id) {
269
212
  break;
270
213
  }
271
214
  void target?.properties.onTransferStart(sourceNode, migrationIndex);
@@ -285,50 +228,41 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
285
228
  [setItem],
286
229
  );
287
230
 
231
+ const onItemHover = useCallback(({ item }: { item: Node.Node }) => Graph.expand(graph, item.id, 'child'), [graph]);
232
+
288
233
  const navTreeContextValue = useMemo(
289
234
  () => ({
235
+ model,
236
+ popoverAnchorId,
237
+ renderItemEnd: NavTreeItemEnd,
290
238
  blockInstruction,
291
239
  canDrop,
292
240
  canSelect,
293
- getProps,
294
- useIsAlternateTree,
295
- useIsCurrent,
296
- useIsOpen,
297
- loadDescendents,
241
+ setAlternateTree,
298
242
  onBack: handleBack,
299
243
  onOpenChange: handleOpenChange,
300
244
  onSelect: handleSelect,
301
245
  onTabChange: handleTabChange,
302
- popoverAnchorId,
303
- renderItemEnd,
304
- setAlternateTree,
305
- tab,
306
- topbar,
307
- useActions,
308
- useItems,
246
+ onItemHover,
309
247
  }),
310
248
  [
249
+ model,
250
+ popoverAnchorId,
311
251
  blockInstruction,
312
252
  canDrop,
313
253
  canSelect,
314
- getProps,
254
+ setAlternateTree,
315
255
  handleBack,
316
256
  handleOpenChange,
317
257
  handleSelect,
318
258
  handleTabChange,
319
- loadDescendents,
320
- popoverAnchorId,
321
- renderItemEnd,
322
- setAlternateTree,
323
- tab,
324
- topbar,
325
- useActions,
259
+ onItemHover,
326
260
  ],
327
261
  );
328
262
 
329
263
  return (
330
264
  <NavTreeContext.Provider value={navTreeContextValue}>
331
- <NavTree id={Node.RootId} root={Graph.getRoot(graph)} open={layout.sidebarOpen} ref={forwardedRef} />
265
+ <NavTree id={Node.RootId} root={Graph.getRoot(graph)} tab={tab} open={layout.sidebarOpen} ref={forwardedRef} />
332
266
  </NavTreeContext.Provider>
333
267
  );
334
268
  },
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export { NavTreeContainer, NODE_TYPE } from './NavTreeContainer';
6
+ export { NavTreeContainer as default } from './NavTreeContainer';
@@ -7,7 +7,7 @@ import { type Node } from '@dxos/app-graph';
7
7
  import { toLocalizedString, useTranslation } from '@dxos/react-ui';
8
8
  import { osTranslations } from '@dxos/ui-theme';
9
9
 
10
- import { meta } from '../meta';
10
+ import { meta } from '../../meta';
11
11
 
12
12
  export const NavTreeDocumentTitle = ({ node }: { node?: Node.Node }) => {
13
13
  const { t } = useTranslation(meta.id);
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { NavTreeDocumentTitle } from './NavTreeDocumentTitle';
6
+
7
+ export default NavTreeDocumentTitle;
@@ -0,0 +1,12 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type ComponentType, lazy } from 'react';
6
+
7
+ export { NODE_TYPE } from './NavTreeContainer';
8
+
9
+ export const CommandsDialogContent: ComponentType<any> = lazy(() => import('./CommandsDialogContent'));
10
+ export const CommandsTrigger: ComponentType<any> = lazy(() => import('./CommandsTrigger'));
11
+ export const NavTreeContainer: ComponentType<any> = lazy(() => import('./NavTreeContainer'));
12
+ export const NavTreeDocumentTitle: ComponentType<any> = lazy(() => import('./NavTreeDocumentTitle'));
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { type Meta } from '@storybook/react-vite';
6
- import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
6
+ import React, { useEffect, useState } from 'react';
7
7
 
8
8
  import { faker } from '@dxos/random';
9
- import { Icon, IconButton } from '@dxos/react-ui';
9
+ import { Icon, IconButton, Panel, ScrollArea } from '@dxos/react-ui';
10
10
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
  import { mx } from '@dxos/ui-theme';
12
12
 
@@ -14,28 +14,6 @@ import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visi
14
14
 
15
15
  faker.seed(1234);
16
16
 
17
- /**
18
- * Space:
19
- * - Space panels are separated from each other to give them more weight than folders.
20
- * - Private space isn't different (just first, different icon).
21
- * - Edit titles in place.
22
- *
23
- * Navigation:
24
- * - up/down across spaces/folder peers
25
- * - right to enter space/folder children
26
- * - left to parent
27
- */
28
- const Container = ({ children, sidebar }: PropsWithChildren<{ sidebar: JSX.Element }>) => {
29
- return (
30
- <div className='flex'>
31
- {/* TODO(burdon): Custom thin scrollbar. */}
32
- {/* TODO(burdon): Horizontal scrolling within navtree? */}
33
- <div className='flex flex-col overflow-y-auto is-[300px] bg-neutral-100 dark:bg-neutral-950'>{sidebar}</div>
34
- <div className='flex flex-col grow overflow-hidden'>{children}</div>
35
- </div>
36
- );
37
- };
38
-
39
17
  // TODO(burdon): Generate.
40
18
  const data: TreeNodeData[] = [
41
19
  {
@@ -134,7 +112,7 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
134
112
  }
135
113
  }, []);
136
114
 
137
- const handleCreateItem: TreeProps['onMenuAction'] = (id, action) => {
115
+ const handleCreateItem: TreeProps['onMenuAction'] = (id) => {
138
116
  setItems((items) => {
139
117
  let parent: TreeNodeData | undefined;
140
118
  visitNodes(root, (node) => {
@@ -167,49 +145,68 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
167
145
  };
168
146
 
169
147
  return (
170
- <div className='flex flex-col overflow-hidden'>
171
- <div className='flex flex-col overflow-y-auto'>
172
- <Tree
173
- className='p-0.5 gap-1'
174
- node={root}
175
- open={openItems}
176
- selected={selectedItems}
177
- active={activeItems}
178
- onChangeOpen={(id, open) => setOpenItems((items) => ({ ...items, [id]: open }))}
179
- onChangeSelected={(id, open) => setSelectedItems((items) => ({ ...items, [id]: open }))}
180
- onMenuAction={handleCreateItem}
181
- getSlots={(node, open, depth, ancestors) => {
182
- if (depth === 1) {
183
- return {
184
- root: 'rounded bg-white dark:bg-neutral-850',
185
- header: mx('rounded-t bg-neutral-50 dark:bg-neutral-900', !open && 'rounded-b'),
186
- };
187
- }
188
- }}
189
- />
190
- </div>
191
-
192
- <div className='flex items-center my-2 pli-2 gap-2'>
193
- <IconButton icon='ph--plus-circle--regular' iconOnly label='Create space' onClick={handleCreateSpace} />
194
- <span className='grow text-sm' onClick={handleCreateSpace}>
195
- New space
196
- </span>
197
- <Icon icon='ph--list--regular' />
198
- </div>
199
- </div>
148
+ <Panel.Root>
149
+ <Panel.Content asChild>
150
+ <ScrollArea.Root orientation='vertical' thin>
151
+ <ScrollArea.Viewport>
152
+ <Tree
153
+ className='p-0.5 gap-1'
154
+ node={root}
155
+ open={openItems}
156
+ selected={selectedItems}
157
+ active={activeItems}
158
+ onChangeOpen={(id, open) => setOpenItems((items) => ({ ...items, [id]: open }))}
159
+ onChangeSelected={(id, open) => setSelectedItems((items) => ({ ...items, [id]: open }))}
160
+ onMenuAction={handleCreateItem}
161
+ getSlots={(node, open, depth) => {
162
+ if (depth === 1) {
163
+ return {
164
+ root: 'rounded-sm bg-white dark:bg-neutral-850',
165
+ header: mx('rounded-t bg-neutral-50 dark:bg-neutral-900', !open && 'rounded-b'),
166
+ };
167
+ }
168
+ }}
169
+ />
170
+ </ScrollArea.Viewport>
171
+ </ScrollArea.Root>
172
+ </Panel.Content>
173
+ <Panel.Statusbar>
174
+ <div className='flex items-center my-2 px-2 gap-2'>
175
+ <IconButton icon='ph--plus-circle--regular' iconOnly label='Create space' onClick={handleCreateSpace} />
176
+ <span className='grow text-sm' onClick={handleCreateSpace}>
177
+ New space
178
+ </span>
179
+ <Icon icon='ph--list--regular' />
180
+ </div>
181
+ </Panel.Statusbar>
182
+ </Panel.Root>
200
183
  );
201
184
  };
202
185
 
203
186
  const meta = {
204
187
  title: 'plugins/plugin-navtree/experimental/Tree',
205
188
  component: Tree,
206
- decorators: [withTheme, withLayout({ layout: 'column' })],
189
+ decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'w-[20rem]' })],
190
+ parameters: {
191
+ layout: 'fullscreen',
192
+ },
207
193
  } satisfies Meta<typeof Tree>;
208
194
 
209
195
  export default meta;
210
196
 
197
+ /**
198
+ * Space:
199
+ * - Space panels are separated from each other to give them more weight than folders.
200
+ * - Private space isn't different (just first, different icon).
201
+ * - Edit titles in place.
202
+ *
203
+ * Navigation:
204
+ * - up/down across spaces/folder peers
205
+ * - right to enter space/folder children
206
+ * - left to parent
207
+ */
211
208
  export const Default = () => {
212
- return <Container sidebar={<Sidebar mutate />} />;
209
+ return <Sidebar mutate />;
213
210
  };
214
211
 
215
212
  export const Visitor = () => {
@@ -19,7 +19,7 @@ export const IconButton = ({
19
19
  } & Pick<HTMLAttributes<HTMLDivElement>, 'onClick'>) => {
20
20
  // TODO(burdon): Density aware.
21
21
  return (
22
- <div className={mx('flex is-6 bs-6 items-center justify-center select-none', classNames)} onClick={onClick}>
22
+ <div className={mx('flex w-6 h-6 items-center justify-center select-none', classNames)} onClick={onClick}>
23
23
  <Icon icon={iconName} classNames='cursor-pointer' size={size} />
24
24
  </div>
25
25
  );
@@ -193,9 +193,9 @@ export const TreeNodeRow = (props: TreeNodeProps & { className?: string }) => {
193
193
  <Grid
194
194
  style={{ paddingLeft: `${(depth - 1) * 24}px` }}
195
195
  className={mx(
196
- 'group is-full items-center cursor-pointer bg-hoverSurface',
196
+ 'group w-full items-center cursor-pointer bg-hover-surface',
197
197
  // TODO(burdon): Use data-active.
198
- selected?.[id] && 'bg-activeSurface',
198
+ selected?.[id] && 'bg-active-surface',
199
199
  className,
200
200
  )}
201
201
  onClick={() => selected && onChangeSelected?.(id, !selected[id])}
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './useActions';
6
+ export * from './useLoadDescendents';
7
+ export * from './useNavTreeModel';
8
+ export * from './useNavTreeState';
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo } from 'react';
6
+
7
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
8
+ import { Graph, Node, useActions as useGraphActions } from '@dxos/plugin-graph';
9
+
10
+ import { type FlattenedActions } from '../types';
11
+
12
+ /** Returns flattened actions and grouped sub-actions for a given graph node. */
13
+ export const useActions = (node: Node.Node): FlattenedActions => {
14
+ const { graph } = useAppGraph();
15
+ const actions = useGraphActions(graph, node.id);
16
+
17
+ return useMemo(() => {
18
+ return actions.reduce(
19
+ (acc: FlattenedActions, arg) => {
20
+ if (arg.properties.disposition === 'item') {
21
+ return acc;
22
+ }
23
+
24
+ acc.actions.push(arg);
25
+ if (!Node.isAction(arg)) {
26
+ const actionGroup = Graph.getActions(graph, arg.id);
27
+ acc.groupedActions[arg.id] = actionGroup;
28
+ }
29
+ return acc;
30
+ },
31
+ { actions: [], groupedActions: {} },
32
+ );
33
+ }, [actions]);
34
+ };
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useEffect } from 'react';
6
+
7
+ import { type Node } from '@dxos/app-graph';
8
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
9
+ import { Graph } from '@dxos/plugin-graph';
10
+
11
+ /**
12
+ * Expands a root node in the app graph.
13
+ */
14
+ export const useLoadDescendents = (root?: Node.Node) => {
15
+ const { graph } = useAppGraph();
16
+ useEffect(() => {
17
+ if (!root) {
18
+ return;
19
+ }
20
+
21
+ Graph.expand(graph, root.id, 'child');
22
+ }, [graph, root]);
23
+ };