@dxos/plugin-navtree 0.8.4-main.a4bbb77 → 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 (56) hide show
  1. package/dist/lib/browser/{app-graph-builder-BCXDEDN7.mjs → app-graph-builder-D7NLWZLU.mjs} +5 -4
  2. package/dist/lib/browser/app-graph-builder-D7NLWZLU.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-LPAREX6J.mjs → chunk-5VLWX4PK.mjs} +2 -2
  4. package/dist/lib/browser/{chunk-6AZKY5WZ.mjs → chunk-BNGPXJB7.mjs} +46 -34
  5. package/dist/lib/browser/chunk-BNGPXJB7.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-E6MZBCU5.mjs +19 -0
  7. package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +3 -3
  9. package/dist/lib/browser/{intent-resolver-G6COWRWW.mjs → intent-resolver-YUVXVPHG.mjs} +4 -4
  10. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +7 -0
  11. package/dist/lib/browser/{keyboard-AHSAWKXA.mjs → keyboard-YYTYJW4M.mjs} +2 -2
  12. package/dist/lib/browser/meta.json +1 -1
  13. package/dist/lib/browser/{react-surface-FOCB4Y5R.mjs → react-surface-7OUBD57A.mjs} +4 -4
  14. package/dist/lib/browser/{state-2Q623V7U.mjs → state-EUSVT6BI.mjs} +3 -3
  15. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  16. package/dist/types/src/capabilities/index.d.ts +12 -9
  17. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  18. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +0 -1
  19. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  21. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  22. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +1 -1
  23. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  24. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  25. package/dist/types/src/experimental/Tree.stories.d.ts +0 -3
  26. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  27. package/dist/types/src/meta.d.ts.map +1 -1
  28. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  29. package/dist/types/src/testing/generator.d.ts +1 -1
  30. package/dist/types/src/testing/generator.d.ts.map +1 -1
  31. package/dist/types/src/types.d.ts +4 -3
  32. package/dist/types/src/types.d.ts.map +1 -1
  33. package/dist/types/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +38 -39
  35. package/src/capabilities/app-graph-builder.ts +3 -2
  36. package/src/capabilities/intent-resolver.ts +1 -1
  37. package/src/components/NavTree/NavTree.stories.tsx +56 -45
  38. package/src/components/NavTreeContainer.tsx +11 -4
  39. package/src/components/NavTreeItem/NavTreeItemAction.tsx +2 -1
  40. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +25 -19
  41. package/src/components/Sidebar/L0Menu.tsx +1 -1
  42. package/src/components/Sidebar/L1Panel.tsx +6 -7
  43. package/src/experimental/Tree.stories.tsx +2 -5
  44. package/src/meta.ts +5 -0
  45. package/src/testing/app-graph-builder.ts +9 -8
  46. package/src/testing/generator.ts +2 -2
  47. package/src/types.ts +4 -3
  48. package/dist/lib/browser/app-graph-builder-BCXDEDN7.mjs.map +0 -7
  49. package/dist/lib/browser/chunk-6AZKY5WZ.mjs.map +0 -7
  50. package/dist/lib/browser/chunk-ZENLR72O.mjs +0 -14
  51. package/dist/lib/browser/chunk-ZENLR72O.mjs.map +0 -7
  52. package/dist/lib/browser/intent-resolver-G6COWRWW.mjs.map +0 -7
  53. /package/dist/lib/browser/{chunk-LPAREX6J.mjs.map → chunk-5VLWX4PK.mjs.map} +0 -0
  54. /package/dist/lib/browser/{keyboard-AHSAWKXA.mjs.map → keyboard-YYTYJW4M.mjs.map} +0 -0
  55. /package/dist/lib/browser/{react-surface-FOCB4Y5R.mjs.map → react-surface-7OUBD57A.mjs.map} +0 -0
  56. /package/dist/lib/browser/{state-2Q623V7U.mjs.map → state-EUSVT6BI.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-navtree",
3
- "version": "0.8.4-main.a4bbb77",
3
+ "version": "0.8.4-main.ae835ea",
4
4
  "description": "DXOS Surface plugin for a tree view visualization of the graph.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -31,50 +31,49 @@
31
31
  "@radix-ui/react-dismissable-layer": "1.1.5",
32
32
  "@radix-ui/react-use-controllable-state": "1.1.0",
33
33
  "date-fns": "^3.3.1",
34
- "@dxos/app-framework": "0.8.4-main.a4bbb77",
35
- "@dxos/app-graph": "0.8.4-main.a4bbb77",
36
- "@dxos/async": "0.8.4-main.a4bbb77",
37
- "@dxos/echo": "0.8.4-main.a4bbb77",
38
- "@dxos/debug": "0.8.4-main.a4bbb77",
39
- "@dxos/echo-schema": "0.8.4-main.a4bbb77",
40
- "@dxos/invariant": "0.8.4-main.a4bbb77",
41
- "@dxos/keyboard": "0.8.4-main.a4bbb77",
42
- "@dxos/lit-ui": "0.8.4-main.a4bbb77",
43
- "@dxos/live-object": "0.8.4-main.a4bbb77",
44
- "@dxos/local-storage": "0.8.4-main.a4bbb77",
45
- "@dxos/log": "0.8.4-main.a4bbb77",
46
- "@dxos/plugin-deck": "0.8.4-main.a4bbb77",
47
- "@dxos/plugin-graph": "0.8.4-main.a4bbb77",
48
- "@dxos/plugin-observability": "0.8.4-main.a4bbb77",
49
- "@dxos/protocols": "0.8.4-main.a4bbb77",
50
- "@dxos/react-client": "0.8.4-main.a4bbb77",
51
- "@dxos/react-ui-attention": "0.8.4-main.a4bbb77",
52
- "@dxos/react-ui-list": "0.8.4-main.a4bbb77",
53
- "@dxos/react-ui-menu": "0.8.4-main.a4bbb77",
54
- "@dxos/react-ui-searchlist": "0.8.4-main.a4bbb77",
55
- "@dxos/react-ui-stack": "0.8.4-main.a4bbb77",
56
- "@dxos/react-ui-tabs": "0.8.4-main.a4bbb77",
57
- "@dxos/util": "0.8.4-main.a4bbb77"
34
+ "@dxos/app-framework": "0.8.4-main.ae835ea",
35
+ "@dxos/app-graph": "0.8.4-main.ae835ea",
36
+ "@dxos/echo": "0.8.4-main.ae835ea",
37
+ "@dxos/keyboard": "0.8.4-main.ae835ea",
38
+ "@dxos/invariant": "0.8.4-main.ae835ea",
39
+ "@dxos/lit-ui": "0.8.4-main.ae835ea",
40
+ "@dxos/live-object": "0.8.4-main.ae835ea",
41
+ "@dxos/async": "0.8.4-main.ae835ea",
42
+ "@dxos/debug": "0.8.4-main.ae835ea",
43
+ "@dxos/local-storage": "0.8.4-main.ae835ea",
44
+ "@dxos/log": "0.8.4-main.ae835ea",
45
+ "@dxos/plugin-deck": "0.8.4-main.ae835ea",
46
+ "@dxos/plugin-graph": "0.8.4-main.ae835ea",
47
+ "@dxos/protocols": "0.8.4-main.ae835ea",
48
+ "@dxos/plugin-observability": "0.8.4-main.ae835ea",
49
+ "@dxos/react-client": "0.8.4-main.ae835ea",
50
+ "@dxos/react-ui-attention": "0.8.4-main.ae835ea",
51
+ "@dxos/react-ui-list": "0.8.4-main.ae835ea",
52
+ "@dxos/react-ui-menu": "0.8.4-main.ae835ea",
53
+ "@dxos/react-ui-tabs": "0.8.4-main.ae835ea",
54
+ "@dxos/react-ui-stack": "0.8.4-main.ae835ea",
55
+ "@dxos/util": "0.8.4-main.ae835ea",
56
+ "@dxos/react-ui-searchlist": "0.8.4-main.ae835ea"
58
57
  },
