@dxos/plugin-navtree 0.8.3 → 0.8.4-main.1068cf700f

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 (185) hide show
  1. package/dist/lib/browser/app-graph-builder-XGA4GPGL.mjs +44 -0
  2. package/dist/lib/browser/app-graph-builder-XGA4GPGL.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-63YLDLLE.mjs +1373 -0
  4. package/dist/lib/browser/chunk-63YLDLLE.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-X2HDG62A.mjs +23 -0
  6. package/dist/lib/browser/chunk-X2HDG62A.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-YKTYPINZ.mjs +19 -0
  8. package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +89 -100
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/{keyboard-DDFATRUG.mjs → keyboard-46XQHY2O.mjs} +21 -19
  12. package/dist/lib/browser/keyboard-46XQHY2O.mjs.map +7 -0
  13. package/dist/lib/browser/meta.json +1 -1
  14. package/dist/lib/browser/operation-resolver-NNNDII45.mjs +53 -0
  15. package/dist/lib/browser/operation-resolver-NNNDII45.mjs.map +7 -0
  16. package/dist/lib/browser/react-surface-APSEG23H.mjs +59 -0
  17. package/dist/lib/browser/react-surface-APSEG23H.mjs.map +7 -0
  18. package/dist/lib/browser/state-M5YUCIQU.mjs +121 -0
  19. package/dist/lib/browser/state-M5YUCIQU.mjs.map +7 -0
  20. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  21. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  23. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  25. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  26. package/dist/types/src/capabilities/index.d.ts +5 -105
  27. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  28. package/dist/types/src/capabilities/keyboard/index.d.ts +3 -0
  29. package/dist/types/src/capabilities/keyboard/index.d.ts.map +1 -0
  30. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +5 -0
  31. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
  33. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
  35. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  37. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  39. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  40. package/dist/types/src/capabilities/state/index.d.ts +3 -0
  41. package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/state/state.d.ts +6 -0
  43. package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
  44. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +3 -2
  45. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
  46. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  47. package/dist/types/src/components/NavTree/NavTree.d.ts +32 -3
  48. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  49. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +29 -4
  50. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/NavTreeContainer.d.ts +8 -1
  52. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  53. package/dist/types/src/components/NavTreeDocumentTitle.d.ts +1 -1
  54. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +6 -6
  56. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  57. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +33 -4
  58. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +2 -2
  60. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/L0Menu.d.ts +6 -4
  62. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  63. package/dist/types/src/components/Sidebar/L1Panel.d.ts +6 -7
  64. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  65. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +12 -0
  66. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
  67. package/dist/types/src/components/Sidebar/index.d.ts +1 -0
  68. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -1
  69. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  70. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  71. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  72. package/dist/types/src/components/index.d.ts +1 -1
  73. package/dist/types/src/components/index.d.ts.map +1 -1
  74. package/dist/types/src/components/types.d.ts +8 -8
  75. package/dist/types/src/components/types.d.ts.map +1 -1
  76. package/dist/types/src/experimental/Tree.d.ts +4 -5
  77. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  78. package/dist/types/src/experimental/Tree.stories.d.ts +23 -6
  79. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  80. package/dist/types/src/hooks.d.ts +32 -1
  81. package/dist/types/src/hooks.d.ts.map +1 -1
  82. package/dist/types/src/index.d.ts +1 -2
  83. package/dist/types/src/index.d.ts.map +1 -1
  84. package/dist/types/src/meta.d.ts +3 -4
  85. package/dist/types/src/meta.d.ts.map +1 -1
  86. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  87. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  88. package/dist/types/src/testing/generator.d.ts +5 -6
  89. package/dist/types/src/testing/generator.d.ts.map +1 -1
  90. package/dist/types/src/testing/update-state.d.ts +2 -2
  91. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  92. package/dist/types/src/translations.d.ts +14 -13
  93. package/dist/types/src/translations.d.ts.map +1 -1
  94. package/dist/types/src/types/capabilities.d.ts +16 -0
  95. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  96. package/dist/types/src/types/events.d.ts +5 -0
  97. package/dist/types/src/types/events.d.ts.map +1 -0
  98. package/dist/types/src/types/index.d.ts +4 -0
  99. package/dist/types/src/types/index.d.ts.map +1 -0
  100. package/dist/types/src/{types.d.ts → types/schema.d.ts} +10 -9
  101. package/dist/types/src/types/schema.d.ts.map +1 -0
  102. package/dist/types/src/util.d.ts +8 -9
  103. package/dist/types/src/util.d.ts.map +1 -1
  104. package/dist/types/tsconfig.tsbuildinfo +1 -1
  105. package/package.json +56 -55
  106. package/src/NavTreePlugin.tsx +64 -84
  107. package/src/capabilities/app-graph-builder/app-graph-builder.ts +42 -0
  108. package/src/capabilities/app-graph-builder/index.ts +7 -0
  109. package/src/capabilities/index.ts +5 -9
  110. package/src/capabilities/keyboard/index.ts +7 -0
  111. package/src/capabilities/keyboard/keyboard.ts +66 -0
  112. package/src/capabilities/operation-resolver/index.ts +7 -0
  113. package/src/capabilities/operation-resolver/operation-resolver.ts +42 -0
  114. package/src/capabilities/react-surface/index.ts +7 -0
  115. package/src/capabilities/react-surface/react-surface.tsx +55 -0
  116. package/src/capabilities/state/index.ts +7 -0
  117. package/src/capabilities/state/state.ts +122 -0
  118. package/src/components/CommandsDialog/CommandsDialog.tsx +108 -98
  119. package/src/components/CommandsDialog/CommandsTrigger.tsx +8 -19
  120. package/src/components/NavTree/NavTree.stories.tsx +100 -94
  121. package/src/components/NavTree/NavTree.tsx +12 -18
  122. package/src/components/NavTreeContainer.tsx +283 -259
  123. package/src/components/NavTreeDocumentTitle.tsx +5 -4
  124. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +21 -13
  125. package/src/components/NavTreeItem/NavTreeItemAction.tsx +28 -21
  126. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +38 -32
  127. package/src/components/Sidebar/L0Menu.tsx +115 -143
  128. package/src/components/Sidebar/L1Panel.tsx +177 -122
  129. package/src/components/Sidebar/L1Tabs.tsx +33 -0
  130. package/src/components/Sidebar/index.ts +1 -0
  131. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -8
  132. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  133. package/src/components/index.ts +2 -1
  134. package/src/components/types.ts +9 -8
  135. package/src/experimental/Tree.stories.tsx +77 -85
  136. package/src/experimental/Tree.tsx +17 -35
  137. package/src/hooks.ts +54 -2
  138. package/src/index.ts +1 -2
  139. package/src/meta.ts +11 -8
  140. package/src/testing/app-graph-builder.ts +159 -234
  141. package/src/testing/generator.ts +20 -18
  142. package/src/testing/update-state.ts +6 -6
  143. package/src/translations.ts +7 -4
  144. package/src/types/capabilities.ts +21 -0
  145. package/src/types/events.ts +12 -0
  146. package/src/types/index.ts +7 -0
  147. package/src/{types.ts → types/schema.ts} +9 -8
  148. package/src/util.ts +14 -35
  149. package/dist/lib/browser/app-graph-builder-P7AY3K72.mjs +0 -45
  150. package/dist/lib/browser/app-graph-builder-P7AY3K72.mjs.map +0 -7
  151. package/dist/lib/browser/chunk-33VLL4IJ.mjs +0 -15
  152. package/dist/lib/browser/chunk-33VLL4IJ.mjs.map +0 -7
  153. package/dist/lib/browser/chunk-IEW3JD5C.mjs +0 -16
  154. package/dist/lib/browser/chunk-IEW3JD5C.mjs.map +0 -7
  155. package/dist/lib/browser/chunk-ZJPL4XJD.mjs +0 -1429
  156. package/dist/lib/browser/chunk-ZJPL4XJD.mjs.map +0 -7
  157. package/dist/lib/browser/intent-resolver-NAYKKAOP.mjs +0 -47
  158. package/dist/lib/browser/intent-resolver-NAYKKAOP.mjs.map +0 -7
  159. package/dist/lib/browser/keyboard-DDFATRUG.mjs.map +0 -7
  160. package/dist/lib/browser/react-surface-H3JRTVNU.mjs +0 -51
  161. package/dist/lib/browser/react-surface-H3JRTVNU.mjs.map +0 -7
  162. package/dist/lib/browser/state-J6AKZRB4.mjs +0 -117
  163. package/dist/lib/browser/state-J6AKZRB4.mjs.map +0 -7
  164. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
  165. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  166. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  167. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  168. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  169. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  170. package/dist/types/src/capabilities/keyboard.d.ts +0 -4
  171. package/dist/types/src/capabilities/keyboard.d.ts.map +0 -1
  172. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  173. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  174. package/dist/types/src/capabilities/state.d.ts +0 -20
  175. package/dist/types/src/capabilities/state.d.ts.map +0 -1
  176. package/dist/types/src/events.d.ts +0 -4
  177. package/dist/types/src/events.d.ts.map +0 -1
  178. package/dist/types/src/types.d.ts.map +0 -1
  179. package/src/capabilities/app-graph-builder.ts +0 -52
  180. package/src/capabilities/capabilities.ts +0 -20
  181. package/src/capabilities/intent-resolver.ts +0 -37
  182. package/src/capabilities/keyboard.ts +0 -59
  183. package/src/capabilities/react-surface.tsx +0 -45
  184. package/src/capabilities/state.ts +0 -104
  185. package/src/events.ts +0 -11
