@dxos/plugin-navtree 0.8.4-main.84f28bd → 0.8.4-main.ae835ea

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/lib/browser/{app-graph-builder-5J4LRWDX.mjs → app-graph-builder-D7NLWZLU.mjs} +12 -11
  2. package/dist/lib/browser/app-graph-builder-D7NLWZLU.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-WCARAZ7J.mjs → chunk-5VLWX4PK.mjs} +4 -4
  4. package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-TZA65AKW.mjs → chunk-BNGPXJB7.mjs} +312 -258
  6. package/dist/lib/browser/chunk-BNGPXJB7.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-E6MZBCU5.mjs +19 -0
  8. package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +8 -11
  10. package/dist/lib/browser/index.mjs.map +3 -3
  11. package/dist/lib/browser/{intent-resolver-3XYN2CBU.mjs → intent-resolver-YUVXVPHG.mjs} +7 -7
  12. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +7 -0
  13. package/dist/lib/browser/{keyboard-QRS6WOQH.mjs → keyboard-YYTYJW4M.mjs} +4 -4
  14. package/dist/lib/{node-esm/keyboard-MOZWHFUM.mjs.map → browser/keyboard-YYTYJW4M.mjs.map} +2 -2
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/{react-surface-N2HPMYCU.mjs → react-surface-7OUBD57A.mjs} +10 -10
  17. package/dist/lib/browser/react-surface-7OUBD57A.mjs.map +7 -0
  18. package/dist/lib/browser/{state-54DY3DOY.mjs → state-EUSVT6BI.mjs} +7 -7
  19. package/dist/lib/browser/state-EUSVT6BI.mjs.map +7 -0
  20. package/dist/types/src/NavTreePlugin.d.ts +1 -1
  21. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
  23. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  24. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  25. package/dist/types/src/capabilities/index.d.ts +18 -15
  26. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  27. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  28. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/keyboard.d.ts +1 -1
  30. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  32. package/dist/types/src/capabilities/state.d.ts +2 -2
  33. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  34. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  35. package/dist/types/src/components/NavTree/NavTree.d.ts +2 -2
  36. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  37. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +26 -4
  38. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  40. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  41. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +32 -4
  42. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +2 -2
  44. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  45. package/dist/types/src/components/Sidebar/L0Menu.d.ts +2 -0
  46. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  47. package/dist/types/src/components/Sidebar/L1Panel.d.ts +5 -6
  48. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  49. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +12 -0
  50. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
  51. package/dist/types/src/components/Sidebar/index.d.ts +1 -0
  52. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -1
  53. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  54. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  55. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/index.d.ts +1 -1
  57. package/dist/types/src/components/index.d.ts.map +1 -1
  58. package/dist/types/src/components/types.d.ts +2 -2
  59. package/dist/types/src/components/types.d.ts.map +1 -1
  60. package/dist/types/src/events.d.ts.map +1 -1
  61. package/dist/types/src/experimental/Tree.d.ts +4 -5
  62. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  63. package/dist/types/src/experimental/Tree.stories.d.ts +7 -4
  64. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  65. package/dist/types/src/meta.d.ts +2 -3
  66. package/dist/types/src/meta.d.ts.map +1 -1
  67. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  68. package/dist/types/src/testing/generator.d.ts +1 -1
  69. package/dist/types/src/testing/generator.d.ts.map +1 -1
  70. package/dist/types/src/translations.d.ts +1 -0
  71. package/dist/types/src/translations.d.ts.map +1 -1
  72. package/dist/types/src/types.d.ts +4 -3
  73. package/dist/types/src/types.d.ts.map +1 -1
  74. package/dist/types/src/util.d.ts +1 -2
  75. package/dist/types/src/util.d.ts.map +1 -1
  76. package/dist/types/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +48 -49
  78. package/src/NavTreePlugin.tsx +75 -76
  79. package/src/capabilities/app-graph-builder.ts +8 -7
  80. package/src/capabilities/capabilities.ts +2 -2
  81. package/src/capabilities/intent-resolver.ts +2 -2
  82. package/src/capabilities/keyboard.ts +2 -2
  83. package/src/capabilities/react-surface.tsx +5 -5
  84. package/src/capabilities/state.ts +4 -3
  85. package/src/components/CommandsDialog/CommandsDialog.tsx +5 -5
  86. package/src/components/CommandsDialog/CommandsTrigger.tsx +5 -7
  87. package/src/components/NavTree/NavTree.stories.tsx +66 -52
  88. package/src/components/NavTree/NavTree.tsx +7 -3
  89. package/src/components/NavTreeContainer.tsx +54 -22
  90. package/src/components/NavTreeDocumentTitle.tsx +2 -2
  91. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +4 -6
  92. package/src/components/NavTreeItem/NavTreeItemAction.tsx +11 -10
  93. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +33 -27
  94. package/src/components/Sidebar/L0Menu.tsx +52 -51
  95. package/src/components/Sidebar/L1Panel.tsx +74 -97
  96. package/src/components/Sidebar/L1Tabs.tsx +58 -0
  97. package/src/components/Sidebar/index.ts +1 -0
  98. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -8
  99. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  100. package/src/components/index.ts +1 -1
  101. package/src/components/types.ts +3 -2
  102. package/src/events.ts +2 -2
  103. package/src/experimental/Tree.stories.tsx +34 -36
  104. package/src/experimental/Tree.tsx +16 -34
  105. package/src/meta.ts +10 -7
  106. package/src/testing/app-graph-builder.ts +10 -9
  107. package/src/testing/generator.ts +2 -2
  108. package/src/translations.ts +1 -0
  109. package/src/types.ts +4 -3
  110. package/src/util.ts +2 -20
  111. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
  112. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  113. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  114. package/dist/lib/browser/chunk-TZA65AKW.mjs.map +0 -7
  115. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  116. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs.map +0 -7
  117. package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
  118. package/dist/lib/browser/react-surface-N2HPMYCU.mjs.map +0 -7
  119. package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
  120. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs +0 -46
  121. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs.map +0 -7
  122. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs +0 -1429
  123. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs.map +0 -7
  124. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs +0 -18
  125. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs.map +0 -7
  126. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs +0 -16
  127. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs.map +0 -7
  128. package/dist/lib/node-esm/index.mjs +0 -150
  129. package/dist/lib/node-esm/index.mjs.map +0 -7
  130. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs +0 -48
  131. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs.map +0 -7
  132. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs +0 -52
  133. package/dist/lib/node-esm/meta.json +0 -1
  134. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs +0 -52
  135. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs.map +0 -7
  136. package/dist/lib/node-esm/state-Z3OTRC2I.mjs +0 -118
  137. 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 '@dxos-theme';
