@dxos/plugin-navtree 0.8.4-main.7ace549 → 0.8.4-main.8360d9e660

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 (232) hide show
  1. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs +113 -0
  2. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs.map +7 -0
  3. package/dist/lib/browser/CommandsTrigger-ABODVVMP.mjs +33 -0
  4. package/dist/lib/browser/CommandsTrigger-ABODVVMP.mjs.map +7 -0
  5. package/dist/lib/browser/NavTreeContainer-IQZCK4Y4.mjs +12 -0
  6. package/dist/lib/browser/NavTreeContainer-IQZCK4Y4.mjs.map +7 -0
  7. package/dist/lib/browser/NavTreeDocumentTitle-L6NLLWLD.mjs +26 -0
  8. package/dist/lib/browser/NavTreeDocumentTitle-L6NLLWLD.mjs.map +7 -0
  9. package/dist/lib/browser/app-graph-builder-Z43OTNI2.mjs +44 -0
  10. package/dist/lib/browser/app-graph-builder-Z43OTNI2.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-E6MZBCU5.mjs → chunk-A4VDCIJC.mjs} +3 -3
  12. package/dist/lib/browser/chunk-A4VDCIJC.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-DINDC6HE.mjs +14 -0
  14. package/dist/lib/browser/chunk-DINDC6HE.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-KF26WA4O.mjs +23 -0
  16. package/dist/lib/browser/chunk-KF26WA4O.mjs.map +7 -0
  17. package/dist/lib/browser/chunk-OGUYILOW.mjs +1462 -0
  18. package/dist/lib/browser/chunk-OGUYILOW.mjs.map +7 -0
  19. package/dist/lib/browser/index.mjs +86 -91
  20. package/dist/lib/browser/index.mjs.map +4 -4
  21. package/dist/lib/browser/{keyboard-YJEK2HTH.mjs → keyboard-HYGFQO7T.mjs} +22 -14
  22. package/dist/lib/browser/keyboard-HYGFQO7T.mjs.map +7 -0
  23. package/dist/lib/browser/meta.json +1 -1
  24. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs +49 -0
  25. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs.map +7 -0
  26. package/dist/lib/browser/react-surface-LFMKIKNY.mjs +59 -0
  27. package/dist/lib/browser/react-surface-LFMKIKNY.mjs.map +7 -0
  28. package/dist/lib/browser/state-CWMNNF7K.mjs +131 -0
  29. package/dist/lib/browser/state-CWMNNF7K.mjs.map +7 -0
  30. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  31. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  32. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  33. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  35. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/index.d.ts +5 -108
  37. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  38. package/dist/types/src/capabilities/keyboard/index.d.ts +3 -0
  39. package/dist/types/src/capabilities/keyboard/index.d.ts.map +1 -0
  40. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +5 -0
  41. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
  43. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
  44. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
  45. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
  46. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  47. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  48. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  49. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  50. package/dist/types/src/capabilities/state/index.d.ts +3 -0
  51. package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
  52. package/dist/types/src/capabilities/state/state.d.ts +6 -0
  53. package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
  54. package/dist/types/src/components/NavTree/NavTree.d.ts +11 -4
  55. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  56. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +2 -2
  57. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  58. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  59. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  60. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  61. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  62. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +10 -8
  63. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  64. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +3 -2
  65. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  66. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  67. package/dist/types/src/components/Sidebar/L0Menu.d.ts +8 -5
  68. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  69. package/dist/types/src/components/Sidebar/L1Panel.d.ts +4 -7
  70. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  71. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +5 -4
  72. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  73. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  74. package/dist/types/src/components/index.d.ts +0 -3
  75. package/dist/types/src/components/index.d.ts.map +1 -1
  76. package/dist/types/src/components/types.d.ts +8 -16
  77. package/dist/types/src/components/types.d.ts.map +1 -1
  78. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts +6 -0
  79. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -0
  80. package/dist/types/src/containers/CommandsDialogContent/index.d.ts +3 -0
  81. package/dist/types/src/containers/CommandsDialogContent/index.d.ts.map +1 -0
  82. package/dist/types/src/containers/CommandsTrigger/CommandsTrigger.d.ts.map +1 -0
  83. package/dist/types/src/containers/CommandsTrigger/index.d.ts +3 -0
  84. package/dist/types/src/containers/CommandsTrigger/index.d.ts.map +1 -0
  85. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts +9 -0
  86. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -0
  87. package/dist/types/src/containers/NavTreeContainer/index.d.ts +3 -0
  88. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -0
  89. package/dist/types/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.d.ts +1 -1
  90. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -0
  91. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts +3 -0
  92. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts.map +1 -0
  93. package/dist/types/src/containers/index.d.ts +7 -0
  94. package/dist/types/src/containers/index.d.ts.map +1 -0
  95. package/dist/types/src/experimental/Tree.stories.d.ts +18 -4
  96. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  97. package/dist/types/src/hooks/index.d.ts +5 -0
  98. package/dist/types/src/hooks/index.d.ts.map +1 -0
  99. package/dist/types/src/hooks/useActions.d.ts +5 -0
  100. package/dist/types/src/hooks/useActions.d.ts.map +1 -0
  101. package/dist/types/src/hooks/useLoadDescendents.d.ts +6 -0
  102. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -0
  103. package/dist/types/src/hooks/useNavTreeModel.d.ts +7 -0
  104. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -0
  105. package/dist/types/src/hooks/useNavTreeState.d.ts +22 -0
  106. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -0
  107. package/dist/types/src/index.d.ts +1 -2
  108. package/dist/types/src/index.d.ts.map +1 -1
  109. package/dist/types/src/meta.d.ts +2 -2
  110. package/dist/types/src/meta.d.ts.map +1 -1
  111. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  112. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  113. package/dist/types/src/testing/generator.d.ts +5 -6
  114. package/dist/types/src/testing/generator.d.ts.map +1 -1
  115. package/dist/types/src/testing/update-state.d.ts +2 -2
  116. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  117. package/dist/types/src/types/capabilities.d.ts +16 -0
  118. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  119. package/dist/types/src/types/events.d.ts +5 -0
  120. package/dist/types/src/types/events.d.ts.map +1 -0
  121. package/dist/types/src/types/index.d.ts +4 -0
  122. package/dist/types/src/types/index.d.ts.map +1 -0
  123. package/dist/types/src/{types.d.ts → types/schema.d.ts} +6 -6
  124. package/dist/types/src/types/schema.d.ts.map +1 -0
  125. package/dist/types/src/util.d.ts +11 -7
  126. package/dist/types/src/util.d.ts.map +1 -1
  127. package/dist/types/tsconfig.tsbuildinfo +1 -1
  128. package/package.json +55 -53
  129. package/src/NavTreePlugin.tsx +54 -73
  130. package/src/capabilities/app-graph-builder/app-graph-builder.ts +42 -0
  131. package/src/capabilities/app-graph-builder/index.ts +7 -0
  132. package/src/capabilities/index.ts +5 -9
  133. package/src/capabilities/keyboard/index.ts +7 -0
  134. package/src/capabilities/keyboard/keyboard.ts +66 -0
  135. package/src/capabilities/operation-resolver/index.ts +7 -0
  136. package/src/capabilities/operation-resolver/operation-resolver.ts +45 -0
  137. package/src/capabilities/react-surface/index.ts +7 -0
  138. package/src/capabilities/react-surface/react-surface.tsx +53 -0
  139. package/src/capabilities/state/index.ts +7 -0
  140. package/src/capabilities/state/state.ts +149 -0
  141. package/src/components/NavTree/NavTree.stories.tsx +44 -51
  142. package/src/components/NavTree/NavTree.tsx +63 -32
  143. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  144. package/src/components/NavTreeContext/index.ts +5 -0
  145. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +18 -8
  146. package/src/components/NavTreeItem/NavTreeItemAction.tsx +55 -32
  147. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +42 -16
  148. package/src/components/Sidebar/L0Menu.tsx +133 -162
  149. package/src/components/Sidebar/L1Panel.tsx +247 -91
  150. package/src/components/Sidebar/L1Tabs.tsx +18 -33
  151. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +2 -3
  152. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +1 -1
  153. package/src/components/index.ts +1 -3
  154. package/src/components/types.ts +14 -19
  155. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +135 -0
  156. package/src/containers/CommandsDialogContent/index.ts +7 -0
  157. package/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.tsx +5 -15
  158. package/src/containers/CommandsTrigger/index.ts +7 -0
  159. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +271 -0
  160. package/src/containers/NavTreeContainer/index.ts +6 -0
  161. package/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.tsx +4 -3
  162. package/src/containers/NavTreeDocumentTitle/index.ts +7 -0
  163. package/src/containers/index.ts +12 -0
  164. package/src/experimental/Tree.stories.tsx +55 -58
  165. package/src/experimental/Tree.tsx +4 -4
  166. package/src/hooks/index.ts +8 -0
  167. package/src/hooks/useActions.ts +34 -0
  168. package/src/hooks/useLoadDescendents.ts +23 -0
  169. package/src/hooks/useNavTreeModel.ts +118 -0
  170. package/src/hooks/useNavTreeState.ts +42 -0
  171. package/src/index.ts +1 -2
  172. package/src/meta.ts +4 -4
  173. package/src/testing/app-graph-builder.ts +158 -234
  174. package/src/testing/generator.ts +13 -13
  175. package/src/testing/update-state.ts +6 -6
  176. package/src/types/capabilities.ts +21 -0
  177. package/src/types/events.ts +12 -0
  178. package/src/types/index.ts +7 -0
  179. package/src/{types.ts → types/schema.ts} +5 -5
  180. package/src/util.ts +27 -16
  181. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs +0 -46
  182. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs.map +0 -7
  183. package/dist/lib/browser/chunk-5VLWX4PK.mjs +0 -15
  184. package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +0 -7
  185. package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +0 -7
  186. package/dist/lib/browser/chunk-UDAINHB6.mjs +0 -1488
  187. package/dist/lib/browser/chunk-UDAINHB6.mjs.map +0 -7
  188. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs +0 -47
  189. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +0 -7
  190. package/dist/lib/browser/keyboard-YJEK2HTH.mjs.map +0 -7
  191. package/dist/lib/browser/react-surface-V6LS3R4L.mjs +0 -51
  192. package/dist/lib/browser/react-surface-V6LS3R4L.mjs.map +0 -7
  193. package/dist/lib/browser/state-BFD5KJCV.mjs +0 -117
  194. package/dist/lib/browser/state-BFD5KJCV.mjs.map +0 -7
  195. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
  196. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  197. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  198. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  199. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  200. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  201. package/dist/types/src/capabilities/keyboard.d.ts +0 -4
  202. package/dist/types/src/capabilities/keyboard.d.ts.map +0 -1
  203. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  204. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  205. package/dist/types/src/capabilities/state.d.ts +0 -20
  206. package/dist/types/src/capabilities/state.d.ts.map +0 -1
  207. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +0 -5
  208. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  209. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  210. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  211. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  212. package/dist/types/src/components/NavTreeContainer.d.ts +0 -9
  213. package/dist/types/src/components/NavTreeContainer.d.ts.map +0 -1
  214. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  215. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +0 -1
  216. package/dist/types/src/events.d.ts +0 -4
  217. package/dist/types/src/events.d.ts.map +0 -1
  218. package/dist/types/src/hooks.d.ts +0 -3
  219. package/dist/types/src/hooks.d.ts.map +0 -1
  220. package/dist/types/src/types.d.ts.map +0 -1
  221. package/src/capabilities/app-graph-builder.ts +0 -53
  222. package/src/capabilities/capabilities.ts +0 -20
  223. package/src/capabilities/intent-resolver.ts +0 -37
  224. package/src/capabilities/keyboard.ts +0 -57
  225. package/src/capabilities/react-surface.tsx +0 -45
  226. package/src/capabilities/state.ts +0 -104
  227. package/src/components/CommandsDialog/CommandsDialog.tsx +0 -126
  228. package/src/components/CommandsDialog/index.ts +0 -6
  229. package/src/components/NavTreeContainer.tsx +0 -378
  230. package/src/events.ts +0 -11
  231. package/src/hooks.ts +0 -21
  232. /package/dist/types/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.d.ts +0 -0
