@dxos/plugin-navtree 0.9.0 → 0.9.1-staging.ee54ba693a

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 (100) hide show
  1. package/dist/lib/neutral/{CommandsDialogContent-UBGIQ5FI.mjs → CommandsDialogContent-TEKBCQ3U.mjs} +3 -3
  2. package/dist/lib/neutral/CommandsDialogContent-TEKBCQ3U.mjs.map +7 -0
  3. package/dist/lib/neutral/{CommandsTrigger-BH5WLMHK.mjs → CommandsTrigger-HGCI22SP.mjs} +2 -2
  4. package/dist/lib/neutral/{CommandsTrigger-BH5WLMHK.mjs.map → CommandsTrigger-HGCI22SP.mjs.map} +3 -3
  5. package/dist/lib/neutral/{NavTreeContainer-6AR4BFDD.mjs → NavTreeContainer-ICDKC2GI.mjs} +2 -2
  6. package/dist/lib/neutral/{NavTreeDocumentTitle-H3E6ZLYE.mjs → NavTreeDocumentTitle-RMERXZGK.mjs} +2 -2
  7. package/dist/lib/neutral/{NavTreeDocumentTitle-H3E6ZLYE.mjs.map → NavTreeDocumentTitle-RMERXZGK.mjs.map} +3 -3
  8. package/dist/lib/neutral/NavTreePlugin.mjs +2 -2
  9. package/dist/lib/neutral/NavTreePlugin.mjs.map +4 -4
  10. package/dist/lib/neutral/{app-graph-builder-Z347LKSF.mjs → app-graph-builder-2DYAIROU.mjs} +2 -2
  11. package/dist/lib/neutral/{app-graph-builder-Z347LKSF.mjs.map → app-graph-builder-2DYAIROU.mjs.map} +3 -3
  12. package/dist/lib/neutral/capabilities/index.mjs +4 -4
  13. package/dist/lib/neutral/{chunk-5OPDKH6B.mjs → chunk-4RN6TH36.mjs} +23 -4
  14. package/dist/lib/neutral/chunk-4RN6TH36.mjs.map +7 -0
  15. package/dist/lib/neutral/{chunk-ZSWYB7JW.mjs → chunk-BAVI2K7A.mjs} +3 -3
  16. package/dist/lib/neutral/{chunk-ZSWYB7JW.mjs.map → chunk-BAVI2K7A.mjs.map} +3 -3
  17. package/dist/lib/neutral/{chunk-T7QS2EFI.mjs → chunk-EAN4K6OL.mjs} +2 -2
  18. package/dist/lib/neutral/chunk-IRQMJLRY.mjs +45 -0
  19. package/dist/lib/neutral/chunk-IRQMJLRY.mjs.map +7 -0
  20. package/dist/lib/neutral/components/index.mjs +52 -43
  21. package/dist/lib/neutral/components/index.mjs.map +3 -3
  22. package/dist/lib/neutral/containers/index.mjs +5 -5
  23. package/dist/lib/neutral/{expose-26FV4W5D.mjs → expose-HIBA2NQG.mjs} +2 -2
  24. package/dist/lib/neutral/hooks/index.mjs +7 -4
  25. package/dist/lib/neutral/hooks/index.mjs.map +3 -3
  26. package/dist/lib/neutral/index.mjs +2 -2
  27. package/dist/lib/neutral/{keyboard-EJ65BVTC.mjs → keyboard-JM2HCF5N.mjs} +5 -3
  28. package/dist/lib/neutral/{keyboard-EJ65BVTC.mjs.map → keyboard-JM2HCF5N.mjs.map} +3 -3
  29. package/dist/lib/neutral/meta.json +1 -1
  30. package/dist/lib/neutral/meta.mjs +1 -1
  31. package/dist/lib/neutral/operations/index.mjs +1 -1
  32. package/dist/lib/neutral/plugin.mjs +2 -2
  33. package/dist/lib/neutral/{react-surface-Q5XMAG7Z.mjs → react-surface-QPMUEDZX.mjs} +11 -13
  34. package/dist/lib/neutral/react-surface-QPMUEDZX.mjs.map +7 -0
  35. package/dist/lib/neutral/{state-VVJEWUHM.mjs → state-PDZU4PIP.mjs} +2 -2
  36. package/dist/lib/neutral/state-PDZU4PIP.mjs.map +7 -0
  37. package/dist/lib/neutral/testing/index.mjs +2 -2
  38. package/dist/lib/neutral/testing/index.mjs.map +3 -3
  39. package/dist/lib/neutral/translations.mjs +1 -1
  40. package/dist/lib/neutral/translations.mjs.map +3 -3
  41. package/dist/lib/neutral/types/index.mjs +1 -1
  42. package/dist/types/dx.config.d.ts +28 -0
  43. package/dist/types/dx.config.d.ts.map +1 -0
  44. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
  45. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  46. package/dist/types/src/capabilities/index.d.ts +4 -4
  47. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  48. package/dist/types/src/capabilities/keyboard.d.ts +1 -1
  49. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  50. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  51. package/dist/types/src/capabilities/state.d.ts +1 -2
  52. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  53. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  54. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  56. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  57. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -1
  58. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -1
  59. package/dist/types/src/meta.d.ts +32 -2
  60. package/dist/types/src/meta.d.ts.map +1 -1
  61. package/dist/types/src/testing/generator.d.ts.map +1 -1
  62. package/dist/types/src/types/NavTreeCapabilities.d.ts.map +1 -1
  63. package/dist/types/src/types/NavTreeEvents.d.ts.map +1 -1
  64. package/dist/types/src/types/schema.d.ts +1 -1
  65. package/dist/types/src/types/schema.d.ts.map +1 -1
  66. package/dist/types/tsconfig.tsbuildinfo +1 -1
  67. package/dx.config.ts +31 -0
  68. package/package.json +41 -41
  69. package/src/NavTreePlugin.test.ts +1 -1
  70. package/src/NavTreePlugin.tsx +1 -1
  71. package/src/capabilities/app-graph-builder.ts +1 -1
  72. package/src/capabilities/keyboard.ts +5 -2
  73. package/src/capabilities/react-surface.tsx +8 -14
  74. package/src/capabilities/state.ts +1 -1
  75. package/src/components/NavTree/NavTree.stories.tsx +43 -36
  76. package/src/components/NavTree/NavTree.tsx +5 -4
  77. package/src/components/NavTreeItem/NavTreeItemAction.tsx +2 -2
  78. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +7 -4
  79. package/src/components/Sidebar/L0Menu.tsx +14 -7
  80. package/src/components/Sidebar/L1Panel.tsx +5 -5
  81. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +2 -2
  82. package/src/containers/CommandsTrigger/CommandsTrigger.tsx +1 -1
  83. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +17 -3
  84. package/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.tsx +1 -1
  85. package/src/hooks/useNavTreeModel.ts +8 -4
  86. package/src/meta.ts +3 -23
  87. package/src/testing/generator.ts +8 -6
  88. package/src/translations.ts +1 -1
  89. package/src/types/NavTreeCapabilities.ts +1 -1
  90. package/src/types/NavTreeEvents.ts +1 -1
  91. package/src/types/schema.ts +2 -2
  92. package/dist/lib/neutral/CommandsDialogContent-UBGIQ5FI.mjs.map +0 -7
  93. package/dist/lib/neutral/chunk-5OPDKH6B.mjs.map +0 -7
  94. package/dist/lib/neutral/chunk-MDGKDQMZ.mjs +0 -37
  95. package/dist/lib/neutral/chunk-MDGKDQMZ.mjs.map +0 -7
  96. package/dist/lib/neutral/react-surface-Q5XMAG7Z.mjs.map +0 -7
  97. package/dist/lib/neutral/state-VVJEWUHM.mjs.map +0 -7
  98. /package/dist/lib/neutral/{NavTreeContainer-6AR4BFDD.mjs.map → NavTreeContainer-ICDKC2GI.mjs.map} +0 -0
  99. /package/dist/lib/neutral/{chunk-T7QS2EFI.mjs.map → chunk-EAN4K6OL.mjs.map} +0 -0
  100. /package/dist/lib/neutral/{expose-26FV4W5D.mjs.map → expose-HIBA2NQG.mjs.map} +0 -0
