@dxos/plugin-navtree 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934

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 (164) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/neutral/CommandsDialogContent-UBGIQ5FI.mjs +112 -0
  4. package/dist/lib/neutral/CommandsDialogContent-UBGIQ5FI.mjs.map +7 -0
  5. package/dist/lib/neutral/CommandsTrigger-BH5WLMHK.mjs +27 -0
  6. package/dist/lib/neutral/CommandsTrigger-BH5WLMHK.mjs.map +7 -0
  7. package/dist/lib/neutral/NavTreeContainer-6AR4BFDD.mjs +12 -0
  8. package/dist/lib/neutral/NavTreeContainer-6AR4BFDD.mjs.map +7 -0
  9. package/dist/lib/neutral/NavTreeDocumentTitle-H3E6ZLYE.mjs +22 -0
  10. package/dist/lib/neutral/NavTreeDocumentTitle-H3E6ZLYE.mjs.map +7 -0
  11. package/dist/lib/neutral/NavTreePlugin.mjs +56 -0
  12. package/dist/lib/neutral/NavTreePlugin.mjs.map +7 -0
  13. package/dist/lib/neutral/NavTreePlugin.node.mjs +18 -0
  14. package/dist/lib/neutral/NavTreePlugin.node.mjs.map +7 -0
  15. package/dist/lib/neutral/app-graph-builder-Z347LKSF.mjs +42 -0
  16. package/dist/lib/neutral/app-graph-builder-Z347LKSF.mjs.map +7 -0
  17. package/dist/lib/neutral/capabilities/index.mjs +17 -0
  18. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-5OPDKH6B.mjs +273 -0
  20. package/dist/lib/neutral/chunk-5OPDKH6B.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  22. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-JOSNCOV4.mjs +79 -0
  24. package/dist/lib/neutral/chunk-JOSNCOV4.mjs.map +7 -0
  25. package/dist/lib/neutral/chunk-T7QS2EFI.mjs +8 -0
  26. package/dist/lib/neutral/chunk-T7QS2EFI.mjs.map +7 -0
  27. package/dist/lib/neutral/chunk-YDE5I3P5.mjs +22 -0
  28. package/dist/lib/neutral/chunk-YDE5I3P5.mjs.map +7 -0
  29. package/dist/lib/neutral/chunk-ZSWYB7JW.mjs +27 -0
  30. package/dist/lib/neutral/chunk-ZSWYB7JW.mjs.map +7 -0
  31. package/dist/lib/neutral/components/index.mjs +879 -0
  32. package/dist/lib/neutral/components/index.mjs.map +7 -0
  33. package/dist/lib/neutral/containers/index.mjs +20 -0
  34. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  35. package/dist/lib/neutral/expose-26FV4W5D.mjs +38 -0
  36. package/dist/lib/neutral/expose-26FV4W5D.mjs.map +7 -0
  37. package/dist/lib/neutral/hooks/index.mjs +169 -0
  38. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  39. package/dist/lib/neutral/index.mjs +34 -0
  40. package/dist/lib/neutral/index.mjs.map +7 -0
  41. package/dist/lib/neutral/keyboard-EJ65BVTC.mjs +56 -0
  42. package/dist/lib/neutral/keyboard-EJ65BVTC.mjs.map +7 -0
  43. package/dist/lib/neutral/meta.json +1 -0
  44. package/dist/lib/neutral/meta.mjs +12 -0
  45. package/dist/lib/neutral/meta.mjs.map +7 -0
  46. package/dist/lib/neutral/operation-handler-ORDOJUL2.mjs +13 -0
  47. package/dist/lib/neutral/operation-handler-ORDOJUL2.mjs.map +7 -0
  48. package/dist/lib/neutral/operations/index.mjs +8 -0
  49. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  50. package/dist/lib/neutral/plugin.mjs +16 -0
  51. package/dist/lib/neutral/plugin.mjs.map +7 -0
  52. package/dist/lib/neutral/react-surface-3AJ6BHL4.mjs +50 -0
  53. package/dist/lib/neutral/react-surface-3AJ6BHL4.mjs.map +7 -0
  54. package/dist/lib/neutral/state-VVITVXWM.mjs +127 -0
  55. package/dist/lib/neutral/state-VVITVXWM.mjs.map +7 -0
  56. package/dist/lib/neutral/testing/index.mjs +436 -0
  57. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  58. package/dist/lib/neutral/translations.mjs +23 -0
  59. package/dist/lib/neutral/translations.mjs.map +7 -0
  60. package/dist/lib/neutral/types/index.mjs +10 -0
  61. package/dist/lib/neutral/types/index.mjs.map +7 -0
  62. package/dist/types/src/NavTreePlugin.d.ts +1 -0
  63. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  64. package/dist/types/src/NavTreePlugin.node.d.ts +4 -0
  65. package/dist/types/src/NavTreePlugin.node.d.ts.map +1 -0
  66. package/dist/types/src/NavTreePlugin.test.d.ts +2 -0
  67. package/dist/types/src/NavTreePlugin.test.d.ts.map +1 -0
  68. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  69. package/dist/types/src/capabilities/index.d.ts +2 -2
  70. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  71. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  72. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  73. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
  74. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/state.d.ts +2 -2
  76. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  77. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +8 -9
  78. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  79. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +2 -6
  80. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  81. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
  82. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  83. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  84. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  85. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  86. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  87. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  88. package/dist/types/src/components/types.d.ts +0 -1
  89. package/dist/types/src/components/types.d.ts.map +1 -1
  90. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -1
  91. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts +8 -9
  92. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts.map +1 -1
  93. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -1
  94. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -1
  95. package/dist/types/src/experimental/Tree.d.ts +2 -2
  96. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  97. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  98. package/dist/types/src/hooks/useActions.d.ts.map +1 -1
  99. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -1
  100. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -1
  101. package/dist/types/src/hooks/useNavTreeState.d.ts +1 -5
  102. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -1
  103. package/dist/types/src/index.d.ts +1 -2
  104. package/dist/types/src/index.d.ts.map +1 -1
  105. package/dist/types/src/meta.d.ts.map +1 -1
  106. package/dist/types/src/operations/expose.d.ts +1 -1
  107. package/dist/types/src/operations/expose.d.ts.map +1 -1
  108. package/dist/types/src/operations/index.d.ts +1 -1
  109. package/dist/types/src/operations/index.d.ts.map +1 -1
  110. package/dist/types/src/plugin.d.ts +4 -0
  111. package/dist/types/src/plugin.d.ts.map +1 -0
  112. package/dist/types/src/testing/generator.d.ts.map +1 -1
  113. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  114. package/dist/types/src/translations.d.ts +8 -10
  115. package/dist/types/src/translations.d.ts.map +1 -1
  116. package/dist/types/src/types/NavTreeCapabilities.d.ts +13 -0
  117. package/dist/types/src/types/NavTreeCapabilities.d.ts.map +1 -0
  118. package/dist/types/src/types/NavTreeEvents.d.ts +3 -0
  119. package/dist/types/src/types/NavTreeEvents.d.ts.map +1 -0
  120. package/dist/types/src/types/index.d.ts +2 -2
  121. package/dist/types/src/types/index.d.ts.map +1 -1
  122. package/dist/types/src/util.d.ts +1 -1
  123. package/dist/types/src/util.d.ts.map +1 -1
  124. package/dist/types/tsconfig.tsbuildinfo +1 -1
  125. package/package.json +103 -53
  126. package/src/NavTreePlugin.node.ts +17 -0
  127. package/src/NavTreePlugin.test.ts +27 -0
  128. package/src/NavTreePlugin.tsx +4 -22
  129. package/src/capabilities/app-graph-builder.ts +1 -1
  130. package/src/capabilities/index.ts +1 -1
  131. package/src/capabilities/operation-handler.ts +1 -1
  132. package/src/capabilities/react-surface.tsx +2 -3
  133. package/src/capabilities/state.ts +12 -8
  134. package/src/components/NavTree/NavTree.stories.tsx +4 -4
  135. package/src/components/NavTree/NavTree.tsx +2 -2
  136. package/src/components/NavTreeItem/NavTreeItemAction.tsx +3 -4
  137. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +1 -1
  138. package/src/components/Sidebar/L0Menu.tsx +6 -8
  139. package/src/components/Sidebar/L1Panel.tsx +84 -142
  140. package/src/components/types.ts +0 -1
  141. package/src/containers/CommandsDialogContent/CommandsDialogContent.stories.tsx +2 -1
  142. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +42 -46
  143. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +0 -9
  144. package/src/experimental/Tree.tsx +2 -2
  145. package/src/hooks/useNavTreeModel.ts +1 -1
  146. package/src/hooks/useNavTreeState.ts +1 -8
  147. package/src/index.ts +1 -2
  148. package/src/meta.ts +1 -0
  149. package/src/operations/expose.ts +1 -1
  150. package/src/operations/index.ts +1 -1
  151. package/src/plugin.ts +11 -0
  152. package/src/translations.ts +0 -1
  153. package/src/types/NavTreeCapabilities.ts +21 -0
  154. package/src/types/{events.ts → NavTreeEvents.ts} +3 -3
  155. package/src/types/index.ts +3 -2
  156. package/src/util.ts +1 -1
  157. package/dist/lib/browser/index.mjs +0 -202
  158. package/dist/lib/browser/index.mjs.map +0 -7
  159. package/dist/lib/browser/meta.json +0 -1
  160. package/dist/types/src/types/capabilities.d.ts +0 -16
  161. package/dist/types/src/types/capabilities.d.ts.map +0 -1
  162. package/dist/types/src/types/events.d.ts +0 -5
  163. package/dist/types/src/types/events.d.ts.map +0 -1
  164. package/src/types/capabilities.ts +0 -21
