@dxos/plugin-navtree 0.8.4-main.f9ba587 → 0.8.4-main.fffef41
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-5J4LRWDX.mjs → app-graph-builder-OHSCN3GX.mjs} +13 -12
- package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs.map +7 -0
- package/dist/lib/browser/{chunk-WCARAZ7J.mjs → chunk-5VLWX4PK.mjs} +4 -4
- package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +7 -0
- package/dist/lib/browser/{chunk-TZA65AKW.mjs → chunk-7D2MPHXV.mjs} +317 -262
- package/dist/lib/browser/chunk-7D2MPHXV.mjs.map +7 -0
- package/dist/lib/browser/chunk-E6MZBCU5.mjs +19 -0
- package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +8 -11
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/{intent-resolver-3XYN2CBU.mjs → intent-resolver-YUVXVPHG.mjs} +7 -7
- package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +7 -0
- package/dist/lib/browser/{keyboard-QRS6WOQH.mjs → keyboard-YYTYJW4M.mjs} +4 -4
- package/dist/lib/{node-esm/keyboard-MOZWHFUM.mjs.map → browser/keyboard-YYTYJW4M.mjs.map} +2 -2
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-N2HPMYCU.mjs → react-surface-J7MWWAGD.mjs} +10 -10
- package/dist/lib/browser/react-surface-J7MWWAGD.mjs.map +7 -0
- package/dist/lib/browser/{state-54DY3DOY.mjs → state-EUSVT6BI.mjs} +7 -7
- package/dist/lib/browser/state-EUSVT6BI.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts +1 -1
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +18 -15
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/keyboard.d.ts +1 -1
- package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +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/CommandsDialog/CommandsDialog.d.ts.map +1 -1
- package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.d.ts +2 -2
- package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts +26 -4
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +32 -4
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +2 -2
- package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L0Menu.d.ts +2 -0
- package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L1Panel.d.ts +5 -6
- package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L1Tabs.d.ts +12 -0
- package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
- package/dist/types/src/components/Sidebar/index.d.ts +1 -0
- package/dist/types/src/components/Sidebar/index.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 +8 -5
- package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +2 -2
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/events.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.d.ts +4 -5
- package/dist/types/src/experimental/Tree.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.stories.d.ts +7 -4
- package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +2 -3
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +1 -1
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +4 -3
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -2
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +49 -50
- package/src/NavTreePlugin.tsx +75 -76
- package/src/capabilities/app-graph-builder.ts +10 -9
- package/src/capabilities/capabilities.ts +2 -2
- package/src/capabilities/intent-resolver.ts +2 -2
- package/src/capabilities/keyboard.ts +2 -2
- package/src/capabilities/react-surface.tsx +5 -5
- package/src/capabilities/state.ts +4 -3
- package/src/components/CommandsDialog/CommandsDialog.tsx +15 -7
- package/src/components/CommandsDialog/CommandsTrigger.tsx +6 -7
- package/src/components/NavTree/NavTree.stories.tsx +67 -53
- package/src/components/NavTree/NavTree.tsx +7 -3
- package/src/components/NavTreeContainer.tsx +91 -36
- package/src/components/NavTreeDocumentTitle.tsx +2 -2
- package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +4 -6
- package/src/components/NavTreeItem/NavTreeItemAction.tsx +11 -10
- package/src/components/NavTreeItem/NavTreeItemColumns.tsx +33 -27
- package/src/components/Sidebar/L0Menu.tsx +56 -55
- package/src/components/Sidebar/L1Panel.tsx +72 -97
- package/src/components/Sidebar/L1Tabs.tsx +58 -0
- package/src/components/Sidebar/index.ts +1 -0
- package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -8
- package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
- package/src/components/index.ts +1 -1
- package/src/components/types.ts +3 -2
- package/src/events.ts +2 -2
- package/src/experimental/Tree.stories.tsx +36 -38
- package/src/experimental/Tree.tsx +17 -35
- package/src/meta.ts +10 -7
- package/src/testing/app-graph-builder.ts +20 -19
- package/src/testing/generator.ts +2 -2
- package/src/translations.ts +1 -0
- package/src/types.ts +4 -3
- package/src/util.ts +2 -20
- package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
- package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
- package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
- package/dist/lib/browser/chunk-TZA65AKW.mjs.map +0 -7
- package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs.map +0 -7
- package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
- package/dist/lib/browser/react-surface-N2HPMYCU.mjs.map +0 -7
- package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
- package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs +0 -46
- package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CY7QWR4R.mjs +0 -1429
- package/dist/lib/node-esm/chunk-CY7QWR4R.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-P2JGW3WR.mjs +0 -18
- package/dist/lib/node-esm/chunk-P2JGW3WR.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-V3QXFDDP.mjs +0 -16
- package/dist/lib/node-esm/chunk-V3QXFDDP.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -150
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs +0 -48
- package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs.map +0 -7
- package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs +0 -52
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs +0 -52
- package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs.map +0 -7
- package/dist/lib/node-esm/state-Z3OTRC2I.mjs +0 -118
- package/dist/lib/node-esm/state-Z3OTRC2I.mjs.map +0 -7
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import { type
|
|
8
|
-
import {
|
|
9
|
-
import React from 'react';
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { type KeyboardEvent, useCallback, useRef } from 'react';
|
|
8
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
Capabilities,
|
|
@@ -16,22 +15,20 @@ import {
|
|
|
16
15
|
contributes,
|
|
17
16
|
createResolver,
|
|
18
17
|
defineCapability,
|
|
19
|
-
useCapability,
|
|
20
18
|
} from '@dxos/app-framework';
|
|
19
|
+
import { useCapability } from '@dxos/app-framework/react';
|
|
21
20
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
22
21
|
import { live } from '@dxos/live-object';
|
|
23
22
|
import { AttentionPlugin } from '@dxos/plugin-attention';
|
|
24
|
-
import { ClientPlugin } from '@dxos/plugin-client';
|
|
25
23
|
import { GraphPlugin } from '@dxos/plugin-graph';
|
|
26
|
-
import { SpacePlugin } from '@dxos/plugin-space';
|
|
27
24
|
import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
|
|
28
25
|
import { ThemePlugin } from '@dxos/plugin-theme';
|
|
29
26
|
import { faker } from '@dxos/random';
|
|
30
27
|
import { IconButton, Input, Main, Toolbar } from '@dxos/react-ui';
|
|
31
|
-
import {
|
|
28
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
29
|
+
import { useAttention, useAttentionAttributes } from '@dxos/react-ui-attention';
|
|
32
30
|
import { Stack, StackItem } from '@dxos/react-ui-stack';
|
|
33
31
|
import { defaultTx, mx } from '@dxos/react-ui-theme';
|
|
34
|
-
import { withLayout } from '@dxos/storybook-utils';
|
|
35
32
|
|
|
36
33
|
import { NavTreePlugin } from '../../NavTreePlugin';
|
|
37
34
|
import { storybookGraphBuilders } from '../../testing';
|
|
@@ -68,6 +65,15 @@ const StoryPlankHeading = ({ attendableId }: { attendableId: string }) => {
|
|
|
68
65
|
|
|
69
66
|
const StoryPlank = ({ attendableId }: { attendableId: string }) => {
|
|
70
67
|
const attentionAttrs = useAttentionAttributes(attendableId);
|
|
68
|
+
const rootElement = useRef<HTMLDivElement | null>(null);
|
|
69
|
+
|
|
70
|
+
// NOTE(thure): This is the same workaround as in Plank, but that component is out of scope for this story.
|
|
71
|
+
// TODO(thure): Tabster’s focus group should handle moving focus to Main, but something is blocking it.
|
|
72
|
+
const handleKeyDown = useCallback((event: KeyboardEvent) => {
|
|
73
|
+
if (event.target === event.currentTarget && event.key === 'Escape') {
|
|
74
|
+
rootElement.current?.closest('main')?.focus();
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
71
77
|
|
|
72
78
|
return (
|
|
73
79
|
<StackItem.Root
|
|
@@ -75,10 +81,12 @@ const StoryPlank = ({ attendableId }: { attendableId: string }) => {
|
|
|
75
81
|
{...attentionAttrs}
|
|
76
82
|
classNames='bg-baseSurface border-ie border-separator'
|
|
77
83
|
size={30}
|
|
84
|
+
onKeyDown={handleKeyDown}
|
|
85
|
+
ref={rootElement}
|
|
78
86
|
>
|
|
79
87
|
<StoryPlankHeading attendableId={attendableId} />
|
|
80
88
|
<StackItem.Content toolbar>
|
|
81
|
-
<Toolbar.Root classNames='border-
|
|
89
|
+
<Toolbar.Root classNames='border-be border-subduedSeparator'>
|
|
82
90
|
<Toolbar.Button>Test</Toolbar.Button>
|
|
83
91
|
</Toolbar.Root>
|
|
84
92
|
|
|
@@ -116,20 +124,21 @@ const DefaultStory = () => {
|
|
|
116
124
|
);
|
|
117
125
|
};
|
|
118
126
|
|
|
119
|
-
const meta
|
|
127
|
+
const meta = {
|
|
120
128
|
title: 'plugins/plugin-navtree/NavTree',
|
|
121
129
|
component: NavTreeContainer,
|
|
122
130
|
render: DefaultStory,
|
|
123
131
|
decorators: [
|
|
132
|
+
withTheme,
|
|
124
133
|
withPluginManager({
|
|
125
134
|
plugins: [
|
|
126
135
|
ThemePlugin({ tx: defaultTx }),
|
|
127
|
-
StorybookLayoutPlugin({ initialState: { sidebarState: 'expanded' } }),
|
|
128
136
|
GraphPlugin(),
|
|
129
137
|
IntentPlugin(),
|
|
130
138
|
SettingsPlugin(),
|
|
131
139
|
AttentionPlugin(),
|
|
132
140
|
NavTreePlugin(),
|
|
141
|
+
StorybookLayoutPlugin({ initialState: { sidebarState: 'expanded' } }),
|
|
133
142
|
],
|
|
134
143
|
capabilities: (context) => [
|
|
135
144
|
contributes(StoryState, live({ tab: 'space-0' })),
|
|
@@ -147,54 +156,59 @@ const meta: Meta<typeof NavTreeContainer> = {
|
|
|
147
156
|
]),
|
|
148
157
|
],
|
|
149
158
|
}),
|
|
150
|
-
withLayout({ fullscreen: true }),
|
|
151
159
|
],
|
|
152
160
|
parameters: {
|
|
161
|
+
layout: 'fullscreen',
|
|
153
162
|
translations,
|
|
154
163
|
},
|
|
155
|
-
}
|
|
164
|
+
} satisfies Meta<typeof NavTreeContainer>;
|
|
156
165
|
|
|
157
166
|
export default meta;
|
|
158
167
|
|
|
159
168
|
type Story = StoryObj<typeof NavTreeContainer>;
|
|
160
169
|
|
|
161
|
-
export const Default: Story = {
|
|
170
|
+
export const Default: Story = {
|
|
171
|
+
play: async ({ canvasElement }) => {
|
|
172
|
+
const canvas = within(canvasElement);
|
|
162
173
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
174
|
+
// Find the element with treegrid role and click on its parent
|
|
175
|
+
const treegridElement = await canvas.findByRole('treegrid');
|
|
176
|
+
const treegridParent = treegridElement.parentElement;
|
|
177
|
+
if (treegridParent) {
|
|
178
|
+
await userEvent.click(treegridParent);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Press Escape
|
|
182
|
+
await userEvent.keyboard('{Escape}');
|
|
183
|
+
|
|
184
|
+
// Confirm that focus is on an element with attribute data-main-landmark="0"
|
|
185
|
+
await expect(document.activeElement).toHaveAttribute('data-main-landmark', '0');
|
|
186
|
+
|
|
187
|
+
// Press Tab
|
|
188
|
+
await userEvent.keyboard('{Tab}');
|
|
189
|
+
|
|
190
|
+
// Confirm that focus is now on an element with data-main-landmark="1"
|
|
191
|
+
await expect(document.activeElement).toHaveAttribute('data-main-landmark', '1');
|
|
192
|
+
|
|
193
|
+
// Press Tab
|
|
194
|
+
await userEvent.keyboard('{Tab}');
|
|
195
|
+
|
|
196
|
+
// Confirm that focus is now on an element with data-main-landmark="0"
|
|
197
|
+
await expect(document.activeElement).toHaveAttribute('data-main-landmark', '0');
|
|
198
|
+
|
|
199
|
+
// Press Shift-Tab
|
|
200
|
+
await userEvent.keyboard('{Shift>}{Tab}{/Shift}');
|
|
201
|
+
|
|
202
|
+
// Press Enter
|
|
203
|
+
await userEvent.keyboard('{Enter}');
|
|
204
|
+
|
|
205
|
+
// Confirm that focus is now on an element with data-attendable-id="space-0:object-0"
|
|
206
|
+
await expect(document.activeElement).toHaveAttribute('data-attendable-id', 'space-0:object-0');
|
|
207
|
+
|
|
208
|
+
// Press Escape
|
|
209
|
+
await userEvent.keyboard('{Escape}');
|
|
210
|
+
|
|
211
|
+
// Confirm that focus is now on an element with data-main-landmark="1"
|
|
212
|
+
await expect(document.activeElement).toHaveAttribute('data-main-landmark', '1');
|
|
213
|
+
},
|
|
200
214
|
};
|
|
@@ -10,11 +10,11 @@ import { Tabs } from '@dxos/react-ui-tabs';
|
|
|
10
10
|
import { useLoadDescendents } from '../../hooks';
|
|
11
11
|
import { type NavTreeItemGraphNode } from '../../types';
|
|
12
12
|
import { useNavTreeContext } from '../NavTreeContext';
|
|
13
|
-
import { L0Menu,
|
|
13
|
+
import { L0Menu, L1Tabs, type L1TabsProps } from '../Sidebar';
|
|
14
14
|
|
|
15
15
|
export const NAV_TREE_ITEM = 'NavTreeItem';
|
|
16
16
|
|
|
17
|
-
export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<
|
|
17
|
+
export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<L1TabsProps, 'open'>;
|
|
18
18
|
|
|
19
19
|
export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
|
|
20
20
|
const { tab, useItems, onBack } = useNavTreeContext();
|
|
@@ -47,6 +47,10 @@ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
|
|
|
47
47
|
const path = useMemo(() => [id], [id]);
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
|
+
// TODO(thure): `Tabs.Root` forces all items that should be able to receive focus to use `Tabs.Tab(Primitive)` since
|
|
51
|
+
// it uses RovingFocus and doesn’t support moving focus to an item that is not a tab. Assess whether this situation
|
|
52
|
+
// should change including whether it should motivate a change in the design/taxonomy, or if this means this should
|
|
53
|
+
// not use `react-ui-tabs` at all.
|
|
50
54
|
<Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
|
|
51
55
|
<L0Menu
|
|
52
56
|
menuActions={topLevelActions}
|
|
@@ -56,7 +60,7 @@ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
|
|
|
56
60
|
path={path}
|
|
57
61
|
parent={root}
|
|
58
62
|
/>
|
|
59
|
-
<
|
|
63
|
+
<L1Tabs topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
|
|
60
64
|
</Tabs.Root>
|
|
61
65
|
);
|
|
62
66
|
};
|
|
@@ -3,34 +3,28 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
6
|
-
import {
|
|
6
|
+
import { type Instruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
7
7
|
import { untracked } from '@preact/signals-core';
|
|
8
8
|
import React, { memo, useCallback, useEffect, useMemo } from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Surface,
|
|
14
|
-
useAppGraph,
|
|
15
|
-
useCapability,
|
|
16
|
-
useIntentDispatcher,
|
|
17
|
-
useLayout,
|
|
18
|
-
} from '@dxos/app-framework';
|
|
19
|
-
import { isAction, isActionLike, ROOT_ID, type Node, type ReadableGraph } from '@dxos/app-graph';
|
|
10
|
+
import { LayoutAction, createIntent } from '@dxos/app-framework';
|
|
11
|
+
import { Surface, useAppGraph, useCapability, useIntentDispatcher, useLayout } from '@dxos/app-framework/react';
|
|
12
|
+
import { type Node, ROOT_ID, type ReadableGraph, isAction, isActionLike } from '@dxos/app-graph';
|
|
20
13
|
import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
|
|
21
|
-
import { useConnections } from '@dxos/plugin-graph';
|
|
14
|
+
import { useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
|
|
22
15
|
import { useMediaQuery, useSidebars } from '@dxos/react-ui';
|
|
23
|
-
import {
|
|
16
|
+
import { type TreeData, type TreeItemDataProps, isTreeData } from '@dxos/react-ui-list';
|
|
24
17
|
import { mx } from '@dxos/react-ui-theme';
|
|
25
18
|
import { arrayMove, byPosition } from '@dxos/util';
|
|
26
19
|
|
|
20
|
+
import { NavTreeCapabilities } from '../capabilities';
|
|
21
|
+
import { meta } from '../meta';
|
|
22
|
+
import { type FlattenedActions, type NavTreeItemGraphNode } from '../types';
|
|
23
|
+
import { getChildren, getParent, resolveMigrationOperation } from '../util';
|
|
24
|
+
|
|
27
25
|
import { NAV_TREE_ITEM, NavTree } from './NavTree';
|
|
28
26
|
import { NavTreeContext } from './NavTreeContext';
|
|
29
27
|
import { type NavTreeContextValue } from './types';
|
|
30
|
-
import { NavTreeCapabilities } from '../capabilities';
|
|
31
|
-
import { NAVTREE_PLUGIN } from '../meta';
|
|
32
|
-
import { type NavTreeItemGraphNode } from '../types';
|
|
33
|
-
import { getChildren, getParent, getActions as naturalGetActions, resolveMigrationOperation } from '../util';
|
|
34
28
|
|
|
35
29
|
// TODO(thure): Is NavTree truly authoritative in this regard?
|
|
36
30
|
export const NODE_TYPE = 'dxos/app-graph/node';
|
|
@@ -40,7 +34,9 @@ const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
|
|
|
40
34
|
);
|
|
41
35
|
|
|
42
36
|
const getChildrenFilter = (node: Node): node is Node =>
|
|
43
|
-
untracked(
|
|
37
|
+
untracked(
|
|
38
|
+
() => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE && node.properties.disposition !== 'hidden',
|
|
39
|
+
);
|
|
44
40
|
|
|
45
41
|
const filterItems = (node: Node, disposition?: string) => {
|
|
46
42
|
if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
|
|
@@ -59,10 +55,34 @@ const getItems = (graph: ReadableGraph, node?: Node, disposition?: string) => {
|
|
|
59
55
|
|
|
60
56
|
const useItems = (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
|
|
61
57
|
const { graph } = useAppGraph();
|
|
62
|
-
const connections = useConnections(graph, node?.id ?? ROOT_ID)
|
|
63
|
-
|
|
58
|
+
const connections = useConnections(graph, node?.id ?? ROOT_ID);
|
|
59
|
+
const filtered = connections.filter((node) => filterItems(node, options?.disposition));
|
|
60
|
+
return options?.sort ? filtered.toSorted((a, b) => byPosition(a.properties, b.properties)) : filtered;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const useActions = (node: Node): FlattenedActions => {
|
|
64
|
+
const { graph } = useAppGraph();
|
|
65
|
+
const actions = useGraphActions(graph, node.id);
|
|
66
|
+
|
|
67
|
+
return useMemo(
|
|
68
|
+
() =>
|
|
69
|
+
actions.reduce(
|
|
70
|
+
(acc: FlattenedActions, arg) => {
|
|
71
|
+
if (arg.properties.disposition === 'item') {
|
|
72
|
+
return acc;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
acc.actions.push(arg);
|
|
76
|
+
if (!isAction(arg)) {
|
|
77
|
+
const actionGroup = graph.getActions(arg.id);
|
|
78
|
+
acc.groupedActions[arg.id] = actionGroup;
|
|
79
|
+
}
|
|
80
|
+
return acc;
|
|
81
|
+
},
|
|
82
|
+
{ actions: [], groupedActions: {} },
|
|
83
|
+
),
|
|
84
|
+
[actions],
|
|
64
85
|
);
|
|
65
|
-
return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
|
|
66
86
|
};
|
|
67
87
|
|
|
68
88
|
export type NavTreeContainerProps = {
|
|
@@ -71,14 +91,12 @@ export type NavTreeContainerProps = {
|
|
|
71
91
|
} & Pick<NavTreeContextValue, 'tab'>;
|
|
72
92
|
|
|
73
93
|
export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeContainerProps) => {
|
|
74
|
-
const [isLg] = useMediaQuery('lg'
|
|
94
|
+
const [isLg] = useMediaQuery('lg');
|
|
75
95
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
76
96
|
const { graph } = useAppGraph();
|
|
77
97
|
const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
|
|
78
98
|
const layout = useLayout();
|
|
79
|
-
const { navigationSidebarState } = useSidebars(
|
|
80
|
-
|
|
81
|
-
const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
|
|
99
|
+
const { navigationSidebarState } = useSidebars(meta.id);
|
|
82
100
|
|
|
83
101
|
const getProps = useCallback(
|
|
84
102
|
(node: Node, path: string[]): TreeItemDataProps => {
|
|
@@ -87,12 +105,13 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
87
105
|
children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
|
|
88
106
|
return {
|
|
89
107
|
id: node.id,
|
|
90
|
-
label: node.properties.label ?? node.id,
|
|
91
108
|
parentOf,
|
|
92
|
-
icon: node.properties.icon,
|
|
93
109
|
disabled: node.properties.disabled,
|
|
94
|
-
|
|
110
|
+
label: node.properties.label ?? node.id,
|
|
111
|
+
className: mx(node.properties.className, node.properties.modified && 'italic'), // TODO(burdon): Italic?
|
|
95
112
|
headingClassName: node.properties.headingClassName,
|
|
113
|
+
icon: node.properties.icon,
|
|
114
|
+
iconHue: node.properties.iconHue,
|
|
96
115
|
testId: node.properties.testId,
|
|
97
116
|
};
|
|
98
117
|
},
|
|
@@ -137,13 +156,23 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
137
156
|
}),
|
|
138
157
|
);
|
|
139
158
|
|
|
140
|
-
await dispatch(
|
|
159
|
+
await dispatch(
|
|
160
|
+
createIntent(LayoutAction.SwitchWorkspace, {
|
|
161
|
+
part: 'workspace',
|
|
162
|
+
subject: node.id,
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
141
165
|
|
|
142
166
|
// Open the first item if the workspace is empty.
|
|
143
167
|
if (layout.active.length === 0) {
|
|
144
168
|
const [item] = getItems(graph, node).filter((node) => !isActionLike(node));
|
|
145
169
|
if (item && item.data) {
|
|
146
|
-
await dispatch(
|
|
170
|
+
await dispatch(
|
|
171
|
+
createIntent(LayoutAction.Open, {
|
|
172
|
+
part: 'main',
|
|
173
|
+
subject: [item.id],
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
147
176
|
}
|
|
148
177
|
}
|
|
149
178
|
},
|
|
@@ -154,6 +183,10 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
154
183
|
return target.item.properties.canDrop?.(source) ?? false;
|
|
155
184
|
}, []);
|
|
156
185
|
|
|
186
|
+
const canSelect = useCallback(({ item }: { item: Node }) => {
|
|
187
|
+
return item.properties.selectable ?? true;
|
|
188
|
+
}, []);
|
|
189
|
+
|
|
157
190
|
const handleSelect = useCallback(
|
|
158
191
|
({ item: node, path, option }: { item: Node; path: string[]; option: boolean }) => {
|
|
159
192
|
if (!node.data) {
|
|
@@ -177,10 +210,19 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
177
210
|
);
|
|
178
211
|
} else if (option) {
|
|
179
212
|
void dispatch(
|
|
180
|
-
createIntent(LayoutAction.Close, {
|
|
213
|
+
createIntent(LayoutAction.Close, {
|
|
214
|
+
part: 'main',
|
|
215
|
+
subject: [node.id],
|
|
216
|
+
options: { state: false },
|
|
217
|
+
}),
|
|
181
218
|
);
|
|
182
219
|
} else {
|
|
183
|
-
void dispatch(
|
|
220
|
+
void dispatch(
|
|
221
|
+
createIntent(LayoutAction.ScrollIntoView, {
|
|
222
|
+
part: 'current',
|
|
223
|
+
subject: node.id,
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
184
226
|
}
|
|
185
227
|
|
|
186
228
|
const defaultAction = graph.getActions(node.id).find((action) => action.properties?.disposition === 'default');
|
|
@@ -189,14 +231,25 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
189
231
|
}
|
|
190
232
|
|
|
191
233
|
if (!isLg) {
|
|
192
|
-
void dispatch(
|
|
234
|
+
void dispatch(
|
|
235
|
+
createIntent(LayoutAction.UpdateSidebar, {
|
|
236
|
+
part: 'sidebar',
|
|
237
|
+
options: { state: 'closed' },
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
193
240
|
}
|
|
194
241
|
},
|
|
195
242
|
[graph, dispatch, isCurrent, isLg],
|
|
196
243
|
);
|
|
197
244
|
|
|
198
245
|
const handleBack = useCallback(
|
|
199
|
-
() =>
|
|
246
|
+
() =>
|
|
247
|
+
dispatch(
|
|
248
|
+
createIntent(LayoutAction.RevertWorkspace, {
|
|
249
|
+
part: 'workspace',
|
|
250
|
+
options: { revert: true },
|
|
251
|
+
}),
|
|
252
|
+
),
|
|
200
253
|
[dispatch],
|
|
201
254
|
);
|
|
202
255
|
|
|
@@ -270,7 +323,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
270
323
|
() => ({
|
|
271
324
|
useItems,
|
|
272
325
|
tab,
|
|
273
|
-
|
|
326
|
+
useActions,
|
|
274
327
|
loadDescendents,
|
|
275
328
|
renderItemEnd,
|
|
276
329
|
popoverAnchorId,
|
|
@@ -279,6 +332,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
279
332
|
isCurrent,
|
|
280
333
|
isOpen,
|
|
281
334
|
canDrop,
|
|
335
|
+
canSelect,
|
|
282
336
|
isAlternateTree,
|
|
283
337
|
setAlternateTree,
|
|
284
338
|
onTabChange: handleTabChange,
|
|
@@ -288,7 +342,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
288
342
|
}),
|
|
289
343
|
[
|
|
290
344
|
tab,
|
|
291
|
-
|
|
345
|
+
useActions,
|
|
292
346
|
loadDescendents,
|
|
293
347
|
renderItemEnd,
|
|
294
348
|
popoverAnchorId,
|
|
@@ -297,6 +351,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
297
351
|
isCurrent,
|
|
298
352
|
isOpen,
|
|
299
353
|
canDrop,
|
|
354
|
+
canSelect,
|
|
300
355
|
isAlternateTree,
|
|
301
356
|
setAlternateTree,
|
|
302
357
|
handleTabChange,
|
|
@@ -6,10 +6,10 @@ import { useEffect } from 'react';
|
|
|
6
6
|
import { type Node } from '@dxos/app-graph';
|
|
7
7
|
import { toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { meta } from '../meta';
|
|
10
10
|
|
|
11
11
|
export const NavTreeDocumentTitle = ({ node }: { node?: Node }) => {
|
|
12
|
-
const { t } = useTranslation(
|
|
12
|
+
const { t } = useTranslation(meta.id);
|
|
13
13
|
useEffect(() => {
|
|
14
14
|
document.title = node ? toLocalizedString(node.properties.label, t) : t('current app name', { ns: 'appkit' });
|
|
15
15
|
}, [node?.properties?.label]);
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
|
|
9
7
|
import { type NodeArg } from '@dxos/app-graph';
|
|
10
8
|
import { faker } from '@dxos/random';
|
|
11
|
-
import {
|
|
9
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
10
|
|
|
13
11
|
import { NavTreeItemAction, type NavTreeItemActionMenuProps } from './NavTreeItemAction';
|
|
14
12
|
|
|
@@ -39,20 +37,20 @@ const menuActions = faker.helpers.multiple(
|
|
|
39
37
|
{ count: 20 },
|
|
40
38
|
);
|
|
41
39
|
|
|
42
|
-
const meta
|
|
40
|
+
const meta = {
|
|
43
41
|
title: 'plugins/plugin-navtree/NavTreeItemAction',
|
|
44
42
|
component: NavTreeItemAction,
|
|
45
|
-
decorators: [withTheme, withLayout()],
|
|
46
43
|
args: {
|
|
47
44
|
icon: 'ph--list--regular',
|
|
48
45
|
parent,
|
|
49
46
|
menuActions,
|
|
50
47
|
label: 'Select action',
|
|
51
48
|
} satisfies Partial<NavTreeItemActionMenuProps>,
|
|
49
|
+
decorators: [withTheme],
|
|
52
50
|
parameters: {
|
|
53
51
|
layout: 'centered',
|
|
54
52
|
},
|
|
55
|
-
}
|
|
53
|
+
} satisfies Meta<typeof NavTreeItemAction>;
|
|
56
54
|
|
|
57
55
|
export default meta;
|
|
58
56
|
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { type Action, type Node } from '@dxos/app-graph';
|
|
8
|
-
import {
|
|
9
|
-
import { DropdownMenu,
|
|
10
|
-
import { hoverableControlItem, hoverableOpenControlItem
|
|
8
|
+
import { IconButton, toLocalizedString, useDensityContext, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
|
|
10
|
+
import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { meta } from '../../meta';
|
|
13
13
|
import { type ActionProperties } from '../../types';
|
|
14
14
|
|
|
15
15
|
export type NavTreeItemActionMenuProps = ActionProperties & {
|
|
@@ -38,7 +38,7 @@ export const NavTreeItemActionDropdownMenu = ({
|
|
|
38
38
|
menuActions,
|
|
39
39
|
caller,
|
|
40
40
|
}: NavTreeItemActionMenuProps) => {
|
|
41
|
-
const { t } = useTranslation(
|
|
41
|
+
const { t } = useTranslation(meta.id);
|
|
42
42
|
const density = useDensityContext();
|
|
43
43
|
return (
|
|
44
44
|
<MenuProvider>
|
|
@@ -46,7 +46,7 @@ export const NavTreeItemActionDropdownMenu = ({
|
|
|
46
46
|
<DropdownMenu.Trigger asChild>
|
|
47
47
|
<IconButton
|
|
48
48
|
{...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
|
|
49
|
-
classNames={
|
|
49
|
+
classNames={['shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem]}
|
|
50
50
|
variant='ghost'
|
|
51
51
|
icon={icon ?? fallbackIcon}
|
|
52
52
|
iconOnly
|
|
@@ -70,21 +70,22 @@ export const NavTreeItemMonolithicAction = ({
|
|
|
70
70
|
<IconButton
|
|
71
71
|
{...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
|
|
72
72
|
variant={variant}
|
|
73
|
-
classNames={
|
|
73
|
+
classNames={[
|
|
74
74
|
'shrink-0',
|
|
75
75
|
hoverableControlItem,
|
|
76
76
|
hoverableOpenControlItem,
|
|
77
77
|
iconOnly ? 'pli-2 pointer-fine:pli-1' : 'p-2 pointer-fine:p-2 mie-1',
|
|
78
|
-
|
|
78
|
+
]}
|
|
79
79
|
icon={icon ?? fallbackIcon}
|
|
80
80
|
iconOnly={iconOnly}
|
|
81
81
|
label={baseLabel}
|
|
82
82
|
disabled={disabled}
|
|
83
83
|
onClick={(event) => {
|
|
84
|
+
event.stopPropagation();
|
|
84
85
|
if (disabled) {
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
void invoke?.(caller ? { parent, caller } : { parent });
|
|
89
90
|
}}
|
|
90
91
|
data-testid={testId}
|
|
@@ -93,7 +94,7 @@ export const NavTreeItemMonolithicAction = ({
|
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
|
|
96
|
-
const { t } = useTranslation(
|
|
97
|
+
const { t } = useTranslation(meta.id);
|
|
97
98
|
|
|
98
99
|
const monolithicAction = menuActions?.length === 1 && menuActions[0];
|
|
99
100
|
const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
|