package/dx.config.ts ADDED
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Config2 } from '@dxos/app-framework/config';
6
+ import { trim } from '@dxos/util';
7
+
8
+ export default Config2.make({
9
+ plugin: {
10
+ key: 'org.dxos.plugin.navtree',
11
+ name: 'Navtree',
12
+ author: 'DXOS',
13
+ description: trim`
14
+ NavTreePlugin renders the application graph as a collapsible hierarchical tree in the Composer sidebar.
15
+ It manages workspace tabs at the root level and provides expand/collapse navigation for spaces, folders,
16
+ and objects, with open/current state persisted to localStorage across sessions.
17
+
18
+ The plugin wires drag-and-drop rearrangement and cross-parent transfer of tree items via Pragmatic DnD,
19
+ and registers graph-action keyboard shortcuts (including the ⌘K / Ctrl+K command palette) through a
20
+ global Keyboard singleton that tracks context as the user navigates.
21
+
22
+ An operation handler for LayoutOperation.Expose expands ancestor paths on demand so that any
23
+ programmatically activated item is scrolled into view in the tree.
24
+
25
+ A commands dialog surfaces all discoverable graph actions as a searchable palette, making it the primary
26
+ keyboard-first entry point for power users.
27
+ `,
28
+ spec: 'PLUGIN.mdl',
29
+ tags: ['system'],
30
+ },
31
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-navtree",
3
- "version": "0.9.0",
3
+ "version": "0.9.1-staging.ee54ba693a",
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",
@@ -91,60 +91,60 @@
91
91
  "types": "dist/types/src/index.d.ts",
92
92
  "files": [
93
93
  "dist",
94
+ "dx.config.ts",
94
95
  "src",
95
96
  "PLUGIN.mdl"
96
97
  ],
97
98
  "dependencies": {
98
99
  "@atlaskit/pragmatic-drag-and-drop": "1.7.7",
99
100
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
100
- "@dxos/app-graph": "0.9.0",
101
- "@dxos/app-framework": "0.9.0",
102
- "@dxos/async": "0.9.0",
103
- "@dxos/app-toolkit": "0.9.0",
104
- "@dxos/debug": "0.9.0",
105
- "@dxos/compute": "0.9.0",
106
- "@dxos/echo": "0.9.0",
107
- "@dxos/keyboard": "0.9.0",
108
- "@dxos/keys": "0.9.0",
109
- "@dxos/lit-ui": "0.9.0",
110
- "@dxos/log": "0.9.0",
111
- "@dxos/plugin-deck": "0.9.0",
112
- "@dxos/plugin-graph": "0.9.0",
113
- "@dxos/random": "0.9.0",
114
- "@dxos/react-ui-attention": "0.9.0",
115
- "@dxos/react-ui-list": "0.9.0",
116
- "@dxos/react-ui-menu": "0.9.0",
117
- "@dxos/react-ui-search": "0.9.0",
118
- "@dxos/react-ui-tabs": "0.9.0",
119
- "@dxos/react-ui-stack": "0.9.0",
120
- "@dxos/util": "0.9.0"
101
+ "@dxos/app-framework": "0.9.1-staging.ee54ba693a",
102
+ "@dxos/app-graph": "0.9.1-staging.ee54ba693a",
103
+ "@dxos/compute": "0.9.1-staging.ee54ba693a",
104
+ "@dxos/debug": "0.9.1-staging.ee54ba693a",
105
+ "@dxos/app-toolkit": "0.9.1-staging.ee54ba693a",
106
+ "@dxos/async": "0.9.1-staging.ee54ba693a",
107
+ "@dxos/echo": "0.9.1-staging.ee54ba693a",
108
+ "@dxos/keyboard": "0.9.1-staging.ee54ba693a",
109
+ "@dxos/keys": "0.9.1-staging.ee54ba693a",
110
+ "@dxos/lit-ui": "0.9.1-staging.ee54ba693a",
111
+ "@dxos/log": "0.9.1-staging.ee54ba693a",
112
+ "@dxos/plugin-deck": "0.9.1-staging.ee54ba693a",
113
+ "@dxos/plugin-graph": "0.9.1-staging.ee54ba693a",
114
+ "@dxos/random": "0.9.1-staging.ee54ba693a",
115
+ "@dxos/react-ui-list": "0.9.1-staging.ee54ba693a",
116
+ "@dxos/react-ui-attention": "0.9.1-staging.ee54ba693a",
117
+ "@dxos/react-ui-menu": "0.9.1-staging.ee54ba693a",
118
+ "@dxos/react-ui-search": "0.9.1-staging.ee54ba693a",
119
+ "@dxos/react-ui-tabs": "0.9.1-staging.ee54ba693a",
120
+ "@dxos/util": "0.9.1-staging.ee54ba693a"
121
121
  },
122
122
  "devDependencies": {
123
123
  "@effect-atom/atom-react": "^0.5.0",
124
- "@effect/platform": "0.96.1",
125
- "@types/react": "~19.2.7",
124
+ "@effect/platform": "0.96.2",
125
+ "@types/react": "~19.2.17",
126
126
  "@types/react-dom": "~19.2.3",
127
- "effect": "3.21.3",
128
- "react": "~19.2.3",
129
- "react-dom": "~19.2.3",
127
+ "effect": "3.21.4",
128
+ "react": "~19.2.7",
129
+ "react-dom": "~19.2.7",
130
130
  "vite": "^8.0.16",
131
- "@dxos/plugin-attention": "0.9.0",
132
- "@dxos/plugin-client": "0.9.0",
133
- "@dxos/plugin-space": "0.9.0",
134
- "@dxos/plugin-theme": "0.9.0",
135
- "@dxos/react-ui": "0.9.0",
136
- "@dxos/plugin-testing": "0.9.0",
137
- "@dxos/ui-theme": "0.9.0",
138
- "@dxos/storybook-utils": "0.9.0"
131
+ "@dxos/plugin-attention": "0.9.1-staging.ee54ba693a",
132
+ "@dxos/plugin-client": "0.9.1-staging.ee54ba693a",
133
+ "@dxos/plugin-space": "0.9.1-staging.ee54ba693a",
134
+ "@dxos/storybook-utils": "0.9.1-staging.ee54ba693a",
135
+ "@dxos/react-ui": "0.9.1-staging.ee54ba693a",
136
+ "@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
137
+ "@dxos/plugin-theme": "0.9.1-staging.ee54ba693a",
138
+ "@dxos/plugin-testing": "0.9.1-staging.ee54ba693a"
139
139
  },
140
140
  "peerDependencies": {
141
141
  "@effect-atom/atom-react": "^0.5.0",
142
- "@effect/platform": "0.96.1",
143
- "effect": "3.21.3",
144
- "react": "~19.2.3",
145
- "react-dom": "~19.2.3",
146
- "@dxos/react-ui": "0.9.0",
147
- "@dxos/ui-theme": "0.9.0"
142
+ "@effect/platform": "0.96.2",
143
+ "effect": "3.21.4",
144
+ "react": "~19.2.7",
145
+ "react-dom": "~19.2.7",
146
+ "@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
147
+ "@dxos/react-ui": "0.9.1-staging.ee54ba693a"
148
148
  },
149
149
  "publishConfig": {
150
150
  "access": "public"
@@ -11,7 +11,7 @@ import { NavTreePlugin } from '#plugin';
11
11
 
12
12
  import { meta } from './meta';
13
13
 
14
- const moduleId = (name: string) => `${meta.id}.module.${name}`;
14
+ const moduleId = (name: string) => `${meta.profile.key}.module.${name}`;
15
15
 
16
16
  describe('NavTreePlugin', () => {
17
17
  test('modules activate on the expected events', async ({ expect }) => {
@@ -56,7 +56,7 @@ export const NavTreePlugin = Plugin.define(meta).pipe(
56
56
  activate: Keyboard,
57
57
  }),
58
58
  AppPlugin.addPluginAssetModule({
59
- asset: { pluginId: meta.id, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
59
+ asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
60
60
  }),
61
61
  Plugin.make,
62
62
  );
@@ -26,7 +26,7 @@ export default Capability.makeModule(
26
26
  blockAlign: 'start',
27
27
  }),
28
28
  properties: {
29
- label: ['open-commands.label', { ns: meta.id }],
29
+ label: ['open-commands.label', { ns: meta.profile.key }],
30
30
  icon: 'ph--magnifying-glass--regular',
31
31
  keyBinding: {
32
32
  macos: 'meta+k',
@@ -46,11 +46,14 @@ export default Capability.makeModule(
46
46
  }
47
47
  };
48
48
 
49
- const eventHandler = debounce(() => {
49
+ const syncBindings = () => {
50
50
  Graph.traverse(graph, { relation: ['child', 'action'], visitor });
51
- }, 500);
51
+ };
52
+
53
+ const eventHandler = debounce(syncBindings, 500);
52
54
 
53
55
  const unsubscribe = graph.onNodeChanged.on(eventHandler);
56
+ syncBindings();
54
57
 
55
58
  // TODO(burdon): Create context and plugin.
56
59
  Keyboard.singleton.initialize();
@@ -9,6 +9,7 @@ import { Capabilities, Capability } from '@dxos/app-framework';
9
9
  import { Surface } from '@dxos/app-framework/ui';
10
10
  import { AppSurface } from '@dxos/app-toolkit/ui';
11
11
  import { Node } from '@dxos/plugin-graph';
12
+ import { Position } from '@dxos/util';
12
13
 
13
14
  import { CommandsDialogContent, CommandsTrigger, NavTreeContainer, NavTreeDocumentTitle } from '#containers';
14
15
  import { COMMANDS_DIALOG } from '#meta';
@@ -23,29 +24,22 @@ export default Capability.makeModule(() =>
23
24
  }),
24
25
  Surface.create({
25
26
  id: 'navigation',
26
- role: 'navigation',
27
- filter: (data): data is { popoverAnchorId?: string; current: string } => typeof data.current === 'string',
28
- component: ({ data, ref }) => {
29
- return (
30
- <NavTreeContainer
31
- tab={data.current}
32
- popoverAnchorId={data.popoverAnchorId as string | undefined}
33
- ref={ref}
34
- />
35
- );
36
- },
27
+ filter: Surface.makeFilter(AppSurface.Navigation),
28
+ component: ({ data, ref }) => (
29
+ <NavTreeContainer tab={data.current} popoverAnchorId={data.popoverAnchorId} ref={ref} />
30
+ ),
37
31
  }),
38
32
  Surface.create({
39
33
  id: 'documentTitle',
40
- role: 'document-title',
34
+ filter: Surface.makeFilter(AppSurface.DocumentTitle),
41
35
  component: ({ data }) => (
42
36
  <NavTreeDocumentTitle node={Node.isGraphNode(data.subject) ? data.subject : undefined} />
43
37
  ),
44
38
  }),
45
39
  Surface.create({
46
40
  id: 'searchInput',
47
- role: 'search-input',
48
- position: 'last',
41
+ filter: Surface.makeFilter(AppSurface.SearchInput),
42
+ position: Position.last,
49
43
  component: () => <CommandsTrigger />,
50
44
  }),
51
45
  ]),
@@ -13,7 +13,7 @@ import { Path } from '@dxos/react-ui-list';
13
13
  import { meta } from '#meta';
14
14
  import { NavTreeCapabilities } from '#types';
15
15
 
16
- const KEY = `${meta.id}.state.v1`;
16
+ const KEY = `${meta.profile.key}.state.v1`;
17
17
 
18
18
  /** Default item state for new entries. */
19
19
  const defaultItemState: NavTreeCapabilities.NavTreeItemState = { open: false, current: false };
@@ -5,7 +5,7 @@
5
5
  import { Atom, type Registry } from '@effect-atom/atom-react';
6
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
7
7
  import * as Effect from 'effect/Effect';
8
- import React, { type KeyboardEvent, useCallback, useRef } from 'react';
8
+ import React, { useEffect, useRef } from 'react';
9
9
  import { expect, userEvent, within } from 'storybook/test';
10
10
 
11
11
  import { Capabilities, Capability } from '@dxos/app-framework';
@@ -15,9 +15,8 @@ import { AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
15
15
  import { Operation, OperationHandlerSet } from '@dxos/compute';
16
16
  import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
17
17
  import { random } from '@dxos/random';
18
- import { IconButton, Input, Main, Toolbar } from '@dxos/react-ui';
18
+ import { Focus, IconButton, Input, Main, Panel, Toolbar } from '@dxos/react-ui';
19
19
  import { useAttention, useAttentionAttributes } from '@dxos/react-ui-attention';
20
- import { Stack, StackItem } from '@dxos/react-ui-stack';
21
20
  import { withLayout } from '@dxos/react-ui/testing';
22
21
  import { mx } from '@dxos/ui-theme';
23
22
 
@@ -35,9 +34,8 @@ const container = 'flex flex-col grow gap-2 p-4 rounded-md';
35
34
 
36
35
  const StoryPlankHeading = ({ attendableId }: { attendableId: string }) => {
37
36
  const { hasAttention } = useAttention(attendableId);
38
- console.log('hasAttention', hasAttention);
39
37
  return (
40
- <div className='flex p-1 items-center border-b border-separator'>
38
+ <Panel.Toolbar classNames='border-b border-separator'>
41
39
  <IconButton
42
40
  density='lg'
43
41
  icon='ph--circle--regular'
@@ -46,8 +44,7 @@ const StoryPlankHeading = ({ attendableId }: { attendableId: string }) => {
46
44
  variant={hasAttention ? 'primary' : 'ghost'}
47
45
  classNames='w-(--dx-rail-action) h-(--dx-rail-action)'
48
46
  />
49
- <StackItem.ResizeHandle />
50
- </div>
47
+ </Panel.Toolbar>
51
48
  );
52
49
  };
53
50
 
@@ -57,40 +54,50 @@ const StoryPlank = ({ attendableId }: { attendableId: string }) => {
57
54
 
58
55
  // NOTE(thure): This is the same workaround as in Plank, but that component is out of scope for this story.
59
56
  // TODO(thure): Tabster’s focus group should handle moving focus to Main, but something is blocking it.
60
- const handleKeyDown = useCallback((event: KeyboardEvent) => {
61
- if (event.target === event.currentTarget && event.key === 'Escape') {
62
- rootElement.current?.closest('main')?.focus();
57
+ // Attached imperatively because `Focus.Item`/`Panel.Root` expose a narrow (slottable) prop surface.
58
+ useEffect(() => {
59
+ const element = rootElement.current;
60
+ if (!element) {
61
+ return;
63
62
  }
63
+
64
+ const handleKeyDown = (event: KeyboardEvent) => {
65
+ if (event.target === element && event.key === 'Escape') {
66
+ element.closest('main')?.focus();
67
+ }
68
+ };
69
+
70
+ element.addEventListener('keydown', handleKeyDown);
71
+ return () => element.removeEventListener('keydown', handleKeyDown);
64
72
  }, []);
65
73
 
66
74
  return (
67
- <StackItem.Root
68
- item={{ id: attendableId }}
69
- {...attentionAttrs}
70
- classNames='bg-base-surface border-e border-separator'
71
- size={30}
72
- onKeyDown={handleKeyDown}
73
- ref={rootElement}
74
- >
75
- <StoryPlankHeading attendableId={attendableId} />
76
- <StackItem.Content toolbar>
77
- <Toolbar.Root classNames='border-b border-subdued-separator'>
78
- <Toolbar.Button>Test</Toolbar.Button>
79
- </Toolbar.Root>
80
-
81
- <div className={mx(container, 'm-2 bg-current-surface')}>
82
- <Input.Root>
83
- <Input.Label>Level 1 (group)</Input.Label>
84
- </Input.Root>
85
- <div className={mx(container, 'bg-base-surface')}>
75
+ <Focus.Item asChild ref={rootElement}>
76
+ <Panel.Root
77
+ {...attentionAttrs}
78
+ role='article'
79
+ classNames='is-[30rem] shrink-0 bs-full bg-base-surface border-e border-separator'
80
+ >
81
+ <StoryPlankHeading attendableId={attendableId} />
82
+ <Panel.Content classNames='grid'>
83
+ <Toolbar.Root classNames='border-b border-subdued-separator'>
84
+ <Toolbar.Button>Test</Toolbar.Button>
85
+ </Toolbar.Root>
86
+
87
+ <div className={mx(container, 'm-2 bg-current-surface')}>
86
88
  <Input.Root>
87
- <Input.Label>Level 2 (base)</Input.Label>
88
- <Input.TextArea placeholder='Enter text' />
89
+ <Input.Label>Level 1 (group)</Input.Label>
89
90
  </Input.Root>
91
+ <div className={mx(container, 'bg-base-surface')}>
92
+ <Input.Root>
93
+ <Input.Label>Level 2 (base)</Input.Label>
94
+ <Input.TextArea placeholder='Enter text' />
95
+ </Input.Root>
96
+ </div>
90
97
  </div>
91
- </div>
92
- </StackItem.Content>
93
- </StackItem.Root>
98
+ </Panel.Content>
99
+ </Panel.Root>
100
+ </Focus.Item>
94
101
  );
95
102
  };
96
103
 
@@ -103,10 +110,10 @@ const DefaultStory = () => {
103
110
  <NavTreeContainer tab={state.tab} />
104
111
  </Main.NavigationSidebar>
105
112
  <Main.Content bounce handlesFocus>
106
- <Stack orientation='horizontal' size='contain'>
113
+ <div role='none' className='flex grow overflow-x-auto'>
107
114
  <StoryPlank attendableId='space-0:object-0' />
108
115
  <StoryPlank attendableId='space-0:object-1' />
109
- </Stack>
116
+ </div>
110
117
  </Main.Content>
111
118
  </Main.Root>
112
119
  );
@@ -6,8 +6,9 @@ import React, { forwardRef, useMemo } from 'react';
6
6
 
7
7
  import { useAppGraph } from '@dxos/app-toolkit/ui';
8
8
  import { Node, useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
9
+ import { type MenuItem } from '@dxos/react-ui-menu';
9
10
  import { Tabs } from '@dxos/react-ui-tabs';
10
- import { byPosition } from '@dxos/util';
11
+ import { Position } from '@dxos/util';
11
12
 
12
13
  import { useLoadDescendents } from '#hooks';
13
14
 
@@ -33,7 +34,7 @@ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, tab
33
34
  // not use `react-ui-tabs` at all.
34
35
  <Tabs.Root value={tab} orientation='vertical' classNames='relative' ref={forwardedRef}>
35
36
  <L0Menu
36
- menuActions={topLevelActions}
37
+ menuActions={topLevelActions as MenuItem[]}
37
38
  topLevelItems={l0Items}
38
39
  pinnedItems={pinnedItems}
39
40
  userAccountItem={userAccountItem}
@@ -75,11 +76,11 @@ const useTopLevelNavItems = (root?: Node.Node) => {
75
76
 
76
77
  const topLevelActions = rootActions
77
78
  .filter((action) => action.properties.disposition === 'menu')
78
- .toSorted((a, b) => byPosition(a.properties, b.properties));
79
+ .toSorted((a, b) => Position.compare(a.properties, b.properties));
79
80
  const pinnedItems = [
80
81
  ...outboundPinnedItems,
81
82
  ...rootActions.filter((action) => action.properties.disposition === 'pin-end'),
82
- ].toSorted((a, b) => byPosition(a.properties, b.properties));
83
+ ].toSorted((a, b) => Position.compare(a.properties, b.properties));
83
84
 
84
85
  return {
85
86
  pinnedItems,
@@ -36,7 +36,7 @@ export type NavTreeItemActionMenuProps = ActionProperties & {
36
36
 
37
37
  export const NavTreeItemActionDropdownMenu = composable<HTMLButtonElement, NavTreeItemActionMenuProps>(
38
38
  ({ parent, path, label, icon, testId, menuActions, caller, ...props }, forwardedRef) => {
39
- const { t } = useTranslation(meta.id);
39
+ const { t } = useTranslation(meta.profile.key);
40
40
  const density = useDensityContext();
41
41
  const runAction = useActionRunner();
42
42
  const handleAction = useCallback(
@@ -118,7 +118,7 @@ export const NavTreeItemAction = ({
118
118
  path,
119
119
  ...props
120
120
  }: NavTreeItemActionMenuProps) => {
121
- const { t } = useTranslation(meta.id);
121
+ const { t } = useTranslation(meta.profile.key);
122
122
 
123
123
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
124
124
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
@@ -4,7 +4,8 @@
4
4
 
5
5
  import React, { Fragment, memo, useMemo } from 'react';
6
6
 
7
- import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
7
+ import { Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
+ import { Treegrid } from '@dxos/react-ui-list';
8
9
 
9
10
  import { getListActions, useActions } from '#hooks';
10
11
  import { meta } from '#meta';
@@ -15,17 +16,19 @@ import { type NavTreeItemColumnsProps } from '../types';
15
16
  import { NavTreeItemActionDropdownMenu, NavTreeItemMonolithicAction } from './NavTreeItemAction';
16
17
 
17
18
  export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
18
- const { t } = useTranslation(meta.id);
19
+ const { t } = useTranslation(meta.profile.key);
19
20
  const { renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
20
21
 
21
22
  const level = path.length - 2;
22
23
  const flattenedActions = useActions(item);
23
24
  const allActions = useMemo(() => getListActions(flattenedActions), [flattenedActions]);
24
25
 
25
- const ActionRoot = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}` ? Popover.Anchor : Fragment;
26
+ const anchored = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}`;
27
+ const ActionRoot = anchored ? Popover.Anchor : Fragment;
26
28
 
27
29
  return (
28
- <div className='contents dx-app-no-drag'>
30
+ // `data-popover-anchor` lets the enclosing row highlight itself while a popover (e.g. rename) is open on it.
31
+ <div className='contents dx-app-no-drag' {...(anchored && { 'data-popover-anchor': '' })}>
29
32
  <ActionRoot>
30
33
  {allActions.length === 1 ? (
31
34
  <Treegrid.Cell classNames='contents'>
@@ -30,7 +30,6 @@ import { useActionRunner } from '@dxos/plugin-graph';
30
30
  import {
31
31
  Icon,
32
32
  IconButton,
33
- ListItem,
34
33
  ScrollArea,
35
34
  type ThemedClassName,
36
35
  Tooltip,
@@ -38,8 +37,8 @@ import {
38
37
  useMediaQuery,
39
38
  useTranslation,
40
39
  } from '@dxos/react-ui';
40
+ import { DropIndicator } from '@dxos/react-ui-list';
41
41
  import { Menu, type MenuItem } from '@dxos/react-ui-menu';
42
- import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
43
42
  import { Tabs } from '@dxos/react-ui-tabs';
44
43
  import { mx } from '@dxos/ui-theme';
45
44
  import { arrayMove } from '@dxos/util';
@@ -60,6 +59,14 @@ type L0ItemData = {
60
59
  type: 'l0Item';
61
60
  };
62
61
 
62
+ // Local rearrange-handler callback shape; drag-and-drop is wired directly via pragmatic-dnd in `L0Item`,
63
+ // so only the callback signature is needed here.
64
+ type StackItemRearrangeHandler<Data extends { id: string } = { id: string }> = (
65
+ source: Data,
66
+ target: Data,
67
+ closestEdge: Edge | null,
68
+ ) => void;
69
+
63
70
  type L0ItemRootProps = {
64
71
  item: Node.Node;
65
72
  parent?: Node.Node;
@@ -106,7 +113,7 @@ const l0Breakpoints: Record<string, string> = {
106
113
  const L0ItemRoot = memo(
107
114
  forwardRef<HTMLButtonElement, PropsWithChildren<L0ItemRootProps>>(
108
115
  ({ item, parent, path, onMouseEnter, children }, forwardedRef) => {
109
- const { t } = useTranslation(meta.id);
116
+ const { t } = useTranslation(meta.profile.key);
110
117
  const { model } = useNavTreeContext();
111
118
  const itemPath = useMemo(() => [...path, item.id], [item.id, path]);
112
119
  const { id, testId } = useAtomValue(model.itemProps(itemPath));
@@ -151,7 +158,7 @@ export const L0ItemActiveTabIndicator = ({ classNames }: ThemedClassName<{}>) =>
151
158
 
152
159
  // TODO(burdon): Factor out pinned (non-draggable) items.
153
160
  const L0Item = memo(({ item, parent, path, pinned, onRearrange, onItemHover }: L0ItemProps) => {
154
- const { t } = useTranslation(meta.id);
161
+ const { t } = useTranslation(meta.profile.key);
155
162
  const itemElement = useRef<HTMLButtonElement | null>(null);
156
163
  const [closestEdge, setEdge] = useState<Edge | null>(null);
157
164
  const localizedString = toLocalizedString(item.properties.label, t);
@@ -232,13 +239,13 @@ const L0Item = memo(({ item, parent, path, pinned, onRearrange, onItemHover }: L
232
239
  <span id={`${item.id}__label`} className='sr-only'>
233
240
  {localizedString}
234
241
  </span>
235
- {closestEdge && <ListItem.DropIndicator edge={closestEdge} />}
242
+ {closestEdge && <DropIndicator edge={closestEdge} />}
236
243
  </L0ItemRoot>
237
244
  );
238
245
  });
239
246
 
240
247
  const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
241
- const { t } = useTranslation(meta.id);
248
+ const { t } = useTranslation(meta.profile.key);
242
249
 
243
250
  // Actions.
244
251
  if (item.properties.icon) {
@@ -280,7 +287,7 @@ export const L0Menu = ({
280
287
  path,
281
288
  onItemHover,
282
289
  }: L0MenuProps) => {
283
- const { t } = useTranslation(meta.id);
290
+ const { t } = useTranslation(meta.profile.key);
284
291
  const runAction = useActionRunner();
285
292
  const handleAction = useCallback(
286
293
  (action: Node.Action, params: Node.InvokeProps) => {
@@ -6,7 +6,7 @@ import * as Option from 'effect/Option';
6
6
  import React, { memo, useCallback, useMemo } from 'react';
7
7
 
8
8
  import { Node } from '@dxos/app-graph';
9
- import { isPinnedWorkspace } from '@dxos/app-toolkit';
9
+ import { Paths } from '@dxos/app-toolkit';
10
10
  import { useAppGraph } from '@dxos/app-toolkit/ui';
11
11
  import { Graph, useActionRunner, useEdges } from '@dxos/plugin-graph';
12
12
  import { DensityProvider, IconButton, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
@@ -34,7 +34,7 @@ export type L1PanelProps = {
34
34
  * Space or settings panel.
35
35
  */
36
36
  const L1Panel$ = ({ open, path, item, isCurrent, onBack }: L1PanelProps) => {
37
- const { t } = useTranslation(meta.id);
37
+ const { t } = useTranslation(meta.profile.key);
38
38
  const title = toLocalizedString(item.properties.label, t);
39
39
  const isActivated = useIsActivatedWorkspace(item);
40
40
  const shouldRenderContent = isCurrent || isActivated;
@@ -114,10 +114,10 @@ const L1PanelContent = ({ path, item, onBack }: Pick<L1PanelProps, 'open' | 'pat
114
114
  * Header row.
115
115
  */
116
116
  const L1PanelHeader = ({ item, path, onBack }: Pick<L1PanelProps, 'item' | 'path' | 'onBack'>) => {
117
- const { t } = useTranslation(meta.id);
117
+ const { t } = useTranslation(meta.profile.key);
118
118
  const { renderItemEnd: ItemEnd } = useNavTreeContext();
119
119
  const title = toLocalizedString(item.properties.label, t);
120
- const backCapableWorkspace = isPinnedWorkspace(item.id);
120
+ const backCapableWorkspace = Paths.isPinnedWorkspace(item.id);
121
121
 
122
122
  const { menuActions, onAction } = useL1MenuActions({ item, path });
123
123
  useLoadDescendents(item);
@@ -169,7 +169,7 @@ const MenuActions = ({
169
169
  }: {
170
170
  item: Node.Node;
171
171
  } & Pick<L1MenuActions, 'menuActions' | 'onAction'>) => {
172
- const { t } = useTranslation(meta.id);
172
+ const { t } = useTranslation(meta.profile.key);
173
173
 
174
174
  if (menuActions.length === 0) {
175
175
  return null;
@@ -24,7 +24,7 @@ export type CommandsDialogContentProps = {
24
24
  // TODO(wittjosiah): This probably deserves its own plugin but for now it lives here w/ other navigation UI.
25
25
  export const CommandsDialogContent = forwardRef<HTMLDivElement, CommandsDialogContentProps>(
26
26
  ({ selected: initial }, forwardedRef) => {
27
- const { t } = useTranslation(meta.id);
27
+ const { t } = useTranslation(meta.profile.key);
28
28
  const { invokePromise } = useOperationInvoker();
29
29
  const runAction = useActionRunner();
30
30
  const { graph } = useAppGraph();
@@ -70,7 +70,7 @@ export const CommandsDialogContent = forwardRef<HTMLDivElement, CommandsDialogCo
70
70
 
71
71
  return (
72
72
  <Dialog.Content ref={forwardedRef}>
73
- <Dialog.Title srOnly>{t('commands-dialog.title', { ns: meta.id })}</Dialog.Title>
73
+ <Dialog.Title srOnly>{t('commands-dialog.title', { ns: meta.profile.key })}</Dialog.Title>
74
74
  <SearchList.Root onSearch={handleSearch}>
75
75
  <SearchList.Input placeholder={t('command-list-input.placeholder')} />
76
76
  <SearchList.Viewport>
@@ -13,7 +13,7 @@ import { COMMANDS_DIALOG, meta } from '#meta';
13
13
  // TODO(thure): Refactor to be handled by a more appropriate plugin.
14
14
  export const CommandsTrigger = () => {
15
15
  const { invokePromise } = useOperationInvoker();
16
- const { t } = useTranslation(meta.id);
16
+ const { t } = useTranslation(meta.profile.key);
17
17
  return (
18
18
  <Button
19
19
  classNames='m-1 px-1 lg:px-2'