@dxos/plugin-navtree 0.7.5-feature-compute.4d9d99a → 0.7.5-labs.071a3e2
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/dist/lib/browser/{app-graph-builder-OF4MMBLH.mjs → app-graph-builder-BL7LRXCM.mjs} +7 -5
- package/dist/lib/browser/{app-graph-builder-OF4MMBLH.mjs.map → app-graph-builder-BL7LRXCM.mjs.map} +3 -3
- package/dist/lib/browser/chunk-54MPRXMH.mjs +1109 -0
- package/dist/lib/browser/chunk-54MPRXMH.mjs.map +7 -0
- package/dist/lib/browser/{chunk-OI7AAA6Q.mjs → chunk-AOAGODQB.mjs} +1 -1
- package/dist/lib/browser/chunk-AOAGODQB.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +21 -26
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/intent-resolver-U25VBFH3.mjs +47 -0
- package/dist/lib/browser/intent-resolver-U25VBFH3.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-surface-LNRTNSM6.mjs +57 -0
- package/dist/lib/browser/react-surface-LNRTNSM6.mjs.map +7 -0
- package/dist/lib/browser/{state-HT65TAYL.mjs → state-65TQKYX7.mjs} +6 -9
- package/dist/lib/browser/state-65TQKYX7.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +22 -22
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts +3 -2
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +88 -32
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.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 +3 -3
- package/dist/types/src/capabilities/state.d.ts.map +1 -1
- package/dist/types/src/components/CommandsDialog.d.ts.map +1 -1
- package/dist/types/src/components/CommandsTrigger.d.ts.map +1 -1
- package/dist/types/src/components/L0Menu.d.ts +9 -0
- package/dist/types/src/components/L0Menu.d.ts.map +1 -0
- package/dist/types/src/components/L1Panels.d.ts +8 -0
- package/dist/types/src/components/L1Panels.d.ts.map +1 -0
- package/dist/types/src/components/NavTree.d.ts +3 -20
- package/dist/types/src/components/NavTree.d.ts.map +1 -1
- package/dist/types/src/components/NavTree.stories.d.ts +2 -2
- package/dist/types/src/components/NavTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts +4 -3
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContext.d.ts +4 -0
- package/dist/types/src/components/NavTreeContext.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeItemAction.d.ts +1 -1
- package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemColumns.d.ts +4 -0
- package/dist/types/src/components/NavTreeItemColumns.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +25 -0
- package/dist/types/src/components/types.d.ts.map +1 -0
- package/dist/types/src/hooks.d.ts +3 -0
- package/dist/types/src/hooks.d.ts.map +1 -0
- package/dist/types/src/stories/Graph.stories.d.ts +2 -2
- package/dist/types/src/stories/Graph.stories.d.ts.map +1 -1
- package/dist/types/src/stories/StorybookNavTree.d.ts +4 -0
- package/dist/types/src/stories/StorybookNavTree.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +0 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +2 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +9 -0
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -31
- package/src/NavTreePlugin.tsx +8 -10
- package/src/capabilities/app-graph-builder.ts +6 -4
- package/src/capabilities/capabilities.ts +3 -2
- package/src/capabilities/intent-resolver.ts +22 -16
- package/src/capabilities/react-surface.tsx +15 -41
- package/src/capabilities/state.ts +5 -8
- package/src/components/CommandsDialog.tsx +3 -4
- package/src/components/CommandsTrigger.tsx +7 -5
- package/src/components/L0Menu.tsx +210 -0
- package/src/components/L1Panels.tsx +100 -0
- package/src/components/NavTree.stories.tsx +5 -5
- package/src/components/NavTree.tsx +26 -116
- package/src/components/NavTreeContainer.tsx +106 -32
- package/src/components/NavTreeContext.tsx +13 -0
- package/src/components/NavTreeItemAction.tsx +12 -4
- package/src/components/NavTreeItemColumns.tsx +71 -0
- package/src/components/NotchStart.tsx +2 -2
- package/src/components/index.ts +2 -0
- package/src/components/types.ts +33 -0
- package/src/hooks.ts +21 -0
- package/src/stories/Graph.stories.tsx +5 -4
- package/src/stories/StorybookNavTree.tsx +63 -0
- package/src/translations.ts +0 -6
- package/src/types.ts +2 -1
- package/src/util.ts +54 -0
- package/dist/lib/browser/chunk-IMGT2LAL.mjs +0 -658
- package/dist/lib/browser/chunk-IMGT2LAL.mjs.map +0 -7
- package/dist/lib/browser/chunk-OI7AAA6Q.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-74JPWYBP.mjs +0 -42
- package/dist/lib/browser/intent-resolver-74JPWYBP.mjs.map +0 -7
- package/dist/lib/browser/react-surface-6QWXI53A.mjs +0 -189
- package/dist/lib/browser/react-surface-6QWXI53A.mjs.map +0 -7
- package/dist/lib/browser/state-HT65TAYL.mjs.map +0 -7
- package/dist/types/src/components/NavTreeFooter.d.ts +0 -6
- package/dist/types/src/components/NavTreeFooter.d.ts.map +0 -1
- package/src/components/NavTreeFooter.tsx +0 -161
|
@@ -5,19 +5,28 @@
|
|
|
5
5
|
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
6
6
|
import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
7
7
|
import { untracked } from '@preact/signals-core';
|
|
8
|
-
import React, { memo, useCallback, useEffect } from 'react';
|
|
8
|
+
import React, { memo, useCallback, useEffect, useMemo } from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createIntent,
|
|
12
|
+
LayoutAction,
|
|
13
|
+
Surface,
|
|
14
|
+
useAppGraph,
|
|
15
|
+
useCapability,
|
|
16
|
+
useIntentDispatcher,
|
|
17
|
+
useLayout,
|
|
18
|
+
} from '@dxos/app-framework';
|
|
11
19
|
import { isAction, isActionLike, type Node } from '@dxos/app-graph';
|
|
12
|
-
import { useGraph } from '@dxos/plugin-graph';
|
|
13
20
|
import { isEchoObject, isSpace } from '@dxos/react-client/echo';
|
|
14
|
-
import { useMediaQuery
|
|
21
|
+
import { useMediaQuery } from '@dxos/react-ui';
|
|
15
22
|
import { isTreeData, type TreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
|
|
16
23
|
import { mx } from '@dxos/react-ui-theme';
|
|
17
24
|
import { arrayMove } from '@dxos/util';
|
|
18
25
|
|
|
19
|
-
import { NAV_TREE_ITEM, NavTree
|
|
20
|
-
import {
|
|
26
|
+
import { NAV_TREE_ITEM, NavTree } from './NavTree';
|
|
27
|
+
import { NavTreeContext } from './NavTreeContext';
|
|
28
|
+
import { type NavTreeContextValue } from './types';
|
|
29
|
+
import { NavTreeCapabilities } from '../capabilities';
|
|
21
30
|
import { type NavTreeItemGraphNode } from '../types';
|
|
22
31
|
import {
|
|
23
32
|
expandActions,
|
|
@@ -26,6 +35,7 @@ import {
|
|
|
26
35
|
getChildren,
|
|
27
36
|
getParent,
|
|
28
37
|
resolveMigrationOperation,
|
|
38
|
+
expandChildrenAndActions,
|
|
29
39
|
} from '../util';
|
|
30
40
|
|
|
31
41
|
// TODO(thure): Is NavTree truly authoritative in this regard?
|
|
@@ -37,24 +47,29 @@ const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
|
|
|
37
47
|
|
|
38
48
|
export type NavTreeContainerProps = {
|
|
39
49
|
popoverAnchorId?: string;
|
|
40
|
-
|
|
50
|
+
topbar?: boolean;
|
|
51
|
+
} & Pick<NavTreeContextValue, 'tab'>;
|
|
41
52
|
|
|
42
|
-
export const NavTreeContainer = memo(({
|
|
43
|
-
const { closeNavigationSidebar } = useSidebars(NAVTREE_PLUGIN);
|
|
53
|
+
export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeContainerProps) => {
|
|
44
54
|
const [isLg] = useMediaQuery('lg', { ssr: false });
|
|
45
55
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
46
|
-
const { graph } =
|
|
56
|
+
const { graph } = useAppGraph();
|
|
57
|
+
const layout = useLayout();
|
|
58
|
+
const { isOpen, isCurrent, setItem } = useCapability(NavTreeCapabilities.State);
|
|
47
59
|
|
|
48
60
|
const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
|
|
49
61
|
|
|
50
62
|
const getItems = useCallback(
|
|
51
|
-
(node?: Node) => {
|
|
63
|
+
(node?: Node, disposition?: string) => {
|
|
52
64
|
return graph.nodes(node ?? graph.root, {
|
|
53
65
|
filter: (node): node is Node => {
|
|
54
66
|
return untracked(() => {
|
|
55
67
|
const action = isActionLike(node);
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
if (!disposition) {
|
|
69
|
+
return !action || node.properties.disposition === 'item';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return node.properties.disposition === disposition;
|
|
58
73
|
});
|
|
59
74
|
},
|
|
60
75
|
});
|
|
@@ -96,6 +111,33 @@ export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }:
|
|
|
96
111
|
[graph],
|
|
97
112
|
);
|
|
98
113
|
|
|
114
|
+
const onOpenChange = useCallback(
|
|
115
|
+
({ item: { id }, path, open }: { item: Node; path: string[]; open: boolean }) => {
|
|
116
|
+
// TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
|
|
117
|
+
setItem(path, 'open', open);
|
|
118
|
+
|
|
119
|
+
if (graph) {
|
|
120
|
+
const node = graph.findNode(id);
|
|
121
|
+
return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[graph],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const onTabChange = useCallback(
|
|
128
|
+
async (node: NavTreeItemGraphNode) => {
|
|
129
|
+
await dispatch(createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: node.id }));
|
|
130
|
+
// Open the first item if the workspace is empty.
|
|
131
|
+
if (layout.active.length === 0) {
|
|
132
|
+
const [item] = getItems(node).filter((node) => !isActionLike(node));
|
|
133
|
+
if (item) {
|
|
134
|
+
await dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [item.id] }));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
[dispatch, layout],
|
|
139
|
+
);
|
|
140
|
+
|
|
99
141
|
const canDrop = useCallback((source: TreeData, target: TreeData) => {
|
|
100
142
|
const sourceNode = source.item as Node;
|
|
101
143
|
const targetNode = target.item as Node;
|
|
@@ -123,11 +165,19 @@ export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }:
|
|
|
123
165
|
|
|
124
166
|
const current = isCurrent(path, node);
|
|
125
167
|
if (!current) {
|
|
126
|
-
void dispatch(
|
|
168
|
+
void dispatch(
|
|
169
|
+
createIntent(LayoutAction.Open, {
|
|
170
|
+
part: 'main',
|
|
171
|
+
subject: [node.id],
|
|
172
|
+
options: { key: node.properties.key },
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
127
175
|
} else if (option) {
|
|
128
|
-
void dispatch(
|
|
176
|
+
void dispatch(
|
|
177
|
+
createIntent(LayoutAction.Close, { part: 'main', subject: [node.id], options: { state: false } }),
|
|
178
|
+
);
|
|
129
179
|
} else {
|
|
130
|
-
void dispatch(createIntent(LayoutAction.ScrollIntoView, {
|
|
180
|
+
void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: node.id }));
|
|
131
181
|
}
|
|
132
182
|
|
|
133
183
|
const defaultAction = graph.actions(node).find((action) => action.properties?.disposition === 'default');
|
|
@@ -136,10 +186,10 @@ export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }:
|
|
|
136
186
|
}
|
|
137
187
|
|
|
138
188
|
if (!isLg) {
|
|
139
|
-
|
|
189
|
+
void dispatch(createIntent(LayoutAction.UpdateSidebar, { part: 'sidebar', options: { state: 'closed' } }));
|
|
140
190
|
}
|
|
141
191
|
},
|
|
142
|
-
[graph, dispatch, isCurrent, isLg
|
|
192
|
+
[graph, dispatch, isCurrent, isLg],
|
|
143
193
|
);
|
|
144
194
|
|
|
145
195
|
// TODO(wittjosiah): Factor out hook.
|
|
@@ -201,20 +251,44 @@ export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }:
|
|
|
201
251
|
});
|
|
202
252
|
}, [graph]);
|
|
203
253
|
|
|
204
|
-
|
|
254
|
+
const navTreeContextValue = useMemo(
|
|
255
|
+
() => ({
|
|
256
|
+
tab,
|
|
257
|
+
onTabChange,
|
|
258
|
+
getActions,
|
|
259
|
+
loadDescendents,
|
|
260
|
+
renderItemEnd,
|
|
261
|
+
popoverAnchorId,
|
|
262
|
+
topbar,
|
|
263
|
+
getItems,
|
|
264
|
+
getProps,
|
|
265
|
+
isCurrent,
|
|
266
|
+
isOpen,
|
|
267
|
+
onOpenChange,
|
|
268
|
+
canDrop,
|
|
269
|
+
onSelect: handleSelect,
|
|
270
|
+
}),
|
|
271
|
+
[
|
|
272
|
+
tab,
|
|
273
|
+
onTabChange,
|
|
274
|
+
getActions,
|
|
275
|
+
loadDescendents,
|
|
276
|
+
renderItemEnd,
|
|
277
|
+
popoverAnchorId,
|
|
278
|
+
topbar,
|
|
279
|
+
getItems,
|
|
280
|
+
getProps,
|
|
281
|
+
isCurrent,
|
|
282
|
+
isOpen,
|
|
283
|
+
onOpenChange,
|
|
284
|
+
canDrop,
|
|
285
|
+
handleSelect,
|
|
286
|
+
],
|
|
287
|
+
);
|
|
288
|
+
|
|
205
289
|
return (
|
|
206
|
-
<
|
|
207
|
-
id={graph.root.id}
|
|
208
|
-
|
|
209
|
-
getItems={getItems}
|
|
210
|
-
getProps={getProps}
|
|
211
|
-
isCurrent={isCurrent}
|
|
212
|
-
loadDescendents={loadDescendents}
|
|
213
|
-
renderItemEnd={renderItemEnd}
|
|
214
|
-
popoverAnchorId={popoverAnchorId}
|
|
215
|
-
canDrop={canDrop}
|
|
216
|
-
onSelect={handleSelect}
|
|
217
|
-
{...props}
|
|
218
|
-
/>
|
|
290
|
+
<NavTreeContext.Provider value={navTreeContextValue}>
|
|
291
|
+
<NavTree root={graph.root} id={graph.root.id} />
|
|
292
|
+
</NavTreeContext.Provider>
|
|
219
293
|
);
|
|
220
294
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext, useContext } from 'react';
|
|
6
|
+
|
|
7
|
+
import { raise } from '@dxos/debug';
|
|
8
|
+
|
|
9
|
+
import { type NavTreeContextValue } from './types';
|
|
10
|
+
|
|
11
|
+
export const NavTreeContext = createContext<NavTreeContextValue | null>(null);
|
|
12
|
+
|
|
13
|
+
export const useNavTreeContext = () => useContext(NavTreeContext) ?? raise(new Error('NavTreeContext not found'));
|
|
@@ -24,11 +24,8 @@ export type NavTreeItemActionMenuProps = ActionProperties & {
|
|
|
24
24
|
const fallbackIcon = 'ph--placeholder--regular';
|
|
25
25
|
|
|
26
26
|
const actionButtonProps = {
|
|
27
|
-
iconOnly: true,
|
|
28
27
|
size: 4 as const,
|
|
29
|
-
variant: 'ghost' as const,
|
|
30
28
|
density: 'fine' as const,
|
|
31
|
-
classNames: mx('shrink-0 !pli-2 pointer-fine:!pli-1', hoverableControlItem, hoverableOpenControlItem),
|
|
32
29
|
};
|
|
33
30
|
|
|
34
31
|
export const NavTreeItemActionDropdownMenu = ({
|
|
@@ -46,7 +43,10 @@ export const NavTreeItemActionDropdownMenu = ({
|
|
|
46
43
|
<DropdownMenu.Trigger asChild>
|
|
47
44
|
<IconButton
|
|
48
45
|
{...actionButtonProps}
|
|
46
|
+
classNames={mx('shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem)}
|
|
47
|
+
variant='ghost'
|
|
49
48
|
icon={icon ?? fallbackIcon}
|
|
49
|
+
iconOnly
|
|
50
50
|
label={toLocalizedString(label, t)}
|
|
51
51
|
data-testid={testId}
|
|
52
52
|
suppressNextTooltip={suppressNextTooltip}
|
|
@@ -59,14 +59,22 @@ export const NavTreeItemActionDropdownMenu = ({
|
|
|
59
59
|
|
|
60
60
|
export const NavTreeItemMonolithicAction = ({
|
|
61
61
|
parent,
|
|
62
|
-
properties: { disabled, caller, testId, icon } = { label: 'never' },
|
|
62
|
+
properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
|
|
63
63
|
data: invoke,
|
|
64
64
|
baseLabel,
|
|
65
65
|
}: Action & { parent: Node; onAction?: (action: Action) => void; baseLabel: string }) => {
|
|
66
66
|
return (
|
|
67
67
|
<IconButton
|
|
68
68
|
{...actionButtonProps}
|
|
69
|
+
variant={variant}
|
|
70
|
+
classNames={mx(
|
|
71
|
+
'shrink-0',
|
|
72
|
+
hoverableControlItem,
|
|
73
|
+
hoverableOpenControlItem,
|
|
74
|
+
iconOnly ? 'pli-2 pointer-fine:pli-1' : 'p-2 pointer-fine:p-2 mie-1',
|
|
75
|
+
)}
|
|
69
76
|
icon={icon ?? fallbackIcon}
|
|
77
|
+
iconOnly={iconOnly}
|
|
70
78
|
label={baseLabel}
|
|
71
79
|
disabled={disabled}
|
|
72
80
|
onClick={(event) => {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { Fragment, memo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { isAction } from '@dxos/app-graph';
|
|
8
|
+
import { Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { NAV_TREE_ITEM } from './NavTree';
|
|
11
|
+
import { useNavTreeContext } from './NavTreeContext';
|
|
12
|
+
import { NavTreeItemAction } from './NavTreeItemAction';
|
|
13
|
+
import type { NavTreeItemColumnsProps } from './types';
|
|
14
|
+
import { useLoadDescendents } from '../hooks';
|
|
15
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
16
|
+
|
|
17
|
+
export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
|
|
18
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
19
|
+
const { getActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
|
|
20
|
+
|
|
21
|
+
const level = path.length - 2;
|
|
22
|
+
|
|
23
|
+
const { actions: _actions, groupedActions } = getActions(item);
|
|
24
|
+
const [primaryAction, ...secondaryActions] = _actions.toSorted((a, b) =>
|
|
25
|
+
a.properties?.disposition === 'toolbar' ? -1 : 1,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const actions = (primaryAction?.properties?.disposition === 'toolbar' ? secondaryActions : _actions)
|
|
29
|
+
.flatMap((action) => (isAction(action) ? [action] : []))
|
|
30
|
+
.filter((action) => !action.properties?.hidden);
|
|
31
|
+
|
|
32
|
+
const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
|
|
33
|
+
|
|
34
|
+
useLoadDescendents(item);
|
|
35
|
+
useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div role='none' className='contents app-no-drag'>
|
|
39
|
+
{primaryAction?.properties?.disposition === 'toolbar' && !primaryAction?.properties?.disabled ? (
|
|
40
|
+
<NavTreeItemAction
|
|
41
|
+
testId={primaryAction.properties?.testId}
|
|
42
|
+
label={toLocalizedString(primaryAction.properties?.label, t)}
|
|
43
|
+
icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
|
|
44
|
+
parent={item}
|
|
45
|
+
monolithic={isAction(primaryAction)}
|
|
46
|
+
menuActions={isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction.id]}
|
|
47
|
+
menuType={primaryAction.properties?.menuType}
|
|
48
|
+
caller={NAV_TREE_ITEM}
|
|
49
|
+
/>
|
|
50
|
+
) : (
|
|
51
|
+
<Treegrid.Cell />
|
|
52
|
+
)}
|
|
53
|
+
<ActionRoot>
|
|
54
|
+
{actions.length > 0 ? (
|
|
55
|
+
<NavTreeItemAction
|
|
56
|
+
testId={`navtree.treeItem.actionsLevel${level}`}
|
|
57
|
+
label={t('tree item actions label')}
|
|
58
|
+
icon='ph--dots-three-vertical--regular'
|
|
59
|
+
parent={item}
|
|
60
|
+
menuActions={actions}
|
|
61
|
+
menuType='dropdown'
|
|
62
|
+
caller={NAV_TREE_ITEM}
|
|
63
|
+
/>
|
|
64
|
+
) : (
|
|
65
|
+
<Treegrid.Cell />
|
|
66
|
+
)}
|
|
67
|
+
</ActionRoot>
|
|
68
|
+
{ItemEnd && <ItemEnd node={item} open={open} />}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { createIntent,
|
|
7
|
+
import { createIntent, useCapabilities, useIntentDispatcher } from '@dxos/app-framework';
|
|
8
8
|
import { ClientAction } from '@dxos/plugin-client/types';
|
|
9
9
|
import { ObservabilityCapabilities } from '@dxos/plugin-observability';
|
|
10
10
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
@@ -19,7 +19,7 @@ export const NotchStart = () => {
|
|
|
19
19
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
20
20
|
return (
|
|
21
21
|
<HaloButton
|
|
22
|
-
size={
|
|
22
|
+
size={12}
|
|
23
23
|
identityKey={identity?.identityKey.toHex()}
|
|
24
24
|
hue={identity?.profile?.data?.hue}
|
|
25
25
|
emoji={identity?.profile?.data?.emoji}
|
package/src/components/index.ts
CHANGED
|
@@ -6,5 +6,7 @@ export * from './CommandsDialog';
|
|
|
6
6
|
export * from './CommandsTrigger';
|
|
7
7
|
export * from './NavTree';
|
|
8
8
|
export * from './NavTreeContainer';
|
|
9
|
+
export * from './NavTreeContext';
|
|
9
10
|
export * from './NavTreeDocumentTitle';
|
|
10
11
|
export * from './NotchStart';
|
|
12
|
+
export * from './types';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { Node } from '@dxos/app-graph';
|
|
8
|
+
import type { TreeProps } from '@dxos/react-ui-list';
|
|
9
|
+
import type { MaybePromise } from '@dxos/util';
|
|
10
|
+
|
|
11
|
+
import type { FlattenedActions, NavTreeItemGraphNode } from '../types';
|
|
12
|
+
|
|
13
|
+
export type NavTreeItemColumnsProps = {
|
|
14
|
+
path: string[];
|
|
15
|
+
item: Node;
|
|
16
|
+
open: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'>;
|
|
20
|
+
|
|
21
|
+
export type NavTreeContextValue = Pick<
|
|
22
|
+
TreeProps<NavTreeItemGraphNode>,
|
|
23
|
+
'getProps' | 'isCurrent' | 'isOpen' | 'onOpenChange' | 'canDrop' | 'onSelect'
|
|
24
|
+
> & {
|
|
25
|
+
tab: string;
|
|
26
|
+
onTabChange?: (node: NavTreeItemGraphNode) => void;
|
|
27
|
+
getItems: (node?: NavTreeItemGraphNode, disposition?: string) => NavTreeItemGraphNode[];
|
|
28
|
+
getActions: (node: Node) => FlattenedActions;
|
|
29
|
+
loadDescendents?: (node: Node) => MaybePromise<void>;
|
|
30
|
+
renderItemEnd?: FC<{ node: Node; open: boolean }>;
|
|
31
|
+
popoverAnchorId?: string;
|
|
32
|
+
topbar?: boolean;
|
|
33
|
+
};
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Node } from '@dxos/app-graph';
|
|
8
|
+
|
|
9
|
+
import { useNavTreeContext } from './components/NavTreeContext';
|
|
10
|
+
|
|
11
|
+
export const useLoadDescendents = (root?: Node) => {
|
|
12
|
+
const { loadDescendents } = useNavTreeContext();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const frame = requestAnimationFrame(() => {
|
|
15
|
+
if (root) {
|
|
16
|
+
void loadDescendents?.(root);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return () => cancelAnimationFrame(frame);
|
|
20
|
+
}, [root]);
|
|
21
|
+
};
|
|
@@ -18,7 +18,7 @@ import { isTreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
|
|
|
18
18
|
import { Path } from '@dxos/react-ui-mosaic';
|
|
19
19
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { StorybookNavTree } from './StorybookNavTree';
|
|
22
22
|
import { getActions, getChildren } from '../util';
|
|
23
23
|
|
|
24
24
|
faker.seed(3);
|
|
@@ -53,9 +53,9 @@ const state = new Map<string, ReactiveObject<{ open: boolean; current: boolean }
|
|
|
53
53
|
|
|
54
54
|
export const Default = {};
|
|
55
55
|
|
|
56
|
-
const meta: Meta<typeof
|
|
56
|
+
const meta: Meta<typeof StorybookNavTree> = {
|
|
57
57
|
title: 'plugins/plugin-navtree/Graph',
|
|
58
|
-
component:
|
|
58
|
+
component: StorybookNavTree,
|
|
59
59
|
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
60
60
|
render: (args) => {
|
|
61
61
|
useEffect(() => {
|
|
@@ -78,9 +78,10 @@ const meta: Meta<typeof NavTree> = {
|
|
|
78
78
|
});
|
|
79
79
|
}, []);
|
|
80
80
|
|
|
81
|
-
return <
|
|
81
|
+
return <StorybookNavTree {...args} />;
|
|
82
82
|
},
|
|
83
83
|
args: {
|
|
84
|
+
root: graph.root,
|
|
84
85
|
id: graph.root.id,
|
|
85
86
|
getActions: (node: Node) => getActions(graph, node),
|
|
86
87
|
getItems: (node?: Node) => graph.nodes(node ?? graph.root),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { NavTree, NavTreeContext, type NavTreeContextValue, type NavTreeProps } from '../components';
|
|
8
|
+
import { type NavTreeItemGraphNode } from '../types';
|
|
9
|
+
|
|
10
|
+
export const StorybookNavTree = ({
|
|
11
|
+
getActions,
|
|
12
|
+
loadDescendents,
|
|
13
|
+
renderItemEnd,
|
|
14
|
+
popoverAnchorId,
|
|
15
|
+
getItems,
|
|
16
|
+
getProps,
|
|
17
|
+
isCurrent,
|
|
18
|
+
canDrop,
|
|
19
|
+
onSelect,
|
|
20
|
+
isOpen,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
...props
|
|
23
|
+
}: NavTreeProps & NavTreeContextValue) => {
|
|
24
|
+
const [tab, setTab] = useState('never');
|
|
25
|
+
|
|
26
|
+
const contextValue = useMemo(
|
|
27
|
+
() => ({
|
|
28
|
+
tab,
|
|
29
|
+
onTabChange: (node: NavTreeItemGraphNode) => setTab(node.id),
|
|
30
|
+
getActions,
|
|
31
|
+
loadDescendents,
|
|
32
|
+
renderItemEnd,
|
|
33
|
+
popoverAnchorId,
|
|
34
|
+
getItems,
|
|
35
|
+
getProps,
|
|
36
|
+
isCurrent,
|
|
37
|
+
isOpen,
|
|
38
|
+
onOpenChange,
|
|
39
|
+
canDrop,
|
|
40
|
+
onSelect,
|
|
41
|
+
}),
|
|
42
|
+
[
|
|
43
|
+
tab,
|
|
44
|
+
setTab,
|
|
45
|
+
getActions,
|
|
46
|
+
loadDescendents,
|
|
47
|
+
renderItemEnd,
|
|
48
|
+
popoverAnchorId,
|
|
49
|
+
getItems,
|
|
50
|
+
getProps,
|
|
51
|
+
isCurrent,
|
|
52
|
+
isOpen,
|
|
53
|
+
onOpenChange,
|
|
54
|
+
canDrop,
|
|
55
|
+
onSelect,
|
|
56
|
+
],
|
|
57
|
+
);
|
|
58
|
+
return (
|
|
59
|
+
<NavTreeContext.Provider value={contextValue}>
|
|
60
|
+
<NavTree {...props} />
|
|
61
|
+
</NavTreeContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
};
|
package/src/translations.ts
CHANGED
|
@@ -13,12 +13,6 @@ export default [
|
|
|
13
13
|
'commands dialog title': 'Commands',
|
|
14
14
|
'command list input placeholder': 'Search for commands…',
|
|
15
15
|
'node actions menu invoker label': 'More options',
|
|
16
|
-
'warning title': 'WARNING',
|
|
17
|
-
'technology preview message': 'Composer is still in technology preview.',
|
|
18
|
-
'learn more label': 'Learn more',
|
|
19
|
-
'released message': 'This version released {{released}}.',
|
|
20
|
-
'see release label': 'See release on GitHub',
|
|
21
|
-
'powered by dxos message': 'Powered by <dxos>DXOS</dxos>',
|
|
22
16
|
'tree item actions label': 'More actions',
|
|
23
17
|
'empty branch message': ' ',
|
|
24
18
|
},
|
package/src/types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Action, type ActionLike, type Node } from '@dxos/app-graph';
|
|
6
6
|
import { type Label } from '@dxos/react-ui';
|
|
7
|
-
import { type MaybePromise } from '@dxos/util';
|
|
7
|
+
import { type MaybePromise, type Position } from '@dxos/util';
|
|
8
8
|
|
|
9
9
|
export type NavTreeItemGraphNode = Node<
|
|
10
10
|
any,
|
|
@@ -31,6 +31,7 @@ type SharedProperties = {
|
|
|
31
31
|
label: Label;
|
|
32
32
|
icon?: string;
|
|
33
33
|
disabled?: boolean;
|
|
34
|
+
position?: Position;
|
|
34
35
|
testId?: string;
|
|
35
36
|
className?: string;
|
|
36
37
|
headingClassName?: string;
|
package/src/util.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ACTION_TYPE,
|
|
11
11
|
ACTION_GROUP_TYPE,
|
|
12
12
|
isAction,
|
|
13
|
+
isActionLike,
|
|
13
14
|
} from '@dxos/app-graph';
|
|
14
15
|
import { nonNullable } from '@dxos/util';
|
|
15
16
|
|
|
@@ -126,3 +127,56 @@ export const expandChildren = (graph: Graph, node: Node) => {
|
|
|
126
127
|
export const expandActions = (graph: Graph, node: Node) => {
|
|
127
128
|
return Promise.all([graph.expand(node, 'outbound', ACTION_TYPE), graph.expand(node, 'outbound', ACTION_GROUP_TYPE)]);
|
|
128
129
|
};
|
|
130
|
+
|
|
131
|
+
export const l0ItemType = (item: Node<any>) => {
|
|
132
|
+
if (item.properties.disposition === 'collection') {
|
|
133
|
+
return 'collection';
|
|
134
|
+
} else if (isActionLike(item)) {
|
|
135
|
+
return 'action';
|
|
136
|
+
} else if (item.properties.disposition === 'navigation') {
|
|
137
|
+
return 'link';
|
|
138
|
+
} else {
|
|
139
|
+
return 'tab';
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Regular expression to match renderable characters
|
|
144
|
+
// Excludes control characters, combining marks, and other non-renderable characters
|
|
145
|
+
const renderableCharRegex =
|
|
146
|
+
/^(?![\p{Control}\p{Mark}\p{Separator}\p{Surrogate}\p{Unassigned}])[\p{L}\p{N}\p{P}\p{S}\p{Emoji}]$/u;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Returns the first two renderable characters from a string that are separated by non-word characters.
|
|
150
|
+
* Handles Unicode characters correctly.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} label - The input string to process
|
|
153
|
+
* @returns {[string, string]} Array containing the two characters, or empty strings if not found
|
|
154
|
+
*/
|
|
155
|
+
export const getFirstTwoRenderableChars = (label: string) => {
|
|
156
|
+
// Convert string to array of Unicode characters
|
|
157
|
+
const characters = Array.from(label);
|
|
158
|
+
|
|
159
|
+
// Keep track of found renderable characters
|
|
160
|
+
const result = ['', ''];
|
|
161
|
+
let foundFirst = false;
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < characters.length; i++) {
|
|
164
|
+
const char = characters[i];
|
|
165
|
+
|
|
166
|
+
if (renderableCharRegex.test(char)) {
|
|
167
|
+
if (!foundFirst) {
|
|
168
|
+
result[0] = char;
|
|
169
|
+
foundFirst = true;
|
|
170
|
+
} else {
|
|
171
|
+
// Check if there's at least one non-word character between the first and current char
|
|
172
|
+
const textBetween = characters.slice(result[0].length, i).join('');
|
|
173
|
+
if (/[^\p{L}\p{N}_]/u.test(textBetween)) {
|
|
174
|
+
result[1] = char;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return result;
|
|
182
|
+
};
|