6
-
7
- import { type StoryObj, type Meta } from '@storybook/react-vite';
8
- import { Schema } from 'effect';
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,
@@ -21,17 +20,15 @@ import {
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 { useAttentionAttributes, useAttention } from '@dxos/react-ui-attention';
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-b border-subduedSeparator'>
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: Meta<typeof NavTreeContainer> = {
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
- // TODO(wittjosiah): Deduplicate plugins/capabilities with default story.
164
- export const WithClient: Story = {
165
- decorators: [
166
- withPluginManager({
167
- plugins: [
168
- ThemePlugin({ tx: defaultTx }),
169
- GraphPlugin(),
170
- IntentPlugin(),
171
- SettingsPlugin(),
172
- AttentionPlugin(),
173
- NavTreePlugin(),
174
- ClientPlugin({
175
- onClientInitialized: async (_, client) => {
176
- await client.halo.createIdentity();
177
- },
178
- }),
179
- SpacePlugin(),
180
- // Needs to be last so that the dialog and popovers have access to all contexts.
181
- StorybookLayoutPlugin({ initialState: { sidebarState: 'expanded' } }),
182
- ],
183
- capabilities: (context) => [
184
- contributes(StoryState, live({ tab: 'space-0' })),
185
- contributes(Capabilities.IntentResolver, [
186
- createResolver({
187
- intent: LayoutAction.UpdateLayout,
188
- filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
189
- Schema.is(LayoutAction.SwitchWorkspace.fields.input)(data),
190
- resolve: ({ subject }) => {
191
- const state = context.getCapability(StoryState);
192
- state.tab = subject;
193
- },
194
- }),
195
- ]),
196
- ],
197
- }),
198
- withLayout({ fullscreen: true }),
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, L1Panels, type L1PanelsProps } from '../Sidebar';
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<L1PanelsProps, 'open'>;
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
- <L1Panels topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
63
+ <L1Tabs topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
60
64
  </Tabs.Root>
61
65
  );
62
66
  };
@@ -3,34 +3,35 @@
3
3
  //
4
4
 
5
5
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
- import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
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
10
  import {
11
- createIntent,
12
11
  LayoutAction,
13
12
  Surface,
13
+ createIntent,
14
14
  useAppGraph,
15
15
  useCapability,
16
16
  useIntentDispatcher,
17
17
  useLayout,
18
18
  } from '@dxos/app-framework';
19
- import { isAction, isActionLike, ROOT_ID, type Node, type ReadableGraph } from '@dxos/app-graph';
19
+ import { type Node, ROOT_ID, type ReadableGraph, isAction, isActionLike } from '@dxos/app-graph';
20
20
  import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
21
- import { useConnections } from '@dxos/plugin-graph';
21
+ import { useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
22
22
  import { useMediaQuery, useSidebars } from '@dxos/react-ui';
23
- import { isTreeData, type TreeItemDataProps, type TreeData } from '@dxos/react-ui-list';
23
+ import { type TreeData, type TreeItemDataProps, isTreeData } from '@dxos/react-ui-list';
24
24
  import { mx } from '@dxos/react-ui-theme';
25
25
  import { arrayMove, byPosition } from '@dxos/util';
26
26
 
27
+ import { NavTreeCapabilities } from '../capabilities';
28
+ import { meta } from '../meta';
29
+ import { type FlattenedActions, type NavTreeItemGraphNode } from '../types';
30
+ import { getChildren, getParent, resolveMigrationOperation } from '../util';
31
+
27
32
  import { NAV_TREE_ITEM, NavTree } from './NavTree';
28
33
  import { NavTreeContext } from './NavTreeContext';
29
34
  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
35
 
35
36
  // TODO(thure): Is NavTree truly authoritative in this regard?
36
37
  export const NODE_TYPE = 'dxos/app-graph/node';
@@ -40,7 +41,9 @@ const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
40
41
  );
41
42
 
42
43
  const getChildrenFilter = (node: Node): node is Node =>
43
- untracked(() => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE);
44
+ untracked(
45
+ () => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE && node.properties.disposition !== 'hidden',
46
+ );
44
47
 
45
48
  const filterItems = (node: Node, disposition?: string) => {
46
49
  if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
@@ -59,10 +62,34 @@ const getItems = (graph: ReadableGraph, node?: Node, disposition?: string) => {
59
62
 
60
63
  const useItems = (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
61
64
  const { graph } = useAppGraph();
62
- const connections = useConnections(graph, node?.id ?? ROOT_ID).filter((node) =>
63
- filterItems(node, options?.disposition),
65
+ const connections = useConnections(graph, node?.id ?? ROOT_ID);
66
+ const filtered = connections.filter((node) => filterItems(node, options?.disposition));
67
+ return options?.sort ? filtered.toSorted((a, b) => byPosition(a.properties, b.properties)) : filtered;
68
+ };
69
+
70
+ const useActions = (node: Node): FlattenedActions => {
71
+ const { graph } = useAppGraph();
72
+ const actions = useGraphActions(graph, node.id);
73
+
74
+ return useMemo(
75
+ () =>
76
+ actions.reduce(
77
+ (acc: FlattenedActions, arg) => {
78
+ if (arg.properties.disposition === 'item') {
79
+ return acc;
80
+ }
81
+
82
+ acc.actions.push(arg);
83
+ if (!isAction(arg)) {
84
+ const actionGroup = graph.getActions(arg.id);
85
+ acc.groupedActions[arg.id] = actionGroup;
86
+ }
87
+ return acc;
88
+ },
89
+ { actions: [], groupedActions: {} },
90
+ ),
91
+ [actions],
64
92
  );
65
- return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
66
93
  };
67
94
 
68
95
  export type NavTreeContainerProps = {
@@ -71,14 +98,12 @@ export type NavTreeContainerProps = {
71
98
  } & Pick<NavTreeContextValue, 'tab'>;
72
99
 
73
100
  export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeContainerProps) => {
74
- const [isLg] = useMediaQuery('lg', { ssr: false });
101
+ const [isLg] = useMediaQuery('lg');
75
102
  const { dispatchPromise: dispatch } = useIntentDispatcher();
76
103
  const { graph } = useAppGraph();
77
104
  const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
78
105
  const layout = useLayout();
79
- const { navigationSidebarState } = useSidebars(NAVTREE_PLUGIN);
80
-
81
- const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
106
+ const { navigationSidebarState } = useSidebars(meta.id);
82
107
 
83
108
  const getProps = useCallback(
84
109
  (node: Node, path: string[]): TreeItemDataProps => {
@@ -87,12 +112,13 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
87
112
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
88
113
  return {
89
114
  id: node.id,
90
- label: node.properties.label ?? node.id,
91
115
  parentOf,
92
- icon: node.properties.icon,
93
116
  disabled: node.properties.disabled,
94
- className: mx(node.properties.modified && 'italic', node.properties.className),
117
+ label: node.properties.label ?? node.id,
118
+ className: mx(node.properties.className, node.properties.modified && 'italic'), // TODO(burdon): Italic?
95
119
  headingClassName: node.properties.headingClassName,
120
+ icon: node.properties.icon,
121
+ iconHue: node.properties.iconHue,
96
122
  testId: node.properties.testId,
97
123
  };
98
124
  },
@@ -154,6 +180,10 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
154
180
  return target.item.properties.canDrop?.(source) ?? false;
155
181
  }, []);
156
182
 
183
+ const canSelect = useCallback(({ item }: { item: Node }) => {
184
+ return item.properties.selectable ?? true;
185
+ }, []);
186
+
157
187
  const handleSelect = useCallback(
158
188
  ({ item: node, path, option }: { item: Node; path: string[]; option: boolean }) => {
159
189
  if (!node.data) {
@@ -270,7 +300,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
270
300
  () => ({
271
301
  useItems,
272
302
  tab,
273
- getActions,
303
+ useActions,
274
304
  loadDescendents,
275
305
  renderItemEnd,
276
306
  popoverAnchorId,
@@ -279,6 +309,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
279
309
  isCurrent,
280
310
  isOpen,
281
311
  canDrop,
312
+ canSelect,
282
313
  isAlternateTree,
283
314
  setAlternateTree,
284
315
  onTabChange: handleTabChange,
@@ -288,7 +319,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
288
319
  }),
289
320
  [
290
321
  tab,
291
- getActions,
322
+ useActions,
292
323
  loadDescendents,
293
324
  renderItemEnd,
294
325
  popoverAnchorId,
@@ -297,6 +328,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
297
328
  isCurrent,
298
329
  isOpen,
299
330
  canDrop,
331
+ canSelect,
300
332
  isAlternateTree,
301
333
  setAlternateTree,
302
334
  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 { NAVTREE_PLUGIN } from '../meta';
9
+ import { meta } from '../meta';
10
10
 
11
11
  export const NavTreeDocumentTitle = ({ node }: { node?: Node }) => {
12
- const { t } = useTranslation(NAVTREE_PLUGIN);
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 { withLayout, withTheme } from '@dxos/storybook-utils';
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: Meta<typeof NavTreeItemAction> = {
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 { useTranslation, toLocalizedString, IconButton, useDensityContext } from '@dxos/react-ui';
9
- import { DropdownMenu, MenuProvider, type MenuItem } from '@dxos/react-ui-menu';
10
- import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
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 { NAVTREE_PLUGIN } from '../../meta';
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(NAVTREE_PLUGIN);
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={mx('shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem)}
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={mx(
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
- event.stopPropagation();
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(NAVTREE_PLUGIN);
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);
@@ -5,23 +5,23 @@
5
5
  import React, { Fragment, memo } from 'react';
6
6
 
7
7
  import { isAction } from '@dxos/app-graph';
8
- import { DensityProvider, Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
8
+ import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { NavTreeItemAction } from './NavTreeItemAction';
11
10
  import { useLoadDescendents } from '../../hooks';
12
- import { NAVTREE_PLUGIN } from '../../meta';
11
+ import { meta } from '../../meta';
13
12
  import { NAV_TREE_ITEM } from '../NavTree';
14
13
  import { useNavTreeContext } from '../NavTreeContext';
15
- import type { NavTreeItemColumnsProps } from '../types';
14
+ import { type NavTreeItemColumnsProps } from '../types';
16
15
 
17
- export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }: NavTreeItemColumnsProps) => {
18
- const { t } = useTranslation(NAVTREE_PLUGIN);
19
- const { getActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
16
+ import { NavTreeItemAction } from './NavTreeItemAction';
20
17
 
21
- const level = path.length - 2;
18
+ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
+ const { t } = useTranslation(meta.id);
20
+ const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
22
21
 
23
- const { actions: _actions, groupedActions } = getActions(item);
24
- const [primaryAction, ...secondaryActions] = _actions.toSorted((a, b) =>
22
+ const level = path.length - 2;
23
+ const { actions: _actions, groupedActions } = useActions(item);
24
+ const [primaryAction, ...secondaryActions] = _actions.toSorted((a, _b) =>
25
25
  a.properties?.disposition === 'list-item-primary' ? -1 : 1,
26
26
  );
27
27
 
@@ -29,15 +29,15 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
29
29
  .flatMap((action) => (isAction(action) ? [action] : []))
30
30
  .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
31
31
 
32
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
33
-
34
32
  useLoadDescendents(item);
35
33
  useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
36
34
 
35
+ const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
36
+
37
37
  return (
38
- <DensityProvider density={density}>
39
- <div role='none' className='contents app-no-drag'>
40
- {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
38
+ <div role='none' className='contents app-no-drag'>
39
+ {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
+ <Treegrid.Cell classNames='contents'>
41
41
  <NavTreeItemAction
42
42
  testId={primaryAction.properties?.testId}
43
43
  label={toLocalizedString(primaryAction.properties?.label, t)}
@@ -48,11 +48,13 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
48
48
  menuType={primaryAction.properties?.menuType}
49
49
  caller={NAV_TREE_ITEM}
50
50
  />
51
- ) : (
52
- <Treegrid.Cell />
53
- )}
54
- <ActionRoot>
55
- {actions.length > 0 ? (
51
+ </Treegrid.Cell>
52
+ ) : (
53
+ <Treegrid.Cell role='none' />
54
+ )}
55
+ <ActionRoot>
56
+ {actions.length > 0 ? (
57
+ <Treegrid.Cell classNames='contents'>
56
58
  <NavTreeItemAction
57
59
  testId={`navtree.treeItem.actionsLevel${level}`}
58
60
  label={t('tree item actions label')}
@@ -62,12 +64,16 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
62
64
  menuType='dropdown'
63
65
  caller={NAV_TREE_ITEM}
64
66
  />
65
- ) : (
66
- <Treegrid.Cell />
67
- )}
68
- </ActionRoot>
69
- {ItemEnd && <ItemEnd node={item} open={open} />}
70
- </div>
71
- </DensityProvider>
67
+ </Treegrid.Cell>
68
+ ) : (
69
+ <Treegrid.Cell role='none' />
70
+ )}
71
+ </ActionRoot>
72
+ {ItemEnd && (
73
+ <Treegrid.Cell classNames='contents'>
74
+ <ItemEnd node={item} open={open} />
75
+ </Treegrid.Cell>
76
+ )}
77
+ </div>
72
78
  );
73
79
  });