@dxos/plugin-navtree 0.8.4-main.c85a9c8dae → 0.8.4-main.d05673bc65

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 (77) hide show
  1. package/dist/lib/browser/{CommandsDialogContent-A27AKTHH.mjs → CommandsDialogContent-2DKIOLJL.mjs} +11 -9
  2. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs.map +7 -0
  3. package/dist/lib/browser/{CommandsTrigger-HBZYQCGV.mjs → CommandsTrigger-ABODVVMP.mjs} +2 -2
  4. package/dist/lib/browser/NavTreeContainer-TLSYS4KK.mjs +12 -0
  5. package/dist/lib/browser/NavTreeContainer-TLSYS4KK.mjs.map +7 -0
  6. package/dist/lib/browser/{NavTreeDocumentTitle-GEE3URWH.mjs → NavTreeDocumentTitle-L6NLLWLD.mjs} +2 -2
  7. package/dist/lib/browser/{app-graph-builder-XGA4GPGL.mjs → app-graph-builder-Z43OTNI2.mjs} +2 -2
  8. package/dist/lib/browser/{chunk-YKTYPINZ.mjs → chunk-A4VDCIJC.mjs} +3 -3
  9. package/dist/lib/browser/{chunk-YKTYPINZ.mjs.map → chunk-A4VDCIJC.mjs.map} +1 -1
  10. package/dist/lib/browser/{chunk-H3OJ4WLF.mjs → chunk-AYF2TX65.mjs} +5 -5
  11. package/dist/lib/browser/{chunk-H3OJ4WLF.mjs.map → chunk-AYF2TX65.mjs.map} +1 -1
  12. package/dist/lib/browser/{chunk-DQCYMXEU.mjs → chunk-JT37R5HS.mjs} +149 -107
  13. package/dist/lib/browser/chunk-JT37R5HS.mjs.map +7 -0
  14. package/dist/lib/browser/{chunk-X2HDG62A.mjs → chunk-KF26WA4O.mjs} +3 -3
  15. package/dist/lib/browser/{chunk-X2HDG62A.mjs.map → chunk-KF26WA4O.mjs.map} +1 -1
  16. package/dist/lib/browser/index.mjs +9 -9
  17. package/dist/lib/browser/{keyboard-OJZC54PK.mjs → keyboard-HYGFQO7T.mjs} +6 -3
  18. package/dist/lib/browser/{keyboard-OJZC54PK.mjs.map → keyboard-HYGFQO7T.mjs.map} +2 -2
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/{operation-resolver-BNAMYOHF.mjs → operation-resolver-VDWVUII5.mjs} +18 -22
  21. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs.map +7 -0
  22. package/dist/lib/browser/{react-surface-AYHOAJ4I.mjs → react-surface-D2RLMV4Z.mjs} +8 -8
  23. package/dist/lib/browser/{react-surface-AYHOAJ4I.mjs.map → react-surface-D2RLMV4Z.mjs.map} +1 -1
  24. package/dist/lib/browser/{state-AC3EETN7.mjs → state-CWMNNF7K.mjs} +4 -4
  25. package/dist/lib/browser/{state-AC3EETN7.mjs.map → state-CWMNNF7K.mjs.map} +1 -1
  26. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
  27. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  28. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  29. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  30. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  31. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +4 -2
  32. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  33. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
  34. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  35. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  36. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -1
  37. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -1
  38. package/dist/types/src/containers/NavTreeContainer/index.d.ts +2 -2
  39. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -1
  40. package/dist/types/src/containers/index.d.ts +1 -1
  41. package/dist/types/src/containers/index.d.ts.map +1 -1
  42. package/dist/types/src/hooks/useActions.d.ts +1 -3
  43. package/dist/types/src/hooks/useActions.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -1
  45. package/dist/types/src/meta.d.ts.map +1 -1
  46. package/dist/types/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +36 -36
  48. package/src/capabilities/keyboard/keyboard.ts +1 -1
  49. package/src/capabilities/operation-resolver/operation-resolver.ts +18 -17
  50. package/src/capabilities/react-surface/react-surface.tsx +3 -3
  51. package/src/capabilities/state/state.ts +1 -1
  52. package/src/components/NavTree/NavTree.stories.tsx +1 -1
  53. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  54. package/src/components/NavTreeContext/index.ts +5 -0
  55. package/src/components/NavTreeItem/NavTreeItemAction.tsx +25 -7
  56. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +3 -1
  57. package/src/components/Sidebar/L0Menu.tsx +1 -1
  58. package/src/components/Sidebar/L1Panel.tsx +21 -4
  59. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +53 -51
  60. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +4 -3
  61. package/src/containers/NavTreeContainer/index.ts +2 -3
  62. package/src/containers/index.ts +1 -1
  63. package/src/hooks/useActions.ts +17 -21
  64. package/src/hooks/useNavTreeModel.ts +8 -2
  65. package/src/meta.ts +2 -2
  66. package/src/testing/app-graph-builder.ts +5 -5
  67. package/src/testing/generator.ts +1 -1
  68. package/src/types/capabilities.ts +1 -1
  69. package/dist/lib/browser/CommandsDialogContent-A27AKTHH.mjs.map +0 -7
  70. package/dist/lib/browser/NavTreeContainer-B5K4FUJS.mjs +0 -12
  71. package/dist/lib/browser/NavTreeContainer-B5K4FUJS.mjs.map +0 -7
  72. package/dist/lib/browser/chunk-DQCYMXEU.mjs.map +0 -7
  73. package/dist/lib/browser/operation-resolver-BNAMYOHF.mjs.map +0 -7
  74. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  75. /package/dist/lib/browser/{CommandsTrigger-HBZYQCGV.mjs.map → CommandsTrigger-ABODVVMP.mjs.map} +0 -0
  76. /package/dist/lib/browser/{NavTreeDocumentTitle-GEE3URWH.mjs.map → NavTreeDocumentTitle-L6NLLWLD.mjs.map} +0 -0
  77. /package/dist/lib/browser/{app-graph-builder-XGA4GPGL.mjs.map → app-graph-builder-Z43OTNI2.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-navtree",
3
- "version": "0.8.4-main.c85a9c8dae",
3
+ "version": "0.8.4-main.d05673bc65",
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",
@@ -33,30 +33,30 @@
33
33
  "@radix-ui/react-dismissable-layer": "1.1.5",
34
34
  "@radix-ui/react-use-controllable-state": "1.1.0",
35
35
  "date-fns": "^3.3.1",
36
- "@dxos/app-framework": "0.8.4-main.c85a9c8dae",
37
- "@dxos/app-graph": "0.8.4-main.c85a9c8dae",
38
- "@dxos/debug": "0.8.4-main.c85a9c8dae",
39
- "@dxos/app-toolkit": "0.8.4-main.c85a9c8dae",
40
- "@dxos/async": "0.8.4-main.c85a9c8dae",
41
- "@dxos/echo": "0.8.4-main.c85a9c8dae",
42
- "@dxos/keyboard": "0.8.4-main.c85a9c8dae",
43
- "@dxos/invariant": "0.8.4-main.c85a9c8dae",
44
- "@dxos/operation": "0.8.4-main.c85a9c8dae",
45
- "@dxos/log": "0.8.4-main.c85a9c8dae",
46
- "@dxos/plugin-deck": "0.8.4-main.c85a9c8dae",
47
- "@dxos/plugin-graph": "0.8.4-main.c85a9c8dae",
48
- "@dxos/plugin-observability": "0.8.4-main.c85a9c8dae",
49
- "@dxos/protocols": "0.8.4-main.c85a9c8dae",
50
- "@dxos/react-client": "0.8.4-main.c85a9c8dae",
51
- "@dxos/react-ui-list": "0.8.4-main.c85a9c8dae",
52
- "@dxos/react-ui-attention": "0.8.4-main.c85a9c8dae",
53
- "@dxos/lit-ui": "0.8.4-main.c85a9c8dae",
54
- "@dxos/react-ui-menu": "0.8.4-main.c85a9c8dae",
55
- "@dxos/react-ui-mosaic": "0.8.4-main.c85a9c8dae",
56
- "@dxos/react-ui-tabs": "0.8.4-main.c85a9c8dae",
57
- "@dxos/react-ui-searchlist": "0.8.4-main.c85a9c8dae",
58
- "@dxos/util": "0.8.4-main.c85a9c8dae",
59
- "@dxos/react-ui-stack": "0.8.4-main.c85a9c8dae"
36
+ "@dxos/app-framework": "0.8.4-main.d05673bc65",
37
+ "@dxos/app-graph": "0.8.4-main.d05673bc65",
38
+ "@dxos/async": "0.8.4-main.d05673bc65",
39
+ "@dxos/echo": "0.8.4-main.d05673bc65",
40
+ "@dxos/debug": "0.8.4-main.d05673bc65",
41
+ "@dxos/app-toolkit": "0.8.4-main.d05673bc65",
42
+ "@dxos/invariant": "0.8.4-main.d05673bc65",
43
+ "@dxos/keyboard": "0.8.4-main.d05673bc65",
44
+ "@dxos/lit-ui": "0.8.4-main.d05673bc65",
45
+ "@dxos/log": "0.8.4-main.d05673bc65",
46
+ "@dxos/operation": "0.8.4-main.d05673bc65",
47
+ "@dxos/plugin-deck": "0.8.4-main.d05673bc65",
48
+ "@dxos/plugin-graph": "0.8.4-main.d05673bc65",
49
+ "@dxos/plugin-observability": "0.8.4-main.d05673bc65",
50
+ "@dxos/protocols": "0.8.4-main.d05673bc65",
51
+ "@dxos/react-client": "0.8.4-main.d05673bc65",
52
+ "@dxos/react-ui-attention": "0.8.4-main.d05673bc65",
53
+ "@dxos/react-ui-list": "0.8.4-main.d05673bc65",
54
+ "@dxos/react-ui-menu": "0.8.4-main.d05673bc65",
55
+ "@dxos/react-ui-mosaic": "0.8.4-main.d05673bc65",
56
+ "@dxos/react-ui-searchlist": "0.8.4-main.d05673bc65",
57
+ "@dxos/react-ui-stack": "0.8.4-main.d05673bc65",
58
+ "@dxos/util": "0.8.4-main.d05673bc65",
59
+ "@dxos/react-ui-tabs": "0.8.4-main.d05673bc65"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@effect-atom/atom-react": "^0.5.0",
@@ -67,15 +67,15 @@
67
67
  "react": "~19.2.3",
68
68
  "react-dom": "~19.2.3",
69
69
  "vite": "^7.1.11",
70
- "@dxos/plugin-attention": "0.8.4-main.c85a9c8dae",
71
- "@dxos/plugin-client": "0.8.4-main.c85a9c8dae",
72
- "@dxos/plugin-testing": "0.8.4-main.c85a9c8dae",
73
- "@dxos/plugin-space": "0.8.4-main.c85a9c8dae",
74
- "@dxos/random": "0.8.4-main.c85a9c8dae",
75
- "@dxos/plugin-theme": "0.8.4-main.c85a9c8dae",
76
- "@dxos/storybook-utils": "0.8.4-main.c85a9c8dae",
77
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
78
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
70
+ "@dxos/plugin-attention": "0.8.4-main.d05673bc65",
71
+ "@dxos/plugin-client": "0.8.4-main.d05673bc65",
72
+ "@dxos/plugin-space": "0.8.4-main.d05673bc65",
73
+ "@dxos/plugin-testing": "0.8.4-main.d05673bc65",
74
+ "@dxos/plugin-theme": "0.8.4-main.d05673bc65",
75
+ "@dxos/random": "0.8.4-main.d05673bc65",
76
+ "@dxos/storybook-utils": "0.8.4-main.d05673bc65",
77
+ "@dxos/ui-theme": "0.8.4-main.d05673bc65",
78
+ "@dxos/react-ui": "0.8.4-main.d05673bc65"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "@effect-atom/atom-react": "^0.5.0",
@@ -83,8 +83,8 @@
83
83
  "effect": "3.19.16",
84
84
  "react": "~19.2.3",
85
85
  "react-dom": "~19.2.3",
86
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
87
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae"
86
+ "@dxos/react-ui": "0.8.4-main.d05673bc65",
87
+ "@dxos/ui-theme": "0.8.4-main.d05673bc65"
88
88
  },
