@dxos/plugin-navtree 0.8.4-main.7ace549 → 0.8.4-main.937b3ca

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 (166) hide show
  1. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs +43 -0
  2. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-6YPQ625I.mjs +1293 -0
  4. package/dist/lib/browser/chunk-6YPQ625I.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-AGQDZBPL.mjs +23 -0
  6. package/dist/lib/browser/chunk-AGQDZBPL.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-E6MZBCU5.mjs → chunk-YKTYPINZ.mjs} +1 -1
  8. package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +82 -91
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/{keyboard-YJEK2HTH.mjs → keyboard-32EH3N4K.mjs} +17 -14
  12. package/dist/lib/browser/keyboard-32EH3N4K.mjs.map +7 -0
  13. package/dist/lib/browser/meta.json +1 -1
  14. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs +52 -0
  15. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs.map +7 -0
  16. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs +58 -0
  17. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs.map +7 -0
  18. package/dist/lib/browser/state-EVYQOUI3.mjs +120 -0
  19. package/dist/lib/browser/state-EVYQOUI3.mjs.map +7 -0
  20. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  21. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  23. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  25. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  26. package/dist/types/src/capabilities/index.d.ts +5 -108
  27. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  28. package/dist/types/src/capabilities/keyboard/index.d.ts +3 -0
  29. package/dist/types/src/capabilities/keyboard/index.d.ts.map +1 -0
  30. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +5 -0
  31. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
  33. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
  35. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  37. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  39. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  40. package/dist/types/src/capabilities/state/index.d.ts +3 -0
  41. package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/state/state.d.ts +6 -0
  43. package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
  44. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +3 -2
  45. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
  46. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  47. package/dist/types/src/components/NavTree/NavTree.d.ts +30 -1
  48. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  49. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +4 -1
  50. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/NavTreeContainer.d.ts +8 -1
  52. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  53. package/dist/types/src/components/NavTreeDocumentTitle.d.ts +1 -1
  54. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +6 -6
  56. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  57. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +2 -1
  58. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/Sidebar/L0Menu.d.ts +4 -4
  60. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/L1Panel.d.ts +1 -1
  62. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  63. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +2 -2
  64. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  65. package/dist/types/src/components/index.d.ts.map +1 -1
  66. package/dist/types/src/components/types.d.ts +7 -7
  67. package/dist/types/src/components/types.d.ts.map +1 -1
  68. package/dist/types/src/hooks.d.ts +32 -1
  69. package/dist/types/src/hooks.d.ts.map +1 -1
  70. package/dist/types/src/index.d.ts +1 -2
  71. package/dist/types/src/index.d.ts.map +1 -1
  72. package/dist/types/src/meta.d.ts +2 -2
  73. package/dist/types/src/meta.d.ts.map +1 -1
  74. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  75. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  76. package/dist/types/src/testing/generator.d.ts +5 -6
  77. package/dist/types/src/testing/generator.d.ts.map +1 -1
  78. package/dist/types/src/testing/update-state.d.ts +2 -2
  79. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  80. package/dist/types/src/types/capabilities.d.ts +16 -0
  81. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  82. package/dist/types/src/types/events.d.ts +5 -0
  83. package/dist/types/src/types/events.d.ts.map +1 -0
  84. package/dist/types/src/types/index.d.ts +4 -0
  85. package/dist/types/src/types/index.d.ts.map +1 -0
  86. package/dist/types/src/{types.d.ts → types/schema.d.ts} +6 -6
  87. package/dist/types/src/types/schema.d.ts.map +1 -0
  88. package/dist/types/src/util.d.ts +7 -7
  89. package/dist/types/src/util.d.ts.map +1 -1
  90. package/dist/types/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +53 -52
  92. package/src/NavTreePlugin.tsx +53 -70
  93. package/src/capabilities/app-graph-builder/app-graph-builder.ts +41 -0
  94. package/src/capabilities/app-graph-builder/index.ts +7 -0
  95. package/src/capabilities/index.ts +5 -9
  96. package/src/capabilities/keyboard/index.ts +7 -0
  97. package/src/capabilities/keyboard/keyboard.ts +65 -0
  98. package/src/capabilities/operation-resolver/index.ts +7 -0
  99. package/src/capabilities/operation-resolver/operation-resolver.ts +41 -0
  100. package/src/capabilities/react-surface/index.ts +7 -0
  101. package/src/capabilities/react-surface/react-surface.tsx +54 -0
  102. package/src/capabilities/state/index.ts +7 -0
  103. package/src/capabilities/state/state.ts +121 -0
  104. package/src/components/CommandsDialog/CommandsDialog.tsx +107 -104
  105. package/src/components/CommandsDialog/CommandsTrigger.tsx +4 -14
  106. package/src/components/NavTree/NavTree.stories.tsx +32 -41
  107. package/src/components/NavTree/NavTree.tsx +5 -15
  108. package/src/components/NavTreeContainer.tsx +251 -292
  109. package/src/components/NavTreeDocumentTitle.tsx +3 -2
  110. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +17 -7
  111. package/src/components/NavTreeItem/NavTreeItemAction.tsx +18 -12
  112. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +5 -5
  113. package/src/components/Sidebar/L0Menu.tsx +65 -91
  114. package/src/components/Sidebar/L1Panel.tsx +9 -7
  115. package/src/components/Sidebar/L1Tabs.tsx +6 -31
  116. package/src/components/index.ts +1 -0
  117. package/src/components/types.ts +7 -7
  118. package/src/experimental/Tree.stories.tsx +2 -2
  119. package/src/experimental/Tree.tsx +1 -1
  120. package/src/hooks.ts +54 -2
  121. package/src/index.ts +1 -2
  122. package/src/meta.ts +2 -2
  123. package/src/testing/app-graph-builder.ts +158 -234
  124. package/src/testing/generator.ts +12 -12
  125. package/src/testing/update-state.ts +6 -6
  126. package/src/types/capabilities.ts +21 -0
  127. package/src/types/events.ts +11 -0
  128. package/src/types/index.ts +7 -0
  129. package/src/{types.ts → types/schema.ts} +5 -5
  130. package/src/util.ts +13 -16
  131. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs +0 -46
  132. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs.map +0 -7
  133. package/dist/lib/browser/chunk-5VLWX4PK.mjs +0 -15
  134. package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +0 -7
  135. package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +0 -7
  136. package/dist/lib/browser/chunk-UDAINHB6.mjs +0 -1488
  137. package/dist/lib/browser/chunk-UDAINHB6.mjs.map +0 -7
  138. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs +0 -47
  139. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +0 -7
  140. package/dist/lib/browser/keyboard-YJEK2HTH.mjs.map +0 -7
  141. package/dist/lib/browser/react-surface-V6LS3R4L.mjs +0 -51
  142. package/dist/lib/browser/react-surface-V6LS3R4L.mjs.map +0 -7
  143. package/dist/lib/browser/state-BFD5KJCV.mjs +0 -117
  144. package/dist/lib/browser/state-BFD5KJCV.mjs.map +0 -7
  145. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
  146. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  147. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  148. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  149. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  150. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  151. package/dist/types/src/capabilities/keyboard.d.ts +0 -4
  152. package/dist/types/src/capabilities/keyboard.d.ts.map +0 -1
  153. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  154. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  155. package/dist/types/src/capabilities/state.d.ts +0 -20
  156. package/dist/types/src/capabilities/state.d.ts.map +0 -1
  157. package/dist/types/src/events.d.ts +0 -4
  158. package/dist/types/src/events.d.ts.map +0 -1
  159. package/dist/types/src/types.d.ts.map +0 -1
  160. package/src/capabilities/app-graph-builder.ts +0 -53
  161. package/src/capabilities/capabilities.ts +0 -20
  162. package/src/capabilities/intent-resolver.ts +0 -37
  163. package/src/capabilities/keyboard.ts +0 -57
  164. package/src/capabilities/react-surface.tsx +0 -45
  165. package/src/capabilities/state.ts +0 -104
  166. package/src/events.ts +0 -11
