@dxos/plugin-navtree 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef

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 (86) hide show
  1. package/dist/lib/browser/NavTreePlugin-53I3ONRE.mjs +74 -0
  2. package/dist/lib/browser/NavTreePlugin-53I3ONRE.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +28 -111
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/translations.mjs +21 -0
  7. package/dist/lib/browser/translations.mjs.map +7 -0
  8. package/dist/types/src/NavTreePlugin.d.ts +1 -0
  9. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  10. package/dist/types/src/NavTreePlugin.node.d.ts +4 -0
  11. package/dist/types/src/NavTreePlugin.node.d.ts.map +1 -0
  12. package/dist/types/src/NavTreePlugin.test.d.ts +2 -0
  13. package/dist/types/src/NavTreePlugin.test.d.ts.map +1 -0
  14. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  15. package/dist/types/src/capabilities/index.d.ts +1 -1
  16. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  17. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  18. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  19. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
  20. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  21. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  22. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +8 -9
  23. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +2 -2
  25. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  26. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
  27. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  29. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  30. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  31. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  32. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  33. package/dist/types/src/components/types.d.ts +0 -1
  34. package/dist/types/src/components/types.d.ts.map +1 -1
  35. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -1
  36. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts +8 -9
  37. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts.map +1 -1
  38. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -1
  39. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -1
  40. package/dist/types/src/experimental/Tree.d.ts +2 -2
  41. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  42. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  43. package/dist/types/src/hooks/useActions.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useNavTreeState.d.ts +1 -5
  47. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -1
  48. package/dist/types/src/index.d.ts +3 -2
  49. package/dist/types/src/index.d.ts.map +1 -1
  50. package/dist/types/src/operations/expose.d.ts +1 -1
  51. package/dist/types/src/operations/expose.d.ts.map +1 -1
  52. package/dist/types/src/operations/index.d.ts +1 -1
  53. package/dist/types/src/operations/index.d.ts.map +1 -1
  54. package/dist/types/src/testing/generator.d.ts.map +1 -1
  55. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  56. package/dist/types/src/translations.d.ts +8 -10
  57. package/dist/types/src/translations.d.ts.map +1 -1
  58. package/dist/types/src/types/capabilities.d.ts +1 -2
  59. package/dist/types/src/types/capabilities.d.ts.map +1 -1
  60. package/dist/types/src/types/events.d.ts.map +1 -1
  61. package/dist/types/src/util.d.ts +1 -1
  62. package/dist/types/src/util.d.ts.map +1 -1
  63. package/dist/types/tsconfig.tsbuildinfo +1 -1
  64. package/package.json +49 -40
  65. package/src/NavTreePlugin.node.ts +37 -0
  66. package/src/NavTreePlugin.test.ts +29 -0
  67. package/src/NavTreePlugin.tsx +4 -3
  68. package/src/capabilities/app-graph-builder.ts +1 -1
  69. package/src/capabilities/index.ts +1 -1
  70. package/src/capabilities/operation-handler.ts +1 -1
  71. package/src/capabilities/react-surface.tsx +2 -3
  72. package/src/capabilities/state.ts +2 -2
  73. package/src/components/NavTree/NavTree.stories.tsx +2 -2
  74. package/src/components/NavTreeItem/NavTreeItemAction.tsx +3 -4
  75. package/src/components/Sidebar/L1Panel.tsx +82 -141
  76. package/src/components/types.ts +0 -1
  77. package/src/containers/CommandsDialogContent/CommandsDialogContent.stories.tsx +2 -1
  78. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +42 -46
  79. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +0 -9
  80. package/src/hooks/useNavTreeState.ts +1 -8
  81. package/src/index.ts +8 -2
  82. package/src/operations/expose.ts +1 -1
  83. package/src/operations/index.ts +1 -1
  84. package/src/translations.ts +0 -1
  85. package/src/types/capabilities.ts +2 -2
  86. package/src/util.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-navtree",
3
- "version": "0.8.4-main.9be5663bfe",
3
+ "version": "0.8.4-main.abd8ff62ef",
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",
@@ -13,6 +13,11 @@
13
13
  "sideEffects": true,
14
14
  "type": "module",