89
89
  "publishConfig": {
90
90
  "access": "public"
@@ -47,7 +47,7 @@ export default Capability.makeModule(
47
47
  };
48
48
 
49
49
  const eventHandler = debounce(() => {
50
- Graph.traverse(graph, { relation: 'child', visitor });
50
+ Graph.traverse(graph, { relation: ['child', 'action'], visitor });
51
51
  }, 500);
52
52
 
53
53
  const unsubscribe = graph.onNodeChanged.on(eventHandler);
@@ -3,9 +3,11 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
+ import * as Option from 'effect/Option';
6
7
 
7
8
  import { Capabilities, Capability } from '@dxos/app-framework';
8
9
  import { AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
10
+ import { expandAttendableId } from '@dxos/react-ui-attention';
9
11
  import { log } from '@dxos/log';
10
12
  import { OperationResolver } from '@dxos/operation';
11
13
  import { Graph } from '@dxos/plugin-graph';
@@ -18,25 +20,24 @@ export default Capability.makeModule(
18
20
  OperationResolver.make({
19
21
  operation: LayoutOperation.Expose,
20
22
  handler: Effect.fnUntraced(function* ({ subject }) {
21
- // TODO(wittjosiah): There's a lot of scenarios where this will fail.
22
- // Probably needs to be aware of the composer ontology to more reliably ensure objects are accessible.
23
23
  const { graph } = yield* Capability.get(AppCapabilities.AppGraph);
24
24
  const { getItem, setItem } = yield* Capability.get(NavTreeCapabilities.State);
25
- yield* Effect.tryPromise(() => Graph.waitForPath(graph, { target: subject }, { timeout: 1_000 })).pipe(
26
- Effect.andThen((path) => {
27
- [...Array(path.length)].forEach((_, index) => {
28
- const subpath = path.slice(0, index);
29
- const value = getItem(subpath);
30
- if (!value.open) {
31
- setItem(subpath, 'open', true);
32
- }
33
- });
34
- }),
35
- Effect.catchAll(() => {
36
- log('Path to node not found', { subject });
37
- return Effect.void;
38
- }),
39
- );
25
+
26
+ const prefixes = expandAttendableId(subject);
27
+
28
+ for (const qualifiedId of prefixes) {
29
+ Graph.expand(graph, qualifiedId, 'child');
30
+
31
+ const treePath = prefixes.slice(0, prefixes.indexOf(qualifiedId) + 1);
32
+ const state = getItem(treePath);
33
+ if (!state.open) {
34
+ setItem(treePath, 'open', true);
35
+ }
36
+ }
37
+
38
+ if (Option.isNone(Graph.getNode(graph, subject))) {
39
+ log('Node not found after expansion', { subject });
40
+ }
40
41
  }),
41
42
  }),
42
43
  ]);
@@ -22,7 +22,7 @@ export default Capability.makeModule(() =>
22
22
  component: ({ data, ref }) => <CommandsDialogContent {...data.props} ref={ref} />,
23
23
  }),
