@dxos/plugin-navtree 0.8.4-main.406dc2a → 0.8.4-main.52d7546f51

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