@dxos/plugin-navtree 0.8.4-main.3a94e84 → 0.8.4-main.3c1ae3b

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