@@ -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,28 @@ 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
+ classNames='pt-[2px]'
92
+ model={navTreeContext.model}
93
+ id={item.id}
94
+ rootId={item.id}
95
+ path={path}
96
+ levelOffset={5}
97
+ draggable
98
+ gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
99
+ renderColumns={NavTreeItemColumns}
100
+ blockInstruction={navTreeContext.blockInstruction}
101
+ canDrop={navTreeContext.canDrop}
102
+ canSelect={navTreeContext.canSelect}
103
+ onOpenChange={navTreeContext.onOpenChange}
104
+ onSelect={navTreeContext.onSelect}
105
+ onItemHover={navTreeContext.onItemHover}
106
+ />
130
107
  </ScrollArea.Viewport>
131
108
  </ScrollArea.Root>
132
109
  </DensityProvider>
@@ -145,20 +122,6 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
145
122
  const { primaryAction, groupedActions, menuActions, onAction } = useL1MenuActions({ item, path });
146
123
  useLoadDescendents(item);
147
124
 
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
125
  return (
163
126
  <div
164
127
  data-tauri-drag-region
@@ -182,7 +145,7 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
182
145
  {title}
183
146
  </h2>
184
147
  {/* TODO(wittjosiah): Reconcile with NavTreeItemColumns. */}
185
- <div role='none' className='contents dx-app-no-drag'>
148
+ <div className='contents dx-app-no-drag'>
186
149
  {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled && (
187
150
  <NavTreeItemAction
188
151
  testId={primaryAction.properties?.testId}
@@ -196,115 +159,94 @@ const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path
196
159
  caller={NAV_TREE_ITEM}
197
160
  />
198
161
  )}
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} />}
162
+ <MenuActions item={item} menuActions={menuActions} onAction={onAction} />
163
+ {ItemEnd && <ItemEnd node={item} open />}
228
164
  </div>