@@ -3,8 +3,12 @@
3
3
  //
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Effect from 'effect/Effect';
6
7
 
7
- import { type NodeArg } from '@dxos/app-graph';
8
+ import { RuntimePlugin } from '@dxos/app-framework';
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { type Node } from '@dxos/app-graph';
11
+ import { corePlugins } from '@dxos/plugin-testing';
8
12
  import { faker } from '@dxos/random';
9
13
  import { withTheme } from '@dxos/react-ui/testing';
10
14
 
@@ -18,7 +22,7 @@ const parent = {
18
22
  label: faker.lorem.words(2),
19
23
  icon: 'ph--circle--regular',
20
24
  },
21
- } satisfies NodeArg<any>;
25
+ } satisfies Node.NodeArg<any>;
22
26
 
23
27
  // TODO(burdon): Factor out across tests.
24
28
  const menuActions = faker.helpers.multiple(
@@ -26,14 +30,15 @@ const menuActions = faker.helpers.multiple(
26
30
  ({
27
31
  id: faker.string.uuid(),
28
32
  type: 'action',
29
- data: () => {
30
- console.log('invoke');
31
- },
33
+ data: () =>
34
+ Effect.sync(() => {
35
+ console.log('invoke');
36
+ }),
32
37
  properties: {
33
38
  label: faker.lorem.words(2),
34
39
  icon: 'ph--circle--regular',
35
40
  },
36
- }) satisfies NodeArg<any>,
41
+ }) satisfies Node.NodeArg<any>,
37
42
  { count: 20 },
