@dxos/plugin-navtree 0.6.14-main.2b6a0f3 → 0.6.14-main.f49f251

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 (46) hide show
  1. package/dist/lib/browser/index.mjs +576 -386
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  5. package/dist/types/src/components/NavTree.d.ts +20 -0
  6. package/dist/types/src/components/NavTree.d.ts.map +1 -0
  7. package/dist/types/src/components/NavTree.stories.d.ts +7 -0
  8. package/dist/types/src/components/NavTree.stories.d.ts.map +1 -0
  9. package/dist/types/src/components/NavTreeContainer.d.ts +8 -8
  10. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  11. package/dist/types/src/components/NavTreeItemAction.d.ts +23 -0
  12. package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -0
  13. package/dist/types/src/components/NavTreeItemAction.stories.d.ts +45 -0
  14. package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/index.d.ts +1 -0
  16. package/dist/types/src/components/index.d.ts.map +1 -1
  17. package/dist/types/src/stories/Graph.stories.d.ts +1 -6
  18. package/dist/types/src/stories/Graph.stories.d.ts.map +1 -1
  19. package/dist/types/src/testing/generator.d.ts +86 -0
  20. package/dist/types/src/testing/generator.d.ts.map +1 -0
  21. package/dist/types/src/testing/index.d.ts +3 -0
  22. package/dist/types/src/testing/index.d.ts.map +1 -0
  23. package/dist/types/src/testing/visitor.d.ts +18 -0
  24. package/dist/types/src/testing/visitor.d.ts.map +1 -0
  25. package/dist/types/src/translations.d.ts +5 -10
  26. package/dist/types/src/translations.d.ts.map +1 -1
  27. package/dist/types/src/types.d.ts +41 -0
  28. package/dist/types/src/types.d.ts.map +1 -0
  29. package/dist/types/src/util.d.ts +8 -9
  30. package/dist/types/src/util.d.ts.map +1 -1
  31. package/package.json +36 -33
  32. package/src/NavTreePlugin.tsx +88 -33
  33. package/src/components/NavTree.stories.tsx +104 -0
  34. package/src/components/NavTree.tsx +127 -0
  35. package/src/components/NavTreeContainer.tsx +141 -325
  36. package/src/components/NavTreeItemAction.stories.tsx +57 -0
  37. package/src/components/NavTreeItemAction.tsx +367 -0
  38. package/src/components/index.ts +1 -0
  39. package/src/experimental/Tree.stories.tsx +1 -1
  40. package/src/stories/Graph.stories.tsx +104 -98
  41. package/src/testing/generator.ts +199 -0
  42. package/src/testing/index.ts +6 -0
  43. package/src/testing/visitor.ts +152 -0
  44. package/src/translations.ts +3 -3
  45. package/src/types.ts +50 -0
  46. package/src/util.ts +52 -63
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@dxos/plugin-navtree",
3
- "version": "0.6.14-main.2b6a0f3",
3
+ "version": "0.6.14-main.f49f251",
4
4
  "description": "DXOS Surface plugin for a tree view visualization of the graph.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
8
8
  "author": "DXOS.org",
9
- "sideEffects": false,
9
+ "sideEffects": true,
10
10
  "exports": {
11
11
  ".": {
12
- "browser": "./dist/lib/browser/index.mjs",
13
- "types": "./dist/types/src/index.d.ts"
12
+ "types": "./dist/types/src/index.d.ts",
13
+ "browser": "./dist/lib/browser/index.mjs"
14
14
  },
15
15
  "./meta": {
16
- "browser": "./dist/lib/browser/meta.mjs",
17
- "types": "./dist/types/src/meta.d.ts"
16
+ "types": "./dist/types/src/meta.d.ts",
17
+ "browser": "./dist/lib/browser/meta.mjs"
18
18
  }
19
19
  },
20
20
  "types": "dist/types/src/index.d.ts",
@@ -30,28 +30,31 @@
30
30
  "src"
31
31
  ],