@@ -2,12 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { Fragment, memo } from 'react';
5
+ import React, { Fragment, memo, useMemo } 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
- import { useLoadDescendents } from '../../hooks';
10
+ import { useActions } from '../../hooks';
11
11
  import { meta } from '../../meta';
12
12
  import { NAV_TREE_ITEM } from '../NavTree';
13
13
  import { useNavTreeContext } from '../NavTreeContext';
@@ -17,25 +17,49 @@ import { NavTreeItemAction } from './NavTreeItemAction';
17
17
 
18
18
  export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
19
  const { t } = useTranslation(meta.id);
20
- const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
20
+ const { renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
21
21
 
22
22
  const level = path.length - 2;
23
- const { actions: _actions, groupedActions } = useActions(item);
24
- const [primaryAction, ...secondaryActions] = _actions.toSorted((a, _b) =>
25
- a.properties?.disposition === 'list-item-primary' ? -1 : 1,
23
+ const { actions: actionsProp, groupedActions } = useActions(item);
24
+ const sortedActions = useMemo(
25
+ () =>
26
+ actionsProp.toSorted((actionA, actionB) => {
27
+ const primaryA = actionA.properties?.disposition === 'list-item-primary';
28
+ const primaryB = actionB.properties?.disposition === 'list-item-primary';
29
+ if (primaryA && !primaryB) {
30
+ return -1;
31
+ }
32
+ if (primaryB && !primaryA) {
33
+ return 1;
34
+ }
35
+ return 0;
36
+ }),
37
+ [actionsProp],
26
38
  );
39
+ const [primaryAction, ...secondaryActions] = sortedActions;
27
40
 
28
- const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
29
- .flatMap((action) => (isAction(action) ? [action] : []))
30
- .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
41
+ const actions = useMemo(
42
+ () =>
43
+ (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : sortedActions)
44
+ .flatMap((action) => (Node.isAction(action) ? [action] : []))
45
+ .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition)),
46
+ [sortedActions, primaryAction],
47
+ );
31
48
 
