@dxos/plugin-navtree 0.8.4-main.67995b8 → 0.8.4-main.69d29f4

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