32
32
  "dependencies": {
33
+ "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
34
+ "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
33
35
  "@preact/signals-core": "^1.6.0",
36
+ "@radix-ui/react-use-controllable-state": "^1.0.0",
34
37
  "date-fns": "^3.3.1",
35
- "@dxos/app-framework": "0.6.14-main.2b6a0f3",
36
- "@dxos/app-graph": "0.6.14-main.2b6a0f3",
37
- "@dxos/async": "0.6.14-main.2b6a0f3",
38
- "@dxos/debug": "0.6.14-main.2b6a0f3",
39
- "@dxos/keyboard": "0.6.14-main.2b6a0f3",
40
- "@dxos/echo-schema": "0.6.14-main.2b6a0f3",
41
- "@dxos/local-storage": "0.6.14-main.2b6a0f3",
42
- "@dxos/plugin-client": "0.6.14-main.2b6a0f3",
43
- "@dxos/log": "0.6.14-main.2b6a0f3",
44
- "@dxos/plugin-graph": "0.6.14-main.2b6a0f3",
45
- "@dxos/plugin-observability": "0.6.14-main.2b6a0f3",
46
- "@dxos/plugin-theme": "0.6.14-main.2b6a0f3",
47
- "@dxos/protocols": "0.6.14-main.2b6a0f3",
48
- "@dxos/react-ui-deck": "0.6.14-main.2b6a0f3",
49
- "@dxos/react-client": "0.6.14-main.2b6a0f3",
50
- "@dxos/react-ui-mosaic": "0.6.14-main.2b6a0f3",
51
- "@dxos/react-ui-navtree": "0.6.14-main.2b6a0f3",
52
- "@dxos/react-ui-list": "0.6.14-main.2b6a0f3",
53
- "@dxos/react-ui-searchlist": "0.6.14-main.2b6a0f3",
54
- "@dxos/util": "0.6.14-main.2b6a0f3"
38
+ "@dxos/app-graph": "0.6.14-main.f49f251",
39
+ "@dxos/app-framework": "0.6.14-main.f49f251",
40
+ "@dxos/async": "0.6.14-main.f49f251",
41
+ "@dxos/invariant": "0.6.14-main.f49f251",
42
+ "@dxos/echo-schema": "0.6.14-main.f49f251",
43
+ "@dxos/keyboard": "0.6.14-main.f49f251",
44
+ "@dxos/local-storage": "0.6.14-main.f49f251",
45
+ "@dxos/log": "0.6.14-main.f49f251",
46
+ "@dxos/plugin-graph": "0.6.14-main.f49f251",
47
+ "@dxos/plugin-client": "0.6.14-main.f49f251",
48
+ "@dxos/plugin-observability": "0.6.14-main.f49f251",
49
+ "@dxos/plugin-theme": "0.6.14-main.f49f251",
50
+ "@dxos/protocols": "0.6.14-main.f49f251",
51
+ "@dxos/react-ui-deck": "0.6.14-main.f49f251",
52
+ "@dxos/react-client": "0.6.14-main.f49f251",
53
+ "@dxos/debug": "0.6.14-main.f49f251",
54
+ "@dxos/react-ui-list": "0.6.14-main.f49f251",
55
+ "@dxos/react-ui-mosaic": "0.6.14-main.f49f251",
56
+ "@dxos/react-ui-searchlist": "0.6.14-main.f49f251",
57
+ "@dxos/util": "0.6.14-main.f49f251"
55
58
  },
56
59
  "devDependencies": {
57
60
  "@phosphor-icons/react": "^2.1.5",
@@ -60,18 +63,18 @@
60
63
  "react": "~18.2.0",
61
64
  "react-dom": "~18.2.0",
62
65
  "vite": "5.4.7",
63
- "@dxos/echo-signals": "0.6.14-main.2b6a0f3",
64
- "@dxos/random": "0.6.14-main.2b6a0f3",
65
- "@dxos/react-ui": "0.6.14-main.2b6a0f3",
66
- "@dxos/react-ui-theme": "0.6.14-main.2b6a0f3",
67
- "@dxos/storybook-utils": "0.6.14-main.2b6a0f3"
66
+ "@dxos/random": "0.6.14-main.f49f251",
67
+ "@dxos/echo-signals": "0.6.14-main.f49f251",
68
+ "@dxos/react-ui": "0.6.14-main.f49f251",
69
+ "@dxos/react-ui-theme": "0.6.14-main.f49f251",
70
+ "@dxos/storybook-utils": "0.6.14-main.f49f251"
68
71
  },