15
15
  "imports": {
16
+ "#plugin": {
17
+ "browser": "./src/NavTreePlugin.tsx",
18
+ "node": "./src/NavTreePlugin.node.ts",
19
+ "default": "./src/NavTreePlugin.tsx"
20
+ },
16
21
  "#capabilities": "./src/capabilities/index.ts",
17
22
  "#components": "./src/components/index.ts",
18
23
  "#containers": "./src/containers/index.ts",
@@ -20,6 +25,7 @@
20
25
  "#meta": "./src/meta.ts",
21
26
  "#operations": "./src/operations/index.ts",
22
27
  "#testing": "./src/testing/index.ts",
28
+ "#translations": "./src/translations.ts",
23
29
  "#types": "./src/types/index.ts"
24
30
  },
25
31
  "exports": {
@@ -27,12 +33,15 @@
27
33
  "source": "./src/index.ts",
28
34
  "types": "./dist/types/src/index.d.ts",
29
35
  "browser": "./dist/lib/browser/index.mjs"
36
+ },
37
+ "./translations": {
38
+ "source": "./src/translations.ts",
39
+ "types": "./dist/types/src/translations.d.ts",
40
+ "browser": "./dist/lib/browser/translations.mjs",
41
+ "node": "./dist/lib/node-esm/translations.mjs"
30
42
  }
31
43
  },
32
44
  "types": "dist/types/src/index.d.ts",
33
- "typesVersions": {
34
- "*": {}
35
- },
36
45
  "files": [
37
46
  "dist",
38
47
  "src"
@@ -43,30 +52,30 @@
43
52
  "@radix-ui/react-dismissable-layer": "1.1.5",
44
53
  "@radix-ui/react-use-controllable-state": "1.1.0",
45
54
  "date-fns": "^3.6.0",
46
- "@dxos/app-graph": "0.8.4-main.9be5663bfe",
47
- "@dxos/debug": "0.8.4-main.9be5663bfe",
48
- "@dxos/app-framework": "0.8.4-main.9be5663bfe",
49
- "@dxos/app-toolkit": "0.8.4-main.9be5663bfe",
50
- "@dxos/async": "0.8.4-main.9be5663bfe",
51
- "@dxos/echo": "0.8.4-main.9be5663bfe",
52
- "@dxos/invariant": "0.8.4-main.9be5663bfe",
53
- "@dxos/keyboard": "0.8.4-main.9be5663bfe",
54
- "@dxos/lit-ui": "0.8.4-main.9be5663bfe",
55
- "@dxos/log": "0.8.4-main.9be5663bfe",
56
- "@dxos/plugin-deck": "0.8.4-main.9be5663bfe",
57
- "@dxos/plugin-graph": "0.8.4-main.9be5663bfe",
58
- "@dxos/plugin-observability": "0.8.4-main.9be5663bfe",
59
- "@dxos/protocols": "0.8.4-main.9be5663bfe",
60
- "@dxos/operation": "0.8.4-main.9be5663bfe",
61
- "@dxos/react-client": "0.8.4-main.9be5663bfe",
62
- "@dxos/react-ui-attention": "0.8.4-main.9be5663bfe",
63
- "@dxos/react-ui-menu": "0.8.4-main.9be5663bfe",
64
- "@dxos/react-ui-list": "0.8.4-main.9be5663bfe",
65
- "@dxos/react-ui-mosaic": "0.8.4-main.9be5663bfe",
66
- "@dxos/react-ui-search": "0.8.4-main.9be5663bfe",
67
- "@dxos/react-ui-tabs": "0.8.4-main.9be5663bfe",
68
- "@dxos/react-ui-stack": "0.8.4-main.9be5663bfe",
69
- "@dxos/util": "0.8.4-main.9be5663bfe"
55
+ "@dxos/app-framework": "0.8.4-main.abd8ff62ef",
56
+ "@dxos/app-toolkit": "0.8.4-main.abd8ff62ef",
57
+ "@dxos/async": "0.8.4-main.abd8ff62ef",
58
+ "@dxos/app-graph": "0.8.4-main.abd8ff62ef",
59
+ "@dxos/debug": "0.8.4-main.abd8ff62ef",
60
+ "@dxos/compute": "0.8.4-main.abd8ff62ef",
61
+ "@dxos/invariant": "0.8.4-main.abd8ff62ef",
62
+ "@dxos/echo": "0.8.4-main.abd8ff62ef",
63
+ "@dxos/keyboard": "0.8.4-main.abd8ff62ef",
64
+ "@dxos/log": "0.8.4-main.abd8ff62ef",
65
+ "@dxos/lit-ui": "0.8.4-main.abd8ff62ef",
66
+ "@dxos/plugin-deck": "0.8.4-main.abd8ff62ef",
67
+ "@dxos/plugin-graph": "0.8.4-main.abd8ff62ef",
68
+ "@dxos/plugin-observability": "0.8.4-main.abd8ff62ef",
69
+ "@dxos/protocols": "0.8.4-main.abd8ff62ef",
70
+ "@dxos/react-client": "0.8.4-main.abd8ff62ef",
71
+ "@dxos/react-ui-attention": "0.8.4-main.abd8ff62ef",
72
+ "@dxos/react-ui-list": "0.8.4-main.abd8ff62ef",
73
+ "@dxos/react-ui-menu": "0.8.4-main.abd8ff62ef",
74
+ "@dxos/react-ui-mosaic": "0.8.4-main.abd8ff62ef",
75
+ "@dxos/react-ui-search": "0.8.4-main.abd8ff62ef",
76
+ "@dxos/react-ui-tabs": "0.8.4-main.abd8ff62ef",
77
+ "@dxos/react-ui-stack": "0.8.4-main.abd8ff62ef",
78
+ "@dxos/util": "0.8.4-main.abd8ff62ef"
70
79
  },