229
165
  </div>
230
166
  );
231
167
  };
232
168
 
169
+ type L1MenuActions = {
170
+ primaryAction: Node.ActionLike;
171
+ groupedActions: Record<string, Node.Action[]>;
172
+ menuActions: Node.Action[];
173
+ onAction: (action: Node.Action, params?: Node.InvokeProps) => void;
174
+ };
175
+
233
176
  /**
234
- * Builds the menu actions for the L1 panel header, combining graph actions with a synthetic settings/back action.
177
+ * Header menu actions for an L1 workspace tab. Renders nothing for an empty
178
+ * `menuActions`, a single inline icon button for one action, and a
179
+ * `…`-menu trigger for multiple.
235
180
  */
236
- const useL1MenuActions = ({ item, path }: Pick<L1PanelProps, 'item' | 'path'>) => {
181
+ const MenuActions = ({
182
+ item,
183
+ menuActions,
184
+ onAction,
185
+ }: {
186
+ item: Node.Node;
187
+ } & Pick<L1MenuActions, 'menuActions' | 'onAction'>) => {
237
188
  const { t } = useTranslation(meta.id);
238
- const { setAlternateTree } = useNavTreeContext();
239
- const { graph } = useAppGraph();
240
- const runAction = useActionRunner();
241
189
 
242
- const alternateTree = useAlternateTreeItem(item);
243
- const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
244
- const isAlternate = useIsAlternateTree(alternatePath, item);
190
+ if (menuActions.length === 0) {
191
+ return null;
192
+ }
193
+
194
+ if (menuActions.length === 1) {
195
+ return (
196
+ <IconButton
197
+ density='coarse'
198
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
199
+ variant='ghost'
200
+ icon={menuActions[0].properties?.icon ?? 'ph--placeholder--regular'}
201
+ iconOnly
202
+ label={toLocalizedString(menuActions[0].properties?.label, t)}
203
+ data-testid={menuActions[0].properties?.testId}
204
+ onClick={() => onAction(menuActions[0] as Node.Action)}
205
+ />
206
+ );
207
+ }
208
+
209
+ return (
210
+ <Menu.Root caller={NAV_TREE_ITEM} onAction={onAction}>
211
+ <Menu.Trigger asChild>
212
+ <IconButton
213
+ density='coarse'
214
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
215
+ variant='ghost'
216
+ icon='ph--dots-three-vertical--regular'
217
+ iconOnly
218
+ label={t('tree-item-actions.label')}
219
+ data-testid='navtree.treeItem.actionsLevel0'
220
+ />
221
+ </Menu.Trigger>
222
+ <Menu.Content group={item} items={menuActions as MenuItem[]} />
223
+ </Menu.Root>
224
+ );
225
+ };
226
+
227
+ /**
228
+ * Builds the menu actions for the L1 panel header.
229
+ */
230
+ const useL1MenuActions = ({ item, path }: Pick<L1PanelProps, 'item' | 'path'>): L1MenuActions => {
231
+ const runAction = useActionRunner();
245
232
 
246
- // Graph actions.
247
233
  const { actions: actionsProp, groupedActions } = useActions(item);
248
234
  const [primaryAction, ...secondaryActions] = actionsProp.toSorted((a, _b) =>
249
235
  a.properties?.disposition === 'list-item-primary' ? -1 : 1,
250
236
  );
251
237
 
252
- const graphMenuActions = (
253
- primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : actionsProp
254
- )
238
+ const menuActions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : actionsProp)
255
239
  .flatMap((action) => (Node.isAction(action) ? [action] : []))
256
240
  .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
257
241
 
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
242
  const onAction = useCallback(
287
243
  (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
- }
244
+ void runAction(action, { ...params, path });
296
245
  },
297
- [settingsActionId, setAlternateTree, alternatePath, isAlternate, runAction, graph, alternateTree, path],
246
+ [runAction, path],
298
247
  );