69
72
  "peerDependencies": {
70
73
  "@phosphor-icons/react": "^2.1.5",
71
74
  "react": "~18.2.0",
72
75
  "react-dom": "~18.2.0",
73
- "@dxos/react-ui-theme": "0.6.14-main.2b6a0f3",
74
- "@dxos/react-ui": "0.6.14-main.2b6a0f3"
76
+ "@dxos/react-ui": "0.6.14-main.f49f251",
77
+ "@dxos/react-ui-theme": "0.6.14-main.f49f251"
75
78
  },
76
79
  "publishConfig": {
77
80
  "access": "public"
@@ -19,11 +19,11 @@ import {
19
19
  parseGraphPlugin,
20
20
  NavigationAction,
21
21
  } from '@dxos/app-framework';
22
- import { createExtension, type Graph, isAction, isGraphNode, type Node } from '@dxos/app-graph';
22
+ import { createExtension, type Graph, isAction, isGraphNode, type Node, type NodeFilter } from '@dxos/app-graph';
23
+ import { invariant } from '@dxos/invariant';
23
24
  import { Keyboard } from '@dxos/keyboard';
24
25
  import { LocalStorageStore } from '@dxos/local-storage';
25
26
  import { Path } from '@dxos/react-ui-mosaic';
26
- import { type OpenItemIds } from '@dxos/react-ui-navtree';
27
27
  import { getHostPlatform } from '@dxos/util';
28
28
 
29
29
  import {
@@ -37,7 +37,14 @@ import {
37
37
  import { CommandsTrigger } from './components/CommandsTrigger';
38
38
  import meta, { KEY_BINDING, NAVTREE_PLUGIN } from './meta';
39
39
  import translations from './translations';
40
- import { expandOpenGraphNodes, getActions, getChildren, type NavTreeItem, type NavTreeItemGraphNode } from './util';
40
+ import { type NavTreeItem } from './types';
41
+ import {
42
+ expandOpenGraphNodes,
43
+ getActions,
44
+ getChildren,
45
+ treeItemsFromRootNode,
46
+ type NavTreeItemGraphNode,
47
+ } from './util';
41
48
 
42
49
  export type NavTreePluginProvides = SurfaceProvides &
43
50
  MetadataRecordsProvides &
@@ -45,22 +52,77 @@ export type NavTreePluginProvides = SurfaceProvides &
45
52
  TranslationsProvides &
46
53
  IntentResolverProvides;
47
54
 
48
- type NavTreeState = { root?: NavTreeItemGraphNode; openItemIds: OpenItemIds };
55
+ type NavTreeState = {
56
+ root?: NavTreeItemGraphNode;
57
+ flatTree: NavTreeItem[];
58
+ open: string[];
59
+ };
49
60
 
50
61
  export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
62
+ let graphPlugin: Plugin<GraphProvides> | undefined;
63
+ let graph: Graph | undefined;
64
+ const itemCache = new Map<string, NavTreeItem>();
65
+
51
66
  const state = new LocalStorageStore<NavTreeState>('dxos.org/settings/navtree', {
67
+ root: undefined,
68
+ get flatTree() {
69
+ if (!this.root || !graph) {
70
+ return [];
71
+ }
72
+
73
+ return treeItemsFromRootNode(graph, this.root, this.open, getItem);
74
+ },
52
75
  // TODO(thure): Do this dynamically.
53
- openItemIds: { root: true, 'dxos.org/plugin/space-spaces': true, 'dxos.org/plugin/files': true },
76
+ open: ['root', 'root~dxos.org/plugin/space-spaces', 'root~dxos.org/plugin/files'],
54
77
  });
55
78
 
56
- let graphPlugin: Plugin<GraphProvides> | undefined;
57
- let graph: Graph | undefined;
79
+ const getItem = (node: NavTreeItemGraphNode, parent: readonly string[], filter?: NodeFilter) => {
80
+ invariant(graph);
81
+ const path = [...parent, node.id];
82
+ const { actions, groupedActions } = getActions(graph, node);
83
+ const children = getChildren(graph, node, filter, path);
84
+ const parentOf =
85
+ children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
86
+ const item = {
87
+ id: node.id,
88
+ label: node.properties.label ?? node.id,
89
+ icon: node.properties.icon,
90
+ disabled: node.properties.disabled,
91
+ testId: node.properties.testId,
92
+ className: node.properties.className,
93
+ headingClassName: node.properties.headingClassName,
94
+ path,
95
+ parentOf,
96
+ node,
97
+ actions,
98
+ groupedActions,
99
+ } satisfies NavTreeItem;
58
100
 
59
- const handleOpenItemIdsChange = (nextOpenItemIds: OpenItemIds) => {
101
+ const cachedItem = itemCache.get(node.id);
102
+ // TODO(wittjosiah): This is not a good enough check.
103
+ // Consider better ways to doing reactive transformations which retain referential equality.
104
+ if (cachedItem && JSON.stringify(item) === JSON.stringify(cachedItem)) {
105
+ return cachedItem;
106
+ } else {
107
+ itemCache.set(node.id, item);
108
+ return item;
109
+ }
110
+ };
111
+
112
+ const handleOpenChange = (item: NavTreeItem, open: boolean) => {
113
+ const path = Path.create(...item.path);
60
114
  // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
61
- state.values.openItemIds = nextOpenItemIds;
115
+ if (open) {
116
+ state.values.open.push(path);
117
+ } else {
118
+ const index = state.values.open.indexOf(path);
119
+ if (index > -1) {
120
+ state.values.open.splice(index, 1);
121
+ }
122
+ }
123
+
62
124
  if (graph) {
63
- void expandOpenGraphNodes(graph, nextOpenItemIds);
125
+ void expandOpenGraphNodes(graph, state.values.open);
64
126
  }
65
127
  };
66
128
 
@@ -77,9 +139,9 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
77
139
  getChildren(graph, state.values.root);
78
140
  getActions(graph, state.values.root);
79
141
 
80
- state.prop({ key: 'openItemIds', storageKey: 'openItemIds', type: LocalStorageStore.json<OpenItemIds>() });
142
+ state.prop({ key: 'open', type: LocalStorageStore.json<string[]>() });
81
143
 
82
- void expandOpenGraphNodes(graph, state.values.openItemIds);
144
+ void expandOpenGraphNodes(graph, state.values.open);
83
145
 
84
146
  // TODO(wittjosiah): Factor out.
85
147
  // TODO(wittjosiah): Handle removal of actions.
@@ -147,19 +209,15 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
147
209
 
148
210
  switch (role) {
149
211
  case 'navigation':
150
- if (state.values.root && state.values.openItemIds) {
151
- return (
152
- <NavTreeContainer
153
- root={state.values.root}
154
- activeIds={data.activeIds as Set<string>}
155
- openItemIds={state.values.openItemIds}
156
- onOpenItemIdsChange={handleOpenItemIdsChange}
157
- attended={data.attended as string[]}
158
- popoverAnchorId={data.popoverAnchorId as string}
159
- />
160
- );
161
- }
162
- break;
212
+ return (
213
+ <NavTreeContainer
214
+ items={state.values.flatTree}
215
+ current={data.activeIds as string[]}
216
+ open={state.values.open}
217
+ onOpenChange={handleOpenChange}
218
+ popoverAnchorId={data.popoverAnchorId as string}
219
+ />
220
+ );
163
221
 
164
222
  case 'document-title': {
165
223
  return <NavTreeDocumentTitle node={isGraphNode(data.activeNode) ? data.activeNode : undefined} />;
@@ -200,17 +258,14 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
200
258
  if (graph && intent.data?.id) {
201
259
  const path = graph.getPath({ target: intent.data.id });
202
260
  if (Array.isArray(path)) {
203
- const additionalOpenItems = [...Array(path.length - 1)].reduce((acc: OpenItemIds, _, index) => {
204
- const itemId = Path.create(...path.slice(1, index + 1));
261
+ const additionalOpenItems = [...Array(path.length)].reduce((acc: string[], _, index) => {
262
+ const itemId = Path.create(...path.slice(0, index));
205
263
  if (itemId.length > 0) {
206
- acc[itemId] = true;
264
+ acc.push(itemId);
207
265
  }
208
266
  return acc;
209
- }, {});
210
- state.values.openItemIds = {
211
- ...additionalOpenItems,
212
- ...state.values.openItemIds,
213
- };
267
+ }, []);
268
+ state.values.open.push(...additionalOpenItems);
214
269
  }
215
270
  }
216
271
  break;
@@ -0,0 +1,104 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
8
+ import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
9
+ import { type Meta } from '@storybook/react';
10
+ import React, { useEffect } from 'react';
11
+
12
+ import { type NodeArg } from '@dxos/app-graph';
13
+ import { create } from '@dxos/echo-schema';
14
+ import { faker } from '@dxos/random';
15
+ import { isItem } from '@dxos/react-ui-list';
16
+ import { Path } from '@dxos/react-ui-mosaic';
17
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
18
+
19
+ import { NavTree, type NavTreeProps } from './NavTree';
20
+ import { createTree, flattenTree, updateState } from '../testing';
21
+ import { type NavTreeItem } from '../types';
22
+
23
+ faker.seed(1234);
24
+
25
+ type State = {
26
+ tree: NodeArg<any>;
27
+ flatTree: NavTreeItem[];
28
+ open: string[];
29
+ current: string[];
30
+ };
31
+
32
+ const state = create<State>({
33
+ tree: createTree(),
34
+ get flatTree() {
35
+ return flattenTree(this.tree, this.open);
36
+ },
37
+ open: [],
38
+ current: [],
39
+ });
40
+
41
+ const Story = (args: Partial<NavTreeProps>) => {
42
+ // NOTE: If passed directly to args, this won't be reactive.
43
+ const items = state.flatTree;
44
+
45
+ useEffect(() => {
46
+ return monitorForElements({
47
+ canMonitor: ({ source }) => isItem(source.data),
48
+ onDrop: ({ location, source }) => {
49
+ // Didn't drop on anything.
50
+ if (!location.current.dropTargets.length) {
51
+ return;
52
+ }
53
+
54
+ const target = location.current.dropTargets[0];
55
+
56
+ const instruction: Instruction | null = extractInstruction(target.data);
57
+ if (instruction !== null) {
58
+ updateState({
59
+ state: state.tree,
60
+ instruction,
61
+ source: source.data as NavTreeItem,
62
+ target: target.data as NavTreeItem,
63
+ });
64
+ }
65
+ },
66
+ });
67
+ }, []);
68
+
69
+ return <NavTree items={items} open={state.open} current={state.current} {...args} />;
70
+ };
71
+
72
+ const meta: Meta<typeof NavTree> = {
73
+ title: 'plugins/plugin-navtree/NavTree',
74
+ component: NavTree,
75
+ render: Story,
76
+ decorators: [withTheme, withLayout({ tooltips: true })],
77
+ args: {
78
+ onOpenChange: (item: NavTreeItem, open: boolean) => {
79
+ const path = Path.create(...item.path);
80
+ if (open) {
81
+ state.open.push(path);
82
+ } else {
83
+ const index = state.open.indexOf(path);
84
+ if (index > -1) {
85
+ state.open.splice(index, 1);
86
+ }
87
+ }
88
+ },
89
+ onSelect: (item: NavTreeItem, current: boolean) => {
90
+ if (current) {
91
+ state.current.push(item.id);
92
+ } else {
93
+ const index = state.current.indexOf(item.id);
94
+ if (index > -1) {
95
+ state.current.splice(index, 1);
96
+ }
97
+ }
98
+ },
99
+ },
100
+ };
101
+
102
+ export default meta;
103
+
104
+ export const Default = {};
@@ -0,0 +1,127 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { Fragment, useCallback, useEffect, type FC } from 'react';
6
+
7
+ import { isAction, type Node } from '@dxos/app-graph';
8
+ import { Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
9
+ import { Tree, type TreeProps } from '@dxos/react-ui-list';
10
+ import { type MaybePromise } from '@dxos/util';
11
+
12
+ import { NavTreeItemAction } from './NavTreeItemAction';
13
+ import { NAVTREE_PLUGIN } from '../meta';
14
+ import { type NavTreeItem } from '../types';
15
+
16
+ export const NAV_TREE_ITEM = 'NavTreeItem';
17
+
18
+ export type NavTreeProps = Omit<TreeProps<NavTreeItem>, 'draggable' | 'gridTemplateColumns' | 'renderColumns'> &
19
+ Pick<NavTreeColumnsProps, 'loadDescendents' | 'renderPresence' | 'popoverAnchorId'>;
20
+
21
+ export const NavTree = ({ loadDescendents, renderPresence, popoverAnchorId, ...props }: NavTreeProps) => {
22
+ const renderColumns = useCallback<NonNullable<TreeProps<NavTreeItem>['renderColumns']>>(
23
+ ({ item, menuOpen, setMenuOpen }) => {
24
+ return (
25
+ <NavTreeColumns
26
+ item={item}
27
+ menuOpen={menuOpen}
28
+ setMenuOpen={setMenuOpen}
29
+ loadDescendents={loadDescendents}
30
+ renderPresence={renderPresence}
31
+ popoverAnchorId={popoverAnchorId}
32
+ />
33
+ );
34
+ },
35
+ [renderPresence, popoverAnchorId, loadDescendents],
36
+ );
37
+
38
+ return (
39
+ <Tree<NavTreeItem>
40
+ {...props}
41
+ draggable
42
+ gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
43
+ classNames='pbs-1'
44
+ renderColumns={renderColumns}
45
+ />
46
+ );
47
+ };
48
+
49
+ type NavTreeColumnsProps = {
50
+ item: NavTreeItem;
51
+ menuOpen: boolean;
52
+ setMenuOpen: (open: boolean) => void;
53
+ loadDescendents?: (node: Node) => MaybePromise<void>;
54
+ renderPresence?: FC<{ item: NavTreeItem }>;
55
+ popoverAnchorId?: string;
56
+ };
57
+
58
+ const NavTreeColumns = ({
59
+ item,
60
+ menuOpen,
61
+ setMenuOpen,
62
+ loadDescendents,
63
+ renderPresence: Presence,
64
+ popoverAnchorId,
65
+ }: NavTreeColumnsProps) => {
66
+ const { t } = useTranslation(NAVTREE_PLUGIN);
67
+
68
+ const level = item.path.length - 2;
69
+
70
+ const [primaryAction, ...secondaryActions] = item.actions.toSorted((a, b) =>
71
+ a.properties?.disposition === 'toolbar' ? -1 : 1,
72
+ );
73
+
74
+ const actions = (primaryAction?.properties?.disposition === 'toolbar' ? secondaryActions : item.actions)
75
+ .flatMap((action) => (isAction(action) ? [action] : []))
76
+ .filter((action) => !action.properties?.hidden);
77
+
78
+ const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
79
+
80
+ // TODO(thure): Ideally this should not be necessary.
81
+ useEffect(() => {
82
+ const frame = requestAnimationFrame(() => {
83
+ void loadDescendents?.(item.node);
84
+ if (primaryAction && !isAction(primaryAction)) {
85
+ void loadDescendents?.(primaryAction);
86
+ }
87
+ });
88
+ return () => cancelAnimationFrame(frame);
89
+ }, [primaryAction]);
90
+
91
+ return (
92
+ <>
93
+ {primaryAction?.properties?.disposition === 'toolbar' ? (
94
+ <NavTreeItemAction
95
+ testId={primaryAction.properties?.testId}
96
+ label={toLocalizedString(primaryAction.properties?.label, t)}
97
+ icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
98
+ parent={item.node}
99
+ monolithic={isAction(primaryAction)}
100
+ menuActions={isAction(primaryAction) ? [primaryAction] : item.groupedActions[primaryAction.id]}
101
+ menuType={primaryAction.properties?.menuType}
102
+ caller={NAV_TREE_ITEM}
103
+ />
104
+ ) : (
105
+ <Treegrid.Cell />
106
+ )}
107
+ <ActionRoot>
108
+ {actions.length > 0 ? (
109
+ <NavTreeItemAction
110
+ testId={`navtree.treeItem.actionsLevel${level}`}
111
+ label={t('tree item actions label')}
112
+ icon='ph--dots-three-vertical--regular'
113
+ parent={item.node}
114
+ menuActions={actions}
115
+ menuType='dropdown'
116
+ caller={NAV_TREE_ITEM}
117
+ menuOpen={menuOpen}
118
+ onChangeMenuOpen={setMenuOpen}
119
+ />
120
+ ) : (
121
+ <Treegrid.Cell />
122
+ )}
123
+ </ActionRoot>
124
+ {Presence && <Presence item={item} />}
125
+ </>
126
+ );
127
+ };