71
80
  "devDependencies": {
72
81
  "@effect-atom/atom-react": "^0.5.0",
@@ -76,16 +85,16 @@
76
85
  "effect": "3.20.0",
77
86
  "react": "~19.2.3",
78
87
  "react-dom": "~19.2.3",
79
- "vite": "^7.1.11",
80
- "@dxos/plugin-attention": "0.8.4-main.9be5663bfe",
81
- "@dxos/plugin-space": "0.8.4-main.9be5663bfe",
82
- "@dxos/plugin-client": "0.8.4-main.9be5663bfe",
83
- "@dxos/plugin-testing": "0.8.4-main.9be5663bfe",
84
- "@dxos/plugin-theme": "0.8.4-main.9be5663bfe",
85
- "@dxos/random": "0.8.4-main.9be5663bfe",
86
- "@dxos/react-ui": "0.8.4-main.9be5663bfe",
87
- "@dxos/storybook-utils": "0.8.4-main.9be5663bfe",
88
- "@dxos/ui-theme": "0.8.4-main.9be5663bfe"
88
+ "vite": "^8.0.10",
89
+ "@dxos/plugin-attention": "0.8.4-main.abd8ff62ef",
90
+ "@dxos/plugin-space": "0.8.4-main.abd8ff62ef",
91
+ "@dxos/plugin-client": "0.8.4-main.abd8ff62ef",
92
+ "@dxos/plugin-testing": "0.8.4-main.abd8ff62ef",
93
+ "@dxos/plugin-theme": "0.8.4-main.abd8ff62ef",
94
+ "@dxos/random": "0.8.4-main.abd8ff62ef",
95
+ "@dxos/react-ui": "0.8.4-main.abd8ff62ef",
96
+ "@dxos/storybook-utils": "0.8.4-main.abd8ff62ef",
97
+ "@dxos/ui-theme": "0.8.4-main.abd8ff62ef"
89
98
  },
90
99
  "peerDependencies": {
91
100
  "@effect-atom/atom-react": "^0.5.0",
@@ -93,8 +102,8 @@
93
102
  "effect": "3.20.0",
94
103
  "react": "~19.2.3",
95
104
  "react-dom": "~19.2.3",
96
- "@dxos/react-ui": "0.8.4-main.9be5663bfe",
97
- "@dxos/ui-theme": "0.8.4-main.9be5663bfe"
105
+ "@dxos/ui-theme": "0.8.4-main.abd8ff62ef",
106
+ "@dxos/react-ui": "0.8.4-main.abd8ff62ef"
98
107
  },