299
248
 
300
249
  return { primaryAction, groupedActions, menuActions, onAction };
301
250
  };
302
251
 
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
252
  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 = () => (
@@ -71,55 +71,51 @@ export const CommandsDialogContent = forwardRef<HTMLDivElement, CommandsDialogCo
71
71
  return (
72
72
  <Dialog.Content ref={forwardedRef}>
73
73
  <Dialog.Title srOnly>{t('commands-dialog.title', { ns: meta.id })}</Dialog.Title>
74
- <Dialog.Body>
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
+ <SearchList.Root onSearch={handleSearch}>
75
+ <SearchList.Input placeholder={t('command-list-input.placeholder')} />
76
+ <SearchList.Viewport>
77
+ {results.map((action) => {
78
+ const shortcut =
79
+ typeof action.properties.keyBinding === 'string'
80
+ ? action.properties.keyBinding
81
+ : action.properties.keyBinding?.[getHostPlatform()];
84
82
 
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
- }
83
+ return (
84
+ <SearchList.Item
85
+ value={action.id}
86
+ key={action.id}
87
+ label={toLocalizedString(action.properties.label, t)}
88
+ icon={action.properties.icon}
89
+ suffix={shortcut ? keySymbols(shortcut).join('') : undefined}
90
+ onSelect={() => {
91
+ if (action.properties.disabled) {
92
+ return;
93
+ }
96
94
 
97
- if (Node.isActionGroup(action)) {
98
- setSelected(action.id);
99
- return;
100
- }
95
+ if (Node.isActionGroup(action)) {
96
+ setSelected(action.id);
97
+ return;
98
+ }
101
99
 
102
- void invokePromise(LayoutOperation.UpdateDialog, { state: false });
103
- setTimeout(() => {
104
- const lookupId = group?.id ?? action.id;
105
- const node = Graph.getConnections(graph, lookupId, Node.actionRelation('inbound'))[0];
106
- if (node && Node.isAction(action)) {
107
- void runAction(action, { parent: node, caller: KEY_BINDING });
108
- }
109
- });
110
- }}
111
- classNames='flex items-center gap-2'
112
- disabled={action.properties.disabled}
113
- {...(action.properties?.testId && {
114
- 'data-testid': action.properties.testId,
115
- })}
116
- />
117
- );
118
- })}
119
- </SearchList.Viewport>
120
- </SearchList.Content>
121
- </SearchList.Root>
122
- </Dialog.Body>
100
+ void invokePromise(LayoutOperation.UpdateDialog, { state: false });
101
+ setTimeout(() => {
102
+ const lookupId = group?.id ?? action.id;
103
+ const node = Graph.getConnections(graph, lookupId, Node.actionRelation('inbound'))[0];
104
+ if (node && Node.isAction(action)) {
105
+ void runAction(action, { parent: node, caller: KEY_BINDING });
106
+ }
107
+ });
108
+ }}
109
+ classNames='flex items-center gap-2'
110
+ disabled={action.properties.disabled}
111
+ {...(action.properties?.testId && {
112
+ 'data-testid': action.properties.testId,
113
+ })}
114
+ />
115
+ );
116
+ })}
117
+ </SearchList.Viewport>
118
+ </SearchList.Root>
123
119
  <Dialog.ActionBar>