38
43
  );
39
44
 
@@ -46,7 +51,12 @@ const meta = {
46
51
  menuActions,
47
52
  label: 'Select action',
48
53
  } satisfies Partial<NavTreeItemActionMenuProps>,
49
- decorators: [withTheme],
54
+ decorators: [
55
+ withTheme,
56
+ withPluginManager({
57
+ plugins: [...corePlugins(), RuntimePlugin()],
58
+ }),
59
+ ],
50
60
  parameters: {
51
61
  layout: 'centered',
52
62
  },
@@ -4,19 +4,20 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { type Action, type Node } from '@dxos/app-graph';
7
+ import { type Node } from '@dxos/app-graph';
8
+ import { useActionRunner } from '@dxos/plugin-graph';
8
9
  import { IconButton, toLocalizedString, useDensityContext, useTranslation } from '@dxos/react-ui';
9
10
  import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
10
- import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
11
+ import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
11
12
 
12
13
  import { meta } from '../../meta';
13
14
  import { type ActionProperties } from '../../types';
14
15
 
15
16
  export type NavTreeItemActionMenuProps = ActionProperties & {
16
- parent: Node;
17
+ parent: Node.Node;
17
18
  caller?: string;
18
19
  monolithic?: boolean;
19
- menuActions?: Action[];
20
+ menuActions?: Node.Action[];
20
21
  };
21
22
 
22
23
  const fallbackIcon = 'ph--placeholder--regular';