99
108
  "publishConfig": {
100
109
  "access": "public"
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { AppPlugin } from '@dxos/app-toolkit';
7
+ import { type TreeData } from '@dxos/react-ui-list';
8
+
9
+ import { AppGraphBuilder, OperationHandler } from '#capabilities';
10
+ import { meta } from '#meta';
11
+
12
+ const NODE_TYPE = 'dxos/app-graph/node';
13
+
14
+ export const NavTreePlugin = Plugin.define(meta).pipe(
15
+ AppPlugin.addAppGraphModule({ activate: AppGraphBuilder }),
16
+ AppPlugin.addMetadataModule({
17
+ metadata: {
18
+ id: NODE_TYPE,
19
+ metadata: {
20
+ parse: ({ item }: TreeData, type: string) => {
21
+ switch (type) {
22
+ case 'node':
23
+ return item;
24
+ case 'object':
25
+ return item.data;
26
+ case 'view-object':
27
+ return { id: `${item.id}-view`, object: item.data };
28
+ }
29
+ },
30
+ },
31
+ },
32
+ }),
33
+ AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),
34
+ Plugin.make,
35
+ );
36
+
37
+ export default NavTreePlugin;
@@ -0,0 +1,29 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { ActivationEvents } from '@dxos/app-framework';
8
+ import { createComposerTestApp } from '@dxos/plugin-testing/harness';
9
+
10
+ import { NavTreePlugin } from '#plugin';
11
+
12
+ import { meta } from './meta';
13
+
14
+ const moduleId = (name: string) => `${meta.id}.module.${name}`;
15
+
16
+ describe('NavTreePlugin', () => {
17
+ test('modules activate on the expected events', async ({ expect }) => {
18
+ await using harness = await createComposerTestApp({
19
+ plugins: [NavTreePlugin()],
20
+ });
21
+
22
+ expect(harness.manager.getActive()).toEqual(
23
+ expect.arrayContaining([moduleId('AppGraphBuilder'), moduleId('metadata')]),
24
+ );
25
+
26
+ await harness.fire(ActivationEvents.SetupOperationHandler);
27
+ expect(harness.manager.getActive()).toContain(moduleId('OperationHandler'));
28
+ });
29
+ });
@@ -12,10 +12,9 @@ import { type TreeData } from '@dxos/react-ui-list';
12
12
  import { AppGraphBuilder, Keyboard, OperationHandler, ReactSurface, State } from '#capabilities';
13
13
  import { NODE_TYPE } from '#containers';
14
14
  import { meta } from '#meta';
15
+ import { translations } from '#translations';
15
16
  import { NavTreeEvents } from '#types';
16
17
 