32
- useLoadDescendents(item);
33
- useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
49
+ const primaryMenuActions = useMemo(
50
+ () =>
51
+ primaryAction
52
+ ? Node.isAction(primaryAction)
53
+ ? [primaryAction]
54
+ : groupedActions[primaryAction?.id ?? '']
55
+ : undefined,
56
+ [primaryAction, groupedActions],
57
+ );
34
58
 
35
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
59
+ const ActionRoot = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}` ? Popover.Anchor : Fragment;
36
60
 
37
61
  return (
38
- <div role='none' className='contents app-no-drag'>
62
+ <div role='none' className='contents dx-app-no-drag'>
39
63
  {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
64
  <Treegrid.Cell classNames='contents'>
41
65
  <NavTreeItemAction
@@ -43,8 +67,9 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
43
67
  label={toLocalizedString(primaryAction.properties?.label, t)}
44
68
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
45
69
  parent={item}
46
- monolithic={isAction(primaryAction)}
47
- menuActions={isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction.id]}
70
+ path={path}
71
+ monolithic={Node.isAction(primaryAction)}
72
+ menuActions={primaryMenuActions}
48
73
  menuType={primaryAction.properties?.menuType}
49
74
  caller={NAV_TREE_ITEM}
50
75
  />
@@ -60,6 +85,7 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
60
85
  label={t('tree item actions label')}
61
86
  icon='ph--dots-three-vertical--regular'
62
87
  parent={item}
88
+ path={path}
63
89
  menuActions={actions}
64
90
  menuType='dropdown'
65
91
  caller={NAV_TREE_ITEM}
@@ -11,10 +11,12 @@ import {
11
11
  attachClosestEdge,
12
12
  extractClosestEdge,
13
13
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
14
+ import { useAtomValue } from '@effect-atom/atom-react';
14
15
  import React, {
15
16
  type MouseEvent,
16
17
  type PropsWithChildren,
17
18
  forwardRef,
19
+ memo,
18
20
  useCallback,
19
21
  useLayoutEffect,
20
22
  useMemo,
@@ -23,10 +25,11 @@ import React, {
23
25
  } from 'react';
24
26
 
25
27
  import { type Node } from '@dxos/app-graph';
26
- import { invariant } from '@dxos/invariant';
27
28
  import { DxAvatar } from '@dxos/lit-ui/react';
29
+ import { useActionRunner } from '@dxos/plugin-graph';
28
30
  import {
29
31
  Icon,
32
+ IconButton,
30
33
  ListItem,
31
34
  ScrollArea,
32
35
  type ThemedClassName,
@@ -35,13 +38,13 @@ import {
35
38
  useMediaQuery,
36
39
  useTranslation,
37
40
  } from '@dxos/react-ui';
38
- import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
41
+ import { Menu, type MenuItem } from '@dxos/react-ui-menu';
39
42
  import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
40
43
  import { Tabs } from '@dxos/react-ui-tabs';
41
- import { mx } from '@dxos/react-ui-theme';
44
+ import { mx } from '@dxos/ui-theme';
42
45
  import { arrayMove } from '@dxos/util';
43
46
 
44
- import { useLoadDescendents } from '../../hooks';
47
+ import { useNavTreeState } from '../../hooks';
45
48
  import { meta } from '../../meta';
46
49
  import { l0ItemType } from '../../util';
47
50
  import { useNavTreeContext } from '../NavTreeContext';
@@ -57,43 +60,41 @@ type L0ItemData = {
57
60
  };
58
61
 
59
62
  type L0ItemRootProps = {
60
- item: Node<any>;
61
- parent?: Node<any>;
63
+ item: Node.Node;
64
+ parent?: Node.Node;
62
65
  path: string[];
66
+ onMouseEnter?: () => void;
63
67
  };
64
68
 
65
69
  type L0ItemProps = L0ItemRootProps & {
66
- item: Node<any>;
67
- parent?: Node<any>;
70
+ item: Node.Node;
71
+ parent?: Node.Node;
68
72
  path: string[];
69
73
  pinned?: boolean;
70
74
  onRearrange?: StackItemRearrangeHandler<L0ItemData>;
75
+ onItemHover?: (params: { item: Node.Node }) => void;
71
76
  };
72
77
 
73
78
  const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
74
- const { tab, isCurrent, onSelect, onTabChange } = useNavTreeContext();
79
+ const { onSelect, onTabChange } = useNavTreeContext();
80
+ const { getItem } = useNavTreeState();
75
81
  const [isLg] = useMediaQuery('lg');
82
+ const runAction = useActionRunner();
76
83
 
77
84
  return useCallback(
78
85
  (event: MouseEvent) => {
79
86
  switch (type) {
80
87
  case 'action': {
81
- const {
82
- data: invoke,
83
- properties: { caller },
84
- } = item;
85
-
86
- return invoke?.(caller ? { node: parent, caller } : { node: parent });
88
+ const { properties: { caller } = {} } = item;
89
+ return void runAction(item as Node.Action, caller ? { parent, path, caller } : { parent, path });
87
90
  }
88
-
89
91
  case 'tab':
90
92
  return onTabChange?.(item);
91
-
92
93
  case 'link':
93
- return onSelect?.({ item, path, current: !isCurrent(path, item), option: event.altKey });
94
+ return onSelect?.({ item, path, current: !getItem(path).current, option: event.altKey });
94
95
  }
95
96
  },
96
- [item, parent, type, tab, isCurrent, onSelect, onTabChange, isLg],
97
+ [item, parent, type, getItem, onSelect, onTabChange, isLg, runAction],
97
98
  );
98
99
  };
99
100
 
@@ -102,55 +103,56 @@ const l0Breakpoints: Record<string, string> = {
102
103
  };
103
104
 
104
105
  const l0ItemRoot =
105
- 'group/l0item flex is-full justify-center items-center relative data[type!="collection"]:cursor-pointer app-no-drag dx-focus-ring-group';
106
+ 'group/l0item flex w-full justify-center items-center relative data[type!="collection"]:cursor-pointer dx-app-no-drag dx-focus-ring-group';
106
107
 
107
108
  const l0ItemContent = 'flex justify-center items-center dx-focus-ring-group-indicator transition-colors rounded-sm';
108
109
 
109
- const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
110
- ({ item, parent, path, children }, forwardedRef) => {
111
- const { getProps } = useNavTreeContext();
112
- const { id, testId } = getProps?.(item, path) ?? {};
113
- const type = l0ItemType(item);
114
- const itemPath = useMemo(() => [...path, item.id], [item.id, path]);
115
-
116
- const { t } = useTranslation(meta.id);
117
- const localizedString = toLocalizedString(item.properties.label, t);
118
-
119
- const handleClick = useL0ItemClick({ item, parent, path: itemPath }, type);
120
- const rootProps =
121
- type === 'tab'
122
- ? { 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' };
126
-
127
- return (
128
- <Tooltip.Trigger asChild delayDuration={0} side='right' content={localizedString}>
129
- <Tabs.TabPrimitive
130
- {...(rootProps as any)}
131
- data-type={type}
132
- className={mx(l0ItemRoot, l0Breakpoints[item.properties.l0Breakpoint])}
133
- ref={forwardedRef}
134
- >
135
- {children}
136
- </Tabs.TabPrimitive>
137
- </Tooltip.Trigger>
138
- );
139
- },
110
+ const L0ItemRoot = memo(
111
+ forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
112
+ ({ item, parent, path, onMouseEnter, children }, forwardedRef) => {
113
+ const { model } = useNavTreeContext();
114
+ const itemPath = useMemo(() => [...path, item.id], [item.id, path]);
115
+ const { id, testId } = useAtomValue(model.itemProps(itemPath));
116
+ const type = l0ItemType(item);
117
+
118
+ const { t } = useTranslation(meta.id);
119
+ const localizedString = toLocalizedString(item.properties.label, t);
120
+
121
+ const handleClick = useL0ItemClick({ item, parent, path: itemPath }, type);
122
+ const rootProps =
123
+ type === 'tab'
124
+ ? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
125
+ : { onClick: handleClick, 'data-testid': testId, 'data-object-id': id };
126
+
127
+ return (
128
+ <Tooltip.Trigger asChild delayDuration={0} side='right' content={localizedString}>
129
+ <Tabs.TabPrimitive
130
+ {...(rootProps as any)}
131
+ data-type={type}
132
+ className={mx(l0ItemRoot, l0Breakpoints[item.properties.l0Breakpoint])}
133
+ ref={forwardedRef}
134
+ onMouseEnter={onMouseEnter}
135
+ >
136
+ {children}
137
+ </Tabs.TabPrimitive>
138
+ </Tooltip.Trigger>
139
+ );
140
+ },
141
+ ),
140
142
  );
141
143
 
142
144
  export const L0ItemActiveTabIndicator = ({ classNames }: ThemedClassName<{}>) => (
143
145
  <div
144
146
  role='none'
145
147
  className={mx(
146
- 'hidden group-aria-selected/l0item:block absolute inline-start-0 inset-block-2 is-1 bg-accentSurface rounded-ie',
148
+ 'hidden group-aria-selected/l0item:block absolute start-0 inset-y-2 w-1 bg-accent-surface rounded-ie',
147
149
  classNames,
148
150
  )}
149
151
  />
150
152
  );
151
153
 
152
154
  // TODO(burdon): Factor out pinned (non-draggable) items.
153
- const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
155
+ const L0Item = memo(({ item, parent, path, pinned, onRearrange, onItemHover }: L0ItemProps) => {
154
156
  const { t } = useTranslation(meta.id);
155
157
  const itemElement = useRef<HTMLElement | null>(null);
156
158
  const [closestEdge, setEdge] = useState<Edge | null>(null);
@@ -213,17 +215,19 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
213
215
  );
214
216
  }, [item, onRearrange]);
215
217
 
218
+ const handleMouseEnter = useCallback(() => onItemHover?.({ item }), [item, onItemHover]);
219
+
216
220
  return (
217
- <L0ItemRoot ref={itemElement} item={item} parent={parent} path={path}>
221
+ <L0ItemRoot ref={itemElement} item={item} parent={parent} path={path} onMouseEnter={handleMouseEnter}>
218
222
  <div
219
223
  role='none'
220
224
  data-frame={true}
221
- {...(hue && { style: { background: `var(--dx-${hue}Surface)` } })}
225
+ {...(hue && { style: { background: `var(--color-${hue}-surface)` } })}
222
226
  className={mx(
223
227
  l0ItemContent,
224
228
  pinned
225
- ? 'p-2 group-hover/l0item:bg-activeSurface'
226
- : 'is-[--l0-avatar-size] bs-[--l0-avatar-size] bg-activeSurface',
229
+ ? 'p-2 group-hover/l0item:bg-active-surface'
230
+ : 'w-(--dx-l0-avatar-size) h-(--dx-l0-avatar-size) bg-active-surface',
227
231
  )}
228
232
  >
229
233
  <ItemAvatar item={item} />
@@ -235,14 +239,14 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
235
239
  {closestEdge && <ListItem.DropIndicator edge={closestEdge} />}
236
240
  </L0ItemRoot>
237
241
  );
238
- };
242
+ });
239
243
 
240
244
  const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
241
245
  const { t } = useTranslation(meta.id);
242
246
  const type = l0ItemType(item);
243
247
  if (item.properties.icon) {
244
248
  const hue = item.properties.hue ?? null;
245
- const hueFgStyle = hue && { style: { color: `var(--dx-${hue}SurfaceText)` } };
249
+ const hueFgStyle = hue && { style: { color: `var(--color-${hue}-surface-text)` } };
246
250
  return <Icon icon={item.properties.icon} size={6} {...hueFgStyle} />;
247
251
  }
248
252
 
@@ -256,137 +260,112 @@ const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
256
260
  };
257
261
 
258
262
  //
259
- // L0Collection
263
+ // L0Menu
260
264
  //
261
265
 
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) ?? {};
266
+ export type L0MenuProps = {
267
+ menuActions: MenuItem[];
268
+ topLevelItems: Node.Node[];
269
+ pinnedItems: Node.Node[];
270
+ userAccountItem?: Node.Node;
271
+ parent?: Node.Node;
272
+ path: string[];
273
+ onItemHover?: (params: { item: Node.Node }) => void;
274
+ };
275
+
276
+ export const L0Menu = ({
277
+ menuActions,
278
+ topLevelItems,
279
+ pinnedItems,
280
+ userAccountItem,
281
+ parent,
282
+ path,
283
+ onItemHover,
284
+ }: L0MenuProps) => {
285
+ const { t } = useTranslation(meta.id);
286
+ const runAction = useActionRunner();
287
+ const handleAction = useCallback(
288
+ (action: Node.Action, params: Node.InvokeProps) => {
289
+ void runAction(action, params);
290
+ },
291
+ [runAction],
292
+ );
268
293
 
269
294
  const handleRearrange = useCallback<StackItemRearrangeHandler<L0ItemData>>(
270
295
  (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);
296
+ const sourceItem = topLevelItems.find((i) => i.id === source.id);
297
+ if (!sourceItem?.properties.onRearrange) {
298
+ return;
299
+ }
300
+
301
+ const sourceIndex = topLevelItems.findIndex((i) => i.id === source.id);
302
+ const targetIndex = topLevelItems.findIndex((i) => i.id === target.id);
274
303
  const insertIndex =
275
304
  source.id === target.id
276
305
  ? sourceIndex
277
306
  : targetIndex +
278
307
  (sourceIndex < targetIndex ? (closestEdge === 'top' ? -1 : 0) : closestEdge === 'bottom' ? 1 : 0);
279
- const nextOrder = arrayMove([...collectionItems], sourceIndex, insertIndex);
280
- return item.properties.onRearrangeChildren(nextOrder);
308
+ const nextOrder = arrayMove([...topLevelItems], sourceIndex, insertIndex);
309
+ return sourceItem.properties.onRearrange(nextOrder.map((item) => item.data));
281
310
  },
282
- [collectionItems, item.properties.onRearrangeChildren],
283
- );
284
-
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>
311
+ [topLevelItems],
303
312
  );
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
313
 
319
- export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
320
- const { t } = useTranslation(meta.id);
314
+ // Check if any items have onRearrange to enable drag-and-drop.
315
+ const hasRearrangeableItems = topLevelItems.some((item) => item.properties.onRearrange);
321
316
 
322
317
  return (
323
318
  <Tabs.Tablist
324
319
  classNames={[
325
- 'group/l0 absolute z-[1] inset-block-0 inline-start-0 rounded-is',
326
- 'grid grid-cols-[var(--l0-size)] grid-rows-[var(--rail-size)_1fr_min-content_var(--l0-size)] contain-layout',
327
- '!is-[--l0-size] bg-baseSurface border-ie border-subduedSeparator app-drag pbe-[env(safe-area-inset-bottom)]',
320
+ 'group/l0 absolute z-[1] inset-y-0 start-0 rounded-is',
321
+ 'grid grid-cols-[var(--dx-l0-size)] grid-rows-[var(--dx-rail-size)_1fr_min-content_var(--dx-l0-size)] dx-contain-layout',
322
+ '!w-(--dx-l0-size) bg-toolbar-surface border-e border-subdued-separator dx-app-drag pb-[env(safe-area-inset-bottom)]',
328
323
  ]}
329
324
  >
330
325
  {/* TODO(wittjosiah): Use L0Item trigger. */}
331
- <MenuProvider>
332
- <DropdownMenu.Root group={parent} items={menuActions}>
333
- <Tooltip.Trigger content={t('app menu label')} side='right' asChild>
334
- <Tabs.TabPrimitive value='options' asChild role='button'>
335
- <DropdownMenu.Trigger
336
- data-testid='spacePlugin.addSpace'
337
- className={mx(l0ItemRoot, 'grid place-items-center dx-focus-ring-group')}
338
- >
339
- <div
340
- role='none'
341
- className={mx(
342
- l0ItemContent,
343
- 'is-[--rail-action] bs-[--rail-action] group-hover/l0item:bg-hoverSurface',
344
- )}
345
- >
346
- <Icon icon='ph--list--regular' size={5} />
347
- </div>
348
- </DropdownMenu.Trigger>
349
- </Tabs.TabPrimitive>
350
- </Tooltip.Trigger>
351
- </DropdownMenu.Root>
352
- </MenuProvider>
326
+ <Menu.Root onAction={handleAction}>
327
+ <Menu.Trigger asChild data-testid='spacePlugin.addSpace'>
328
+ <div role='none' className='grid place-items-center'>
329
+ <IconButton variant='ghost' icon='ph--list--regular' iconOnly label={t('app menu label')} size={5} />
330
+ </div>
331
+ </Menu.Trigger>
332
+ <Menu.Content group={parent} items={menuActions} />
333
+ </Menu.Root>
353
334
 
354
335
  {/* Space list. */}
355
- <ScrollArea.Root>
336
+ <ScrollArea.Root thin orientation='vertical'>
356
337
  <ScrollArea.Viewport>
357
338
  <div
358
339
  role='none'
359
340
  className={mx([
360
- 'flex flex-col gap-1 pbs-1',
361
- '[body[data-platform="darwin"]_&]:pbs-[calc(30px+0.25rem)]',
362
- '[body[data-platform="ios"]_&]:pbs-[max(env(safe-area-inset-top),0.25rem)]',
341
+ 'flex flex-col gap-2 pt-1',
342
+ '[body[data-platform="macos"]_&]:py-[30px]',
343
+ '[body[data-platform="ios"]_&]:py-[max(env(safe-area-inset-top),0.25rem)]',
363
344
  ])}
364
345
  >
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
- })}
346
+ {topLevelItems.map((item) => (
347
+ <L0Item
348
+ key={item.id}
349
+ item={item}
350
+ parent={parent}
351
+ path={path}
352
+ onItemHover={onItemHover}
353
+ {...(hasRearrangeableItems && { onRearrange: handleRearrange })}
354
+ />
355
+ ))}
372
356
  </div>
373
- <ScrollArea.Scrollbar orientation='vertical'>
374
- <ScrollArea.Thumb />
375
- </ScrollArea.Scrollbar>
376
357
  </ScrollArea.Viewport>
377
358
  </ScrollArea.Root>
378
359
 
379
360
  {/* Actions. */}
380
- <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
- ))}
361
+ <div role='none' className='grid grid-cols-1 auto-rows-(--dx-rail-action) pt-2'>
362
+ {pinnedItems.map((item) => (
363
+ <L0Item key={item.id} item={item} parent={parent} path={path} pinned />
364
+ ))}
386
365
  </div>
387
366
 
388
367
  {userAccountItem && (
389
- <div role='none' className='grid app-no-drag'>
368
+ <div role='none' className='grid dx-app-no-drag'>
390
369
  <L0ItemRoot key={userAccountItem.id} item={userAccountItem} parent={parent} path={path}>
391
370
  <UserAccountAvatar
392
371
  userId={userAccountItem.properties.userId}
@@ -398,14 +377,6 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
398
377
  </L0ItemRoot>
399
378
  </div>
400
379
  )}
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
380
  </Tabs.Tablist>
410
381
  );
411
382
  };