@dxos/plugin-explorer 0.8.4-main.fd6878d → 0.9.0

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 (278) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +340 -0
  3. package/dist/lib/neutral/ExplorerArticle-4I7PNGDC.mjs +459 -0
  4. package/dist/lib/neutral/ExplorerArticle-4I7PNGDC.mjs.map +7 -0
  5. package/dist/lib/neutral/ExplorerPlugin.mjs +10 -0
  6. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  7. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  8. package/dist/lib/neutral/chunk-3D7BYXOR.mjs +37 -0
  9. package/dist/lib/neutral/chunk-3D7BYXOR.mjs.map +7 -0
  10. package/dist/lib/neutral/chunk-42BYLQQA.mjs +42 -0
  11. package/dist/lib/neutral/chunk-42BYLQQA.mjs.map +7 -0
  12. package/dist/lib/neutral/chunk-7XUDLV6E.mjs +287 -0
  13. package/dist/lib/neutral/chunk-7XUDLV6E.mjs.map +7 -0
  14. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs +20 -0
  15. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs.map +7 -0
  16. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  17. package/dist/lib/neutral/chunk-YBCHBVCJ.mjs +69 -0
  18. package/dist/lib/neutral/chunk-YBCHBVCJ.mjs.map +7 -0
  19. package/dist/lib/{node-esm/chunk-W4ZNCGOD.mjs → neutral/components/index.mjs} +890 -314
  20. package/dist/lib/neutral/components/index.mjs.map +7 -0
  21. package/dist/lib/neutral/containers/index.mjs +9 -0
  22. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  23. package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
  24. package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
  25. package/dist/lib/neutral/hooks/index.mjs +45 -0
  26. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  27. package/dist/lib/neutral/index.mjs +14 -0
  28. package/dist/lib/neutral/meta.json +1 -0
  29. package/dist/lib/{browser → neutral}/meta.mjs +2 -3
  30. package/dist/lib/neutral/plugin.mjs +12 -0
  31. package/dist/lib/neutral/plugin.mjs.map +7 -0
  32. package/dist/lib/neutral/react-surface-APBW2VQG.mjs +26 -0
  33. package/dist/lib/neutral/react-surface-APBW2VQG.mjs.map +7 -0
  34. package/dist/lib/neutral/testing/index.mjs +139 -0
  35. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  36. package/dist/lib/neutral/translations.mjs +33 -0
  37. package/dist/lib/neutral/translations.mjs.map +7 -0
  38. package/dist/lib/neutral/types/index.mjs +10 -0
  39. package/dist/lib/neutral/types/index.mjs.map +7 -0
  40. package/dist/types/data/cities.d.ts +4 -4
  41. package/dist/types/data/cities.d.ts.map +1 -1
  42. package/dist/types/data/countries-110m.d.ts +19 -22
  43. package/dist/types/data/countries-110m.d.ts.map +1 -1
  44. package/dist/types/src/ExplorerPlugin.d.ts +3 -1
  45. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  46. package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
  47. package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
  48. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  49. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  50. package/dist/types/src/capabilities/index.d.ts +8 -2
  51. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  52. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  53. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  54. package/dist/types/src/components/Chart/Chart.d.ts +1 -1
  55. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  56. package/dist/types/src/components/Chart/Chart.stories.d.ts +12 -5
  57. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  58. package/dist/types/src/components/Globe/Globe.d.ts +1 -1
  59. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  60. package/dist/types/src/components/Globe/Globe.stories.d.ts +12 -5
  61. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  62. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
  63. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
  64. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts +17 -0
  65. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
  66. package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
  67. package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
  68. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +15 -4
  69. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  70. package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +2 -2
  71. package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
  72. package/dist/types/src/components/Graph/index.d.ts +1 -1
  73. package/dist/types/src/components/Graph/index.d.ts.map +1 -1
  74. package/dist/types/src/components/Lattice/Lattice.d.ts +20 -0
  75. package/dist/types/src/components/Lattice/Lattice.d.ts.map +1 -0
  76. package/dist/types/src/components/Lattice/Lattice.stories.d.ts +8 -0
  77. package/dist/types/src/components/Lattice/Lattice.stories.d.ts.map +1 -0
  78. package/dist/types/src/components/Lattice/index.d.ts +2 -0
  79. package/dist/types/src/components/Lattice/index.d.ts.map +1 -0
  80. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts +21 -0
  81. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts.map +1 -0
  82. package/dist/types/src/components/Tree/Tree.d.ts +20 -23
  83. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  84. package/dist/types/src/components/Tree/Tree.stories.d.ts +8 -18
  85. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  86. package/dist/types/src/components/Tree/index.d.ts +2 -0
  87. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  88. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts +37 -2
  89. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  90. package/dist/types/src/components/Tree/layout/RadialTree.d.ts +35 -2
  91. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  92. package/dist/types/src/components/Tree/layout/TidyTree.d.ts +24 -2
  93. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  94. package/dist/types/src/components/Tree/layout/hierarchy.d.ts +17 -0
  95. package/dist/types/src/components/Tree/layout/hierarchy.d.ts.map +1 -0
  96. package/dist/types/src/components/Tree/layout/index.d.ts +5 -4
  97. package/dist/types/src/components/Tree/layout/index.d.ts.map +1 -1
  98. package/dist/types/src/components/Tree/layout/slots.d.ts +7 -0
  99. package/dist/types/src/components/Tree/layout/slots.d.ts.map +1 -0
  100. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts +15 -0
  101. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts.map +1 -0
  102. package/dist/types/src/components/Tree/types/tree.d.ts +51 -28
  103. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  104. package/dist/types/src/components/Tree/types/types.d.ts +14 -4
  105. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  106. package/dist/types/src/components/index.d.ts +1 -4
  107. package/dist/types/src/components/index.d.ts.map +1 -1
  108. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts +8 -0
  109. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts.map +1 -0
  110. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts +15 -0
  111. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts.map +1 -0
  112. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts +18 -0
  113. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts.map +1 -0
  114. package/dist/types/src/containers/ExplorerArticle/index.d.ts +2 -0
  115. package/dist/types/src/containers/ExplorerArticle/index.d.ts.map +1 -0
  116. package/dist/types/src/containers/ExplorerArticle/variants.d.ts +9 -0
  117. package/dist/types/src/containers/ExplorerArticle/variants.d.ts.map +1 -0
  118. package/dist/types/src/containers/index.d.ts +3 -0
  119. package/dist/types/src/containers/index.d.ts.map +1 -0
  120. package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
  121. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  122. package/dist/types/src/index.d.ts +1 -3
  123. package/dist/types/src/index.d.ts.map +1 -1
  124. package/dist/types/src/meta.d.ts +2 -3
  125. package/dist/types/src/meta.d.ts.map +1 -1
  126. package/dist/types/src/plugin.d.ts +3 -0
  127. package/dist/types/src/plugin.d.ts.map +1 -0
  128. package/dist/types/src/{components/Tree/testing → testing}/generator.d.ts +1 -1
  129. package/dist/types/src/testing/generator.d.ts.map +1 -0
  130. package/dist/types/src/testing/index.d.ts +4 -0
  131. package/dist/types/src/testing/index.d.ts.map +1 -0
  132. package/dist/types/src/testing/relations.d.ts +32 -0
  133. package/dist/types/src/testing/relations.d.ts.map +1 -0
  134. package/dist/types/src/translations.d.ts +34 -13
  135. package/dist/types/src/translations.d.ts.map +1 -1
  136. package/dist/types/src/types/ExplorerAction.d.ts +6 -0
  137. package/dist/types/src/types/ExplorerAction.d.ts.map +1 -0
  138. package/dist/types/src/types/Graph.d.ts +22 -0
  139. package/dist/types/src/types/Graph.d.ts.map +1 -0
  140. package/dist/types/src/types/index.d.ts +2 -2
  141. package/dist/types/src/types/index.d.ts.map +1 -1
  142. package/dist/types/src/util/index.d.ts +3 -0
  143. package/dist/types/src/util/index.d.ts.map +1 -0
  144. package/dist/types/src/util/node-color.d.ts +13 -0
  145. package/dist/types/src/util/node-color.d.ts.map +1 -0
  146. package/dist/types/src/{components → util}/plot.d.ts +1 -1
  147. package/dist/types/src/util/plot.d.ts.map +1 -0
  148. package/dist/types/tsconfig.tsbuildinfo +1 -1
  149. package/package.json +113 -64
  150. package/src/ExplorerPlugin.test.ts +26 -0
  151. package/src/ExplorerPlugin.tsx +21 -54
  152. package/src/capabilities/create-object.ts +36 -0
  153. package/src/capabilities/index.ts +3 -3
  154. package/src/capabilities/react-surface.tsx +24 -15
  155. package/src/components/Chart/Chart.stories.tsx +21 -27
  156. package/src/components/Chart/Chart.tsx +1 -1
  157. package/src/components/Globe/Globe.stories.tsx +23 -25
  158. package/src/components/Globe/Globe.tsx +1 -1
  159. package/src/components/Graph/CanvasForceGraph.stories.tsx +97 -0
  160. package/src/components/Graph/CanvasForceGraph.tsx +124 -0
  161. package/src/components/Graph/ForceGraph.stories.tsx +109 -41
  162. package/src/components/Graph/ForceGraph.tsx +105 -85
  163. package/src/components/Graph/{adapter.ts → graph-adapter.ts} +14 -8
  164. package/src/components/Graph/index.ts +1 -1
  165. package/src/components/Lattice/Lattice.stories.tsx +104 -0
  166. package/src/components/Lattice/Lattice.tsx +182 -0
  167. package/src/components/Lattice/index.ts +5 -0
  168. package/src/components/Tree/EdgeBundling.stories.tsx +144 -0
  169. package/src/components/Tree/Tree.stories.tsx +30 -42
  170. package/src/components/Tree/Tree.tsx +69 -95
  171. package/src/components/Tree/index.ts +2 -0
  172. package/src/components/Tree/layout/HierarchicalEdgeBundling.tsx +335 -0
  173. package/src/components/Tree/layout/RadialTree.tsx +242 -0
  174. package/src/components/Tree/layout/TidyTree.tsx +246 -0
  175. package/src/components/Tree/layout/hierarchy.ts +32 -0
  176. package/src/components/Tree/layout/index.ts +5 -5
  177. package/src/components/Tree/layout/slots.ts +19 -0
  178. package/src/components/Tree/layout/useContainerSize.ts +43 -0
  179. package/src/components/Tree/types/tree.test.ts +8 -7
  180. package/src/components/Tree/types/tree.ts +52 -36
  181. package/src/components/Tree/types/types.ts +38 -29
  182. package/src/components/index.ts +1 -4
  183. package/src/containers/ExplorerArticle/ExplorerArticle.stories.tsx +152 -0
  184. package/src/containers/ExplorerArticle/ExplorerArticle.tsx +120 -0
  185. package/src/containers/ExplorerArticle/Visualization.tsx +523 -0
  186. package/src/containers/ExplorerArticle/index.ts +5 -0
  187. package/src/containers/ExplorerArticle/variants.ts +47 -0
  188. package/src/containers/index.ts +7 -0
  189. package/src/hooks/useGraphModel.ts +25 -14
  190. package/src/index.ts +1 -4
  191. package/src/meta.ts +30 -8
  192. package/src/plugin.ts +9 -0
  193. package/src/{components/Tree/testing → testing}/generator.ts +6 -4
  194. package/src/testing/index.ts +9 -0
  195. package/src/testing/relations.ts +117 -0
  196. package/src/translations.ts +17 -12
  197. package/src/types/ExplorerAction.ts +20 -0
  198. package/src/types/Graph.ts +41 -0
  199. package/src/types/index.ts +2 -2
  200. package/src/typings.d.ts +8 -0
  201. package/src/util/index.ts +6 -0
  202. package/src/util/node-color.ts +23 -0
  203. package/src/{components → util}/plot.ts +16 -4
  204. package/src/vite-env.d.ts +10 -0
  205. package/dist/lib/browser/ExplorerContainer-5QHLD2B2.mjs +0 -37
  206. package/dist/lib/browser/ExplorerContainer-5QHLD2B2.mjs.map +0 -7
  207. package/dist/lib/browser/chunk-2MKBRIUT.mjs +0 -31
  208. package/dist/lib/browser/chunk-2MKBRIUT.mjs.map +0 -7
  209. package/dist/lib/browser/chunk-CZZ3DDR7.mjs +0 -38
  210. package/dist/lib/browser/chunk-CZZ3DDR7.mjs.map +0 -7
  211. package/dist/lib/browser/chunk-L4U4MPSZ.mjs +0 -190
  212. package/dist/lib/browser/chunk-L4U4MPSZ.mjs.map +0 -7
  213. package/dist/lib/browser/chunk-LGK64HLU.mjs +0 -11089
  214. package/dist/lib/browser/chunk-LGK64HLU.mjs.map +0 -7
  215. package/dist/lib/browser/chunk-UL5EDJPE.mjs +0 -21
  216. package/dist/lib/browser/chunk-UL5EDJPE.mjs.map +0 -7
  217. package/dist/lib/browser/index.mjs +0 -112
  218. package/dist/lib/browser/index.mjs.map +0 -7
  219. package/dist/lib/browser/intent-resolver-7MVEYNX7.mjs +0 -24
  220. package/dist/lib/browser/intent-resolver-7MVEYNX7.mjs.map +0 -7
  221. package/dist/lib/browser/meta.json +0 -1
  222. package/dist/lib/browser/react-surface-FABRDFTF.mjs +0 -31
  223. package/dist/lib/browser/react-surface-FABRDFTF.mjs.map +0 -7
  224. package/dist/lib/browser/types/index.mjs +0 -10
  225. package/dist/lib/node-esm/ExplorerContainer-AMYAVLO4.mjs +0 -38
  226. package/dist/lib/node-esm/ExplorerContainer-AMYAVLO4.mjs.map +0 -7
  227. package/dist/lib/node-esm/chunk-3ODK27PU.mjs +0 -33
  228. package/dist/lib/node-esm/chunk-3ODK27PU.mjs.map +0 -7
  229. package/dist/lib/node-esm/chunk-4GWDNZ4Z.mjs +0 -39
  230. package/dist/lib/node-esm/chunk-4GWDNZ4Z.mjs.map +0 -7
  231. package/dist/lib/node-esm/chunk-MCOXQ3ML.mjs +0 -192
  232. package/dist/lib/node-esm/chunk-MCOXQ3ML.mjs.map +0 -7
  233. package/dist/lib/node-esm/chunk-PIAXA43R.mjs +0 -23
  234. package/dist/lib/node-esm/chunk-PIAXA43R.mjs.map +0 -7
  235. package/dist/lib/node-esm/chunk-W4ZNCGOD.mjs.map +0 -7
  236. package/dist/lib/node-esm/index.mjs +0 -113
  237. package/dist/lib/node-esm/index.mjs.map +0 -7
  238. package/dist/lib/node-esm/intent-resolver-NL3SR2XF.mjs +0 -25
  239. package/dist/lib/node-esm/intent-resolver-NL3SR2XF.mjs.map +0 -7
  240. package/dist/lib/node-esm/meta.json +0 -1
  241. package/dist/lib/node-esm/meta.mjs +0 -10
  242. package/dist/lib/node-esm/react-surface-EYCZUAAI.mjs +0 -32
  243. package/dist/lib/node-esm/react-surface-EYCZUAAI.mjs.map +0 -7
  244. package/dist/lib/node-esm/types/index.mjs +0 -11
  245. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  246. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  247. package/dist/types/src/components/ExplorerContainer.d.ts +0 -9
  248. package/dist/types/src/components/ExplorerContainer.d.ts.map +0 -1
  249. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -14
  250. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
  251. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +0 -6
  252. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
  253. package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
  254. package/dist/types/src/components/Graph/testing.d.ts +0 -14
  255. package/dist/types/src/components/Graph/testing.d.ts.map +0 -1
  256. package/dist/types/src/components/Tree/testing/generator.d.ts.map +0 -1
  257. package/dist/types/src/components/Tree/testing/index.d.ts +0 -2
  258. package/dist/types/src/components/Tree/testing/index.d.ts.map +0 -1
  259. package/dist/types/src/components/plot.d.ts.map +0 -1
  260. package/dist/types/src/types/schema.d.ts +0 -12
  261. package/dist/types/src/types/schema.d.ts.map +0 -1
  262. package/dist/types/src/types/types.d.ts +0 -18
  263. package/dist/types/src/types/types.d.ts.map +0 -1
  264. package/src/capabilities/intent-resolver.ts +0 -19
  265. package/src/components/ExplorerContainer.tsx +0 -37
  266. package/src/components/Graph/D3ForceGraph.stories.tsx +0 -65
  267. package/src/components/Graph/D3ForceGraph.tsx +0 -101
  268. package/src/components/Graph/testing.ts +0 -55
  269. package/src/components/Tree/layout/HierarchicalEdgeBundling.ts +0 -162
  270. package/src/components/Tree/layout/RadialTree.ts +0 -94
  271. package/src/components/Tree/layout/TidyTree.ts +0 -101
  272. package/src/components/Tree/testing/index.ts +0 -5
  273. package/src/types/schema.ts +0 -16
  274. package/src/types/types.ts +0 -22
  275. /package/dist/lib/{browser/meta.mjs.map → neutral/ExplorerPlugin.mjs.map} +0 -0
  276. /package/dist/lib/{browser/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  277. /package/dist/lib/{node-esm/types → neutral}/index.mjs.map +0 -0
  278. /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
@@ -0,0 +1,104 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, useAtomValue } from '@effect-atom/atom-react';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import * as Effect from 'effect/Effect';
8
+ import React, { useMemo } from 'react';
9
+
10
+ import { withPluginManager } from '@dxos/app-framework/testing';
11
+ import { Type, View } from '@dxos/echo';
12
+ import { ClientPlugin, initializeIdentity } from '@dxos/plugin-client/testing';
13
+ import { PreviewPlugin } from '@dxos/plugin-preview/testing';
14
+ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
15
+ import { random } from '@dxos/random';
16
+ import { useSpaces } from '@dxos/react-client/echo';
17
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
18
+ import { type SpaceGraphNode, ViewModel } from '@dxos/schema';
19
+ import { type ValueGenerator, createObjectFactory, createRelationFactory } from '@dxos/schema/testing';
20
+ import { HasRelationship, Organization, Person, Pipeline } from '@dxos/types';
21
+
22
+ import { useGraphModel } from '#hooks';
23
+ import { Graph } from '#types';
24
+
25
+ import { Lattice } from './Lattice';
26
+
27
+ const generator = random as any as ValueGenerator;
28
+
29
+ random.seed(7);
30
+
31
+ const EMPTY_ATOM = Atom.make<{ nodes: SpaceGraphNode[] }>({ nodes: [] });
32
+
33
+ const DefaultStory = () => {
34
+ const [space] = useSpaces();
35
+ const model = useGraphModel(space?.db);
36
+ const graphSnapshot = useAtomValue((model?.graphAtom ?? EMPTY_ATOM) as typeof EMPTY_ATOM);
37
+ const nodes = useMemo(() => graphSnapshot.nodes.filter((node) => node.type === 'object'), [graphSnapshot]);
38
+
39
+ if (!space || !model) {
40
+ return <Loading data={{ space: !!space, model: !!model }} />;
41
+ }
42
+
43
+ return <Lattice nodes={nodes} />;
44
+ };
45
+
46
+ const meta: Meta<typeof DefaultStory> = {
47
+ title: 'plugins/plugin-explorer/components/Lattice',
48
+ render: DefaultStory,
49
+ decorators: [
50
+ withTheme(),
51
+ withLayout({ layout: 'fullscreen' }),
52
+ withPluginManager({
53
+ plugins: [
54
+ ...corePlugins(),
55
+ StorybookPlugin({}),
56
+ ClientPlugin({
57
+ types: [
58
+ Graph.Graph,
59
+ View.View,
60
+ HasRelationship.HasRelationship,
61
+ Organization.Organization,
62
+ Pipeline.Pipeline,
63
+ Person.Person,
64
+ ],
65
+ onClientInitialized: ({ client }) =>
66
+ Effect.gen(function* () {
67
+ const { personalSpace } = yield* initializeIdentity(client);
68
+ yield* Effect.promise(() =>
69
+ createObjectFactory(
70
+ personalSpace.db,
71
+ generator,
72
+ )([
73
+ { type: Organization.Organization, count: 20 },
74
+ { type: Person.Person, count: 30 },
75
+ { type: Pipeline.Pipeline, count: 10 },
76
+ ]),
77
+ );
78
+ yield* Effect.promise(() =>
79
+ createRelationFactory(
80
+ personalSpace.db,
81
+ generator,
82
+ )([{ type: HasRelationship.HasRelationship, count: 20, data: { kind: 'friend' } }]),
83
+ );
84
+ const { view } = yield* Effect.promise(() =>
85
+ ViewModel.makeFromDatabase({ db: personalSpace.db, typename: Type.getTypename(Graph.Graph) }),
86
+ );
87
+ personalSpace.db.add(Graph.make({ name: 'Test', view }));
88
+ yield* Effect.promise(() => personalSpace.db.flush({ indexes: true }));
89
+ }),
90
+ }),
91
+ PreviewPlugin(),
92
+ ],
93
+ }),
94
+ ],
95
+ parameters: {
96
+ layout: 'fullscreen',
97
+ },
98
+ };
99
+
100
+ export default meta;
101
+
102
+ type Story = StoryObj<typeof DefaultStory>;
103
+
104
+ export const Default: Story = {};
@@ -0,0 +1,182 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { select } from 'd3';
6
+ import React, { useEffect, useMemo, useRef } from 'react';
7
+
8
+ import { Obj } from '@dxos/echo';
9
+ import { type SpaceGraphNode } from '@dxos/schema';
10
+
11
+ import { getNodeFillForObject } from '../../util/node-color';
12
+ import { useContainerSize } from '../Tree/layout/useContainerSize';
13
+ import { type TreeNode } from '../Tree/types';
14
+
15
+ const TRANSITION_MS = 350;
16
+
17
+ export type LatticeProps = {
18
+ /** Object nodes from the space graph (typically `model.graph.nodes` filtered to `type === 'object'`). */
19
+ nodes: SpaceGraphNode[];
20
+ /** Padding (in screen pixels) reserved around the lattice. */
21
+ padding?: number;
22
+ /** Mirrors the hover preview contract used by the other variants. */
23
+ onNodeHover?: (node: TreeNode<Obj.Unknown> | null, event?: MouseEvent) => void;
24
+ };
25
+
26
+ type LatticeCell = {
27
+ id: string;
28
+ label: string;
29
+ typename: string;
30
+ object: Obj.Unknown;
31
+ };
32
+
33
+ /**
34
+ * Renders objects as an SVG lattice that fits the container without scrolling.
35
+ * Each object is a rounded rect, colored by typename via the shared hue-hash used by every
36
+ * other variant. Cells are sorted by typename then label so objects of the same type cluster.
37
+ * Hover dispatches the standard preview event — there is no rendered label.
38
+ */
39
+ export const Lattice = ({ nodes, padding = 16, onNodeHover }: LatticeProps) => {
40
+ const svgRef = useRef<SVGSVGElement | null>(null);
41
+ const { setRef, width, height } = useContainerSize();
42
+
43
+ const cells = useMemo<LatticeCell[]>(() => {
44
+ return nodes
45
+ .map((node): LatticeCell | undefined => {
46
+ const object = node.data?.object;
47
+ if (!object) {
48
+ return undefined;
49
+ }
50
+ const label = node.data?.label ?? Obj.getLabel(object) ?? node.id;
51
+ const typename = Obj.getTypename(object) ?? '(untyped)';
52
+ return { id: node.id, label, typename, object };
53
+ })
54
+ .filter((cell): cell is LatticeCell => cell !== undefined)
55
+ .sort((a, b) => a.typename.localeCompare(b.typename) || a.label.localeCompare(b.label));
56
+ }, [nodes]);
57
+
58
+ // Stable hover ref so the effect doesn't rebind handlers on every render.
59
+ const handleHoverRef = useRef<LatticeProps['onNodeHover']>(undefined);
60
+ handleHoverRef.current = onNodeHover;
61
+
62
+ useEffect(() => {
63
+ if (!svgRef.current || !width || !height) {
64
+ return;
65
+ }
66
+ renderLattice(svgRef.current, cells, {
67
+ width,
68
+ height,
69
+ padding,
70
+ onNodeHover: (n, e) => handleHoverRef.current?.(n, e),
71
+ });
72
+ // Clear any pinned preview when the lattice unmounts or re-renders, so the
73
+ // shared hover target doesn't keep pointing at a cell that no longer exists.
74
+ return () => {
75
+ handleHoverRef.current?.(null);
76
+ };
77
+ }, [cells, width, height, padding]);
78
+
79
+ return (
80
+ <div ref={setRef} className='dx-expander relative'>
81
+ {width > 0 && height > 0 && (
82
+ <svg
83
+ ref={svgRef}
84
+ xmlns='http://www.w3.org/2000/svg'
85
+ width={width}
86
+ height={height}
87
+ viewBox={`0 0 ${width} ${height}`}
88
+ />
89
+ )}
90
+ </div>
91
+ );
92
+ };
93
+
94
+ type RenderOptions = {
95
+ width: number;
96
+ height: number;
97
+ padding: number;
98
+ onNodeHover: (node: TreeNode<Obj.Unknown> | null, event?: MouseEvent) => void;
99
+ };
100
+
101
+ const renderLattice = (svgElement: SVGSVGElement, cells: LatticeCell[], options: RenderOptions) => {
102
+ const { width, height, padding, onNodeHover } = options;
103
+ const svg = select(svgElement);
104
+
105
+ if (!cells.length) {
106
+ onNodeHover(null);
107
+ svg.selectAll('g.dx-lattice-root').remove();
108
+ return;
109
+ }
110
+
111
+ // Columns ≈ √N so the grid stays as square as possible.
112
+ const count = cells.length;
113
+ const columns = Math.max(1, Math.ceil(Math.sqrt(count)));
114
+ const rows = Math.ceil(count / columns);
115
+
116
+ // Derive a cell size that fits both axes after reserving padding around the whole lattice.
117
+ const innerW = Math.max(0, width - 2 * padding);
118
+ const innerH = Math.max(0, height - 2 * padding);
119
+ const cellSize = Math.max(0, Math.min(innerW / columns, innerH / rows));
120
+ // Leave a little gutter between cells; the rect occupies the inner area.
121
+ const gutter = Math.max(2, cellSize * 0.12);
122
+ const rectSize = Math.max(0, cellSize - gutter);
123
+ const radius = Math.max(2, rectSize * 0.18);
124
+
125
+ // Center the lattice in the container.
126
+ const gridW = cellSize * columns;
127
+ const gridH = cellSize * rows;
128
+ const offsetX = (width - gridW) / 2;
129
+ const offsetY = (height - gridH) / 2;
130
+
131
+ const g = svg
132
+ .selectAll<SVGGElement, null>('g.dx-lattice-root')
133
+ .data([null])
134
+ .join('g')
135
+ .classed('dx-lattice-root', true);
136
+
137
+ type Positioned = LatticeCell & { x: number; y: number };
138
+ const positioned: Positioned[] = cells.map((cell, i) => ({
139
+ ...cell,
140
+ x: offsetX + (i % columns) * cellSize + gutter / 2,
141
+ y: offsetY + Math.floor(i / columns) * cellSize + gutter / 2,
142
+ }));
143
+
144
+ const node = g
145
+ .selectAll<SVGGElement, Positioned>('g.dx-lattice-cell')
146
+ .data(positioned, (d) => d.id)
147
+ .join(
148
+ (enter) => {
149
+ const ge = enter.append('g').classed('dx-lattice-cell', true).attr('opacity', 0);
150
+ ge.append('rect').style('cursor', 'pointer');
151
+ return ge;
152
+ },
153
+ (update) => update,
154
+ (exit) =>
155
+ exit
156
+ .each(function () {
157
+ select(this).interrupt();
158
+ })
159
+ .transition()
160
+ .duration(TRANSITION_MS)
161
+ .attr('opacity', 0)
162
+ .remove(),
163
+ );
164
+
165
+ node
166
+ .transition()
167
+ .duration(TRANSITION_MS)
168
+ .attr('opacity', 1)
169
+ .attr('transform', (d) => `translate(${d.x},${d.y})`);
170
+
171
+ node
172
+ .select<SVGRectElement>('rect')
173
+ .attr('width', rectSize)
174
+ .attr('height', rectSize)
175
+ .attr('rx', radius)
176
+ .attr('ry', radius)
177
+ .style('fill', (d) => getNodeFillForObject(d.object))
178
+ .on('pointerenter', (event: MouseEvent, d: Positioned) =>
179
+ onNodeHover({ id: d.id, label: d.label, data: d.object }, event),
180
+ )
181
+ .on('pointerleave', () => onNodeHover(null));
182
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './Lattice';
@@ -0,0 +1,144 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Effect from 'effect/Effect';
7
+ import React, { useEffect, useMemo, useState } from 'react';
8
+
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { Query } from '@dxos/echo';
11
+ import { ClientPlugin, initializeIdentity } from '@dxos/plugin-client/testing';
12
+ import { corePlugins } from '@dxos/plugin-testing';
13
+ import { random } from '@dxos/random';
14
+ import { useSpaces } from '@dxos/react-client/echo';
15
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
16
+ import { type ValueGenerator } from '@dxos/schema/testing';
17
+ import { HasConnection, Organization, Person } from '@dxos/types';
18
+
19
+ import { buildOrgHierarchy, connectionsToEdges, generateConnectedOrgs } from '../../testing';
20
+ import { Tree, type TreeComponentProps } from './Tree';
21
+ import { type TreeNode } from './types';
22
+
23
+ const generator = random as any as ValueGenerator;
24
+
25
+ random.seed(42);
26
+
27
+ const DefaultStory = ({ variant = 'edge', tension }: { variant?: TreeComponentProps['variant']; tension?: number }) => {
28
+ const [space] = useSpaces();
29
+ const [{ data, edges }, setState] = useState<{ data?: TreeNode; edges: TreeComponentProps['edges'] }>({
30
+ data: undefined,
31
+ edges: [],
32
+ });
33
+ const [hovered, setHovered] = useState<TreeNode | null>(null);
34
+
35
+ useEffect(() => {
36
+ if (!space) {
37
+ return;
38
+ }
39
+ let cancelled = false;
40
+ void (async () => {
41
+ const orgs = await space.db.query(Query.type(Organization.Organization)).run();
42
+ if (cancelled || !orgs.length) {
43
+ return;
44
+ }
45
+ const connections = await space.db.query(Query.type(HasConnection.HasConnection)).run();
46
+ if (cancelled) {
47
+ return;
48
+ }
49
+ setState({
50
+ data: buildOrgHierarchy(orgs as any),
51
+ edges: connectionsToEdges(connections as any),
52
+ });
53
+ })();
54
+ return () => {
55
+ cancelled = true;
56
+ };
57
+ }, [space]);
58
+
59
+ const slots = useMemo<TreeComponentProps['slots']>(
60
+ () => ({
61
+ node: 'fill-neutral-700 dark:fill-neutral-300',
62
+ path: 'stroke-orange-400/40 dark:stroke-orange-500/40',
63
+ text: 'fill-neutral-700 dark:fill-neutral-200 text-xs',
64
+ }),
65
+ [],
66
+ );
67
+
68
+ if (!space || !data) {
69
+ return <Loading data={{ space: !!space, data: !!data }} />;
70
+ }
71
+
72
+ return (
73
+ <div className='relative flex h-full w-full'>
74
+ <Tree
75
+ data={data}
76
+ edges={edges}
77
+ variant={variant}
78
+ slots={slots}
79
+ onNodeHover={setHovered}
80
+ // Pass-through; only `edge` variant uses `edges`/`onNodeHover`.
81
+ />
82
+ {hovered && (
83
+ <div className='pointer-events-none absolute left-2 top-2 rounded bg-neutral-900/80 px-2 py-1 text-xs text-white dark:bg-white/80 dark:text-neutral-900'>
84
+ {hovered.label ?? hovered.id}
85
+ </div>
86
+ )}
87
+ </div>
88
+ );
89
+ };
90
+
91
+ const meta = {
92
+ title: 'plugins/plugin-explorer/components/EdgeBundling',
93
+ component: Tree as any,
94
+ render: DefaultStory,
95
+ decorators: [
96
+ withTheme(),
97
+ withLayout({ layout: 'fullscreen' }),
98
+ withPluginManager({
99
+ plugins: [
100
+ ...corePlugins(),
101
+ ClientPlugin({
102
+ types: [Organization.Organization, Person.Person, HasConnection.HasConnection],
103
+ onClientInitialized: ({ client }) =>
104
+ Effect.gen(function* () {
105
+ const { personalSpace } = yield* initializeIdentity(client);
106
+ yield* Effect.promise(() =>
107
+ generateConnectedOrgs(personalSpace, generator, {
108
+ organizationCount: 16,
109
+ personCount: 24,
110
+ connectionCount: 22,
111
+ }),
112
+ );
113
+ yield* Effect.promise(() => personalSpace.db.flush({ indexes: true }));
114
+ }),
115
+ }),
116
+ ],
117
+ }),
118
+ ],
119
+ parameters: {
120
+ layout: 'fullscreen',
121
+ },
122
+ } satisfies Meta<typeof DefaultStory>;
123
+
124
+ export default meta;
125
+
126
+ type Story = StoryObj<typeof meta>;
127
+
128
+ export const Default: Story = {
129
+ args: {
130
+ variant: 'edge',
131
+ },
132
+ };
133
+
134
+ export const Tidy: Story = {
135
+ args: {
136
+ variant: 'tidy',
137
+ },
138
+ };
139
+
140
+ export const Radial: Story = {
141
+ args: {
142
+ variant: 'radial',
143
+ },
144
+ };
@@ -2,68 +2,56 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
6
7
 
7
- import { type Meta } from '@storybook/react-vite';
8
- import React, { type FC, useEffect, useState } from 'react';
9
-
10
- import { faker } from '@dxos/random';
11
- import { useClient } from '@dxos/react-client';
12
- import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
8
+ import { random } from '@dxos/random';
9
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
14
10
 
11
+ import { createTree } from '../../testing';
15
12
  import { Tree, type TreeComponentProps } from './Tree';
16
- import { Tree as TreeModel, TreeType } from './types';
17
-
18
- // TODO(burdon): Storybook for Graph/Tree/Plot (generics); incl. GraphModel.
19
- // TODO(burdon): Type for all Explorer components (Space, Object, Query, etc.) incl.
13
+ import { treeTypeToTreeNode } from './types';
20
14
 
21
- faker.seed(1);
15
+ random.seed(1);
22
16
 
23
- const Story: FC<ClientRepeatedComponentProps & { type?: TreeComponentProps<any>['variant'] }> = ({ type }) => {
24
- const client = useClient();
25
- const space = client.spaces.default;
26
- const [object, setObject] = useState<TreeType>();
27
- useEffect(() => {
28
- setTimeout(() => {
29
- const tree = space.db.add(TreeModel.create());
30
- setObject(tree);
31
- });
32
- }, []);
17
+ type StoryArgs = Pick<TreeComponentProps, 'variant'>;
33
18
 
34
- if (!object) {
35
- return null;
19
+ const DefaultStory = ({ variant }: StoryArgs) => {
20
+ const data = useMemo(() => treeTypeToTreeNode(createTree([3, [2, 4], [1, 3]]).tree), []);
21
+ if (!data) {
22
+ return <Loading />;
36
23
  }
37
24
 
38
- return <Tree space={space} selected={object?.id} variant={type} />;
25
+ return <Tree data={data} variant={variant} />;
39
26
  };
40
27
 
41
- export const Tidy = {
42
- args: {
43
- type: 'tidy',
28
+ const meta: Meta<StoryArgs> = {
29
+ title: 'plugins/plugin-explorer/components/Tree',
30
+ render: DefaultStory,
31
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
32
+ parameters: {
33
+ layout: 'fullscreen',
44
34
  },
45
35
  };
46
36
 
47
- export const Radial = {
37
+ export default meta;
38
+
39
+ type Story = StoryObj<StoryArgs>;
40
+
41
+ export const Tidy: Story = {
48
42
  args: {
49
- type: 'radial',
43
+ variant: 'tidy',
50
44
  },
51
45
  };
52
46
 
53
- export const Edge = {
47
+ export const Radial: Story = {
54
48
  args: {
55
- type: 'edge',
49
+ variant: 'radial',
56
50
  },
57
51
  };
58
52
 
59
- const meta: Meta = {
60
- title: 'plugins/plugin-explorer/Tree',
61
- component: Tree,
62
- render: () => <ClientRepeater component={Story} types={[TreeType]} createSpace />,
63
- decorators: [withTheme, withLayout({ fullscreen: true })],
64
- parameters: {
65
- layout: 'fullscreen',
53
+ export const Edge: Story = {
54
+ args: {
55
+ variant: 'edge',
66
56
  },
67
57
  };
68
-
69
- export default meta;