@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.
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/neutral/CommandsDialogContent-UBGIQ5FI.mjs +112 -0
- package/dist/lib/neutral/CommandsDialogContent-UBGIQ5FI.mjs.map +7 -0
- package/dist/lib/neutral/CommandsTrigger-BH5WLMHK.mjs +27 -0
- package/dist/lib/neutral/CommandsTrigger-BH5WLMHK.mjs.map +7 -0
- package/dist/lib/neutral/NavTreeContainer-6AR4BFDD.mjs +12 -0
- package/dist/lib/neutral/NavTreeContainer-6AR4BFDD.mjs.map +7 -0
- package/dist/lib/neutral/NavTreeDocumentTitle-H3E6ZLYE.mjs +22 -0
- package/dist/lib/neutral/NavTreeDocumentTitle-H3E6ZLYE.mjs.map +7 -0
- package/dist/lib/neutral/NavTreePlugin.mjs +56 -0
- package/dist/lib/neutral/NavTreePlugin.mjs.map +7 -0
- package/dist/lib/neutral/NavTreePlugin.node.mjs +18 -0
- package/dist/lib/neutral/NavTreePlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/app-graph-builder-Z347LKSF.mjs +42 -0
- package/dist/lib/neutral/app-graph-builder-Z347LKSF.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +17 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-5OPDKH6B.mjs +273 -0
- package/dist/lib/neutral/chunk-5OPDKH6B.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-JOSNCOV4.mjs +79 -0
- package/dist/lib/neutral/chunk-JOSNCOV4.mjs.map +7 -0
- package/dist/lib/neutral/chunk-T7QS2EFI.mjs +8 -0
- package/dist/lib/neutral/chunk-T7QS2EFI.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YDE5I3P5.mjs +22 -0
- package/dist/lib/neutral/chunk-YDE5I3P5.mjs.map +7 -0
- package/dist/lib/neutral/chunk-ZSWYB7JW.mjs +27 -0
- package/dist/lib/neutral/chunk-ZSWYB7JW.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +879 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +20 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/expose-26FV4W5D.mjs +38 -0
- package/dist/lib/neutral/expose-26FV4W5D.mjs.map +7 -0
- package/dist/lib/neutral/hooks/index.mjs +169 -0
- package/dist/lib/neutral/hooks/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +34 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/keyboard-EJ65BVTC.mjs +56 -0
- package/dist/lib/neutral/keyboard-EJ65BVTC.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +12 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/operation-handler-ORDOJUL2.mjs +13 -0
- package/dist/lib/neutral/operation-handler-ORDOJUL2.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +8 -0
- package/dist/lib/neutral/operations/index.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +16 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-3AJ6BHL4.mjs +50 -0
- package/dist/lib/neutral/react-surface-3AJ6BHL4.mjs.map +7 -0
- package/dist/lib/neutral/state-VVITVXWM.mjs +127 -0
- package/dist/lib/neutral/state-VVITVXWM.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +436 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +23 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +10 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts +1 -0
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/NavTreePlugin.node.d.ts +4 -0
- package/dist/types/src/NavTreePlugin.node.d.ts.map +1 -0
- package/dist/types/src/NavTreePlugin.test.d.ts +2 -0
- package/dist/types/src/NavTreePlugin.test.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +2 -2
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/state.d.ts +2 -2
- package/dist/types/src/capabilities/state.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts +8 -9
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +2 -6
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
- package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
- package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +0 -1
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -1
- package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts +8 -9
- package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts.map +1 -1
- package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.d.ts +2 -2
- package/dist/types/src/experimental/Tree.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useActions.d.ts.map +1 -1
- package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -1
- package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useNavTreeState.d.ts +1 -5
- package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/expose.d.ts +1 -1
- package/dist/types/src/operations/expose.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +1 -1
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/testing/update-state.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +8 -10
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/NavTreeCapabilities.d.ts +13 -0
- package/dist/types/src/types/NavTreeCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/NavTreeEvents.d.ts +3 -0
- package/dist/types/src/types/NavTreeEvents.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +103 -53
- package/src/NavTreePlugin.node.ts +17 -0
- package/src/NavTreePlugin.test.ts +27 -0
- package/src/NavTreePlugin.tsx +4 -22
- package/src/capabilities/app-graph-builder.ts +1 -1
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/operation-handler.ts +1 -1
- package/src/capabilities/react-surface.tsx +2 -3
- package/src/capabilities/state.ts +12 -8
- package/src/components/NavTree/NavTree.stories.tsx +4 -4
- package/src/components/NavTree/NavTree.tsx +2 -2
- package/src/components/NavTreeItem/NavTreeItemAction.tsx +3 -4
- package/src/components/NavTreeItem/NavTreeItemColumns.tsx +1 -1
- package/src/components/Sidebar/L0Menu.tsx +6 -8
- package/src/components/Sidebar/L1Panel.tsx +84 -142
- package/src/components/types.ts +0 -1
- package/src/containers/CommandsDialogContent/CommandsDialogContent.stories.tsx +2 -1
- package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +42 -46
- package/src/containers/NavTreeContainer/NavTreeContainer.tsx +0 -9
- package/src/experimental/Tree.tsx +2 -2
- package/src/hooks/useNavTreeModel.ts +1 -1
- package/src/hooks/useNavTreeState.ts +1 -8
- package/src/index.ts +1 -2
- package/src/meta.ts +1 -0
- package/src/operations/expose.ts +1 -1
- package/src/operations/index.ts +1 -1
- package/src/plugin.ts +11 -0
- package/src/translations.ts +0 -1
- package/src/types/NavTreeCapabilities.ts +21 -0
- package/src/types/{events.ts → NavTreeEvents.ts} +3 -3
- package/src/types/index.ts +3 -2
- package/src/util.ts +1 -1
- package/dist/lib/browser/index.mjs +0 -202
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/types/src/types/capabilities.d.ts +0 -16
- package/dist/types/src/types/capabilities.d.ts.map +0 -1
- package/dist/types/src/types/events.d.ts +0 -5
- package/dist/types/src/types/events.d.ts.map +0 -1
- 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,
|
|
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,
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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
|
|
200
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
[
|
|
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$);
|
package/src/components/types.ts
CHANGED
|
@@ -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
|
-
<
|
|
75
|
-
<SearchList.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
if (Node.isActionGroup(action)) {
|
|
96
|
+
setSelected(action.id);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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-
|
|
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
|
|
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
|
|
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'
|
|
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
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.
|
package/src/operations/expose.ts
CHANGED
|
@@ -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
|
|
package/src/operations/index.ts
CHANGED
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';
|
package/src/translations.ts
CHANGED
|
@@ -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
|
|
11
|
-
export const StateReady: ActivationEvent.ActivationEvent = AppActivationEvents.createStateEvent(meta.id);
|
|
12
|
-
}
|
|
12
|
+
export const StateReady: ActivationEvent.ActivationEvent = AppActivationEvents.createStateEvent(meta.id);
|
package/src/types/index.ts
CHANGED
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 &&
|
|
96
|
+
if (!disposition && node.properties.disposition === 'hidden') {
|
|
97
97
|
return false;
|
|
98
98
|
} else if (!disposition) {
|
|
99
99
|
const action = Node.isAction(node);
|