17
- import { translations } from './translations';
18
-
19
18
  export const NavTreePlugin = Plugin.define(meta).pipe(
20
19
  AppPlugin.addAppGraphModule({ activate: AppGraphBuilder }),
21
20
  AppPlugin.addMetadataModule({
@@ -41,7 +40,7 @@ export const NavTreePlugin = Plugin.define(meta).pipe(
41
40
  Plugin.addModule({
42
41
  id: 'state',
43
42
  activatesOn: AppActivationEvents.LayoutReady,
44
- activatesAfter: [NavTreeEvents.StateReady],
43
+ firesAfterActivation: [NavTreeEvents.StateReady],
45
44
  activate: State,
46
45
  }),
47
46
  Plugin.addModule({
@@ -74,3 +73,5 @@ export const NavTreePlugin = Plugin.define(meta).pipe(
74
73
  }),
75
74
  Plugin.make,
76
75
  );
76
+
77
+ export default NavTreePlugin;
@@ -6,7 +6,7 @@ import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Capability } from '@dxos/app-framework';
8
8
  import { AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
9
- import { Operation } from '@dxos/operation';
9
+ import { Operation } from '@dxos/compute';
10
10
  import { GraphBuilder, Node, NodeMatcher } from '@dxos/plugin-graph';
11
11
 
12
12
  import { COMMANDS_DIALOG, meta } from '#meta';
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { Capability } from '@dxos/app-framework';
6
- import { OperationHandlerSet } from '@dxos/operation';
6
+ import { OperationHandlerSet } from '@dxos/compute';
7
7
 
8
8
  export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
9
9
  export const Keyboard = Capability.lazy('Keyboard', () => import('./keyboard'));
@@ -5,7 +5,7 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Capabilities, Capability } from '@dxos/app-framework';
8
- import type { OperationHandlerSet } from '@dxos/operation';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
9
 
10
10
  import { NavTreeOperationHandlerSet } from '#operations';
11
11
 
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
- import React from 'react';
6
+ import React, { type ComponentProps } from 'react';
7
7
 
8
8
  import { Capabilities, Capability } from '@dxos/app-framework';
9
9
  import { Surface } from '@dxos/app-framework/ui';
@@ -18,8 +18,7 @@ export default Capability.makeModule(() =>
18
18
  Capability.contributes(Capabilities.ReactSurface, [
19
19
  Surface.create({
20
20
  id: COMMANDS_DIALOG,
21
- role: 'dialog',
22
- filter: AppSurface.componentDialog(COMMANDS_DIALOG),
21
+ filter: AppSurface.component<ComponentProps<typeof CommandsDialogContent>>(AppSurface.Dialog, COMMANDS_DIALOG),
23
22
  component: ({ data, ref }) => <CommandsDialogContent {...data.props} ref={ref} />,
24
23
  }),
25
24
  Surface.create({
@@ -16,7 +16,7 @@ import { type NavTreeCapabilities as NC, NavTreeCapabilities } from '#types';
16
16
  const KEY = `${meta.id}.state.v1`;
17
17
 
18
18
  /** Default item state for new entries. */
19
- const defaultItemState: NC.NavTreeItemState = { open: false, current: false, alternateTree: false };
19
+ const defaultItemState: NC.NavTreeItemState = { open: false, current: false };
20
20
 
21
21
  /** L0 (top-level workspace) paths are direct children of root — not part of the expandable tree model. */
22
22
  const isTopLevelPath = (path: string[]): boolean => path.length === 2 && path[0] === Node.RootId;
@@ -66,7 +66,7 @@ export default Capability.makeModule(
66
66
  return registry.get(getItemAtom(path));
67
67
  };
68
68
 
69
- const setItem = (path: string[], key: 'open' | 'current' | 'alternateTree', next: boolean) => {
69
+ const setItem = (path: string[], key: 'open' | 'current', next: boolean) => {
70
70
  const pathString = Path.create(...path);
71
71
  const atom = itemAtomFamily(pathString);
72
72
  const currentValue = registry.get(atom);
@@ -12,7 +12,7 @@ import { Capabilities, Capability } from '@dxos/app-framework';
12
12
  import { withPluginManager } from '@dxos/app-framework/testing';
13
13
  import { useAtomCapability } from '@dxos/app-framework/ui';
14
14
  import { AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
15
- import { Operation, OperationHandlerSet } from '@dxos/operation';
15
+ import { Operation, OperationHandlerSet } from '@dxos/compute';
16
16
  import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
17
17
  import { random } from '@dxos/random';
18
18
  import { IconButton, Input, Main, Toolbar } from '@dxos/react-ui';
@@ -23,9 +23,9 @@ import { mx } from '@dxos/ui-theme';
23
23
 
24
24
  import { NavTreeContainer } from '#containers';
25
25
  import { storybookGraphBuilders } from '#testing';
26
+ import { translations } from '#translations';
26
27
 
27
28
  import { NavTreePlugin } from '../../NavTreePlugin';
28
- import { translations } from '../../translations';
29
29
 
30
30
  random.seed(1234);
31
31
 
@@ -113,7 +113,7 @@ export const NavTreeItemAction = ({
113
113
  monolithic,
114
114
  menuActions,
115
115
  menuType,
116
- parent: node,
116
+ parent,
117
117
  path,
118
118
  ...props
119
119
  }: NavTreeItemActionMenuProps) => {
@@ -121,10 +121,9 @@ export const NavTreeItemAction = ({
121
121
 
122
122
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
123
123
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
124
-
125
124
  return monolithic && menuActions?.length === 1 ? (
126
- <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} path={path} {...menuActions[0]} />
125
+ <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={parent} path={path} {...menuActions[0]} />
127
126
  ) : (
128
- <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} path={path} menuActions={menuActions} />
127
+ <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={parent} path={path} menuActions={menuActions} />
129
128
  );
130
129
  };
@@ -2,22 +2,20 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import * as Effect from 'effect/Effect';
6
5
  import * as Option from 'effect/Option';
7
6
  import React, { memo, useCallback, useMemo } from 'react';
8
7
 
9
8
  import { Node } from '@dxos/app-graph';
10
9
  import { isPinnedWorkspace } from '@dxos/app-toolkit';
11
10
  import { useAppGraph } from '@dxos/app-toolkit/ui';
12
- import { Graph, useActionRunner, useConnections, useEdges } from '@dxos/plugin-graph';
11
+ import { Graph, useActionRunner, useEdges } from '@dxos/plugin-graph';
13
12
  import { DensityProvider, IconButton, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
14
- import { useAttended } from '@dxos/react-ui-attention';
15
13
  import { Tree } from '@dxos/react-ui-list';
16
14
  import { Menu, type MenuItem } from '@dxos/react-ui-menu';
17
15
  import { Tabs } from '@dxos/react-ui-tabs';
18
16
  import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
19
17
 
20
- import { useActions, useIsAlternateTree, useLoadDescendents } from '#hooks';
18
+ import { useActions, useLoadDescendents } from '#hooks';
21
19
  import { meta } from '#meta';
22
20
 
23
21
  import { NAV_TREE_ITEM } from '../NavTree';
@@ -84,49 +82,27 @@ const useIsActivatedWorkspace = (item: Node.Node): boolean => {
84
82
  const L1PanelContent = ({ path, item, onBack }: Pick<L1PanelProps, 'open' | 'path' | 'item' | 'onBack'>) => {
85
83
  const navTreeContext = useNavTreeContext();
86
84
 
87
- const alternateTree = useAlternateTreeItem(item);
88
- const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
89
- const isAlternate = useIsAlternateTree(alternatePath, item);
90
-
91
85
  return (
92
86
  <DensityProvider density='fine'>
93
87
  <L1PanelHeader path={path} item={item} onBack={onBack} />
94
88
  <ScrollArea.Root thin orientation='vertical'>
95
89
  <ScrollArea.Viewport>
96
- {isAlternate && alternateTree ? (
97
- <Tree
98
- model={navTreeContext.model}
99
- id={alternateTree.id}
100
- rootId={alternateTree.id}
101
- path={alternatePath}
102
- levelOffset={5}
103
- gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
104
- renderColumns={NavTreeItemColumns}
105
- blockInstruction={navTreeContext.blockInstruction}
106
- canDrop={navTreeContext.canDrop}
107
- canSelect={navTreeContext.canSelect}
108
- onOpenChange={navTreeContext.onOpenChange}
109
- onSelect={navTreeContext.onSelect}
110
- onItemHover={navTreeContext.onItemHover}
111
- />
112
- ) : (
113
- <Tree
114
- model={navTreeContext.model}
115
- id={item.id}
116
- rootId={item.id}
117
- path={path}
118
- levelOffset={5}
119
- gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
120
- draggable
121
- renderColumns={NavTreeItemColumns}
122
- blockInstruction={navTreeContext.blockInstruction}
123
- canDrop={navTreeContext.canDrop}
124
- canSelect={navTreeContext.canSelect}
125
- onOpenChange={navTreeContext.onOpenChange}
126
- onSelect={navTreeContext.onSelect}
127
- onItemHover={navTreeContext.onItemHover}
128
- />
129
- )}
90
+ <Tree
91
+ model={navTreeContext.model}
92
+ id={item.id}
93
+ rootId={item.id}
94
+ path={path}
95
+ levelOffset={5}
96
+ gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
97
+ draggable
98
+ renderColumns={NavTreeItemColumns}
99
+ blockInstruction={navTreeContext.blockInstruction}
100
+ canDrop={navTreeContext.canDrop}
101
+ canSelect={navTreeContext.canSelect}
102
+ onOpenChange={navTreeContext.onOpenChange}
103
+ onSelect={navTreeContext.onSelect}
104
+ onItemHover={navTreeContext.onItemHover}
105
+ />
130
106
  </ScrollArea.Viewport>
131
107
  </ScrollArea.Root>
132
108
  </DensityProvider>
@@ -145,20 +121,6 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
145
121
  const { primaryAction, groupedActions, menuActions, onAction } = useL1MenuActions({ item, path });
146
122
  useLoadDescendents(item);
147
123
 
148
- // TODO(wittjosiah): Hack to only show containsAttended when attended item is within alternate tree (e.g. settings).
149
- // Since every object in the space is a descendant of the space node, we'd otherwise always show containsAttended.
150
- const alternateTree = useAlternateTreeItem(item);
151
- const attended = useAttended();
152
- const alternateTreeOpen = useMemo(() => {
153
- if (!alternateTree) {
154
- return true;
155
- }
156
- const hasAlternateAttended = attended.some(
157
- (attendedId) => attendedId === alternateTree.id || attendedId.startsWith(alternateTree.id + '/'),
158
- );
159
- return !hasAlternateAttended;
160
- }, [alternateTree, attended]);
161
-
162
124
  return (
163
125
  <div
164
126
  data-tauri-drag-region
@@ -196,115 +158,94 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
196
158
  caller={NAV_TREE_ITEM}
197
159
  />
198
160
  )}
199
- {menuActions.length === 1 && (
200
- <IconButton
201
- density='coarse'
202
- classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
203
- variant='ghost'
204
- icon={menuActions[0].properties?.icon ?? 'ph--placeholder--regular'}
205
- iconOnly
206
- label={toLocalizedString(menuActions[0].properties?.label, t)}
207
- data-testid={menuActions[0].properties?.testId}
208
- onClick={() => onAction(menuActions[0] as Node.Action)}
209
- />
210
- )}
211
- {menuActions.length > 1 && (
212
- <Menu.Root caller={NAV_TREE_ITEM} onAction={onAction}>
213
- <Menu.Trigger asChild>
214
- <IconButton
215
- density='coarse'
216
- classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
217
- variant='ghost'
218
- icon='ph--dots-three-vertical--regular'
219
- iconOnly
220
- label={t('tree-item-actions.label')}
221
- data-testid='navtree.treeItem.actionsLevel0'
222
- />
223
- </Menu.Trigger>
224
- <Menu.Content group={item} items={menuActions as MenuItem[]} />
225
- </Menu.Root>
226
- )}
227
- {ItemEnd && <ItemEnd node={item} open={alternateTreeOpen} />}
161
+ <MenuActions item={item} menuActions={menuActions} onAction={onAction} />
162
+ {ItemEnd && <ItemEnd node={item} open />}
228
163
  </div>
229
164
  </div>
230
165
  );
231
166
  };
232
167
 
168
+ type L1MenuActions = {
169
+ primaryAction: Node.ActionLike;
170
+ groupedActions: Record<string, Node.Action[]>;
171
+ menuActions: Node.Action[];
172
+ onAction: (action: Node.Action, params?: Node.InvokeProps) => void;
173
+ };
174
+
233
175
  /**
234
- * Builds the menu actions for the L1 panel header, combining graph actions with a synthetic settings/back action.
176
+ * Header menu actions for an L1 workspace tab. Renders nothing for an empty
177
+ * `menuActions`, a single inline icon button for one action, and a
178
+ * `…`-menu trigger for multiple.
235
179
  */
236
- const useL1MenuActions = ({ item, path }: Pick<L1PanelProps, 'item' | 'path'>) => {
180
+ const MenuActions = ({
181
+ item,
182
+ menuActions,
183
+ onAction,
184
+ }: {
185
+ item: Node.Node;
186
+ } & Pick<L1MenuActions, 'menuActions' | 'onAction'>) => {
237
187
  const { t } = useTranslation(meta.id);
238
- const { setAlternateTree } = useNavTreeContext();
239
- const { graph } = useAppGraph();
240
- const runAction = useActionRunner();
241
188
 
242
- const alternateTree = useAlternateTreeItem(item);
243
- const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
244
- const isAlternate = useIsAlternateTree(alternatePath, item);
189
+ if (menuActions.length === 0) {
190
+ return null;
191
+ }
192
+
193
+ if (menuActions.length === 1) {
194
+ return (
195
+ <IconButton
196
+ density='coarse'
197
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
198
+ variant='ghost'
199
+ icon={menuActions[0].properties?.icon ?? 'ph--placeholder--regular'}
200
+ iconOnly
201
+ label={toLocalizedString(menuActions[0].properties?.label, t)}
202
+ data-testid={menuActions[0].properties?.testId}
203
+ onClick={() => onAction(menuActions[0] as Node.Action)}
204
+ />
205
+ );
206
+ }
207
+
208
+ return (
209
+ <Menu.Root caller={NAV_TREE_ITEM} onAction={onAction}>
210
+ <Menu.Trigger asChild>
211
+ <IconButton
212
+ density='coarse'
213
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
214
+ variant='ghost'
215
+ icon='ph--dots-three-vertical--regular'
216
+ iconOnly
217
+ label={t('tree-item-actions.label')}
218
+ data-testid='navtree.treeItem.actionsLevel0'
219
+ />
220
+ </Menu.Trigger>
221
+ <Menu.Content group={item} items={menuActions as MenuItem[]} />
222
+ </Menu.Root>
223
+ );
224
+ };
225
+
226
+ /**
227
+ * Builds the menu actions for the L1 panel header.
228
+ */
229
+ const useL1MenuActions = ({ item, path }: Pick<L1PanelProps, 'item' | 'path'>): L1MenuActions => {
230
+ const runAction = useActionRunner();
245
231
 
246
- // Graph actions.
247
232
  const { actions: actionsProp, groupedActions } = useActions(item);
248
233
  const [primaryAction, ...secondaryActions] = actionsProp.toSorted((a, _b) =>
249
234
  a.properties?.disposition === 'list-item-primary' ? -1 : 1,
250
235
  );
251
236
 
252
- const graphMenuActions = (
253
- primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : actionsProp
254
- )
237
+ const menuActions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : actionsProp)
255
238
  .flatMap((action) => (Node.isAction(action) ? [action] : []))
256
239
  .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
257
240
 
258
- // Synthetic settings/back action.
259
- const settingsActionId = `${item.id}~settings`;
260
- const settingsAction = useMemo((): Node.Action | undefined => {
261
- if (!alternateTree) {
262
- return undefined;
263
- }
264
- return {
265
- id: settingsActionId,
266
- type: Node.ActionType,
267
- data: () => Effect.void,
268
- properties: {
269
- label: isAlternate
270
- ? ['button-back.button', { ns: meta.id }]
271
- : (alternateTree.properties.label ?? alternateTree.id),
272
- icon: isAlternate
273
- ? 'ph--arrow-u-down-left--regular'
274
- : (alternateTree.properties.icon ?? 'ph--placeholder--regular'),
275
- disposition: 'list-item',
276
- testId: isAlternate ? 'navtree.backToSpace' : 'navtree.spaceSettings',
277
- },
278
- } as Node.Action;
279
- }, [alternateTree, isAlternate, settingsActionId, t]);
280
-
281
- const menuActions = useMemo(
282
- () => [...graphMenuActions, ...(settingsAction ? [settingsAction] : [])],
283
- [graphMenuActions, settingsAction],
284
- );
285
-
286
241
  const onAction = useCallback(
287
242
  (action: Node.Action, params?: Node.InvokeProps) => {
288
- if (action.id === settingsActionId) {
289
- if (alternateTree && !isAlternate) {
290
- Graph.expand(graph, alternateTree.id, 'child');
291
- }
292
- setAlternateTree?.(alternatePath, !isAlternate);
293
- } else {
294
- void runAction(action, { ...params, path });
295
- }
243
+ void runAction(action, { ...params, path });
296
244
  },
297
- [settingsActionId, setAlternateTree, alternatePath, isAlternate, runAction, graph, alternateTree, path],
245
+ [runAction, path],
298
246
  );
299
247
 
300
248
  return { primaryAction, groupedActions, menuActions, onAction };
301
249
  };
302
250
 
303
- /** Finds the first child with disposition 'alternate-tree' using graph connections directly. */
304
- const useAlternateTreeItem = (item: Node.Node): Node.Node | undefined => {
305
- const { graph } = useAppGraph();
306
- const connections = useConnections(graph, item.id, 'child');
307
- return connections.find((node) => node.properties.disposition === 'alternate-tree');
308
- };
309
-
310
251
  export const L1Panel = memo(L1Panel$);
@@ -16,7 +16,6 @@ export type NavTreeContextValue = {
16
16
  model: TreeModel<NavTreeItemGraphNode>;
17
17
  popoverAnchorId?: string;
18
18
  renderItemEnd?: FC<{ node: Node.Node; open: boolean }>;
19
- setAlternateTree?: (path: string[], open: boolean) => void;
20
19
  onTabChange?: (node: NavTreeItemGraphNode) => void;
21
20
  } & Pick<
22
21
  TreeProps<NavTreeItemGraphNode>,
@@ -10,8 +10,9 @@ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
10
10
  import { Dialog } from '@dxos/react-ui';
11
11
  import { withLayout } from '@dxos/react-ui/testing';
12
12
 
13
+ import { translations } from '#translations';
14
+
13
15
  import { NavTreePlugin } from '../../NavTreePlugin';
14
- import { translations } from '../../translations';
15
16
  import { CommandsDialogContent } from './CommandsDialogContent';
16
17
 
17
18
  const DefaultStory = () => (