59
58
  "devDependencies": {
60
59
  "@effect-rx/rx-react": "0.42.4",
61
60
  "@effect/platform": "0.92.1",
62
- "@types/react": "~19.2.0",
63
- "@types/react-dom": "~19.2.0",
61
+ "@types/react": "~19.2.2",
62
+ "@types/react-dom": "~19.2.2",
64
63
  "effect": "3.18.3",
65
64
  "react": "~19.2.0",
66
65
  "react-dom": "~19.2.0",
67
66
  "vite": "7.1.9",
68
- "@dxos/echo-signals": "0.8.4-main.a4bbb77",
69
- "@dxos/plugin-attention": "0.8.4-main.a4bbb77",
70
- "@dxos/plugin-client": "0.8.4-main.a4bbb77",
71
- "@dxos/plugin-space": "0.8.4-main.a4bbb77",
72
- "@dxos/plugin-storybook-layout": "0.8.4-main.a4bbb77",
73
- "@dxos/plugin-theme": "0.8.4-main.a4bbb77",
74
- "@dxos/random": "0.8.4-main.a4bbb77",
75
- "@dxos/react-ui": "0.8.4-main.a4bbb77",
76
- "@dxos/react-ui-theme": "0.8.4-main.a4bbb77",
77
- "@dxos/storybook-utils": "0.8.4-main.a4bbb77"
67
+ "@dxos/echo-signals": "0.8.4-main.ae835ea",
68
+ "@dxos/plugin-client": "0.8.4-main.ae835ea",
69
+ "@dxos/plugin-attention": "0.8.4-main.ae835ea",
70
+ "@dxos/plugin-space": "0.8.4-main.ae835ea",
71
+ "@dxos/plugin-storybook-layout": "0.8.4-main.ae835ea",
72
+ "@dxos/plugin-theme": "0.8.4-main.ae835ea",
73
+ "@dxos/random": "0.8.4-main.ae835ea",
74
+ "@dxos/react-ui": "0.8.4-main.ae835ea",
75
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
76
+ "@dxos/storybook-utils": "0.8.4-main.ae835ea"
78
77
  },