@@ -40,8 +41,10 @@ export const NavTreeItemActionDropdownMenu = ({
40
41
  }: NavTreeItemActionMenuProps) => {
41
42
  const { t } = useTranslation(meta.id);
42
43
  const density = useDensityContext();
44
+ const runAction = useActionRunner();
45
+
43
46
  return (
44
- <MenuProvider>
47
+ <MenuProvider onAction={runAction}>
45
48
  <DropdownMenu.Root group={parent} items={menuActions as MenuItem[]} caller={caller}>
46
49
  <DropdownMenu.Trigger asChild>
47
50
  <IconButton
@@ -59,13 +62,16 @@ export const NavTreeItemActionDropdownMenu = ({
59
62
  );
60
63
  };
61
64
 
62
- export const NavTreeItemMonolithicAction = ({
63
- parent,
64
- properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
65
- data: invoke,
66
- baseLabel,
67
- }: Action & { parent: Node; onAction?: (action: Action) => void; baseLabel: string }) => {
65
+ export const NavTreeItemMonolithicAction = (
66
+ props: Node.Action & { parent: Node.Node; onAction?: (action: Node.Action) => void; baseLabel: string },
67
+ ) => {
68
+ const {
69
+ parent,
70
+ properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
71
+ baseLabel,
72
+ } = props;
68
73
  const density = useDensityContext();
74
+ const runAction = useActionRunner();
69
75
  return (
70
76
  <IconButton
71
77
  {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
@@ -86,7 +92,7 @@ export const NavTreeItemMonolithicAction = ({
86
92
  return;
87
93
  }
88
94
 
89
- void invoke?.(caller ? { parent, caller } : { parent });
95
+ void runAction(props, caller ? { parent, caller } : { parent });
90
96
  }}
91
97
  data-testid={testId}
92
98
  />
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { Fragment, memo } from 'react';
6
6
 
7
- import { isAction } from '@dxos/app-graph';
7
+ import { Node } from '@dxos/app-graph';
8
8
  import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
10
  import { useLoadDescendents } from '../../hooks';
@@ -26,11 +26,11 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
26
26
  );
27
27
 
28
28
  const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
29
- .flatMap((action) => (isAction(action) ? [action] : []))
29
+ .flatMap((action) => (Node.isAction(action) ? [action] : []))
30
30
  .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
31
31
 
32
32
  useLoadDescendents(item);
33
- useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
33
+ useLoadDescendents(primaryAction && !Node.isAction(primaryAction) ? (primaryAction as Node.Node) : undefined);
34
34
 
35
35
  const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
36
36
 
@@ -43,8 +43,8 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
43
43
  label={toLocalizedString(primaryAction.properties?.label, t)}
44
44
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
45
45
  parent={item}
46
- monolithic={isAction(primaryAction)}
47
- menuActions={isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction.id]}
46
+ monolithic={Node.isAction(primaryAction)}
47
+ menuActions={Node.isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction?.id ?? '']}
48
48
  menuType={primaryAction.properties?.menuType}
49
49
  caller={NAV_TREE_ITEM}
50
50
  />
@@ -23,8 +23,8 @@ import React, {
23
23
  } from 'react';
24
24
 
25
25
  import { type Node } from '@dxos/app-graph';
26
- import { invariant } from '@dxos/invariant';
27
26
  import { DxAvatar } from '@dxos/lit-ui/react';
27
+ import { useActionRunner } from '@dxos/plugin-graph';
28
28
  import {
29
29
  Icon,
30
30
  ListItem,
@@ -38,10 +38,10 @@ import {
38
38
  import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
39
39
  import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
40
40
  import { Tabs } from '@dxos/react-ui-tabs';
41
- import { mx } from '@dxos/react-ui-theme';
41
+ import { mx } from '@dxos/ui-theme';
42
42
  import { arrayMove } from '@dxos/util';
43
43
 
44
- import { useLoadDescendents } from '../../hooks';
44
+ import { useNavTreeState } from '../../hooks';
45
45
  import { meta } from '../../meta';
46
46
  import { l0ItemType } from '../../util';
47
47
  import { useNavTreeContext } from '../NavTreeContext';
@@ -57,43 +57,41 @@ type L0ItemData = {
57
57
  };
58
58
 
59
59
  type L0ItemRootProps = {
60
- item: Node<any>;
61
- parent?: Node<any>;
60
+ item: Node.Node;
61
+ parent?: Node.Node;
62
62
  path: string[];
63
63
  };
64
64
 
65
65
  type L0ItemProps = L0ItemRootProps & {
66
- item: Node<any>;
67
- parent?: Node<any>;
66
+ item: Node.Node;
67
+ parent?: Node.Node;
68
68
  path: string[];
69
69
  pinned?: boolean;
70
70
  onRearrange?: StackItemRearrangeHandler<L0ItemData>;
71
71
  };
72
72
 
73
73
  const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
74
- const { tab, isCurrent, onSelect, onTabChange } = useNavTreeContext();
74
+ const { tab, onSelect, onTabChange } = useNavTreeContext();
75
+ const { getItem } = useNavTreeState();
75
76
  const [isLg] = useMediaQuery('lg');
77
+ const runAction = useActionRunner();
76
78
 
77
79
  return useCallback(
78
80
  (event: MouseEvent) => {
79
81
  switch (type) {
80
82
  case 'action': {
81
- const {
82
- data: invoke,
83
- properties: { caller },
84
- } = item;
85
-
86
- return invoke?.(caller ? { node: parent, caller } : { node: parent });
83
+ const { properties: { caller } = {} } = item;
84
+ return void runAction(item as Node.Action, caller ? { parent, caller } : { parent });
87
85
  }
88
86
 
89
87
  case 'tab':
90
88
  return onTabChange?.(item);
91
89
 
92
90
  case 'link':
93
- return onSelect?.({ item, path, current: !isCurrent(path, item), option: event.altKey });
91
+ return onSelect?.({ item, path, current: !getItem(path).current, option: event.altKey });
94
92
  }
95
93
  },
96
- [item, parent, type, tab, isCurrent, onSelect, onTabChange, isLg],
94
+ [item, parent, type, tab, getItem, onSelect, onTabChange, isLg, runAction],
97
95
  );
98
96
  };
99
97
 
@@ -120,9 +118,7 @@ const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
120
118
  const rootProps =
121
119
  type === 'tab'
122
120
  ? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
123
- : type !== 'collection'
124
- ? { onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
125
- : { onClick: handleClick, role: 'button' };
121
+ : { onClick: handleClick, 'data-testid': testId, 'data-object-id': id };
126
122
 
127
123
  return (
128
124
  <Tooltip.Trigger asChild delayDuration={0} side='right' content={localizedString}>
@@ -256,68 +252,50 @@ const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
256
252
  };
257
253
 
258
254
  //
259
- // L0Collection
255
+ // L0Menu
260
256
  //
261
257
 
262
- const L0Collection = ({ item, path }: L0ItemProps) => {
263
- const navTreeContext = useNavTreeContext();
264
- useLoadDescendents(item);
265
- const collectionItems = navTreeContext.useItems(item);
266
- const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
267
- const { id, testId } = navTreeContext.getProps?.(item, path) ?? {};
258
+ export type L0MenuProps = {
259
+ menuActions: MenuItem[];
260
+ topLevelItems: Node.Node[];
261
+ pinnedItems: Node.Node[];
262
+ userAccountItem?: Node.Node;
263
+ parent?: Node.Node;
264
+ path: string[];
265
+ };
266
+
267
+ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
268
+ const { t } = useTranslation(meta.id);
269
+ const runAction = useActionRunner();
270
+ const handleAction = useCallback(
271
+ (action: Node.Action, params: Node.InvokeProps) => {
272
+ void runAction(action, params);
273
+ },
274
+ [runAction],
275
+ );
268
276
 
269
277
  const handleRearrange = useCallback<StackItemRearrangeHandler<L0ItemData>>(
270
278
  (source, target, closestEdge) => {
271
- invariant(item.properties.onRearrangeChildren);
272
- const sourceIndex = collectionItems.findIndex((i) => i.id === source.id);
273
- const targetIndex = collectionItems.findIndex((i) => i.id === target.id);
279
+ const sourceItem = topLevelItems.find((i) => i.id === source.id);
280
+ if (!sourceItem?.properties.onRearrange) {
281
+ return;
282
+ }
283
+
284
+ const sourceIndex = topLevelItems.findIndex((i) => i.id === source.id);
285
+ const targetIndex = topLevelItems.findIndex((i) => i.id === target.id);
274
286
  const insertIndex =
275
287
  source.id === target.id
276
288
  ? sourceIndex
277
289
  : targetIndex +
278
290
  (sourceIndex < targetIndex ? (closestEdge === 'top' ? -1 : 0) : closestEdge === 'bottom' ? 1 : 0);
279
- const nextOrder = arrayMove([...collectionItems], sourceIndex, insertIndex);
280
- return item.properties.onRearrangeChildren(nextOrder);
291
+ const nextOrder = arrayMove([...topLevelItems], sourceIndex, insertIndex);
292
+ return sourceItem.properties.onRearrange(nextOrder.map((item) => item.data));
281
293
  },
282
- [collectionItems, item.properties.onRearrangeChildren],
294
+ [topLevelItems],
283
295
  );
284
296
 
285
- return (
286
- <div
287
- role='group'
288
- className='contents group/l0c'
289
- aria-labelledby={`${item.id}__label`}
290
- data-object-id={id}
291
- data-testid={testId}
292
- >
293
- {collectionItems.map((collectionItem) => (
294
- <L0Item
295
- key={collectionItem.id}
296
- item={collectionItem}
297
- parent={item}
298
- path={groupPath}
299
- {...(item.properties.onRearrangeChildren && { onRearrange: handleRearrange })}
300
- />
301
- ))}
302
- </div>
303
- );
304
- };
305
-
306
- //
307
- // L0Menu
308
- //
309
-
310
- export type L0MenuProps = {
311
- menuActions: MenuItem[];
312
- topLevelItems: Node<any>[];
313
- pinnedItems: Node<any>[];
314
- userAccountItem?: Node<any>;
315
- parent?: Node<any>;
316
- path: string[];
317
- };
318
-
319
- export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
320
- const { t } = useTranslation(meta.id);
297
+ // Check if any items have onRearrange to enable drag-and-drop.
298
+ const hasRearrangeableItems = topLevelItems.some((item) => item.properties.onRearrange);
321
299
 
322
300
  return (
323
301
  <Tabs.Tablist
@@ -328,13 +306,17 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
328
306
  ]}
329
307
  >
330
308
  {/* TODO(wittjosiah): Use L0Item trigger. */}
331
- <MenuProvider>
309
+ <MenuProvider onAction={handleAction}>
332
310
  <DropdownMenu.Root group={parent} items={menuActions}>
333
311
  <Tooltip.Trigger content={t('app menu label')} side='right' asChild>
334
312
  <Tabs.TabPrimitive value='options' asChild role='button'>
335
313
  <DropdownMenu.Trigger
336
314
  data-testid='spacePlugin.addSpace'
337
- className={mx(l0ItemRoot, 'grid place-items-center dx-focus-ring-group')}
315
+ className={mx(
316
+ l0ItemRoot,
317
+ 'grid place-items-center dx-focus-ring-group',
318
+ '[body[data-platform="macos"]_&]:mt-[30px]',
319
+ )}
338
320
  >
339
321
  <div
340
322
  role='none'
@@ -358,17 +340,19 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
358
340
  role='none'
359
341
  className={mx([
360
342
  'flex flex-col gap-1 pbs-1',
361
- '[body[data-platform="darwin"]_&]:pbs-[calc(30px+0.25rem)]',
343
+ '[body[data-platform="macos"]_&]:pbs-[30px]',
362
344
  '[body[data-platform="ios"]_&]:pbs-[max(env(safe-area-inset-top),0.25rem)]',
363
345
  ])}
364
346
  >
365
- {topLevelItems.map((item) => {
366
- if (l0ItemType(item) === 'collection') {
367
- return <L0Collection key={item.id} item={item} parent={parent} path={path} />;
368
- } else {
369
- return <L0Item key={item.id} item={item} parent={parent} path={path} />;
370
- }
371
- })}
347
+ {topLevelItems.map((item) => (
348
+ <L0Item
349
+ key={item.id}
350
+ item={item}
351
+ parent={parent}
352
+ path={path}
353
+ {...(hasRearrangeableItems && { onRearrange: handleRearrange })}
354
+ />
355
+ ))}
372
356
  </div>
373
357
  <ScrollArea.Scrollbar orientation='vertical'>
374
358
  <ScrollArea.Thumb />
@@ -378,11 +362,9 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
378
362
 
379
363
  {/* Actions. */}
380
364
  <div role='none' className='grid grid-cols-1 auto-rows-[--rail-action] pbs-2'>
381
- {pinnedItems
382
- .filter((item) => l0ItemType(item) !== 'collection')
383
- .map((item) => (
384
- <L0Item key={item.id} item={item} parent={parent} path={path} pinned />
385
- ))}
365
+ {pinnedItems.map((item) => (
366
+ <L0Item key={item.id} item={item} parent={parent} path={path} pinned />
367
+ ))}
386
368
  </div>
387
369
 
388
370
  {userAccountItem && (
@@ -398,14 +380,6 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
398
380
  </L0ItemRoot>
399
381
  </div>
400
382
  )}
401
- <div
402
- role='none'
403
- className='hidden [body[data-platform="darwin"]_&]:block absolute block-start-0 is-[calc(var(--l0-size)-1px)] bs-[calc(40px+0.25rem)]'
404
- style={{
405
- background:
406
- 'linear-gradient(to bottom, var(--dx-sidebarSurface) 0%, var(--dx-sidebarSurface) 70%, transparent 100%)',
407
- }}
408
- />
409
383
  </Tabs.Tablist>
410
384
  );
411
385
  };
@@ -8,8 +8,9 @@ import { type Node } from '@dxos/app-graph';
8
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
- import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
11
+ import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
12
12
 
13
+ import { useIsAlternateTree } from '../../hooks';
13
14
  import { meta } from '../../meta';
14
15
  import { useNavTreeContext } from '../NavTreeContext';
15
16
  import { NavTreeItemColumns } from '../NavTreeItem';
@@ -17,7 +18,7 @@ import { NavTreeItemColumns } from '../NavTreeItem';
17
18
  export type L1PanelProps = {
18
19
  open?: boolean;
19
20
  path: string[];
20
- item: Node<any>;
21
+ item: Node.Node;
21
22
  currentItemId: string;
22
23
  onBack?: () => void;
23
24
  };
@@ -27,16 +28,16 @@ export type L1PanelProps = {
27
28
  */
28
29
  export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProps) => {
29
30
  const { t } = useTranslation(meta.id);
30
- const { isAlternateTree, ...navTreeContext } = useNavTreeContext();
31
+ const navTreeContext = useNavTreeContext();
31
32
  const title = toLocalizedString(item.properties.label, t);
32
33
 
33
34
  // TODO(wittjosiah): Support multiple alternate trees.
34
35
  const { useItems } = navTreeContext;
35
36
  const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
36
37
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
37
- const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
38
+ const isAlternate = useIsAlternateTree(alternatePath, item);
38
39
  const useAlternateItems = useCallback(
39
- (node?: Node, { disposition }: { disposition?: string } = {}) => {
40
+ (node?: Node.Node, { disposition }: { disposition?: string } = {}) => {
40
41
  // TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
41
42
  return useItems(node, { disposition, sort: node?.id === alternateTree.id });
42
43
  },
@@ -96,13 +97,13 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
96
97
  */
97
98
  const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
98
99
  const { t } = useTranslation(meta.id);
99
- const { isAlternateTree, setAlternateTree, ...navTreeContext } = useNavTreeContext();
100
+ const { setAlternateTree, ...navTreeContext } = useNavTreeContext();
100
101
  const title = toLocalizedString(item.properties.label, t);
101
102
 
102
103
  // TODO(wittjosiah): Support multiple alternate trees.
103
104
  const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
104
105
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
105
- const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
106
+ const isAlternate = useIsAlternateTree(alternatePath, item);
106
107
  const backCapable = item.id.startsWith('!') || isAlternate;
107
108
 
108
109
  const handleOpen = useCallback(() => {
@@ -124,6 +125,7 @@ const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
124
125
  <h2 className='flex-1 truncate min-is-0'>{title}</h2>
125
126
  {(backCapable && (
126
127
  <IconButton
128
+ data-testid='treeView.primaryTreeButton'
127
129
  variant='ghost'
128
130
  icon='ph--arrow-u-down-left--regular'
129
131
  iconOnly
@@ -2,57 +2,32 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useMemo } from 'react';
5
+ import React from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
8
 
9
- import { useLoadDescendents } from '../../hooks';
10
9
  import { l0ItemType } from '../../util';
11
- import { useNavTreeContext } from '../NavTreeContext';
12
10
 
13
11
  import { L1Panel, type L1PanelProps } from './L1Panel';
14
12
 
15
13
  export type L1TabsProps = Pick<L1PanelProps, 'open' | 'currentItemId' | 'onBack'> & {
16
14
  path: string[];
17
- topLevelItems: Node<any>[];
15
+ topLevelItems: Node.Node[];
18
16
  };
19
17
 
20
18
  /**
21
- * Each space is an L1 tab.
19
+ * Each workspace is an L1 tab.
22
20
  */
23
21
  export const L1Tabs = ({ topLevelItems, onBack, ...props }: L1TabsProps) => {
24
22
  return (
25
23
  <>
26
24
  {topLevelItems.map((item) => {
27
25
  const type = l0ItemType(item);
28
- switch (type) {
29
- case 'tab':
30
- return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
31
- case 'collection':
32
- return <L1PanelCollection key={item.id} item={item} {...props} />;
33
- default:
34
- return null;
26
+ if (type === 'tab') {
27
+ return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
35
28
  }
29
+ return null;
36
30
  })}
37
31
  </>
38
32
  );
39
33
  };
40
-
41
- /**
42
- *
43
- */
44
- const L1PanelCollection = ({ item, path, ...props }: L1PanelProps) => {
45
- const { useItems } = useNavTreeContext();
46
- useLoadDescendents(item);
47
- const collectionItems = useItems(item);
48
- const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
49
- return (
50
- <>
51
- {collectionItems
52
- .filter((item) => l0ItemType(item) === 'tab')
53
- .map((item) => (
54
- <L1Panel key={item.id} item={item} path={groupPath} {...props} />
55
- ))}
56
- </>
57
- );
58
- };
@@ -7,4 +7,5 @@ export * from './NavTree';
7
7
  export * from './NavTreeContainer';
8
8
  export * from './NavTreeContext';
9
9
  export * from './NavTreeDocumentTitle';
10
+
10
11
  export type * from './types';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { FC } from 'react';
6
6
 
7
- import type { Node } from '@dxos/app-graph';
7
+ import { type Node } from '@dxos/app-graph';
8
8
  import type { Density } from '@dxos/react-ui';
9
9
  import type { TreeProps } from '@dxos/react-ui-list';
10
10
  import type { MaybePromise } from '@dxos/util';
@@ -15,23 +15,23 @@ import type { L1PanelProps } from './Sidebar';
15
15
 
16
16
  export type NavTreeContextValue = Pick<
17
17
  TreeProps<NavTreeItemGraphNode, { disposition?: string; sort?: boolean }>,
18
- 'useItems' | 'getProps' | 'isCurrent' | 'isOpen' | 'blockInstruction' | 'canDrop' | 'onOpenChange' | 'onSelect'
18
+ 'useItems' | 'getProps' | 'useIsCurrent' | 'useIsOpen' | 'blockInstruction' | 'canDrop' | 'onOpenChange' | 'onSelect'
19
19
  > &
20
20
  Pick<L1PanelProps, 'onBack'> & {
21
21
  tab: string;
22
22
  topbar?: boolean;
23
23
  popoverAnchorId?: string;
24
- renderItemEnd?: FC<{ node: Node; open: boolean }>;
25
- useActions: (node: Node) => FlattenedActions;
26
- loadDescendents?: (node: Node) => MaybePromise<void>;
27
- isAlternateTree?: (path: string[], item: NavTreeItemGraphNode) => boolean;
24
+ renderItemEnd?: FC<{ node: Node.Node; open: boolean }>;
25
+ useActions: (node: Node.Node) => FlattenedActions;
26
+ loadDescendents?: (node: Node.Node) => MaybePromise<void>;
27
+ useIsAlternateTree?: (path: string[], item: NavTreeItemGraphNode) => boolean;
28
28
  setAlternateTree?: (path: string[], open: boolean) => void;
29
29
  onTabChange?: (node: NavTreeItemGraphNode) => void;
30
30
  };
31
31
 
32
32
  export type NavTreeItemColumnsProps = {
33
33
  path: string[];
34
- item: Node;
34
+ item: Node.Node;
35
35
  open: boolean;
36
36
  density?: Density;
37
37
  };
@@ -8,7 +8,7 @@ import React, { type JSX, type PropsWithChildren, useEffect, useState } from 're
8
8
  import { faker } from '@dxos/random';
9
9
  import { Icon, IconButton } from '@dxos/react-ui';
10
10
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
- import { mx } from '@dxos/react-ui-theme';
11
+ import { mx } from '@dxos/ui-theme';
12
12
 
13
13
  import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visitor } from './Tree';
14
14
 
@@ -203,7 +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, withLayout({ container: 'column' })],
206
+ decorators: [withTheme, withLayout({ layout: 'column' })],
207
207
  } satisfies Meta<typeof Tree>;
208
208
 
209
209
  export default meta;
@@ -5,7 +5,7 @@
5
5
  import React, { type HTMLAttributes, type PropsWithChildren } from 'react';
6
6
 
7
7
  import { type ClassNameValue, Icon, type Size } from '@dxos/react-ui';
8
- import { mx } from '@dxos/react-ui-theme';
8
+ import { mx } from '@dxos/ui-theme';
9
9
 
10
10
  export const IconButton = ({
11
11
  iconName,