@@ -3,66 +3,86 @@
3
3
  //
4
4
 
5
5
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
- import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
7
- import { untracked } from '@preact/signals-core';
8
- import React, { memo, useCallback, useEffect, useMemo } from 'react';
9
-
10
- import {
11
- createIntent,
12
- LayoutAction,
13
- Surface,
14
- useAppGraph,
15
- useCapability,
16
- useIntentDispatcher,
17
- useLayout,
18
- } from '@dxos/app-framework';
19
- import { isAction, isActionLike, ROOT_ID, type Node, type ReadableGraph } from '@dxos/app-graph';
6
+ import { type Instruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
7
+ import React, { forwardRef, memo, useCallback, useEffect, useMemo } from 'react';
8
+
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';
20
12
  import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
21
- import { useConnections } from '@dxos/plugin-graph';
13
+ import { Graph, Node, useActionRunner } from '@dxos/plugin-graph';
14
+ import { useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
22
15
  import { useMediaQuery, useSidebars } from '@dxos/react-ui';
23
- import { isTreeData, type TreeItemDataProps, type TreeData } from '@dxos/react-ui-list';
24
- import { mx } from '@dxos/react-ui-theme';
16
+ import { type TreeData, type TreeItemDataProps, isTreeData } from '@dxos/react-ui-list';
17
+ import { mx } from '@dxos/ui-theme';
25
18
  import { arrayMove, byPosition } from '@dxos/util';
26
19
 
20
+ import { useIsAlternateTree, useIsCurrent, useIsOpen, useNavTreeState } from '../hooks';
21
+ import { meta } from '../meta';
22
+ import { type FlattenedActions, type NavTreeItemGraphNode } from '../types';
23
+ import { getChildren, getParent, resolveMigrationOperation } from '../util';
24
+
27
25
  import { NAV_TREE_ITEM, NavTree } from './NavTree';
28
26
  import { NavTreeContext } from './NavTreeContext';
29
27
  import { type NavTreeContextValue } from './types';
30
- import { NavTreeCapabilities } from '../capabilities';
31
- import { NAVTREE_PLUGIN } from '../meta';
32
- import { type NavTreeItemGraphNode } from '../types';
33
- import { getChildren, getParent, getActions as naturalGetActions, resolveMigrationOperation } from '../util';
34
28
 
35
29
  // TODO(thure): Is NavTree truly authoritative in this regard?
36
30
  export const NODE_TYPE = 'dxos/app-graph/node';
37
31
 
38
- const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
39
- <Surface role='navtree-item-end' data={{ id: node.id, subject: node.data, open }} limit={1} />
32
+ const renderItemEnd = ({ node, open }: { node: Node.Node; open: boolean }) => (
33
+ <Surface.Surface role='navtree-item-end' data={{ id: node.id, subject: node.data, open }} limit={1} />
40
34
  );
41
35
 
42
- const getChildrenFilter = (node: Node): node is Node =>
43
- untracked(() => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE);
36
+ const getChildrenFilter = (node: Node.Node): node is Node.Node =>
37
+ !Node.isActionLike(node) && node.type !== PLANK_COMPANION_TYPE && node.properties.disposition !== 'hidden';
44
38
 
45
- const filterItems = (node: Node, disposition?: string) => {
39
+ const filterItems = (node: Node.Node, disposition?: string) => {
46
40
  if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
47
41
  return false;
48
42
  } else if (!disposition) {
49
- const action = isAction(node);
43
+ const action = Node.isAction(node);
50
44
  return !action || node.properties.disposition === 'item';
51
45
  } else {
52
46
  return node.properties.disposition === disposition;
53
47
  }
54
48
  };
55
49
 
56
- const getItems = (graph: ReadableGraph, node?: Node, disposition?: string) => {
57
- return graph.getConnections(node?.id ?? ROOT_ID, 'outbound').filter((node) => filterItems(node, disposition));
50
+ const getItems = (graph: Graph.ReadableGraph, node?: Node.Node, disposition?: string) => {
51
+ return Graph.getConnections(graph, node?.id ?? Node.RootId, 'outbound').filter((node) =>
52
+ filterItems(node, disposition),
53
+ );
54
+ };
55
+
56
+ const useItems = (node?: Node.Node, options?: { disposition?: string; sort?: boolean }) => {
57
+ const { graph } = useAppGraph();
58
+ const connections = useConnections(graph, node?.id ?? Node.RootId);
59
+ const filtered = connections.filter((node) => filterItems(node, options?.disposition));
60
+ return options?.sort ? filtered.toSorted((a, b) => byPosition(a.properties, b.properties)) : filtered;
58
61
  };
59
62
 
60
- const useItems = (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
63
+ const useActions = (node: Node.Node): FlattenedActions => {
61
64
  const { graph } = useAppGraph();
62
- const connections = useConnections(graph, node?.id ?? ROOT_ID).filter((node) =>
63
- filterItems(node, options?.disposition),
65
+ const actions = useGraphActions(graph, node.id);
66
+
67
+ return useMemo(
68
+ () =>
69
+ actions.reduce(
70
+ (acc: FlattenedActions, arg) => {
71
+ if (arg.properties.disposition === 'item') {
72
+ return acc;
73
+ }
74
+
75
+ acc.actions.push(arg);
76
+ if (!Node.isAction(arg)) {
77
+ const actionGroup = Graph.getActions(graph, arg.id);
78
+ acc.groupedActions[arg.id] = actionGroup;
79
+ }
80
+ return acc;
81
+ },
82
+ { actions: [], groupedActions: {} },
83
+ ),
84
+ [actions],
64
85
  );
65
- return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
66
86
  };
67
87
 
68
88
  export type NavTreeContainerProps = {
@@ -70,245 +90,249 @@ export type NavTreeContainerProps = {
70
90
  topbar?: boolean;
71
91
  } & Pick<NavTreeContextValue, 'tab'>;
72
92
 
73
- export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeContainerProps) => {
74
- const [isLg] = useMediaQuery('lg', { ssr: false });
75
- const { dispatchPromise: dispatch } = useIntentDispatcher();
76
- const { graph } = useAppGraph();
77
- const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
78
- const layout = useLayout();
79
- const { navigationSidebarState } = useSidebars(NAVTREE_PLUGIN);
80
-
81
- const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
82
-
83
- const getProps = useCallback(
84
- (node: Node, path: string[]): TreeItemDataProps => {
85
- const children = getChildren(graph, node, path).filter(getChildrenFilter);
86
- const parentOf =
87
- children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
88
- return {
89
- id: node.id,
90
- label: node.properties.label ?? node.id,
91
- parentOf,
92
- icon: node.properties.icon,
93
- disabled: node.properties.disabled,
94
- className: mx(node.properties.modified && 'italic', node.properties.className),
95
- headingClassName: node.properties.headingClassName,
96
- testId: node.properties.testId,
97
- };
98
- },
99
- [graph],
100
- );
93
+ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProps>(
94
+ ({ tab, popoverAnchorId, topbar }, forwardedRef) => {
95
+ const [isLg] = useMediaQuery('lg');
96
+ const { invokeSync, invokePromise } = useOperationInvoker();
97
+ const runAction = useActionRunner();
98
+ const { graph } = useAppGraph();
99
+ const { getItem, setItem } = useNavTreeState();
100
+ const layout = useLayout();
101
+ const { navigationSidebarState } = useSidebars(meta.id);
101
102
 
102
- const loadDescendents = useCallback(
103
- (node: Node) => {
104
- graph.expand(node.id, 'outbound');
105
- // Load one level deeper, which resolves some juddering observed on open/close.
106
- graph.getConnections(node.id, 'outbound').forEach((child) => {
107
- graph.expand(child.id, 'outbound');
108
- });
109
- },
110
- [graph],
111
- );
103
+ const getProps = useCallback(
104
+ (node: Node.Node, path: string[]): TreeItemDataProps => {
105
+ const children = getChildren(graph, node, path).filter(getChildrenFilter);
106
+ const parentOf =
107
+ children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
108
+ return {
109
+ id: node.id,
110
+ parentOf,
111
+ disabled: node.properties.disabled,
112
+ label: node.properties.label ?? node.id,
113
+ className: mx(node.properties.className, node.properties.modified && 'italic'), // TODO(burdon): Italic?
114
+ headingClassName: node.properties.headingClassName,
115
+ icon: node.properties.icon,
116
+ iconHue: node.properties.iconHue,
117
+ testId: node.properties.testId,
118
+ };
119
+ },
120
+ [graph],
121
+ );
112
122
 
113
- const handleOpenChange = useCallback(
114
- ({ item: { id }, path, open }: { item: Node; path: string[]; open: boolean }) => {
115
- // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
116
- setItem(path, 'open', open);
117
- graph.expand(id, 'outbound');
118
- },
119
- [graph],
120
- );
123
+ const loadDescendents = useCallback(
124
+ (node: Node.Node) => {
125
+ Graph.expand(graph, node.id, 'outbound');
126
+ // Load one level deeper, which resolves some juddering observed on open/close.
127
+ Graph.getConnections(graph, node.id, 'outbound').forEach((child) => {
128
+ Graph.expand(graph, child.id, 'outbound');
129
+ });
130
+ },
131
+ [graph],
132
+ );
133
+
134
+ const handleOpenChange = useCallback(
135
+ ({ item: { id }, path, open }: { item: Node.Node; path: string[]; open: boolean }) => {
136
+ // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
137
+ setItem(path, 'open', open);
138
+ Graph.expand(graph, id, 'outbound');
139
+ },
140
+ [graph],
141
+ );
121
142
 
122
- const handleTabChange = useCallback(
123
- async (node: NavTreeItemGraphNode) => {
124
- await dispatch(
125
- createIntent(LayoutAction.UpdateSidebar, {
126
- part: 'sidebar',
127
- options: {
128
- state:
129
- node.id === tab
130
- ? navigationSidebarState === 'expanded'
131
- ? isLg
132
- ? 'collapsed'
133
- : 'closed'
134
- : 'expanded'
135
- : 'expanded',
136
- },
137
- }),
138
- );
139
-
140
- await dispatch(createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: node.id }));
141
-
142
- // Open the first item if the workspace is empty.
143
- if (layout.active.length === 0) {
144
- const [item] = getItems(graph, node).filter((node) => !isActionLike(node));
145
- if (item && item.data) {
146
- await dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [item.id] }));
143
+ const handleTabChange = useCallback(
144
+ (node: NavTreeItemGraphNode) => {
145
+ invokeSync(LayoutOperation.UpdateSidebar, {
146
+ state:
147
+ node.id === tab
148
+ ? navigationSidebarState === 'expanded'
149
+ ? isLg
150
+ ? 'collapsed'
151
+ : 'closed'
152
+ : 'expanded'
153
+ : 'expanded',
154
+ });
155
+
156
+ invokeSync(LayoutOperation.SwitchWorkspace, { subject: node.id });
157
+
158
+ // Open the first item if the workspace is empty.
159
+ if (layout.active.length === 0) {
160
+ const [item] = getItems(graph, node).filter((node) => !Node.isActionLike(node));
161
+ if (item && item.data) {
162
+ invokeSync(LayoutOperation.Open, { subject: [item.id] });
163
+ }
147
164
  }
148
- }
149
- },
150
- [dispatch, layout.active, tab, navigationSidebarState, isLg],
151
- );
165
+ },
166
+ [invokeSync, layout.active, tab, navigationSidebarState, isLg],
167
+ );
152
168
 
153
- const canDrop = useCallback(({ source, target }: { source: TreeData; target: TreeData }) => {
154
- return target.item.properties.canDrop?.(source) ?? false;
155
- }, []);
156
-
157
- const handleSelect = useCallback(
158
- ({ item: node, path, option }: { item: Node; path: string[]; option: boolean }) => {
159
- if (!node.data) {
160
- return;
161
- }
162
-
163
- if (isAction(node)) {
164
- const [parent] = graph.getConnections(node.id, 'inbound');
165
- void (parent && node.data({ parent, caller: NAV_TREE_ITEM }));
166
- return;
167
- }
168
-
169
- const current = isCurrent(path, node);
170
- if (!current) {
171
- void dispatch(
172
- createIntent(LayoutAction.Open, {
173
- part: 'main',
174
- subject: [node.id],
175
- options: { key: node.properties.key },
176
- }),
177
- );
178
- } else if (option) {
179
- void dispatch(
180
- createIntent(LayoutAction.Close, { part: 'main', subject: [node.id], options: { state: false } }),
181
- );
182
- } else {
183
- void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: node.id }));
184
- }
185
-
186
- const defaultAction = graph.getActions(node.id).find((action) => action.properties?.disposition === 'default');
187
- if (isAction(defaultAction)) {
188
- void (defaultAction.data as () => void)();
189
- }
190
-
191
- if (!isLg) {
192
- void dispatch(createIntent(LayoutAction.UpdateSidebar, { part: 'sidebar', options: { state: 'closed' } }));
193
- }
194
- },
195
- [graph, dispatch, isCurrent, isLg],
196
- );
169
+ const blockInstruction = useCallback(
170
+ ({ instruction, source, target }: { instruction: Instruction; source: TreeData; target: TreeData }) => {
171
+ return target.item.properties.blockInstruction?.(source, instruction) ?? false;
172
+ },
173
+ [],
174
+ );
197
175
 
198
- const handleBack = useCallback(
199
- () => dispatch(createIntent(LayoutAction.RevertWorkspace, { part: 'workspace', options: { revert: true } })),
200
- [dispatch],
201
- );
176
+ const canDrop = useCallback(({ source, target }: { source: TreeData; target: TreeData }) => {
177
+ return target.item.properties.canDrop?.(source) ?? false;
178
+ }, []);
179
+
180
+ const canSelect = useCallback(({ item }: { item: Node.Node }) => {
181
+ return item.properties.selectable ?? true;
182
+ }, []);
202
183
 
203
- // TODO(wittjosiah): Factor out hook.
204
- useEffect(() => {
205
- return monitorForElements({
206
- canMonitor: ({ source }) => isTreeData(source.data),
207
- onDrop: ({ location, source }) => {
208
- // Didn't drop on anything.
209
- if (!location.current.dropTargets.length) {
184
+ const handleSelect = useCallback(
185
+ ({ item: node, path, option }: { item: Node.Node; path: string[]; option: boolean }) => {
186
+ if (!node.data) {
210
187
  return;
211
188
  }
212
- const target = location.current.dropTargets[0];
213
- const instruction: Instruction | null = extractInstruction(target.data);
214
- if (instruction !== null && instruction.type !== 'instruction-blocked') {
215
- const sourceNode = source.data.item as NavTreeItemGraphNode;
216
- const targetNode = target.data.item as NavTreeItemGraphNode;
217
- const sourcePath = source.data.path as string[];
218
- const targetPath = target.data.path as string[];
219
- const operation =
220
- sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join() && instruction.type !== 'make-child'
221
- ? 'rearrange'
222
- : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
223
- const sourceParent = getParent(graph, sourceNode, sourcePath);
224
- const targetParent = getParent(graph, targetNode, targetPath);
225
- const sourceItems = getItems(graph, sourceParent);
226
- const targetItems = getItems(graph, targetParent);
227
- const sourceIndex = sourceItems.findIndex(({ id }) => id === sourceNode.id);
228
- const targetIndex = targetItems.findIndex(({ id }) => id === targetNode.id);
229
- const migrationIndex =
230
- instruction.type === 'make-child'
231
- ? undefined
232
- : instruction.type === 'reorder-below'
233
- ? targetIndex + 1
234
- : targetIndex;
235
- switch (operation) {
236
- case 'rearrange': {
237
- const nextItems = sourceItems.map(({ data }) => data);
238
- arrayMove(nextItems, sourceIndex, targetIndex);
239
- void sourceParent?.properties.onRearrangeChildren?.(nextItems);
240
- break;
241
- }
242
- case 'copy': {
243
- const target = instruction.type === 'make-child' ? targetNode : targetParent;
244
- void target?.properties.onCopy?.(sourceNode, migrationIndex);
245
- break;
246
- }
247
- case 'transfer': {
248
- const target = instruction.type === 'make-child' ? targetNode : targetParent;
249
- if (!target?.properties.onTransferStart || !sourceParent?.properties.onTransferEnd) {
189
+
190
+ if (Node.isAction(node)) {
191
+ const [parent] = Graph.getConnections(graph, node.id, 'inbound');
192
+ if (parent) {
193
+ void runAction(node, { parent, caller: NAV_TREE_ITEM });
194
+ }
195
+ return;
196
+ }
197
+
198
+ const current = getItem(path).current;
199
+ if (!current) {
200
+ invokeSync(LayoutOperation.Open, { subject: [node.id], key: node.properties.key });
201
+ } else if (option) {
202
+ invokeSync(LayoutOperation.Close, { subject: [node.id] });
203
+ } else {
204
+ void invokePromise(LayoutOperation.ScrollIntoView, { subject: node.id });
205
+ }
206
+
207
+ const defaultAction = Graph.getActions(graph, node.id).find(
208
+ (action) => action.properties?.disposition === 'default',
209
+ );
210
+ if (Node.isAction(defaultAction)) {
211
+ void runAction(defaultAction);
212
+ }
213
+
214
+ if (!isLg) {
215
+ invokeSync(LayoutOperation.UpdateSidebar, { state: 'closed' });
216
+ }
217
+ },
218
+ [graph, invokeSync, invokePromise, getItem, isLg, runAction],
219
+ );
220
+
221
+ const handleBack = useCallback(() => invokeSync(LayoutOperation.RevertWorkspace), [invokeSync]);
222
+
223
+ // TODO(wittjosiah): Factor out hook.
224
+ useEffect(() => {
225
+ return monitorForElements({
226
+ canMonitor: ({ source }) => isTreeData(source.data),
227
+ onDrop: ({ location, source }) => {
228
+ // Didn't drop on anything.
229
+ if (!location.current.dropTargets.length) {
230
+ return;
231
+ }
232
+ const target = location.current.dropTargets[0];
233
+ const instruction: Instruction | null = extractInstruction(target.data);
234
+ if (instruction !== null && instruction.type !== 'instruction-blocked') {
235
+ const sourceNode = source.data.item as NavTreeItemGraphNode;
236
+ const targetNode = target.data.item as NavTreeItemGraphNode;
237
+ const sourcePath = source.data.path as string[];
238
+ const targetPath = target.data.path as string[];
239
+ const operation =
240
+ sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join() && instruction.type !== 'make-child'
241
+ ? 'rearrange'
242
+ : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
243
+ const sourceParent = getParent(graph, sourceNode, sourcePath);
244
+ const targetParent = getParent(graph, targetNode, targetPath);
245
+ const sourceItems = getItems(graph, sourceParent);
246
+ const targetItems = getItems(graph, targetParent);
247
+ const sourceIndex = sourceItems.findIndex(({ id }) => id === sourceNode.id);
248
+ const targetIndex = targetItems.findIndex(({ id }) => id === targetNode.id);
249
+ const migrationIndex =
250
+ instruction.type === 'make-child'
251
+ ? undefined
252
+ : instruction.type === 'reorder-below'
253
+ ? targetIndex + 1
254
+ : targetIndex;
255
+ switch (operation) {
256
+ case 'rearrange': {
257
+ const nextItems = sourceItems.map(({ data }) => data);
258
+ arrayMove(nextItems, sourceIndex, targetIndex);
259
+ void sourceNode.properties.onRearrange?.(nextItems);
260
+ break;
261
+ }
262
+ case 'copy': {
263
+ const target = instruction.type === 'make-child' ? targetNode : targetParent;
264
+ void target?.properties.onCopy?.(sourceNode, migrationIndex);
265
+ break;
266
+ }
267
+ case 'transfer': {
268
+ const target = instruction.type === 'make-child' ? targetNode : targetParent;
269
+ if (!target?.properties.onTransferStart || !sourceParent?.properties.onTransferEnd) {
270
+ break;
271
+ }
272
+ void target?.properties.onTransferStart(sourceNode, migrationIndex);
273
+ void sourceParent?.properties.onTransferEnd?.(sourceNode, target);
250
274
  break;
251
275
  }
252
- void target?.properties.onTransferStart(sourceNode, migrationIndex);
253
- void sourceParent?.properties.onTransferEnd?.(sourceNode, target);
254
- break;
255
276
  }
256
277
  }
257
- }
278
+ },
279
+ });
280
+ }, [graph]);
281
+
282
+ const setAlternateTree = useCallback(
283
+ (path: string[], open: boolean) => {
284
+ setItem(path, 'alternateTree', open);
258
285
  },
259
- });
260
- }, [graph]);
261
-
262
- const setAlternateTree = useCallback(
263
- (path: string[], open: boolean) => {
264
- setItem(path, 'alternateTree', open);
265
- },
266
- [setItem],
267
- );
286
+ [setItem],
287
+ );
268
288
 
269
- const navTreeContextValue = useMemo(
270
- () => ({
271
- useItems,
272
- tab,
273
- getActions,
274
- loadDescendents,
275
- renderItemEnd,
276
- popoverAnchorId,
277
- topbar,
278
- getProps,
279
- isCurrent,
280
- isOpen,
281
- canDrop,
282
- isAlternateTree,
283
- setAlternateTree,
284
- onTabChange: handleTabChange,
285
- onOpenChange: handleOpenChange,
286
- onSelect: handleSelect,
287
- onBack: handleBack,
288
- }),
289
- [
290
- tab,
291
- getActions,
292
- loadDescendents,
293
- renderItemEnd,
294
- popoverAnchorId,
295
- topbar,
296
- getProps,
297
- isCurrent,
298
- isOpen,
299
- canDrop,
300
- isAlternateTree,
301
- setAlternateTree,
302
- handleTabChange,
303
- handleOpenChange,
304
- handleSelect,
305
- handleBack,
306
- ],
307
- );
289
+ const navTreeContextValue = useMemo(
290
+ () => ({
291
+ blockInstruction,
292
+ canDrop,
293
+ canSelect,
294
+ getProps,
295
+ useIsAlternateTree,
296
+ useIsCurrent,
297
+ useIsOpen,
298
+ loadDescendents,
299
+ onBack: handleBack,
300
+ onOpenChange: handleOpenChange,
301
+ onSelect: handleSelect,
302
+ onTabChange: handleTabChange,
303
+ popoverAnchorId,
304
+ renderItemEnd,
305
+ setAlternateTree,
306
+ tab,
307
+ topbar,
308
+ useActions,
309
+ useItems,
310
+ }),
311
+ [
312
+ blockInstruction,
313
+ canDrop,
314
+ canSelect,
315
+ getProps,
316
+ handleBack,
317
+ handleOpenChange,
318
+ handleSelect,
319
+ handleTabChange,
320
+ loadDescendents,
321
+ popoverAnchorId,
322
+ renderItemEnd,
323
+ setAlternateTree,
324
+ tab,
325
+ topbar,
326
+ useActions,
327
+ ],
328
+ );
308
329
 
309
- return (
310
- <NavTreeContext.Provider value={navTreeContextValue}>
311
- <NavTree id={ROOT_ID} root={graph.root} open={layout.sidebarOpen} />
312
- </NavTreeContext.Provider>
313
- );
314
- });
330
+ return (
331
+ <NavTreeContext.Provider value={navTreeContextValue}>
332
+ <NavTree id={Node.RootId} root={Graph.getRoot(graph)} open={layout.sidebarOpen} ref={forwardedRef} />
333
+ </NavTreeContext.Provider>
334
+ );
335
+ },
336
+ );
337
+
338
+ export const NavTreeContainer = memo(NavTreeContainer$);
@@ -5,13 +5,14 @@ import { useEffect } from 'react';
5
5
 
6
6
  import { type Node } from '@dxos/app-graph';
7
7
  import { toLocalizedString, useTranslation } from '@dxos/react-ui';
8
+ import { osTranslations } from '@dxos/ui-theme';
8
9
 
9
- import { NAVTREE_PLUGIN } from '../meta';
10
+ import { meta } from '../meta';
10
11
 
11
- export const NavTreeDocumentTitle = ({ node }: { node?: Node }) => {
12
- const { t } = useTranslation(NAVTREE_PLUGIN);
12
+ export const NavTreeDocumentTitle = ({ node }: { node?: Node.Node }) => {
13
+ const { t } = useTranslation(meta.id);
13
14
  useEffect(() => {
14
- document.title = node ? toLocalizedString(node.properties.label, t) : t('current app name', { ns: 'appkit' });
15
+ document.title = node ? toLocalizedString(node.properties.label, t) : t('current app name', { ns: osTranslations });
15
16
  }, [node?.properties?.label]);
16
17
  return null;
17
18
  };