24
24
  Surface.create({
25
- id: `${meta.id}/navigation`,
25
+ id: `${meta.id}.navigation`,
26
26
  role: 'navigation',
27
27
  filter: (data): data is { popoverAnchorId?: string; current: string } => typeof data.current === 'string',
28
28
  component: ({ data, ref }) => {
@@ -36,14 +36,14 @@ export default Capability.makeModule(() =>
36
36
  },
37
37
  }),
38
38
  Surface.create({
39
- id: `${meta.id}/document-title`,
39
+ id: `${meta.id}.document-title`,
40
40
  role: 'document-title',
41
41
  component: ({ data }) => (
42
42
  <NavTreeDocumentTitle node={Node.isGraphNode(data.subject) ? data.subject : undefined} />
43
43
  ),
44
44
  }),
45
45
  Surface.create({
46
- id: `${meta.id}/search-input`,
46
+ id: `${meta.id}.search-input`,
47
47
  role: 'search-input',
48
48
  position: 'fallback',
49
49
  component: () => <CommandsTrigger />,
@@ -13,7 +13,7 @@ import { Path } from '@dxos/react-ui-list';
13
13
  import { meta } from '../../meta';
14
14
  import { type NavTreeCapabilities as NC, NavTreeCapabilities } from '../../types';
15
15
 
16
- const KEY = `${meta.id}/state/v1`;
16
+ const KEY = `${meta.id}.state.v1`;
17
17
 
18
18
  /** Default item state for new entries. */
19
19
  const defaultItemState: NC.NavTreeItemState = { open: false, current: false, alternateTree: false };
@@ -132,7 +132,7 @@ const meta = {
132
132
  NavTreePlugin(),
133
133
  ],
134
134
  capabilities: () => {
135
- const storyStateAtom = Atom.make({ tab: 'space-0' }).pipe(Atom.keepAlive);
135
+ const storyStateAtom = Atom.make({ tab: 'root/space-0' }).pipe(Atom.keepAlive);
136
136
  return [
137
137
  Capability.contributes(StoryState, storyStateAtom),
138
138
  Capability.contributes(AppCapabilities.AppGraphBuilder, storybookGraphBuilders()),
@@ -6,7 +6,7 @@ import { createContext, useContext } from 'react';
6
6
 
7
7
  import { raise } from '@dxos/debug';
8
8
 
9
- import { type NavTreeContextValue } from './types';
9
+ import { type NavTreeContextValue } from '../types';
10
10
 
11
11
  export const NavTreeContext = createContext<NavTreeContextValue | null>(null);
12
12
 
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './NavTreeContext';
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback } from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
8
  import { useActionRunner } from '@dxos/plugin-graph';
@@ -15,6 +15,7 @@ import { type ActionProperties } from '../../types';
15
15
 
16
16
  export type NavTreeItemActionMenuProps = ActionProperties & {
17
17
  parent: Node.Node;
18
+ path?: string[];
18
19
  caller?: string;
19
20
  monolithic?: boolean;
20
21
  menuActions?: Node.Action[];
@@ -33,6 +34,7 @@ const coarseActionButtonProps = {
33
34
 
34
35
  export const NavTreeItemActionDropdownMenu = ({
35
36
  parent,
37
+ path,
36
38
  label,
37
39
  icon,
38
40
  testId,
@@ -42,9 +44,13 @@ export const NavTreeItemActionDropdownMenu = ({
42
44
  const { t } = useTranslation(meta.id);
43
45
  const density = useDensityContext();
44
46
  const runAction = useActionRunner();
47
+ const handleAction = useCallback(
48
+ (action: Node.Action, params: Node.InvokeProps = {}) => runAction(action, { ...params, path }),
49
+ [runAction, path],
50
+ );
45
51
 
46
52
  return (
47
- <Menu.Root caller={caller} onAction={runAction}>
53
+ <Menu.Root caller={caller} onAction={handleAction}>
48
54
  <Menu.Trigger asChild>
49
55
  <IconButton
50
56
  {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
@@ -62,10 +68,16 @@ export const NavTreeItemActionDropdownMenu = ({
62
68
  };
63
69
 
64
70
  export const NavTreeItemMonolithicAction = (
65
- props: Node.Action & { parent: Node.Node; onAction?: (action: Node.Action) => void; baseLabel: string },
71
+ props: Node.Action & {
72
+ parent: Node.Node;
73
+ path?: string[];
74
+ onAction?: (action: Node.Action) => void;
75
+ baseLabel: string;
76
+ },
66
77
  ) => {
67
78
  const {
68
79
  parent,
80
+ path,
69
81
  properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
70
82
  baseLabel,
71
83
  } = props;
@@ -91,22 +103,28 @@ export const NavTreeItemMonolithicAction = (
91
103
  return;
92
104
  }
93
105
 
94
- void runAction(props, caller ? { parent, caller } : { parent });
106
+ void runAction(props, caller ? { parent, caller, path } : { parent, path });
95
107
  }}
96
108
  data-testid={testId}
97
109
  />
98
110
  );
99
111
  };
100
112
 
101
- export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
113
+ export const NavTreeItemAction = ({
114
+ monolithic,
115
+ menuActions,
116
+ parent: node,
117
+ path,
118
+ ...props
119
+ }: NavTreeItemActionMenuProps) => {
102
120
  const { t } = useTranslation(meta.id);
103
121
 
104
122
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
105
123
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
106
124
 
107
125
  return monolithic && menuActions?.length === 1 ? (
108
- <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
126
+ <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} path={path} {...menuActions[0]} />
109
127
  ) : (
110
- <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} menuActions={menuActions} />
128
+ <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} path={path} menuActions={menuActions} />
111
129
  );
112
130
  };
@@ -56,7 +56,7 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
56
56
  [primaryAction, groupedActions],
57
57
  );
58
58
 
59
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
59
+ const ActionRoot = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}` ? Popover.Anchor : Fragment;
60
60
 
61
61
  return (
62
62
  <div role='none' className='contents dx-app-no-drag'>
@@ -67,6 +67,7 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
67
67
  label={toLocalizedString(primaryAction.properties?.label, t)}
68
68
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
69
69
  parent={item}
70
+ path={path}
70
71
  monolithic={Node.isAction(primaryAction)}
71
72
  menuActions={primaryMenuActions}
72
73
  menuType={primaryAction.properties?.menuType}
@@ -84,6 +85,7 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
84
85
  label={t('tree item actions label')}
85
86
  icon='ph--dots-three-vertical--regular'
86
87
  parent={item}
88
+ path={path}
87
89
  menuActions={actions}
88
90
  menuType='dropdown'
89
91
  caller={NAV_TREE_ITEM}
@@ -85,7 +85,7 @@ const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
85
85
  switch (type) {
86
86
  case 'action': {
87
87
  const { properties: { caller } = {} } = item;
88
- return void runAction(item as Node.Action, caller ? { parent, caller } : { parent });
88
+ return void runAction(item as Node.Action, caller ? { parent, path, caller } : { parent, path });
89
89
  }
90
90
 
91
91
  case 'tab':
@@ -7,9 +7,11 @@ import * as Option from 'effect/Option';
7
7
  import React, { memo, useCallback, useMemo } from 'react';
8
8
 
9
9
  import { Node } from '@dxos/app-graph';
10
+ import { isPinnedWorkspace } from '@dxos/app-toolkit';
10
11
  import { useAppGraph } from '@dxos/app-toolkit/ui';
11
12
  import { Graph, useActionRunner, useConnections, useEdges } from '@dxos/plugin-graph';
12
13
  import { DensityProvider, IconButton, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
14
+ import { useAttended } from '@dxos/react-ui-attention';
13
15
  import { Tree } from '@dxos/react-ui-list';
14
16
  import { Menu, type MenuItem } from '@dxos/react-ui-menu';
15
17
  import { Tabs } from '@dxos/react-ui-tabs';
@@ -137,11 +139,25 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
137
139
  const { t } = useTranslation(meta.id);
138
140
  const { renderItemEnd: ItemEnd } = useNavTreeContext();
139
141
  const title = toLocalizedString(item.properties.label, t);
140
- const backCapableWorkspace = item.id.startsWith('!');
142
+ const backCapableWorkspace = isPinnedWorkspace(item.id);
141
143
 
142
144
  const { primaryAction, groupedActions, menuActions, onAction } = useL1MenuActions({ item, path });
143
145
  useLoadDescendents(item);
144
146
 
147
+ // TODO(wittjosiah): Hack to only show containsAttended when attended item is within alternate tree (e.g. settings).
148
+ // Since every object in the space is a descendant of the space node, we'd otherwise always show containsAttended.
149
+ const alternateTree = useAlternateTreeItem(item);
150
+ const attended = useAttended();
151
+ const alternateTreeOpen = useMemo(() => {
152
+ if (!alternateTree) {
153
+ return true;
154
+ }
155
+ const hasAlternateAttended = attended.some(
156
+ (attendedId) => attendedId === alternateTree.id || attendedId.startsWith(alternateTree.id + '/'),
157
+ );
158
+ return !hasAlternateAttended;
159
+ }, [alternateTree, attended]);
160
+
145
161
  return (
146
162
  <div className='flex w-full items-center border-b border-subdued-separator dx-app-drag dx-density-coarse pe-1'>
147
163
  {backCapableWorkspace ? (
@@ -168,6 +184,7 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
168
184
  label={toLocalizedString(primaryAction.properties?.label, t)}
169
185
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
170
186
  parent={item}
187
+ path={path}
171
188
  monolithic={Node.isAction(primaryAction)}
172
189
  menuActions={Node.isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction?.id ?? '']}
173
190
  menuType={primaryAction.properties?.menuType}
@@ -204,7 +221,7 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
204
221
  <Menu.Content group={item} items={menuActions as MenuItem[]} />
205
222
  </Menu.Root>
206
223
  )}
207
- {ItemEnd && <ItemEnd node={item} open />}
224
+ {ItemEnd && <ItemEnd node={item} open={alternateTreeOpen} />}
208
225
  </div>
209
226
  </div>
210
227
  );
@@ -269,10 +286,10 @@ const useL1MenuActions = ({ item, path }: Pick<L1PanelProps, 'item' | 'path'>) =
269
286
  }
270
287
  setAlternateTree?.(alternatePath, !isAlternate);
271
288
  } else {
272
- void runAction(action, params);
289
+ void runAction(action, { ...params, path });
273
290
  }
274
291
  },
275
- [settingsActionId, setAlternateTree, alternatePath, isAlternate, runAction, graph, alternateTree],
292
+ [settingsActionId, setAlternateTree, alternatePath, isAlternate, runAction, graph, alternateTree, path],
276
293
  );
277
294
 
278
295
  return { primaryAction, groupedActions, menuActions, onAction };
@@ -38,7 +38,7 @@ export const CommandsDialogContent = forwardRef<HTMLDivElement, CommandsDialogCo
38
38
  const actionMap = new Set<string>();
39
39
  const actions: Node.ActionLike[] = [];
40
40
  Graph.traverse(graph, {
41
- relation: 'child',
41
+ relation: ['child', 'action'],
42
42
  visitor: (node, path) => {
43
43
  if (
44
44
  (Node.isAction(node) || Node.isActionGroup(node)) &&
@@ -71,62 +71,64 @@ export const CommandsDialogContent = forwardRef<HTMLDivElement, CommandsDialogCo
71
71
 
72
72
  return (
73
73
  <Dialog.Content ref={forwardedRef}>
74
- <Dialog.Title>{t('commands dialog title', { ns: meta.id })}</Dialog.Title>
75
- <SearchList.Root onSearch={handleSearch}>
76
- <SearchList.Content>
77
- <SearchList.Input placeholder={t('command list input placeholder')} />
78
- <SearchList.Viewport>
79
- {results.map((action) => {
80
- const shortcut =
81
- typeof action.properties.keyBinding === 'string'
82
- ? action.properties.keyBinding
83
- : action.properties.keyBinding?.[getHostPlatform()];
74
+ <Dialog.Title srOnly>{t('commands dialog title', { ns: meta.id })}</Dialog.Title>
75
+ <Dialog.Body>
76
+ <SearchList.Root onSearch={handleSearch}>
77
+ <SearchList.Content>
78
+ <SearchList.Input placeholder={t('command list input placeholder')} />
79
+ <SearchList.Viewport>
80
+ {results.map((action) => {
81
+ const shortcut =
82
+ typeof action.properties.keyBinding === 'string'
83
+ ? action.properties.keyBinding
84
+ : action.properties.keyBinding?.[getHostPlatform()];
84
85
 
85
- return (
86
- <SearchList.Item
87
- value={action.id}
88
- key={action.id}
89
- label={toLocalizedString(action.properties.label, t)}
90
- icon={action.properties.icon}
91
- suffix={shortcut ? keySymbols(shortcut).join('') : undefined}
92
- onSelect={() => {
93
- if (action.properties.disabled) {
94
- return;
95
- }
96
-
97
- if (Node.isActionGroup(action)) {
98
- setSelected(action.id);
99
- return;
100
- }
86
+ return (
87
+ <SearchList.Item
88
+ value={action.id}
89
+ key={action.id}
90
+ label={toLocalizedString(action.properties.label, t)}
91
+ icon={action.properties.icon}
92
+ suffix={shortcut ? keySymbols(shortcut).join('') : undefined}
93
+ onSelect={() => {
94
+ if (action.properties.disabled) {
95
+ return;
96
+ }
101
97
 
102
- invokeSync(LayoutOperation.UpdateDialog, { state: false });
103
- setTimeout(() => {
104
- const node = Graph.getConnections(
105
- graph,
106
- group?.id ?? action.id,
107
- Node.childRelation('inbound'),
108
- )[0];
109
- if (node && Node.isAction(action)) {
110
- void runAction(action, { parent: node, caller: KEY_BINDING });
98
+ if (Node.isActionGroup(action)) {
99
+ setSelected(action.id);
100
+ return;
111
101
  }
112
- });
113
- }}
114
- classNames='flex items-center gap-2'
115
- disabled={action.properties.disabled}
116
- {...(action.properties?.testId && {
117
- 'data-testid': action.properties.testId,
118
- })}
119
- />
120
- );
121
- })}
122
- </SearchList.Viewport>
123
- </SearchList.Content>
124
- </SearchList.Root>
125
- <div role='none' className='p-form-padding'>
102
+
103
+ invokeSync(LayoutOperation.UpdateDialog, { state: false });
104
+ setTimeout(() => {
105
+ const node = Graph.getConnections(
106
+ graph,
107
+ group?.id ?? action.id,
108
+ Node.childRelation('inbound'),
109
+ )[0];
110
+ if (node && Node.isAction(action)) {
111
+ void runAction(action, { parent: node, caller: KEY_BINDING });
112
+ }
113
+ });
114
+ }}
115
+ classNames='flex items-center gap-2'
116
+ disabled={action.properties.disabled}
117
+ {...(action.properties?.testId && {
118
+ 'data-testid': action.properties.testId,
119
+ })}
120
+ />
121
+ );
122
+ })}
123
+ </SearchList.Viewport>
124
+ </SearchList.Content>
125
+ </SearchList.Root>
126
+ </Dialog.Body>
127
+ <Dialog.ActionBar>
126
128
  <Dialog.Close asChild>
127
129
  <Button classNames='w-full'>{t('close label', { ns: osTranslations })}</Button>
128
130
  </Dialog.Close>
129
- </div>
131
+ </Dialog.ActionBar>
130
132
  </Dialog.Content>
131
133
  );
132
134
  },
@@ -131,7 +131,7 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
131
131
  if (Node.isAction(node)) {
132
132
  const [parent] = Graph.getConnections(graph, node.id, Node.childRelation('inbound'));
133
133
  if (parent) {
134
- void runAction(node, { parent, caller: NAV_TREE_ITEM });
134
+ void runAction(node, { parent, path, caller: NAV_TREE_ITEM });
135
135
  }
136
136
  return;
137
137
  }
@@ -177,8 +177,9 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
177
177
  const targetNode = target.data.item as NavTreeItemGraphNode;
178
178
  const sourcePath = source.data.path as string[];
179
179
  const targetPath = target.data.path as string[];
180
+ const sameParent = sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join();
180
181
  const operation =
181
- sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join() && instruction.type !== 'make-child'
182
+ sameParent && instruction.type !== 'make-child'
182
183
  ? 'rearrange'
183
184
  : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
184
185
  const sourceParent = getParent(graph, sourceNode, sourcePath);
@@ -207,7 +208,7 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
207
208
  }
208
209
  case 'transfer': {
209
210
  const target = instruction.type === 'make-child' ? targetNode : targetParent;
210
- if (!target?.properties.onTransferStart || !sourceParent?.properties.onTransferEnd) {
211
+ if (!target?.properties.onTransferStart || target?.id === sourceParent?.id) {
211
212
  break;
212
213
  }
213
214
  void target?.properties.onTransferStart(sourceNode, migrationIndex);
@@ -2,6 +2,5 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { NavTreeContainer } from './NavTreeContainer';
6
-
7
- export default NavTreeContainer;
5
+ export { NavTreeContainer, NODE_TYPE } from './NavTreeContainer';
6
+ export { NavTreeContainer as default } from './NavTreeContainer';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type ComponentType, lazy } from 'react';
6
6
 
7
- export { NODE_TYPE } from './NavTreeContainer/NavTreeContainer';
7
+ export { NODE_TYPE } from './NavTreeContainer';
8
8
 
9
9
  export const CommandsDialogContent: ComponentType<any> = lazy(() => import('./CommandsDialogContent'));
10
10
  export const CommandsTrigger: ComponentType<any> = lazy(() => import('./CommandsTrigger'));
@@ -9,30 +9,26 @@ import { Graph, Node, useActions as useGraphActions } from '@dxos/plugin-graph';
9
9
 
10
10
  import { type FlattenedActions } from '../types';
11
11
 
12
- /**
13
- * Returns flattened actions and grouped sub-actions for a given graph node.
14
- */
12
+ /** Returns flattened actions and grouped sub-actions for a given graph node. */
15
13
  export const useActions = (node: Node.Node): FlattenedActions => {
16
14
  const { graph } = useAppGraph();
17
15
  const actions = useGraphActions(graph, node.id);
18
16
 
19
- return useMemo(
20
- () =>
21
- actions.reduce(
22
- (acc: FlattenedActions, arg) => {
23
- if (arg.properties.disposition === 'item') {
24
- return acc;
25
- }
26
-
27
- acc.actions.push(arg);
28
- if (!Node.isAction(arg)) {
29
- const actionGroup = Graph.getActions(graph, arg.id);
30
- acc.groupedActions[arg.id] = actionGroup;
31
- }
17
+ return useMemo(() => {
18
+ return actions.reduce(
19
+ (acc: FlattenedActions, arg) => {
20
+ if (arg.properties.disposition === 'item') {
32
21
  return acc;
33
- },
34
- { actions: [], groupedActions: {} },
35
- ),
36
- [actions],
37
- );
22
+ }
23
+
24
+ acc.actions.push(arg);
25
+ if (!Node.isAction(arg)) {
26
+ const actionGroup = Graph.getActions(graph, arg.id);
27
+ acc.groupedActions[arg.id] = actionGroup;
28
+ }
29
+ return acc;
30
+ },
31
+ { actions: [], groupedActions: {} },
32
+ );
33
+ }, [actions]);
38
34
  };