124
120
  <Dialog.Close asChild>
125
121
  <Button classNames='w-full'>{t('close.label', { ns: osTranslations })}</Button>
@@ -230,13 +230,6 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
230
230
  });
231
231
  }, [graph]);
232
232
 
233
- const setAlternateTree = useCallback(
234
- (path: string[], open: boolean) => {
235
- setItem(path, 'alternateTree', open);
236
- },
237
- [setItem],
238
- );
239
-
240
233
  const onItemHover = useCallback(({ item }: { item: Node.Node }) => Graph.expand(graph, item.id, 'child'), [graph]);
241
234
 
242
235
  const navTreeContextValue = useMemo(
@@ -247,7 +240,6 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
247
240
  blockInstruction,
248
241
  canDrop,
249
242
  canSelect,
250
- setAlternateTree,
251
243
  onBack: handleBack,
252
244
  onOpenChange: handleOpenChange,
253
245
  onSelect: handleSelect,
@@ -260,7 +252,6 @@ export const NavTreeContainer$ = forwardRef<HTMLDivElement, NavTreeContainerProp
260
252
  blockInstruction,
261
253
  canDrop,
262
254
  canSelect,
263
- setAlternateTree,
264
255
  handleBack,
265
256
  handleOpenChange,
266
257
  handleSelect,
@@ -195,7 +195,7 @@ export const TreeNodeRow = (props: TreeNodeProps & { className?: string }) => {
195
195
  className={mx(
196
196
  'group w-full items-center cursor-pointer bg-hover-surface',
197
197
  // TODO(burdon): Use data-active.
198
- selected?.[id] && 'bg-active-surface',
198
+ selected?.[id] && 'bg-current-surface',
199
199
  className,
200
200
  )}
201
201
  onClick={() => selected && onChangeSelected?.(id, !selected[id])}
@@ -220,7 +220,7 @@ export const TreeNode = (props: TreeNodeProps & { ancestors?: TreeNodeData[] })
220
220
  const { root, header } = getSlots?.(props.node, open?.[id] ?? false, depth, ancestors) ?? {};
221
221
 
222
222
  return (
223
- <div role='none' className={mx('flex flex-col', root)}>
223
+ <div className={mx('flex flex-col', root)}>
224
224
  <TreeNodeRow {...props} className={header} />
225
225
  {(children?.length ?? 0) > 0 && open?.[id] && <TreeChildNodes {...props} ancestors={ancestors} />}
226
226
  </div>
@@ -7,7 +7,7 @@ import * as Option from 'effect/Option';
7
7
  import { useMemo } from 'react';
8
8
 
9
9
  import { useAppGraph } from '@dxos/app-toolkit/ui';
10
- import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
10
+ import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck';
11
11
  import { type Node } from '@dxos/plugin-graph';
12
12
  import { Path, type TreeModel } from '@dxos/react-ui-list';
13
13
  import { mx } from '@dxos/ui-theme';
@@ -12,7 +12,7 @@ import { NavTreeCapabilities } from '#types';
12
12
  export type UseNavTreeStateResult = {
13
13
  getItem: (path: string[]) => NavTreeCapabilities.NavTreeItemState;
14
14
  getItemAtom: (path: string[]) => Atom.Atom<NavTreeCapabilities.NavTreeItemState>;
15
- setItem: (path: string[], key: 'open' | 'current' | 'alternateTree', next: boolean) => void;
15
+ setItem: (path: string[], key: 'open' | 'current', next: boolean) => void;
16
16
  };
17
17
 
18
18
  /**
@@ -33,10 +33,3 @@ export const useNavTreeItemState = (path: string[]): NavTreeCapabilities.NavTree
33
33
  const atom = useMemo(() => getItemAtom(path), [getItemAtom, pathKey]);
34
34
  return useAtomValue(atom);
35
35
  };
36
-
37
- /**
38
- * Hook that subscribes to the alternateTree state for a tree item.
39
- */
40
- export const useIsAlternateTree = (path: string[], _item?: any): boolean => {
41
- return useNavTreeItemState(path).alternateTree ?? false;
42
- };
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export { NavTreeCapabilities, NavTreeEvents } from './types';
6
- export * from './NavTreePlugin';
7
5
  export * from './meta';
6
+ export * from './types';
8
7
  export * from './util';
package/src/meta.ts CHANGED
@@ -12,6 +12,7 @@ export const meta: Plugin.Meta = {
12
12
  Hierarchical navigation tree for browsing spaces, folders, and objects.
13
13
  Provides sidebar navigation with collapsible sections and quick access to workspace content.
14
14
  `,
15
+ tags: ['system'],
15
16
  };
16
17
 
17
18
  // TODO(wittjosiah): Factor out.
@@ -7,8 +7,8 @@ import * as Option from 'effect/Option';
7
7
 
8
8
  import { Capability } from '@dxos/app-framework';
9
9
  import { AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
10
+ import { Operation } from '@dxos/compute';
10
11
  import { log } from '@dxos/log';
11
- import { Operation } from '@dxos/operation';
12
12
  import { Graph } from '@dxos/plugin-graph';
13
13
  import { expandAttendableId } from '@dxos/react-ui-attention';
14
14
 
@@ -2,6 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { OperationHandlerSet } from '@dxos/operation';
5
+ import { OperationHandlerSet } from '@dxos/compute';
6
6
 
7
7
  export const NavTreeOperationHandlerSet = OperationHandlerSet.lazy(() => import('./expose'));
package/src/plugin.ts ADDED
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+
7
+ import { meta } from './meta';
8
+
9
+ export const NavTreePlugin = Plugin.lazy(meta, () => import('#plugin'));
10
+
11
+ export { NavTreeOperationHandlerSet } from './operations';
@@ -11,7 +11,6 @@ export const translations = [
11
11
  'en-US': {
12
12
  [meta.id]: {
13
13
  'app-menu.label': 'App menu',
14
- 'open-settings.label': 'Settings',
15
14
  'open-commands.label': 'Search commands',
16
15
  'commands-dialog.title': 'Commands',
17
16
  'command-list-input.placeholder': 'Search for commands…',
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import { type Atom } from '@effect-atom/atom-react';
8
+
9
+ import { Capability } from '@dxos/app-framework';
10
+
11
+ import { meta } from '#meta';
12
+
13
+ export type NavTreeItemState = { open: boolean; current: boolean };
14
+
15
+ export type NavTreeStateStore = {
16
+ getItem: (path: string[]) => NavTreeItemState;
17
+ getItemAtom: (path: string[]) => Atom.Atom<NavTreeItemState>;
18
+ setItem: (path: string[], key: 'open' | 'current', next: boolean) => void;
19
+ };
20
+
21
+ export const State = Capability.make<NavTreeStateStore>(`${meta.id}.capability.state`);
@@ -2,11 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ // @import-as-namespace
6
+
5
7
  import { type ActivationEvent } from '@dxos/app-framework';
6
8
  import { AppActivationEvents } from '@dxos/app-toolkit';
7
9
 
8
10
  import { meta } from '#meta';
9
11
 
10
- export namespace NavTreeEvents {
11
- export const StateReady: ActivationEvent.ActivationEvent = AppActivationEvents.createStateEvent(meta.id);
12
- }
12
+ export const StateReady: ActivationEvent.ActivationEvent = AppActivationEvents.createStateEvent(meta.id);
@@ -2,6 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './capabilities';
6
- export * from './events';
7
5
  export * from './schema';
6
+
7
+ export * as NavTreeCapabilities from './NavTreeCapabilities';
8
+ export * as NavTreeEvents from './NavTreeEvents';
package/src/util.ts CHANGED
@@ -93,7 +93,7 @@ export const getChildren = (
93
93
  * Determines whether a node should be visible based on its disposition.
94
94
  */
95
95
  export const filterItems = (node: Node.Node, disposition?: string) => {
96
- if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
96
+ if (!disposition && node.properties.disposition === 'hidden') {
97
97
  return false;
98
98
  } else if (!disposition) {
99
99
  const action = Node.isAction(node);