79
78
  "peerDependencies": {
80
79
  "@effect-rx/rx-react": "^0.34.1",
@@ -82,8 +81,8 @@
82
81
  "effect": "^3.13.3",
83
82
  "react": "^19.0.0",
84
83
  "react-dom": "^19.0.0",
85
- "@dxos/react-ui": "0.8.4-main.a4bbb77",
86
- "@dxos/react-ui-theme": "0.8.4-main.a4bbb77"
84
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
85
+ "@dxos/react-ui": "0.8.4-main.ae835ea"
87
86
  },
88
87
  "publishConfig": {
89
88
  "access": "public"
@@ -3,7 +3,8 @@
3
3
  //
4
4
 
5
5
  import { Rx } from '@effect-rx/rx-react';
6
- import { Option, pipe } from 'effect';
6
+ import * as Function from 'effect/Function';
7
+ import * as Option from 'effect/Option';
7
8
 
8
9
  import { Capabilities, LayoutAction, type PluginContext, contributes, createIntent } from '@dxos/app-framework';
9
10
  import { ROOT_ID, createExtension } from '@dxos/plugin-graph';
@@ -17,7 +18,7 @@ export default (context: PluginContext) =>
17
18
  id: meta.id,
18
19
  actions: (node) =>
19
20
  Rx.make((get) =>
20
- pipe(
21
+ Function.pipe(
21
22
  get(node),
22
23
  Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
23
24
  Option.map(() => [
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
7
  import { Capabilities, LayoutAction, type PluginContext, contributes, createResolver } from '@dxos/app-framework';
8
8
  import { log } from '@dxos/log';
@@ -3,8 +3,9 @@
3
3
  //
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import { Schema } from 'effect';
7
- import React from 'react';
6
+ import * as Schema from 'effect/Schema';
7
+ import React, { type KeyboardEvent, useCallback, useRef } from 'react';
8
+ import { expect, userEvent, within } from 'storybook/test';
8
9
 
9
10
  import {
10
11
  Capabilities,
@@ -19,9 +20,7 @@ import {
19
20
  import { withPluginManager } from '@dxos/app-framework/testing';
20
21
  import { live } from '@dxos/live-object';
21
22
  import { AttentionPlugin } from '@dxos/plugin-attention';
22
- import { ClientPlugin } from '@dxos/plugin-client';
23
23
  import { GraphPlugin } from '@dxos/plugin-graph';
24
- import { SpacePlugin } from '@dxos/plugin-space';
25
24
  import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
26
25
  import { ThemePlugin } from '@dxos/plugin-theme';
27
26
  import { faker } from '@dxos/random';
@@ -66,6 +65,15 @@ const StoryPlankHeading = ({ attendableId }: { attendableId: string }) => {
66
65
 
67
66
  const StoryPlank = ({ attendableId }: { attendableId: string }) => {
68
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
+ }, []);
69
77
 
70
78
  return (
71
79
  <StackItem.Root
@@ -73,10 +81,12 @@ const StoryPlank = ({ attendableId }: { attendableId: string }) => {
73
81
  {...attentionAttrs}
74
82
  classNames='bg-baseSurface border-ie border-separator'
75
83
  size={30}
84
+ onKeyDown={handleKeyDown}
85
+ ref={rootElement}
76
86
  >
77
87
  <StoryPlankHeading attendableId={attendableId} />
78
88
  <StackItem.Content toolbar>
79
- <Toolbar.Root classNames='border-b border-subduedSeparator'>
89
+ <Toolbar.Root classNames='border-be border-subduedSeparator'>
80
90
  <Toolbar.Button>Test</Toolbar.Button>
81
91
  </Toolbar.Root>
82
92
 
@@ -157,47 +167,48 @@ export default meta;
157
167
 
158
168
  type Story = StoryObj<typeof NavTreeContainer>;
159
169
 
160
- export const Default: Story = {};
170
+ export const Default: Story = {
171
+ play: async ({ canvasElement }) => {
172
+ const canvas = within(canvasElement);
161
173
 
162
- // TODO(wittjosiah): Deduplicate plugins/capabilities with default story.
163
- export const WithClient: Story = {
164
- decorators: [
165
- withPluginManager({
166
- plugins: [
167
- ClientPlugin({
168
- onClientInitialized: async ({ client }) => {
169
- await client.halo.createIdentity();
170
- },
171
- }),
172
- SpacePlugin({}),
173
- GraphPlugin(),
174
- IntentPlugin(),
175
- SettingsPlugin(),
176
- AttentionPlugin(),
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
+ }
177
180
 
178
- // UI
179
- ThemePlugin({ tx: defaultTx }),
180
- NavTreePlugin(),
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
- ],
199
- parameters: {
200
- layout: 'fullscreen',
201
- translations,
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');
202
213
  },
203
214
  };
@@ -98,7 +98,7 @@ export type NavTreeContainerProps = {
98
98
  } & Pick<NavTreeContextValue, 'tab'>;
99
99
 
100
100
  export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeContainerProps) => {
101
- const [isLg] = useMediaQuery('lg', { ssr: false });
101
+ const [isLg] = useMediaQuery('lg');
102
102
  const { dispatchPromise: dispatch } = useIntentDispatcher();
103
103
  const { graph } = useAppGraph();
104
104
  const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
@@ -112,12 +112,13 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
112
112
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
113
113
  return {
114
114
  id: node.id,
115
- label: node.properties.label ?? node.id,
116
115
  parentOf,
117
- icon: node.properties.icon,
118
116
  disabled: node.properties.disabled,
119
- 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?
120
119
  headingClassName: node.properties.headingClassName,
120
+ icon: node.properties.icon,
121
+ iconHue: node.properties.iconHue,
121
122
  testId: node.properties.testId,
122
123
  };
123
124
  },
@@ -179,6 +180,10 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
179
180
  return target.item.properties.canDrop?.(source) ?? false;
180
181
  }, []);
181
182
 
183
+ const canSelect = useCallback(({ item }: { item: Node }) => {
184
+ return item.properties.selectable ?? true;
185
+ }, []);
186
+
182
187
  const handleSelect = useCallback(
183
188
  ({ item: node, path, option }: { item: Node; path: string[]; option: boolean }) => {
184
189
  if (!node.data) {
@@ -304,6 +309,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
304
309
  isCurrent,
305
310
  isOpen,
306
311
  canDrop,
312
+ canSelect,
307
313
  isAlternateTree,
308
314
  setAlternateTree,
309
315
  onTabChange: handleTabChange,
@@ -322,6 +328,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
322
328
  isCurrent,
323
329
  isOpen,
324
330
  canDrop,
331
+ canSelect,
325
332
  isAlternateTree,
326
333
  setAlternateTree,
327
334
  handleTabChange,
@@ -81,10 +81,11 @@ export const NavTreeItemMonolithicAction = ({
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}
@@ -5,7 +5,7 @@
5
5
  import React, { Fragment, memo } from 'react';
6
6
 
7
7
  import { isAction } from '@dxos/app-graph';
8
- import { DensityProvider, Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
+ import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
10
  import { useLoadDescendents } from '../../hooks';
11
11
  import { meta } from '../../meta';
@@ -15,7 +15,7 @@ import { type NavTreeItemColumnsProps } from '../types';
15
15
 
16
16
  import { NavTreeItemAction } from './NavTreeItemAction';
17
17
 
18
- export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }: NavTreeItemColumnsProps) => {
18
+ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
19
  const { t } = useTranslation(meta.id);
20
20
  const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
21
21
 
@@ -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
  });
@@ -72,7 +72,7 @@ type L0ItemProps = L0ItemRootProps & {
72
72
 
73
73
  const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
74
74
  const { tab, isCurrent, onSelect, onTabChange } = useNavTreeContext();
75
- const [isLg] = useMediaQuery('lg', { ssr: false });
75
+ const [isLg] = useMediaQuery('lg');
76
76
 
77
77
  return useCallback(
78
78
  (event: MouseEvent) => {
@@ -5,7 +5,7 @@
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
- import { IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
+ import { DensityProvider, IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
  import { Tree } from '@dxos/react-ui-list';
10
10
  import { Tabs } from '@dxos/react-ui-tabs';
11
11
  import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
@@ -28,11 +28,10 @@ export type L1PanelProps = {
28
28
  export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProps) => {
29
29
  const { t } = useTranslation(meta.id);
30
30
  const { isAlternateTree, ...navTreeContext } = useNavTreeContext();
31
- // NOTE(burdon): I'm very uneasy about this pattern of declaring hooks as context props.
32
- const { useItems } = navTreeContext;
33
31
  const title = toLocalizedString(item.properties.label, t);
34
32
 
35
33
  // TODO(wittjosiah): Support multiple alternate trees.
34
+ const { useItems } = navTreeContext;
36
35
  const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
37
36
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
38
37
  const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
@@ -59,19 +58,19 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
59
58
  {...(!open && { inert: true })}
60
59
  >
61
60
  {item.id === currentItemId && (
62
- <>
61
+ <DensityProvider density='fine'>
63
62
  <L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
64
63
  <div role='none' className='overflow-y-auto'>
65
64
  {isAlternate ? (
66
65
  <Tree
67
66
  {...navTreeContext}
68
- useItems={useAlternateItems}
69
67
  id={alternateTree.id}
70
68
  root={alternateTree}
71
69
  path={alternatePath}
72
70
  levelOffset={5}
73
71
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
74
72
  renderColumns={NavTreeItemColumns}
73
+ useItems={useAlternateItems}
75
74
  />
76
75
  ) : (
77
76
  <Tree
@@ -80,13 +79,13 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
80
79
  root={item}
81
80
  path={path}
82
81
  levelOffset={5}
83
- draggable
84
82
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
85
83
  renderColumns={NavTreeItemColumns}
84
+ draggable
86
85
  />
87
86
  )}
88
87
  </div>
89
- </>
88
+ </DensityProvider>
90
89
  )}
91
90
  </Tabs.Tabpanel>
92
91
  );
@@ -7,7 +7,7 @@ import React, { type JSX, type PropsWithChildren, useEffect, useState } from 're
7
7
 
8
8
  import { faker } from '@dxos/random';
9
9
  import { Icon, IconButton } from '@dxos/react-ui';
10
- import { withTheme } from '@dxos/react-ui/testing';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
  import { mx } from '@dxos/react-ui-theme';
12
12
 
13
13
  import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visitor } from './Tree';
@@ -203,10 +203,7 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
203
203
  const meta = {
204
204
  title: 'plugins/plugin-navtree/experimental/Tree',
205
205
  component: Tree,
206
- decorators: [withTheme],
207
- parameters: {
208
- layout: 'column',
209
- },
206
+ decorators: [withTheme, withLayout({ container: 'column' })],
210
207
  } satisfies Meta<typeof Tree>;
211
208
 
212
209
  export default meta;
package/src/meta.ts CHANGED
@@ -3,10 +3,15 @@
3
3
  //
4
4
 
5
5
  import { type PluginMeta } from '@dxos/app-framework';
6
+ import { trim } from '@dxos/util';
6
7
 
7
8
  export const meta: PluginMeta = {
8
9
  id: 'dxos.org/plugin/navtree',
9
10
  name: 'Navtree',
11
+ description: trim`
12
+ Hierarchical navigation tree for browsing spaces, folders, and objects.
13
+ Provides sidebar navigation with collapsible sections and quick access to workspace content.
14
+ `,
10
15
  };
11
16
 
12
17
  // TODO(wittjosiah): Factor out.
@@ -3,7 +3,8 @@
3
3
  //
4
4
 
5
5
  import { Rx } from '@effect-rx/rx-react';
6
- import { Option, pipe } from 'effect';
6
+ import * as Function from 'effect/Function';
7
+ import * as Option from 'effect/Option';
7
8
 
8
9
  import { type PluginContext } from '@dxos/app-framework';
9
10
  import { log } from '@dxos/log';
@@ -28,7 +29,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
28
29
  id: 'app-menu',
29
30
  actions: (node) =>
30
31
  Rx.make((get) =>
31
- pipe(
32
+ Function.pipe(
32
33
  get(node),
33
34
  Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
34
35
  Option.map((node) => {
@@ -55,7 +56,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
55
56
  id: 'user-account',
56
57
  connector: (node) =>
57
58
  Rx.make((get) =>
58
- pipe(
59
+ Function.pipe(
59
60
  get(node),
60
61
  Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
61
62
  Option.map(() => {
@@ -112,7 +113,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
112
113
  position: 'hoist',
113
114
  connector: (node) =>
114
115
  Rx.make((get) =>
115
- pipe(
116
+ Function.pipe(
116
117
  get(node),
117
118
  Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
118
119
  Option.map(() => {
@@ -153,7 +154,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
153
154
  });
154
155
 
155
156
  return Rx.make((get) =>
156
- pipe(
157
+ Function.pipe(
157
158
  get(node),
158
159
  Option.flatMap((node) => (node.id === 'spaces-root' ? Option.some(node) : Option.none())),
159
160
  Option.map(() => {
@@ -179,7 +180,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
179
180
  id: 'space-actions',
180
181
  actions: (node) =>
181
182
  Rx.make((get) =>
182
- pipe(
183
+ Function.pipe(
183
184
  get(node),
184
185
  Option.flatMap((node) => (node.type === 'space' ? Option.some(node) : Option.none())),
185
186
  Option.map((node) => {
@@ -220,7 +221,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
220
221
  });
221
222
 
222
223
  return Rx.make((get) =>
223
- pipe(
224
+ Function.pipe(
224
225
  get(node),
225
226
  Option.flatMap((node) => (node.type === 'space' ? Option.some(node) : Option.none())),
226
227
  Option.map((node) => {
@@ -245,7 +246,7 @@ export const storybookGraphBuilders = (context: PluginContext): BuilderExtension
245
246
  id: 'object-actions',
246
247
  actions: (node) =>
247
248
  Rx.make((get) =>
248
- pipe(
249
+ Function.pipe(
249
250
  get(node),
250
251
  Option.flatMap((node) => (node.type === 'object' ? Option.some(node) : Option.none())),
251
252
  Option.map((node) => {
@@ -2,11 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
7
  import { type NodeArg } from '@dxos/app-graph';
8
8
  import { type Live, Obj, Type } from '@dxos/echo';
9
- import { TypedObject } from '@dxos/echo-schema';
9
+ import { TypedObject } from '@dxos/echo/internal';
10
10
  import { faker } from '@dxos/random';
11
11
  import { range } from '@dxos/util';
12
12
 
package/src/types.ts CHANGED
@@ -28,13 +28,14 @@ export type FlattenedActions = {
28
28
  };
29
29
 
30
30
  type SharedProperties = {
31
- label: Label;
32
- icon?: string;
31
+ testId?: string;
33
32
  disabled?: boolean;
34
33
  position?: Position;
35
- testId?: string;
34
+ label: Label;
36
35
  className?: string;
37
36
  headingClassName?: string;
37
+ icon?: string;
38
+ iconHue?: string;
38
39
  };
39
40
 
40
41
  export type NodeProperties = SharedProperties & {
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/capabilities/app-graph-builder.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Rx } from '@effect-rx/rx-react';\nimport { Option, pipe } from 'effect';\n\nimport { Capabilities, LayoutAction, type PluginContext, contributes, createIntent } from '@dxos/app-framework';\nimport { ROOT_ID, createExtension } from '@dxos/plugin-graph';\n\nimport { COMMANDS_DIALOG, meta } from '../meta';\n\nexport default (context: PluginContext) =>\n contributes(\n Capabilities.AppGraphBuilder,\n createExtension({\n id: meta.id,\n actions: (node) =>\n Rx.make((get) =>\n pipe(\n get(node),\n Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),\n Option.map(() => [\n {\n id: COMMANDS_DIALOG,\n data: async () => {\n const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);\n await dispatch(\n createIntent(LayoutAction.UpdateDialog, {\n part: 'dialog',\n subject: COMMANDS_DIALOG,\n options: {\n blockAlign: 'start',\n },\n }),\n );\n },\n properties: {\n label: ['open commands label', { ns: meta.id }],\n icon: 'ph--magnifying-glass--regular',\n keyBinding: {\n macos: 'meta+k',\n windows: 'ctrl+k',\n },\n },\n },\n ]),\n Option.getOrElse(() => []),\n ),\n ),\n }),\n );\n"],
5
- "mappings": ";;;;;;AAIA,SAASA,UAAU;AACnB,SAASC,QAAQC,YAAY;AAE7B,SAASC,cAAcC,cAAkCC,aAAaC,oBAAoB;AAC1F,SAASC,SAASC,uBAAuB;AAIzC,IAAA,6BAAe,CAACC,YACdC,YACEC,aAAaC,iBACbC,gBAAgB;EACdC,IAAIC,KAAKD;EACTE,SAAS,CAACC,SACRC,GAAGC,KAAK,CAACC,QACPC,KACED,IAAIH,IAAAA,GACJK,OAAOC,QAAQ,CAACN,UAAUA,MAAKH,OAAOU,UAAUF,OAAOG,KAAKR,KAAAA,IAAQK,OAAOI,KAAI,CAAA,GAC/EJ,OAAOK,IAAI,MAAM;IACf;MACEb,IAAIc;MACJC,MAAM,YAAA;AACJ,cAAM,EAAEC,iBAAiBC,SAAQ,IAAKtB,QAAQuB,cAAcrB,aAAasB,gBAAgB;AACzF,cAAMF,SACJG,aAAaC,aAAaC,cAAc;UACtCC,MAAM;UACNC,SAASV;UACTW,SAAS;YACPC,YAAY;UACd;QACF,CAAA,CAAA;MAEJ;MACAC,YAAY;QACVC,OAAO;UAAC;UAAuB;YAAEC,IAAI5B,KAAKD;UAAG;;QAC7C8B,MAAM;QACNC,YAAY;UACVC,OAAO;UACPC,SAAS;QACX;MACF;IACF;GACD,GACDzB,OAAO0B,UAAU,MAAM,CAAA,CAAE,CAAA,CAAA;AAGjC,CAAA,CAAA;",
6
- "names": ["Rx", "Option", "pipe", "Capabilities", "LayoutAction", "contributes", "createIntent", "ROOT_ID", "createExtension", "context", "contributes", "Capabilities", "AppGraphBuilder", "createExtension", "id", "meta", "actions", "node", "Rx", "make", "get", "pipe", "Option", "flatMap", "ROOT_ID", "some", "none", "map", "COMMANDS_DIALOG", "data", "dispatchPromise", "dispatch", "getCapability", "IntentDispatcher", "createIntent", "LayoutAction", "UpdateDialog", "part", "subject", "options", "blockAlign", "properties", "label", "ns", "icon", "keyBinding", "macos", "windows", "getOrElse"]
7
- }