@dxos/plugin-explorer 0.8.4-main.422d1c7879 → 0.8.4-main.43cb759274

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 (238) 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/{browser/types/index.mjs → neutral/chunk-HI324IB4.mjs} +12 -13
  15. package/dist/lib/neutral/chunk-HI324IB4.mjs.map +7 -0
  16. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs +20 -0
  17. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs.map +7 -0
  18. package/dist/lib/{browser → neutral/components}/index.mjs +730 -437
  19. package/dist/lib/neutral/components/index.mjs.map +7 -0
  20. package/dist/lib/neutral/containers/index.mjs +9 -0
  21. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  22. package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
  23. package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
  24. package/dist/lib/neutral/hooks/index.mjs +45 -0
  25. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  26. package/dist/lib/neutral/index.mjs +14 -0
  27. package/dist/lib/neutral/meta.json +1 -0
  28. package/dist/lib/{browser → neutral}/meta.mjs +1 -1
  29. package/dist/lib/neutral/plugin.mjs +12 -0
  30. package/dist/lib/neutral/plugin.mjs.map +7 -0
  31. package/dist/lib/neutral/react-surface-APBW2VQG.mjs +26 -0
  32. package/dist/lib/neutral/react-surface-APBW2VQG.mjs.map +7 -0
  33. package/dist/lib/neutral/testing/index.mjs +139 -0
  34. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  35. package/dist/lib/neutral/translations.mjs +33 -0
  36. package/dist/lib/neutral/translations.mjs.map +7 -0
  37. package/dist/lib/neutral/types/index.mjs +10 -0
  38. package/dist/lib/neutral/types/index.mjs.map +7 -0
  39. package/dist/types/data/cities.d.ts +4 -4
  40. package/dist/types/data/cities.d.ts.map +1 -1
  41. package/dist/types/data/countries-110m.d.ts +19 -22
  42. package/dist/types/data/countries-110m.d.ts.map +1 -1
  43. package/dist/types/src/ExplorerPlugin.d.ts +1 -0
  44. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  45. package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
  46. package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
  47. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  48. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  49. package/dist/types/src/capabilities/index.d.ts +6 -0
  50. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  51. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  52. package/dist/types/src/components/Chart/Chart.d.ts +1 -1
  53. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  54. package/dist/types/src/components/Chart/Chart.stories.d.ts +4 -1
  55. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/Globe/Globe.d.ts +1 -1
  57. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  58. package/dist/types/src/components/Globe/Globe.stories.d.ts +5 -2
  59. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  60. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
  61. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
  62. package/dist/types/src/components/Graph/{D3ForceGraph.stories.d.ts → CanvasForceGraph.stories.d.ts} +3 -3
  63. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
  64. package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
  65. package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
  66. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +3 -1
  67. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  68. package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +1 -1
  69. package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
  70. package/dist/types/src/components/Graph/index.d.ts +1 -1
  71. package/dist/types/src/components/Graph/index.d.ts.map +1 -1
  72. package/dist/types/src/components/Lattice/Lattice.d.ts +20 -0
  73. package/dist/types/src/components/Lattice/Lattice.d.ts.map +1 -0
  74. package/dist/types/src/components/Lattice/Lattice.stories.d.ts +8 -0
  75. package/dist/types/src/components/Lattice/Lattice.stories.d.ts.map +1 -0
  76. package/dist/types/src/components/Lattice/index.d.ts +2 -0
  77. package/dist/types/src/components/Lattice/index.d.ts.map +1 -0
  78. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts +21 -0
  79. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts.map +1 -0
  80. package/dist/types/src/components/Tree/Tree.d.ts +20 -23
  81. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  82. package/dist/types/src/components/Tree/Tree.stories.d.ts +5 -12
  83. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  84. package/dist/types/src/components/Tree/index.d.ts +2 -0
  85. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  86. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts +37 -2
  87. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  88. package/dist/types/src/components/Tree/layout/RadialTree.d.ts +35 -2
  89. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  90. package/dist/types/src/components/Tree/layout/TidyTree.d.ts +24 -2
  91. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  92. package/dist/types/src/components/Tree/layout/hierarchy.d.ts +17 -0
  93. package/dist/types/src/components/Tree/layout/hierarchy.d.ts.map +1 -0
  94. package/dist/types/src/components/Tree/layout/index.d.ts +5 -4
  95. package/dist/types/src/components/Tree/layout/index.d.ts.map +1 -1
  96. package/dist/types/src/components/Tree/layout/slots.d.ts +7 -0
  97. package/dist/types/src/components/Tree/layout/slots.d.ts.map +1 -0
  98. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts +15 -0
  99. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts.map +1 -0
  100. package/dist/types/src/components/Tree/types/tree.d.ts +41 -20
  101. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  102. package/dist/types/src/components/Tree/types/types.d.ts +14 -4
  103. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  104. package/dist/types/src/components/index.d.ts +1 -0
  105. package/dist/types/src/components/index.d.ts.map +1 -1
  106. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts +8 -0
  107. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts.map +1 -0
  108. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts +15 -0
  109. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts.map +1 -0
  110. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts +18 -0
  111. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts.map +1 -0
  112. package/dist/types/src/containers/ExplorerArticle/index.d.ts +2 -0
  113. package/dist/types/src/containers/ExplorerArticle/index.d.ts.map +1 -0
  114. package/dist/types/src/containers/ExplorerArticle/variants.d.ts +9 -0
  115. package/dist/types/src/containers/ExplorerArticle/variants.d.ts.map +1 -0
  116. package/dist/types/src/containers/index.d.ts +1 -1
  117. package/dist/types/src/containers/index.d.ts.map +1 -1
  118. package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
  119. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  120. package/dist/types/src/index.d.ts +1 -3
  121. package/dist/types/src/index.d.ts.map +1 -1
  122. package/dist/types/src/meta.d.ts +1 -1
  123. package/dist/types/src/meta.d.ts.map +1 -1
  124. package/dist/types/src/plugin.d.ts +3 -0
  125. package/dist/types/src/plugin.d.ts.map +1 -0
  126. package/dist/types/src/{components/Tree/testing → testing}/generator.d.ts +1 -1
  127. package/dist/types/src/testing/generator.d.ts.map +1 -0
  128. package/dist/types/src/testing/index.d.ts +4 -0
  129. package/dist/types/src/testing/index.d.ts.map +1 -0
  130. package/dist/types/src/testing/relations.d.ts +32 -0
  131. package/dist/types/src/testing/relations.d.ts.map +1 -0
  132. package/dist/types/src/translations.d.ts +17 -17
  133. package/dist/types/src/translations.d.ts.map +1 -1
  134. package/dist/types/src/types/Graph.d.ts +5 -6
  135. package/dist/types/src/types/Graph.d.ts.map +1 -1
  136. package/dist/types/src/util/index.d.ts +3 -0
  137. package/dist/types/src/util/index.d.ts.map +1 -0
  138. package/dist/types/src/util/node-color.d.ts +13 -0
  139. package/dist/types/src/util/node-color.d.ts.map +1 -0
  140. package/dist/types/src/{components → util}/plot.d.ts +1 -1
  141. package/dist/types/src/util/plot.d.ts.map +1 -0
  142. package/dist/types/tsconfig.tsbuildinfo +1 -1
  143. package/package.json +102 -58
  144. package/src/ExplorerPlugin.test.ts +26 -0
  145. package/src/ExplorerPlugin.tsx +11 -34
  146. package/src/capabilities/create-object.ts +36 -0
  147. package/src/capabilities/index.ts +1 -0
  148. package/src/capabilities/react-surface.tsx +2 -2
  149. package/src/components/Chart/Chart.stories.tsx +14 -20
  150. package/src/components/Chart/Chart.tsx +1 -1
  151. package/src/components/Globe/Globe.stories.tsx +17 -19
  152. package/src/components/Globe/Globe.tsx +1 -1
  153. package/src/components/Graph/CanvasForceGraph.stories.tsx +97 -0
  154. package/src/components/Graph/CanvasForceGraph.tsx +124 -0
  155. package/src/components/Graph/ForceGraph.stories.tsx +88 -38
  156. package/src/components/Graph/ForceGraph.tsx +105 -85
  157. package/src/components/Graph/index.ts +1 -1
  158. package/src/components/Lattice/Lattice.stories.tsx +104 -0
  159. package/src/components/Lattice/Lattice.tsx +182 -0
  160. package/src/components/Lattice/index.ts +5 -0
  161. package/src/components/Tree/EdgeBundling.stories.tsx +144 -0
  162. package/src/components/Tree/Tree.stories.tsx +17 -38
  163. package/src/components/Tree/Tree.tsx +69 -100
  164. package/src/components/Tree/index.ts +2 -0
  165. package/src/components/Tree/layout/HierarchicalEdgeBundling.tsx +335 -0
  166. package/src/components/Tree/layout/RadialTree.tsx +242 -0
  167. package/src/components/Tree/layout/TidyTree.tsx +246 -0
  168. package/src/components/Tree/layout/hierarchy.ts +32 -0
  169. package/src/components/Tree/layout/index.ts +5 -5
  170. package/src/components/Tree/layout/slots.ts +19 -0
  171. package/src/components/Tree/layout/useContainerSize.ts +43 -0
  172. package/src/components/Tree/types/tree.test.ts +2 -2
  173. package/src/components/Tree/types/tree.ts +23 -28
  174. package/src/components/Tree/types/types.ts +38 -29
  175. package/src/components/index.ts +1 -0
  176. package/src/containers/ExplorerArticle/ExplorerArticle.stories.tsx +152 -0
  177. package/src/containers/ExplorerArticle/ExplorerArticle.tsx +120 -0
  178. package/src/containers/ExplorerArticle/Visualization.tsx +523 -0
  179. package/src/containers/ExplorerArticle/index.ts +5 -0
  180. package/src/containers/ExplorerArticle/variants.ts +47 -0
  181. package/src/containers/index.ts +1 -1
  182. package/src/hooks/useGraphModel.ts +10 -6
  183. package/src/index.ts +1 -4
  184. package/src/meta.ts +26 -7
  185. package/src/plugin.ts +9 -0
  186. package/src/{components/Tree/testing → testing}/generator.ts +3 -3
  187. package/src/testing/index.ts +9 -0
  188. package/src/testing/relations.ts +117 -0
  189. package/src/translations.ts +1 -1
  190. package/src/types/ExplorerAction.ts +1 -1
  191. package/src/types/Graph.ts +6 -14
  192. package/src/util/index.ts +6 -0
  193. package/src/util/node-color.ts +23 -0
  194. package/src/{components → util}/plot.ts +16 -4
  195. package/src/vite-env.d.ts +10 -0
  196. package/dist/lib/browser/chunk-LSUP47BZ.mjs +0 -24
  197. package/dist/lib/browser/chunk-LSUP47BZ.mjs.map +0 -7
  198. package/dist/lib/browser/index.mjs.map +0 -7
  199. package/dist/lib/browser/meta.json +0 -1
  200. package/dist/lib/browser/types/index.mjs.map +0 -7
  201. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs +0 -26
  202. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs.map +0 -7
  203. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  204. package/dist/lib/node-esm/index.mjs +0 -11375
  205. package/dist/lib/node-esm/index.mjs.map +0 -7
  206. package/dist/lib/node-esm/meta.json +0 -1
  207. package/dist/lib/node-esm/meta.mjs +0 -9
  208. package/dist/lib/node-esm/types/index.mjs +0 -71
  209. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  210. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -15
  211. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
  212. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
  213. package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
  214. package/dist/types/src/components/Graph/testing.d.ts +0 -14
  215. package/dist/types/src/components/Graph/testing.d.ts.map +0 -1
  216. package/dist/types/src/components/Tree/testing/generator.d.ts.map +0 -1
  217. package/dist/types/src/components/Tree/testing/index.d.ts +0 -2
  218. package/dist/types/src/components/Tree/testing/index.d.ts.map +0 -1
  219. package/dist/types/src/components/plot.d.ts.map +0 -1
  220. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +0 -6
  221. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +0 -1
  222. package/dist/types/src/containers/ExplorerContainer/index.d.ts +0 -2
  223. package/dist/types/src/containers/ExplorerContainer/index.d.ts.map +0 -1
  224. package/src/components/Graph/D3ForceGraph.stories.tsx +0 -83
  225. package/src/components/Graph/D3ForceGraph.tsx +0 -108
  226. package/src/components/Graph/testing.ts +0 -58
  227. package/src/components/Tree/layout/HierarchicalEdgeBundling.ts +0 -162
  228. package/src/components/Tree/layout/RadialTree.ts +0 -94
  229. package/src/components/Tree/layout/TidyTree.ts +0 -101
  230. package/src/components/Tree/testing/index.ts +0 -5
  231. package/src/containers/ExplorerContainer/ExplorerContainer.tsx +0 -53
  232. package/src/containers/ExplorerContainer/index.ts +0 -5
  233. /package/dist/lib/{browser/chunk-J5LGTIGS.mjs.map → neutral/ExplorerPlugin.mjs.map} +0 -0
  234. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  235. /package/dist/lib/{browser/meta.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  236. /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/index.mjs.map} +0 -0
  237. /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
  238. /package/src/components/Graph/{adapter.ts → graph-adapter.ts} +0 -0
@@ -1,19 +1,16 @@
1
1
  import {
2
- meta
3
- } from "./chunk-LSUP47BZ.mjs";
4
- import "./chunk-J5LGTIGS.mjs";
2
+ treeTypeToTreeNode
3
+ } from "../chunk-7XUDLV6E.mjs";
4
+ import {
5
+ createAdapter,
6
+ getNodeFillForObject
7
+ } from "../chunk-IKHJV3Q4.mjs";
8
+ import "../chunk-J5LGTIGS.mjs";
5
9
 
6
10
  // src/components/Chart/Chart.tsx
7
11
  import * as Plot from "@observablehq/plot";
8
12
  import React, { useEffect } from "react";
9
13
  import { useResizeDetector } from "react-resize-detector";
10
-
11
- // src/components/plot.ts
12
- var createAdapter = (prop, accessor) => accessor ? {
13
- transform: (values) => values.map((value) => accessor(value)[prop])
14
- } : prop;
15
-
16
- // src/components/Chart/Chart.tsx
17
14
  var defaultOptions = {
18
15
  r: 4,
19
16
  stroke: "gray",
@@ -10825,45 +10822,159 @@ var Globe = ({ items = [], accessor, projection = "orthographic", options = defa
10825
10822
  });
10826
10823
  };
10827
10824
 
10828
- // src/components/Graph/D3ForceGraph.tsx
10825
+ // src/components/Graph/CanvasForceGraph.tsx
10826
+ import { forceLink, forceManyBody } from "d3";
10827
+ import NativeForceGraph from "force-graph";
10828
+ import React3, { useCallback, useEffect as useEffect3, useRef, useState } from "react";
10829
+ import { useResizeDetector as useResizeDetector3 } from "react-resize-detector";
10830
+ import { composable, composableProps } from "@dxos/react-ui";
10831
+
10832
+ // src/components/Graph/graph-adapter.ts
10833
+ var GraphAdapter = class {
10834
+ graph;
10835
+ _nodes = [];
10836
+ _links = [];
10837
+ constructor(graph) {
10838
+ this.graph = graph;
10839
+ this._nodes = graph.nodes.map((node) => ({
10840
+ id: node.id,
10841
+ type: node.type,
10842
+ data: node.data
10843
+ }));
10844
+ const nodeIds = new Set(this._nodes.map((node) => node.id));
10845
+ this._links = graph.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)).map((edge) => ({
10846
+ type: edge.type,
10847
+ source: edge.source,
10848
+ target: edge.target,
10849
+ data: edge.data
10850
+ }));
10851
+ }
10852
+ get nodes() {
10853
+ return this._nodes;
10854
+ }
10855
+ get links() {
10856
+ return this._links;
10857
+ }
10858
+ };
10859
+
10860
+ // src/components/Graph/CanvasForceGraph.tsx
10861
+ var CanvasForceGraph = composable(({ model, match, onClick, ...props }, forwardedRef) => {
10862
+ const { ref: resizeRef, width, height } = useResizeDetector3({
10863
+ refreshRate: 200
10864
+ });
10865
+ const setRef = useCallback((node) => {
10866
+ resizeRef(node);
10867
+ assignRef(forwardedRef, node);
10868
+ }, [
10869
+ resizeRef,
10870
+ forwardedRef
10871
+ ]);
10872
+ const rootRef = useRef(null);
10873
+ const forceGraph = useRef(null);
10874
+ const [data, setData] = useState();
10875
+ useEffect3(() => {
10876
+ return model?.subscribe((model2) => setData(new GraphAdapter(model2.graph)));
10877
+ }, [
10878
+ model
10879
+ ]);
10880
+ useEffect3(() => {
10881
+ if (rootRef.current) {
10882
+ forceGraph.current = new NativeForceGraph(rootRef.current).nodeRelSize(6).nodeLabel((node) => node.type === "schema" ? node.data.typename : node.data.label ?? node.id).nodeAutoColorBy((node) => node.type === "schema" ? "schema" : node.data.typename).linkAutoColorBy((link) => link.type);
10883
+ }
10884
+ return () => {
10885
+ forceGraph.current?.pauseAnimation().graphData({
10886
+ nodes: [],
10887
+ links: []
10888
+ });
10889
+ forceGraph.current = null;
10890
+ };
10891
+ }, []);
10892
+ useEffect3(() => {
10893
+ if (!data || !width || !height || !forceGraph.current) {
10894
+ return;
10895
+ }
10896
+ forceGraph.current.pauseAnimation().width(width).height(height).onEngineStop(() => handleZoomToFit()).onNodeClick((node) => {
10897
+ forceGraph.current?.emitParticle(node);
10898
+ }).d3Force("link", forceLink().distance(160).strength(0.5)).d3Force("charge", forceManyBody().strength(-30)).graphData(data).warmupTicks(100).cooldownTime(1e3).resumeAnimation();
10899
+ }, [
10900
+ data,
10901
+ width,
10902
+ height
10903
+ ]);
10904
+ const handleZoomToFit = () => {
10905
+ forceGraph.current?.zoomToFit(400, 40);
10906
+ };
10907
+ const handleClick = useCallback((event) => {
10908
+ onClick?.(event);
10909
+ if (!event.defaultPrevented) {
10910
+ handleZoomToFit();
10911
+ }
10912
+ }, [
10913
+ onClick
10914
+ ]);
10915
+ return /* @__PURE__ */ React3.createElement("div", {
10916
+ ...composableProps(props, {
10917
+ classNames: "relative grow"
10918
+ }),
10919
+ onClick: handleClick,
10920
+ ref: setRef
10921
+ }, /* @__PURE__ */ React3.createElement("div", {
10922
+ ref: rootRef,
10923
+ className: "absolute inset-0"
10924
+ }));
10925
+ });
10926
+ var assignRef = (ref, value) => {
10927
+ if (typeof ref === "function") {
10928
+ ref(value);
10929
+ } else if (ref) {
10930
+ ref.current = value;
10931
+ }
10932
+ };
10933
+
10934
+ // src/components/Graph/ForceGraph.tsx
10829
10935
  import { Atom, useAtomValue } from "@effect-atom/atom-react";
10830
- import React3, { useCallback, useEffect as useEffect3, useMemo, useRef } from "react";
10936
+ import React4, { useCallback as useCallback2, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState2 } from "react";
10831
10937
  import { Obj } from "@dxos/echo";
10832
10938
  import { SelectionModel } from "@dxos/graph";
10939
+ import { composable as composable2, composableProps as composableProps2 } from "@dxos/react-ui";
10833
10940
  import { GraphForceProjector, SVG } from "@dxos/react-ui-graph";
10834
- import { composable, composableProps, getHashStyles } from "@dxos/ui-theme";
10941
+ import { getHashStyles } from "@dxos/ui-theme";
10835
10942
  import "@dxos/react-ui-graph/styles/graph.css";
10836
10943
  var EMPTY_ATOM = Atom.make({
10837
10944
  nodes: [],
10838
10945
  edges: []
10839
10946
  });
10840
- var D3ForceGraph = composable(({ model, selection: _selection, grid, drag, ...props }, forwardedRef) => {
10947
+ var ForceGraph = composable2(({ model, selection: selectionProp, grid, drag, onInspect, ...props }, forwardedRef) => {
10841
10948
  useAtomValue(model?.graphAtom ?? EMPTY_ATOM);
10842
- const svgRef = useRef(null);
10843
- const projector = useMemo(() => {
10949
+ const graph = useRef2(null);
10950
+ const selection = useMemo(() => selectionProp ?? new SelectionModel(), [
10951
+ selectionProp
10952
+ ]);
10953
+ useEffect4(() => {
10954
+ const unsubscribe = selection.subscribe(() => graph.current?.repaint());
10955
+ return unsubscribe;
10956
+ }, [
10957
+ selection
10958
+ ]);
10959
+ const svgRef = useRef2(null);
10960
+ const [projector, setProjector] = useState2();
10961
+ useEffect4(() => {
10844
10962
  if (svgRef.current) {
10845
- return new GraphForceProjector(svgRef.current, {
10963
+ setProjector(new GraphForceProjector(svgRef.current, {
10846
10964
  attributes: {
10847
- linkForce: (edge) => {
10848
- return edge.data?.object?.active !== false;
10849
- }
10965
+ // TODO(burdon): Check type (currently assumes Employee property).
10966
+ // Edge shouldn't contribute to force if it's not active.
10967
+ linkForce: (edge) => edge.data?.object?.active !== false
10850
10968
  },
10851
10969
  forces: {
10852
10970
  point: {
10853
10971
  strength: 0.01
10854
10972
  }
10855
10973
  }
10856
- });
10974
+ }));
10857
10975
  }
10858
10976
  }, []);
10859
- const graph = useRef(null);
10860
- const selection = useMemo(() => _selection ?? new SelectionModel(), [
10861
- _selection
10862
- ]);
10863
- useEffect3(() => selection.subscribe(() => graph.current?.repaint()), [
10864
- selection
10865
- ]);
10866
- const handleSelect = useCallback((node) => {
10977
+ const handleSelect = useCallback2((node) => {
10867
10978
  if (selection.contains(node.id)) {
10868
10979
  selection.remove(node.id);
10869
10980
  } else {
@@ -10872,29 +10983,27 @@ var D3ForceGraph = composable(({ model, selection: _selection, grid, drag, ...pr
10872
10983
  }, [
10873
10984
  selection
10874
10985
  ]);
10875
- return /* @__PURE__ */ React3.createElement("div", {
10876
- ...composableProps(props, {
10986
+ return /* @__PURE__ */ React4.createElement("div", {
10987
+ ...composableProps2(props, {
10877
10988
  classNames: "dx-container"
10878
10989
  }),
10879
10990
  ref: forwardedRef
10880
- }, /* @__PURE__ */ React3.createElement(SVG.Root, {
10991
+ }, /* @__PURE__ */ React4.createElement(SVG.Root, {
10881
10992
  ref: svgRef
10882
- }, /* @__PURE__ */ React3.createElement(SVG.Markers, null), grid && /* @__PURE__ */ React3.createElement(SVG.Grid, {
10993
+ }, /* @__PURE__ */ React4.createElement(SVG.Markers, null), grid && /* @__PURE__ */ React4.createElement(SVG.Grid, {
10883
10994
  axis: true
10884
- }), /* @__PURE__ */ React3.createElement(SVG.Zoom, {
10995
+ }), /* @__PURE__ */ React4.createElement(SVG.Zoom, {
10885
10996
  extent: [
10886
10997
  1 / 2,
10887
10998
  2
10888
10999
  ]
10889
- }, /* @__PURE__ */ React3.createElement(SVG.Graph, {
10890
- drag,
11000
+ }, /* @__PURE__ */ React4.createElement(SVG.Graph, {
10891
11001
  ref: graph,
11002
+ drag,
10892
11003
  model,
10893
11004
  projector,
10894
11005
  labels: {
10895
- text: (node) => {
10896
- return node.data?.data.label ?? node.id;
10897
- }
11006
+ text: (node) => node.data?.data.label ?? node.id
10898
11007
  },
10899
11008
  attributes: {
10900
11009
  node: (node) => {
@@ -10909,466 +11018,650 @@ var D3ForceGraph = composable(({ model, selection: _selection, grid, drag, ...pr
10909
11018
  };
10910
11019
  }
10911
11020
  },
10912
- onSelect: handleSelect
11021
+ onSelect: handleSelect,
11022
+ onInspect
10913
11023
  }))));
10914
11024
  });
10915
11025
 
10916
- // src/components/Graph/ForceGraph.tsx
10917
- import { forceLink, forceManyBody } from "d3";
10918
- import NativeForceGraph from "force-graph";
10919
- import React4, { useEffect as useEffect4, useRef as useRef2, useState } from "react";
10920
- import { useResizeDetector as useResizeDetector3 } from "react-resize-detector";
10921
- import { filterObjectsSync } from "@dxos/plugin-search";
10922
-
10923
- // src/components/Graph/adapter.ts
10924
- var GraphAdapter = class {
10925
- graph;
10926
- _nodes = [];
10927
- _links = [];
10928
- constructor(graph) {
10929
- this.graph = graph;
10930
- this._nodes = graph.nodes.map((node) => ({
10931
- id: node.id,
10932
- type: node.type,
10933
- data: node.data
10934
- }));
10935
- const nodeIds = new Set(this._nodes.map((node) => node.id));
10936
- this._links = graph.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)).map((edge) => ({
10937
- type: edge.type,
10938
- source: edge.source,
10939
- target: edge.target,
10940
- data: edge.data
10941
- }));
10942
- }
10943
- get nodes() {
10944
- return this._nodes;
10945
- }
10946
- get links() {
10947
- return this._links;
10948
- }
10949
- };
11026
+ // src/components/Lattice/Lattice.tsx
11027
+ import { select } from "d3";
11028
+ import React5, { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
11029
+ import { Obj as Obj2 } from "@dxos/echo";
10950
11030
 
10951
- // src/components/Graph/ForceGraph.tsx
10952
- var ForceGraph = ({ model, match }) => {
10953
- const { ref, width, height } = useResizeDetector3({
10954
- refreshRate: 200
11031
+ // src/components/Tree/layout/useContainerSize.ts
11032
+ import { useEffect as useEffect5, useState as useState3 } from "react";
11033
+ var useContainerSize = () => {
11034
+ const [el, setEl] = useState3(null);
11035
+ const [size, setSize] = useState3({
11036
+ width: 0,
11037
+ height: 0
10955
11038
  });
10956
- const rootRef = useRef2(null);
10957
- const forceGraph = useRef2(null);
10958
- const filteredRef = useRef2([]);
10959
- filteredRef.current = filterObjectsSync(model?.objects ?? [], match);
10960
- const [data, setData] = useState();
10961
- useEffect4(() => {
10962
- return model?.subscribe((model2) => {
10963
- setData(new GraphAdapter(model2.graph));
11039
+ useEffect5(() => {
11040
+ if (!el) {
11041
+ return;
11042
+ }
11043
+ const rect = el.getBoundingClientRect();
11044
+ setSize({
11045
+ width: rect.width,
11046
+ height: rect.height
11047
+ });
11048
+ const observer = new ResizeObserver((entries) => {
11049
+ const entry = entries[0];
11050
+ if (!entry) {
11051
+ return;
11052
+ }
11053
+ const { width, height } = entry.contentRect;
11054
+ setSize((prev) => prev.width === width && prev.height === height ? prev : {
11055
+ width,
11056
+ height
11057
+ });
10964
11058
  });
11059
+ observer.observe(el);
11060
+ return () => observer.disconnect();
10965
11061
  }, [
10966
- model
11062
+ el
10967
11063
  ]);
10968
- useEffect4(() => {
10969
- if (rootRef.current) {
10970
- forceGraph.current = new NativeForceGraph(rootRef.current).nodeRelSize(6).nodeLabel((node) => node.type === "schema" ? node.data.typename : node.data.label ?? node.id).nodeAutoColorBy((node) => node.type === "schema" ? "schema" : node.data.typename).linkAutoColorBy((link2) => link2.type);
11064
+ return {
11065
+ setRef: setEl,
11066
+ width: size.width,
11067
+ height: size.height
11068
+ };
11069
+ };
11070
+
11071
+ // src/components/Lattice/Lattice.tsx
11072
+ var TRANSITION_MS = 350;
11073
+ var Lattice = ({ nodes, padding = 16, onNodeHover }) => {
11074
+ const svgRef = useRef3(null);
11075
+ const { setRef, width, height } = useContainerSize();
11076
+ const cells = useMemo2(() => {
11077
+ return nodes.map((node) => {
11078
+ const object = node.data?.object;
11079
+ if (!object) {
11080
+ return void 0;
11081
+ }
11082
+ const label = node.data?.label ?? Obj2.getLabel(object) ?? node.id;
11083
+ const typename = Obj2.getTypename(object) ?? "(untyped)";
11084
+ return {
11085
+ id: node.id,
11086
+ label,
11087
+ typename,
11088
+ object
11089
+ };
11090
+ }).filter((cell) => cell !== void 0).sort((a, b) => a.typename.localeCompare(b.typename) || a.label.localeCompare(b.label));
11091
+ }, [
11092
+ nodes
11093
+ ]);
11094
+ const handleHoverRef = useRef3(void 0);
11095
+ handleHoverRef.current = onNodeHover;
11096
+ useEffect6(() => {
11097
+ if (!svgRef.current || !width || !height) {
11098
+ return;
10971
11099
  }
11100
+ renderLattice(svgRef.current, cells, {
11101
+ width,
11102
+ height,
11103
+ padding,
11104
+ onNodeHover: (n, e) => handleHoverRef.current?.(n, e)
11105
+ });
10972
11106
  return () => {
10973
- forceGraph.current?.pauseAnimation().graphData({
10974
- nodes: [],
10975
- links: []
10976
- });
10977
- forceGraph.current = null;
11107
+ handleHoverRef.current?.(null);
10978
11108
  };
10979
- }, []);
10980
- useEffect4(() => {
10981
- if (!data || !width || !height || !forceGraph.current) {
10982
- return;
10983
- }
10984
- forceGraph.current.pauseAnimation().width(width).height(height).onEngineStop(() => {
10985
- handleZoomToFit();
10986
- }).onNodeClick((node) => {
10987
- forceGraph.current?.emitParticle(node);
10988
- }).d3Force("link", forceLink().distance(160).strength(0.5)).d3Force("charge", forceManyBody().strength(-30)).graphData(data).warmupTicks(100).cooldownTime(1e3).resumeAnimation();
10989
11109
  }, [
10990
- data,
11110
+ cells,
10991
11111
  width,
10992
11112
  height,
10993
- forceGraph.current
11113
+ padding
10994
11114
  ]);
10995
- const handleZoomToFit = () => {
10996
- forceGraph.current?.zoomToFit(400, 40);
10997
- };
10998
- return /* @__PURE__ */ React4.createElement("div", {
10999
- ref,
11000
- className: "relative grow",
11001
- onClick: handleZoomToFit
11002
- }, /* @__PURE__ */ React4.createElement("div", {
11003
- ref: rootRef,
11004
- className: "absolute inset-0"
11115
+ return /* @__PURE__ */ React5.createElement("div", {
11116
+ ref: setRef,
11117
+ className: "dx-expander relative"
11118
+ }, width > 0 && height > 0 && /* @__PURE__ */ React5.createElement("svg", {
11119
+ ref: svgRef,
11120
+ xmlns: "http://www.w3.org/2000/svg",
11121
+ width,
11122
+ height,
11123
+ viewBox: `0 0 ${width} ${height}`
11005
11124
  }));
11006
11125
  };
11126
+ var renderLattice = (svgElement, cells, options) => {
11127
+ const { width, height, padding, onNodeHover } = options;
11128
+ const svg = select(svgElement);
11129
+ if (!cells.length) {
11130
+ onNodeHover(null);
11131
+ svg.selectAll("g.dx-lattice-root").remove();
11132
+ return;
11133
+ }
11134
+ const count = cells.length;
11135
+ const columns = Math.max(1, Math.ceil(Math.sqrt(count)));
11136
+ const rows = Math.ceil(count / columns);
11137
+ const innerW = Math.max(0, width - 2 * padding);
11138
+ const innerH = Math.max(0, height - 2 * padding);
11139
+ const cellSize = Math.max(0, Math.min(innerW / columns, innerH / rows));
11140
+ const gutter = Math.max(2, cellSize * 0.12);
11141
+ const rectSize = Math.max(0, cellSize - gutter);
11142
+ const radius = Math.max(2, rectSize * 0.18);
11143
+ const gridW = cellSize * columns;
11144
+ const gridH = cellSize * rows;
11145
+ const offsetX = (width - gridW) / 2;
11146
+ const offsetY = (height - gridH) / 2;
11147
+ const g = svg.selectAll("g.dx-lattice-root").data([
11148
+ null
11149
+ ]).join("g").classed("dx-lattice-root", true);
11150
+ const positioned = cells.map((cell, i) => ({
11151
+ ...cell,
11152
+ x: offsetX + i % columns * cellSize + gutter / 2,
11153
+ y: offsetY + Math.floor(i / columns) * cellSize + gutter / 2
11154
+ }));
11155
+ const node = g.selectAll("g.dx-lattice-cell").data(positioned, (d) => d.id).join((enter) => {
11156
+ const ge = enter.append("g").classed("dx-lattice-cell", true).attr("opacity", 0);
11157
+ ge.append("rect").style("cursor", "pointer");
11158
+ return ge;
11159
+ }, (update) => update, (exit) => exit.each(function() {
11160
+ select(this).interrupt();
11161
+ }).transition().duration(TRANSITION_MS).attr("opacity", 0).remove());
11162
+ node.transition().duration(TRANSITION_MS).attr("opacity", 1).attr("transform", (d) => `translate(${d.x},${d.y})`);
11163
+ node.select("rect").attr("width", rectSize).attr("height", rectSize).attr("rx", radius).attr("ry", radius).style("fill", (d) => getNodeFillForObject(d.object)).on("pointerenter", (event, d) => onNodeHover({
11164
+ id: d.id,
11165
+ label: d.label,
11166
+ data: d.object
11167
+ }, event)).on("pointerleave", () => onNodeHover(null));
11168
+ };
11007
11169
 
11008
11170
  // src/components/Tree/Tree.tsx
11009
- import { RegistryContext } from "@effect-atom/atom-react";
11010
- import React5, { useContext, useEffect as useEffect5, useRef as useRef3, useState as useState2 } from "react";
11011
- import { useAsyncState } from "@dxos/react-ui";
11012
- import { SVG as SVG2 } from "@dxos/react-ui-graph";
11013
- import { SpaceGraphModel } from "@dxos/schema";
11171
+ import React9, { useMemo as useMemo6 } from "react";
11014
11172
 
11015
- // src/components/Tree/layout/HierarchicalEdgeBundling.ts
11016
- import { cluster, curveBundle, hierarchy, lineRadial, select } from "d3";
11017
- var HierarchicalEdgeBundling = (s, data, options) => {
11018
- const svg = select(s);
11019
- svg.selectAll("*").remove();
11020
- const { radius = 600, padding = 100, slots } = options;
11021
- const root = hierarchy(flatten(data));
11022
- const tree3 = cluster().size([
11023
- 2 * Math.PI,
11024
- radius - padding
11173
+ // src/components/Tree/layout/HierarchicalEdgeBundling.tsx
11174
+ import { cluster, curveBundle, hierarchy, lineRadial, select as select2 } from "d3";
11175
+ import React6, { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef4 } from "react";
11176
+ import { mx } from "@dxos/ui-theme";
11177
+
11178
+ // src/components/Tree/layout/slots.ts
11179
+ var defaultTreeLayoutSlots = {
11180
+ // Cursor + transition so the hover swap reads clearly; SVG circles support the `:hover` pseudo-class
11181
+ // via Tailwind variants exactly like HTML elements.
11182
+ node: "fill-blue-600 hover:fill-orange-500 cursor-pointer transition-colors",
11183
+ // 0.5px is fine on a white background, but on a dark Storybook background the lines disappear.
11184
+ // Use stroke-1 with opacity 50% so they read in both themes; dx-bundle-dim/out/in further tune on hover.
11185
+ path: "fill-none stroke-blue-500/50 stroke-[1px] dark:stroke-blue-400/60",
11186
+ text: "fill-neutral-700 dark:fill-neutral-300 text-xs hover:fill-orange-500 cursor-pointer transition-colors"
11187
+ };
11188
+
11189
+ // src/components/Tree/layout/HierarchicalEdgeBundling.tsx
11190
+ var TRANSITION_MS2 = 350;
11191
+ var HierarchicalEdgeBundling = ({ classNames, data, edges = [], label = (d) => d.label ?? d.id, padding = 120, tension = 0.85, r = 4, slots = defaultTreeLayoutSlots, onNodeHover }) => {
11192
+ const svgRef = useRef4(null);
11193
+ const { setRef, width, height } = useContainerSize();
11194
+ const root = useMemo3(() => buildBundleHierarchy(data, edges), [
11195
+ data,
11196
+ edges
11025
11197
  ]);
11026
- const layout = tree3(addLinks(root));
11027
- const node = svg.append("g").selectAll().data(layout.leaves()).join("g").attr("transform", (d) => `rotate(${d.x * (180 / Math.PI) - 90}) translate(${d.y},0)`).append("text").attr("class", slots?.text ?? "").attr("dy", "0.31em").attr("x", (d) => d.x < Math.PI ? 6 : -6).attr("text-anchor", (d) => d.x < Math.PI ? "start" : "end").attr("transform", (d) => d.x >= Math.PI ? "rotate(180)" : null).call((text) => text.text((d) => d.data.id.slice(0, 8)));
11028
- const line = lineRadial().curve(curveBundle.beta(0.85)).radius((d) => d.y).angle((d) => d.x);
11029
- const links = svg.append("g").selectAll().data(layout.leaves().flatMap((leaf) => leaf.outgoing)).join("path").style("mix-blend-mode", "multiply").attr("class", slots?.path ?? "").attr("d", ([i, o]) => {
11030
- return line(i.path(o));
11031
- }).each(function(d) {
11032
- d.path = this;
11198
+ const handleHoverRef = useRef4(() => {
11033
11199
  });
11034
- };
11035
- var addLinks = (root) => {
11036
- const nodes = new Map(root.descendants().map((d) => [
11037
- d.data.id,
11038
- d
11039
- ]));
11040
- const parents = root.descendants().reduce((map, d) => {
11041
- if (d.children?.length) {
11042
- map.set(d.data.id, d);
11200
+ handleHoverRef.current = (node, event) => onNodeHover?.(node, event);
11201
+ useEffect7(() => {
11202
+ if (!svgRef.current || !width || !height) {
11203
+ return;
11043
11204
  }
11044
- return map;
11045
- }, /* @__PURE__ */ new Map());
11046
- for (const d of root.leaves()) {
11047
- const parent = parents.get(d.data.id);
11048
- if (parent) {
11049
- d.outgoing = parent.data.children?.slice(1).map((child) => {
11050
- return [
11051
- d,
11052
- nodes.get(child.id)
11053
- ];
11054
- }) ?? [];
11055
- } else {
11056
- d.outgoing = [];
11205
+ const radius = Math.max(0, Math.min(width, height) / 2 - padding);
11206
+ renderBundling(svgRef.current, root, {
11207
+ radius,
11208
+ r,
11209
+ label,
11210
+ slots,
11211
+ tension,
11212
+ onNodeHover: (n, e) => handleHoverRef.current(n, e)
11213
+ });
11214
+ }, [
11215
+ root,
11216
+ width,
11217
+ height,
11218
+ padding,
11219
+ tension,
11220
+ r,
11221
+ label,
11222
+ slots
11223
+ ]);
11224
+ return /* @__PURE__ */ React6.createElement("div", {
11225
+ ref: setRef,
11226
+ className: mx("dx-expander relative", classNames)
11227
+ }, width > 0 && height > 0 && /* @__PURE__ */ React6.createElement("svg", {
11228
+ ref: svgRef,
11229
+ xmlns: "http://www.w3.org/2000/svg",
11230
+ width,
11231
+ height,
11232
+ viewBox: `${-width / 2} ${-height / 2} ${width} ${height}`
11233
+ }));
11234
+ };
11235
+ var buildBundleHierarchy = (data, edges) => {
11236
+ const root = hierarchy(data);
11237
+ const byId = /* @__PURE__ */ new Map();
11238
+ for (const node of root.descendants()) {
11239
+ byId.set(node.data.id, node);
11240
+ node.outgoing = [];
11241
+ node.incoming = [];
11242
+ }
11243
+ for (const edge of edges) {
11244
+ const source = byId.get(edge.source);
11245
+ const target = byId.get(edge.target);
11246
+ if (!source || !target || source === target) {
11247
+ continue;
11057
11248
  }
11249
+ source.outgoing.push([
11250
+ source,
11251
+ target,
11252
+ edge
11253
+ ]);
11254
+ target.incoming.push([
11255
+ source,
11256
+ target,
11257
+ edge
11258
+ ]);
11058
11259
  }
11059
11260
  return root;
11060
11261
  };
11061
- var flatten = (node) => {
11062
- const clone = {
11063
- id: node.id
11064
- };
11065
- if (node.children?.length) {
11066
- const children = node.children.map((child) => flatten(child));
11067
- clone.children = [
11068
- {
11069
- id: node.id
11070
- },
11071
- ...children
11072
- ];
11262
+ var renderBundling = (svgElement, root, options) => {
11263
+ const { radius, r, tension, label, slots, onNodeHover } = options;
11264
+ const svg = select2(svgElement);
11265
+ if (!root.children?.length) {
11266
+ svg.selectAll("g.dx-bundle-root").remove();
11267
+ return;
11073
11268
  }
11074
- return clone;
11075
- };
11076
- var HierarchicalEdgeBundling_default = HierarchicalEdgeBundling;
11077
-
11078
- // src/components/Tree/layout/RadialTree.ts
11079
- import { hierarchy as hierarchy2, linkRadial, select as select2, tree } from "d3";
11080
- var RadialTree = (s, data, options) => {
11081
- const svg = select2(s);
11082
- svg.selectAll("*").remove();
11083
- const { label, radius = 400, r = 4, slots } = options;
11084
- const arc = 2 * Math.PI;
11085
- const root = hierarchy2(data);
11086
- const descendants = root.descendants();
11087
- const getLabel = label === null ? null : descendants.map((d) => label(d.data));
11088
- const layout = tree().size([
11089
- arc,
11269
+ cluster().size([
11270
+ 2 * Math.PI,
11090
11271
  radius
11091
- ]).separation((a, b) => (a.parent === b.parent ? 1 : 2) / a.depth);
11092
- layout(root);
11093
- svg.append("g").selectAll("path").data(root.links()).join("path").attr("class", slots?.path ?? "").attr("d", linkRadial().angle((d) => d.x + Math.PI / 2).radius((d) => d.y));
11094
- const node = svg.append("g").selectAll("a").data(root.descendants()).join("a").attr("transform", (d) => `rotate(${d.x * 180 / Math.PI}) translate(${d.y},0)`);
11095
- node.append("circle").attr("class", slots?.node ?? "").attr("r", r);
11096
- if (getLabel) {
11097
- node.append("text").attr("transform", (d) => `rotate(${d.x >= Math.PI ? 180 : 0})`).attr("dy", "0.32em").attr("x", (d) => d.x < Math.PI === !d.children ? 6 : -6).attr("text-anchor", (d) => d.x < Math.PI === !d.children ? "start" : "end").attr("class", slots?.text ?? "").text((d, i) => getLabel[i]);
11098
- }
11099
- return svg.node();
11272
+ ])(root);
11273
+ const g = svg.selectAll("g.dx-bundle-root").data([
11274
+ null
11275
+ ]).join("g").classed("dx-bundle-root", true);
11276
+ const linksLayer = g.selectAll("g.dx-bundle-links").data([
11277
+ null
11278
+ ]).join("g").classed("dx-bundle-links", true);
11279
+ const nodesLayer = g.selectAll("g.dx-bundle-nodes").data([
11280
+ null
11281
+ ]).join("g").classed("dx-bundle-nodes", true);
11282
+ const line = lineRadial().curve(curveBundle.beta(tension)).radius((d) => d.y).angle((d) => d.x);
11283
+ const leaves = root.leaves();
11284
+ const flatEdges = leaves.flatMap((leaf) => leaf.outgoing ?? []);
11285
+ const paths = linksLayer.selectAll("path").data(flatEdges, (d) => `${d[0].data.id}->${d[1].data.id}`).join((enter) => enter.append("path").attr("class", slots.path ?? "").attr("fill", "none").attr("opacity", 0), (update) => update, (exit) => exit.each(function() {
11286
+ select2(this).interrupt();
11287
+ }).transition().duration(TRANSITION_MS2).attr("opacity", 0).remove());
11288
+ paths.each(function(d) {
11289
+ d[0].pathEl = this;
11290
+ }).transition().duration(TRANSITION_MS2).attr("opacity", 1).attr("d", ([s, t]) => line(s.path(t)));
11291
+ const labels = nodesLayer.selectAll("g.dx-bundle-leaf").data(leaves, (d) => d.data.id).join((enter) => {
11292
+ const ge = enter.append("g").classed("dx-bundle-leaf", true).attr("opacity", 0);
11293
+ ge.append("circle").style("cursor", "pointer");
11294
+ ge.append("text").attr("dy", "0.32em").attr("paint-order", "stroke").style("cursor", "pointer");
11295
+ return ge;
11296
+ }, (update) => update, (exit) => exit.each(function() {
11297
+ select2(this).interrupt();
11298
+ }).transition().duration(TRANSITION_MS2).attr("opacity", 0).remove());
11299
+ labels.transition().duration(TRANSITION_MS2).attr("opacity", 1).attr("transform", (d) => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`);
11300
+ const onEnter = function(event, d) {
11301
+ onNodeHover(d.data, event);
11302
+ hover(linksLayer, leaves, d, true);
11303
+ };
11304
+ const onLeave = function(event, d) {
11305
+ onNodeHover(null);
11306
+ hover(linksLayer, leaves, d, false);
11307
+ };
11308
+ labels.select("circle").attr("class", [
11309
+ slots.node ?? "",
11310
+ "dx-leaf"
11311
+ ].filter(Boolean).join(" ")).attr("r", r).style("fill", (d) => getNodeFillForObject(d.data.data)).each(function(d) {
11312
+ d.circle = this;
11313
+ }).on("pointerenter", onEnter).on("pointerleave", onLeave);
11314
+ labels.select("text").attr("class", slots.text ?? "").attr("x", (d) => d.x < Math.PI ? r + 4 : -(r + 4)).attr("text-anchor", (d) => d.x < Math.PI ? "start" : "end").attr("transform", (d) => d.x >= Math.PI ? "rotate(180)" : null).each(function(d) {
11315
+ d.text = this;
11316
+ }).text((d) => label(d.data)).on("pointerenter", onEnter).on("pointerleave", onLeave);
11100
11317
  };
11101
- var RadialTree_default = RadialTree;
11102
-
11103
- // src/components/Tree/layout/TidyTree.ts
11104
- import { curveBumpX, hierarchy as hierarchy3, link, select as select3, tree as tree2 } from "d3";
11105
- var TidyTree = (s, data, options) => {
11106
- const svg = select3(s);
11107
- svg.selectAll("*").remove();
11108
- const { label, width, height, r = 4, padding = 4, margin = 60, slots } = options;
11109
- const root = hierarchy3(data);
11110
- const descendants = root.descendants();
11111
- const getLabel = label == null ? null : descendants.map((d) => label(d.data));
11112
- const dx = 16;
11113
- const dy = width / (root.height + padding);
11114
- const layout = tree2().nodeSize([
11115
- dx,
11116
- dy
11117
- ]);
11118
- layout(root);
11119
- let x0 = Infinity;
11120
- let x1 = -x0;
11121
- let y0 = Infinity;
11122
- let y1 = -y0;
11123
- root.each((d) => {
11124
- if (d.x > x1) {
11125
- x1 = d.x;
11318
+ var hover = (linksLayer, leaves, focused, on) => {
11319
+ const outgoing = new Set((focused.outgoing ?? []).map(([, t]) => t));
11320
+ const incoming = new Set((focused.incoming ?? []).map(([s]) => s));
11321
+ linksLayer.selectAll("path").style("stroke", (d) => {
11322
+ if (!on) {
11323
+ return null;
11126
11324
  }
11127
- if (d.x < x0) {
11128
- x0 = d.x;
11325
+ if (d[0] === focused) {
11326
+ return "var(--color-orange-500)";
11129
11327
  }
11130
- if (d.y > y1) {
11131
- y1 = d.y;
11328
+ if (d[1] === focused) {
11329
+ return "var(--color-sky-500)";
11132
11330
  }
11133
- if (d.y < y0) {
11134
- y0 = d.y;
11331
+ return null;
11332
+ }).style("stroke-width", (d) => on && (d[0] === focused || d[1] === focused) ? "1.5px" : null).style("opacity", (d) => on && d[0] !== focused && d[1] !== focused ? "0.08" : null);
11333
+ for (const leaf of leaves) {
11334
+ const isOut = outgoing.has(leaf);
11335
+ const isIn = incoming.has(leaf);
11336
+ const isConnected = isOut || isIn;
11337
+ if (leaf.text) {
11338
+ select2(leaf.text).style("fill", () => {
11339
+ if (!on) {
11340
+ return null;
11341
+ }
11342
+ if (leaf === focused) {
11343
+ return "var(--color-neutral-900)";
11344
+ }
11345
+ if (isOut) {
11346
+ return "var(--color-orange-500)";
11347
+ }
11348
+ if (isIn) {
11349
+ return "var(--color-sky-500)";
11350
+ }
11351
+ return null;
11352
+ }).style("font-weight", () => on && leaf === focused ? "600" : null);
11353
+ }
11354
+ if (leaf.circle) {
11355
+ select2(leaf.circle).style("stroke", () => on && isConnected ? "var(--color-orange-400)" : null).style("stroke-width", () => on && isConnected ? "2.5px" : null).style("opacity", () => on && !isConnected && leaf !== focused ? "0.15" : null);
11135
11356
  }
11136
- });
11137
- const sx = Math.min(2, Math.max(1, (height - margin * 2) / (x1 - x0)));
11138
- const oy = -(width - (y1 - y0)) / 2;
11139
- svg.append("g").selectAll("path").data(root.links()).join("path").attr("class", slots?.path ?? "").attr("d", link(curveBumpX).x((d) => d.y + oy).y((d) => d.x * sx));
11140
- const node = svg.append("g").selectAll("a").data(root.descendants()).join("a").attr("transform", (d) => `translate(${d.y + oy},${d.x * sx})`);
11141
- node.append("circle").attr("class", slots?.node ?? "").attr("r", r);
11142
- if (getLabel) {
11143
- node.append("text").attr("dy", "0.32em").attr("x", (d) => d.children ? -6 : 6).attr("text-anchor", (d) => d.children ? "end" : "start").attr("class", slots?.text ?? "").text((d, i) => getLabel[i]);
11144
11357
  }
11145
11358
  };
11146
- var TidyTree_default = TidyTree;
11147
11359
 
11148
- // src/components/Tree/types/tree.ts
11149
- import * as Schema from "effect/Schema";
11150
- import { Key, Obj as Obj2, Ref, Type } from "@dxos/echo";
11151
- import { TestSchema } from "@dxos/echo/testing";
11152
- import { invariant } from "@dxos/invariant";
11153
- var TreeNodeType = Schema.Struct({
11154
- id: Key.ObjectId,
11155
- children: Schema.mutable(Schema.Array(Key.ObjectId)),
11156
- data: Schema.mutable(Schema.Record({
11157
- key: Schema.String,
11158
- value: Schema.Any
11159
- })),
11160
- ref: Schema.optional(Ref.Ref(TestSchema.Expando))
11161
- }).pipe(Schema.mutable);
11162
- var TreeType = Schema.Struct({
11163
- root: Key.ObjectId,
11164
- nodes: Schema.mutable(Schema.Record({
11165
- key: Key.ObjectId,
11166
- value: TreeNodeType
11167
- }))
11168
- }).pipe(Type.object({
11169
- typename: "org.dxos.type.tree",
11170
- version: "0.1.0"
11171
- }));
11360
+ // src/components/Tree/layout/RadialTree.tsx
11361
+ import { cluster as d3Cluster, linkRadial, select as select3, tree as d3Tree } from "d3";
11362
+ import React7, { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef5, useState as useState4 } from "react";
11363
+ import { mx as mx2 } from "@dxos/ui-theme";
11172
11364
 
11173
- // src/components/Tree/types/types.ts
11174
- var mapGraphToTreeData = (model, maxDepth = 8) => {
11175
- let data;
11176
- return data;
11365
+ // src/components/Tree/layout/hierarchy.ts
11366
+ import { hierarchy as d3Hierarchy } from "d3";
11367
+ var buildHierarchy = (data, collapsed = /* @__PURE__ */ new Set()) => {
11368
+ return d3Hierarchy(data, (d) => {
11369
+ if (!d.children?.length) {
11370
+ return void 0;
11371
+ }
11372
+ return collapsed.has(d.id) ? void 0 : d.children;
11373
+ });
11177
11374
  };
11375
+ var isCollapsed = (data, collapsed) => Boolean(data.children?.length) && collapsed.has(data.id);
11376
+ var isLeaf = (data) => !data.children?.length;
11178
11377
 
11179
- // src/components/Tree/Tree.tsx
11180
- var defaultTreeLayoutSlots = {
11181
- node: "fill-blue-600",
11182
- path: "fill-none stroke-blue-400 stroke-[0.5px]",
11183
- text: "stroke-[0.5px] stroke-neutral-700 text-xs"
11184
- };
11185
- var renderers = /* @__PURE__ */ new Map([
11186
- [
11187
- "tidy",
11188
- TidyTree_default
11189
- ],
11190
- [
11191
- "radial",
11192
- RadialTree_default
11193
- ],
11194
- [
11195
- "edge",
11196
- HierarchicalEdgeBundling_default
11197
- ]
11198
- ]);
11199
- var Tree = ({ space, selected, variant = "tidy", onNodeClick }) => {
11200
- const registry = useContext(RegistryContext);
11201
- const [model] = useAsyncState(async () => space ? new SpaceGraphModel(registry).open(space.db) : void 0, [
11202
- space,
11203
- selected,
11204
- registry
11205
- ]);
11206
- const [tree3, setTree] = useState2();
11207
- useEffect5(() => {
11208
- return model?.subscribe(() => {
11209
- const tree4 = mapGraphToTreeData(model);
11210
- setTree(tree4);
11211
- }, true);
11212
- }, [
11213
- model
11214
- ]);
11215
- const context = useRef3(null);
11216
- useEffect5(() => {
11217
- if (context.current?.size) {
11218
- const { width, height } = context.current.size;
11219
- const size = Math.min(width, height);
11220
- const radius = size * 0.4;
11221
- const options = {
11222
- // TODO(burdon): Type.
11223
- label: (d) => d.label ?? d.id,
11224
- width,
11225
- height,
11226
- radius,
11227
- marginLeft: (width - radius * 2) / 2,
11228
- marginRight: (width - radius * 2) / 2,
11229
- marginTop: (height - radius * 2) / 2,
11230
- marginBottom: (height - radius * 2) / 2,
11231
- slots: defaultTreeLayoutSlots
11232
- };
11233
- if (tree3) {
11234
- const renderer = renderers.get(variant);
11235
- renderer?.(context.current.svg, tree3, options);
11378
+ // src/components/Tree/layout/RadialTree.tsx
11379
+ var TRANSITION_MS3 = 350;
11380
+ var RadialTree = ({ classNames, data, label = (d) => d.label ?? d.id, slots = defaultTreeLayoutSlots, r = 4, padding = 80, initialCollapsed, cluster: cluster2 = false, onNodeClick, onNodeHover }) => {
11381
+ const svgRef = useRef5(null);
11382
+ const { setRef, width, height } = useContainerSize();
11383
+ const [collapsed, setCollapsed] = useState4(() => new Set(initialCollapsed ?? []));
11384
+ const toggle = useCallback3((id) => {
11385
+ setCollapsed((prev) => {
11386
+ const next = new Set(prev);
11387
+ if (next.has(id)) {
11388
+ next.delete(id);
11389
+ } else {
11390
+ next.add(id);
11236
11391
  }
11392
+ return next;
11393
+ });
11394
+ }, []);
11395
+ const handleClickRef = useRef5(() => {
11396
+ });
11397
+ handleClickRef.current = (node) => {
11398
+ onNodeClick?.(node);
11399
+ if (node.children?.length) {
11400
+ toggle(node.id);
11401
+ }
11402
+ };
11403
+ const handleHoverRef = useRef5(() => {
11404
+ });
11405
+ handleHoverRef.current = (node, event) => onNodeHover?.(node, event);
11406
+ const root = useMemo4(() => buildHierarchy(data, collapsed), [
11407
+ data,
11408
+ collapsed
11409
+ ]);
11410
+ useEffect8(() => {
11411
+ if (!svgRef.current || !width || !height) {
11412
+ return;
11237
11413
  }
11414
+ const radius = Math.max(0, Math.min(width, height) / 2 - padding);
11415
+ renderRadialTree(svgRef.current, root, {
11416
+ radius,
11417
+ r,
11418
+ label,
11419
+ slots,
11420
+ collapsed,
11421
+ cluster: cluster2,
11422
+ onNodeClick: (n) => handleClickRef.current(n),
11423
+ onNodeHover: (n, e) => handleHoverRef.current(n, e)
11424
+ });
11238
11425
  }, [
11239
- context.current,
11240
- tree3
11426
+ root,
11427
+ width,
11428
+ height,
11429
+ r,
11430
+ padding,
11431
+ label,
11432
+ slots,
11433
+ collapsed,
11434
+ cluster2
11241
11435
  ]);
11242
- return /* @__PURE__ */ React5.createElement("div", {
11243
- className: "grow",
11244
- onClick: () => onNodeClick?.()
11245
- }, /* @__PURE__ */ React5.createElement(SVG2.Root, {
11246
- ref: context
11436
+ return /* @__PURE__ */ React7.createElement("div", {
11437
+ ref: setRef,
11438
+ className: mx2("dx-expander relative", classNames)
11439
+ }, width > 0 && height > 0 && /* @__PURE__ */ React7.createElement("svg", {
11440
+ ref: svgRef,
11441
+ xmlns: "http://www.w3.org/2000/svg",
11442
+ width,
11443
+ height,
11444
+ viewBox: `${-width / 2} ${-height / 2} ${width} ${height}`
11247
11445
  }));
11248
11446
  };
11447
+ var renderRadialTree = (svgElement, root, options) => {
11448
+ const { radius, r, label, slots, collapsed, cluster: cluster2, onNodeClick, onNodeHover } = options;
11449
+ const svg = select3(svgElement);
11450
+ const layout = cluster2 ? d3Cluster() : d3Tree();
11451
+ layout.size([
11452
+ 2 * Math.PI,
11453
+ radius
11454
+ ]).separation((a, b) => (a.parent === b.parent ? 1 : 2) / Math.max(1, a.depth))(root);
11455
+ const g = svg.selectAll("g.dx-radial-root").data([
11456
+ null
11457
+ ]).join("g").classed("dx-radial-root", true);
11458
+ const linksLayer = g.selectAll("g.dx-radial-links").data([
11459
+ null
11460
+ ]).join("g").classed("dx-radial-links", true);
11461
+ const nodesLayer = g.selectAll("g.dx-radial-nodes").data([
11462
+ null
11463
+ ]).join("g").classed("dx-radial-nodes", true);
11464
+ const linkPath = linkRadial().angle((d) => d.x).radius((d) => d.y);
11465
+ linksLayer.selectAll("path").data(root.links(), (d) => `${d.source.data.id}->${d.target.data.id}`).join((enter) => enter.append("path").attr("class", slots.path ?? "").attr("fill", "none").attr("opacity", 0), (update) => update, (exit) => exit.transition().duration(TRANSITION_MS3).attr("opacity", 0).remove()).transition().duration(TRANSITION_MS3).attr("opacity", 1).attr("d", linkPath);
11466
+ const node = nodesLayer.selectAll("g.dx-radial-node").data(root.descendants(), (d) => d.data.id);
11467
+ const nodeEnter = node.enter().append("g").classed("dx-radial-node", true).attr("opacity", 0).attr("transform", (d) => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`).style("cursor", (d) => d.data.children?.length ? "pointer" : "default").on("click", (_, d) => onNodeClick(d.data));
11468
+ nodeEnter.append("circle").attr("r", r).on("pointerenter", (event, d) => onNodeHover(d.data, event)).on("pointerleave", (event) => onNodeHover(null, event));
11469
+ nodeEnter.append("text").attr("dy", "0.32em").attr("paint-order", "stroke").text((d) => label(d.data));
11470
+ const nodeMerge = nodeEnter.merge(node);
11471
+ nodeMerge.transition().duration(TRANSITION_MS3).attr("opacity", 1).attr("transform", (d) => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`);
11472
+ nodeMerge.select("circle").attr("class", (d) => {
11473
+ const collapsedHere = isCollapsed(d.data, collapsed);
11474
+ const leaf = isLeaf(d.data);
11475
+ return [
11476
+ slots.node ?? "",
11477
+ collapsedHere ? "dx-collapsed" : leaf ? "dx-leaf" : "dx-branch"
11478
+ ].filter(Boolean).join(" ");
11479
+ }).attr("r", r).style("fill", (d) => isLeaf(d.data) ? getNodeFillForObject(d.data.data) : null);
11480
+ nodeMerge.select("text").attr("class", slots.text ?? "").attr("transform", (d) => d.x >= Math.PI ? "rotate(180)" : null).attr("x", (d) => d.x < Math.PI === !d.children ? r + 4 : -(r + 4)).attr("text-anchor", (d) => d.x < Math.PI === !d.children ? "start" : "end").text((d) => label(d.data));
11481
+ node.exit().transition().duration(TRANSITION_MS3).attr("opacity", 0).remove();
11482
+ };
11249
11483
 
11250
- // src/hooks/useGraphModel.ts
11251
- import { useEffect as useEffect6, useState as useState3 } from "react";
11252
- import { Capabilities } from "@dxos/app-framework";
11253
- import { useCapability } from "@dxos/app-framework/ui";
11254
- import { SpaceGraphModel as SpaceGraphModel2 } from "@dxos/schema";
11255
- var useGraphModel = (space, filter, options, queue) => {
11256
- const registry = useCapability(Capabilities.AtomRegistry);
11257
- const [model, setModel] = useState3(void 0);
11258
- useEffect6(() => {
11259
- if (!space) {
11260
- setModel(void 0);
11261
- return;
11484
+ // src/components/Tree/layout/TidyTree.tsx
11485
+ import { curveBumpX, link as d3Link, select as select4, tree as d3Tree2 } from "d3";
11486
+ import React8, { useCallback as useCallback4, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef6, useState as useState5 } from "react";
11487
+ import { mx as mx3 } from "@dxos/ui-theme";
11488
+ var TRANSITION_MS4 = 350;
11489
+ var TidyTree = ({ classNames, data, label = (d) => d.label ?? d.id, slots = defaultTreeLayoutSlots, r = 4, margin = 24, initialCollapsed, onNodeClick }) => {
11490
+ const svgRef = useRef6(null);
11491
+ const { setRef, width, height } = useContainerSize();
11492
+ const [collapsed, setCollapsed] = useState5(() => new Set(initialCollapsed ?? []));
11493
+ const toggle = useCallback4((id) => {
11494
+ setCollapsed((prev) => {
11495
+ const next = new Set(prev);
11496
+ if (next.has(id)) {
11497
+ next.delete(id);
11498
+ } else {
11499
+ next.add(id);
11500
+ }
11501
+ return next;
11502
+ });
11503
+ }, []);
11504
+ const handleClickRef = useRef6(() => {
11505
+ });
11506
+ handleClickRef.current = (node) => {
11507
+ onNodeClick?.(node);
11508
+ if (node.children?.length) {
11509
+ toggle(node.id);
11262
11510
  }
11263
- const newModel = new SpaceGraphModel2(registry);
11264
- void newModel.open(space.db, queue);
11265
- setModel(newModel);
11266
- return () => {
11267
- setModel(void 0);
11268
- void newModel.close();
11269
- };
11270
- }, [
11271
- space,
11272
- registry,
11273
- queue
11511
+ };
11512
+ const root = useMemo5(() => buildHierarchy(data, collapsed), [
11513
+ data,
11514
+ collapsed
11274
11515
  ]);
11275
- useEffect6(() => {
11276
- model?.setFilter(filter).setOptions(options);
11516
+ useEffect9(() => {
11517
+ if (!svgRef.current || !width || !height) {
11518
+ return;
11519
+ }
11520
+ renderTidyTree(svgRef.current, root, {
11521
+ width,
11522
+ height,
11523
+ r,
11524
+ margin,
11525
+ label,
11526
+ slots,
11527
+ collapsed,
11528
+ onNodeClick: (n) => handleClickRef.current(n)
11529
+ });
11277
11530
  }, [
11278
- model,
11279
- filter,
11280
- options
11531
+ root,
11532
+ width,
11533
+ height,
11534
+ r,
11535
+ margin,
11536
+ label,
11537
+ slots,
11538
+ collapsed
11281
11539
  ]);
11282
- return model;
11540
+ return /* @__PURE__ */ React8.createElement("div", {
11541
+ ref: setRef,
11542
+ className: mx3("dx-expander relative", classNames)
11543
+ }, width > 0 && height > 0 && /* @__PURE__ */ React8.createElement("svg", {
11544
+ ref: svgRef,
11545
+ xmlns: "http://www.w3.org/2000/svg",
11546
+ width,
11547
+ height,
11548
+ viewBox: `${-width / 2} ${-height / 2} ${width} ${height}`
11549
+ }));
11283
11550
  };
11284
-
11285
- // src/ExplorerPlugin.tsx
11286
- import * as Effect from "effect/Effect";
11287
- import * as Option from "effect/Option";
11288
- import { Plugin } from "@dxos/app-framework";
11289
- import { AppPlugin } from "@dxos/app-toolkit";
11290
- import { Annotation, Type as Type3 } from "@dxos/echo";
11291
- import { Operation } from "@dxos/operation";
11292
- import { SpaceOperation } from "@dxos/plugin-space/operations";
11293
- import { ViewModel } from "@dxos/schema";
11294
- import { ReactSurface } from "#capabilities";
11295
- import { meta as meta3 } from "#meta";
11296
- import { ExplorerAction, Graph as Graph2 } from "#types";
11297
-
11298
- // src/translations.ts
11299
- import { Type as Type2 } from "@dxos/echo";
11300
- import { translations as componentsTranslations } from "@dxos/react-ui-components";
11301
- import { meta as meta2 } from "#meta";
11302
- import { Graph } from "#types";
11303
- var translations = [
11304
- {
11305
- "en-US": {
11306
- [Type2.getTypename(Graph.Graph)]: {
11307
- "typename.label": "Explorer",
11308
- "typename.label_zero": "Explorers",
11309
- "typename.label_one": "Explorer",
11310
- "typename.label_other": "Explorers",
11311
- "object-name.placeholder": "New explorer",
11312
- "add-object.label": "Add explorer",
11313
- "rename-object.label": "Rename explorer",
11314
- "delete-object.label": "Delete explorer",
11315
- "object-deleted.label": "Explorer deleted"
11316
- },
11317
- [meta2.id]: {
11318
- "plugin.name": "Explorer",
11319
- "object-title.label": "Title"
11320
- }
11551
+ var renderTidyTree = (svgElement, root, options) => {
11552
+ const { width, height, r, margin, label, slots, collapsed, onNodeClick } = options;
11553
+ const svg = select4(svgElement);
11554
+ const dx = 18;
11555
+ const dy = Math.max(60, (width - margin * 2) / Math.max(1, root.height + 1));
11556
+ d3Tree2().nodeSize([
11557
+ dx,
11558
+ dy
11559
+ ])(root);
11560
+ let x0 = Infinity;
11561
+ let x1 = -x0;
11562
+ root.each((d) => {
11563
+ if (d.x > x1) {
11564
+ x1 = d.x;
11321
11565
  }
11322
- },
11323
- ...componentsTranslations
11324
- ];
11566
+ if (d.x < x0) {
11567
+ x0 = d.x;
11568
+ }
11569
+ });
11570
+ const treeWidth = width - margin * 2;
11571
+ const treeHeight = x1 - x0;
11572
+ const scaleY = treeHeight > 0 ? Math.min(1, (height - margin * 2) / treeHeight) : 1;
11573
+ const offsetX = -treeWidth / 2;
11574
+ const offsetY = -(x0 + x1) / 2;
11575
+ const g = svg.selectAll("g.dx-tidy-root").data([
11576
+ null
11577
+ ]).join("g").classed("dx-tidy-root", true);
11578
+ const linksLayer = g.selectAll("g.dx-tidy-links").data([
11579
+ null
11580
+ ]).join("g").classed("dx-tidy-links", true);
11581
+ const nodesLayer = g.selectAll("g.dx-tidy-nodes").data([
11582
+ null
11583
+ ]).join("g").classed("dx-tidy-nodes", true);
11584
+ const linkPath = d3Link(curveBumpX).x((d) => offsetX + d.y).y((d) => (d.x + offsetY) * scaleY);
11585
+ linksLayer.selectAll("path").data(root.links(), (d) => `${d.source.data.id}->${d.target.data.id}`).join((enter) => enter.append("path").attr("class", slots.path ?? "").attr("fill", "none").attr("opacity", 0), (update) => update, (exit) => exit.transition().duration(TRANSITION_MS4).attr("opacity", 0).remove()).transition().duration(TRANSITION_MS4).attr("opacity", 1).attr("d", linkPath);
11586
+ const node = nodesLayer.selectAll("g.dx-tidy-node").data(root.descendants(), (d) => d.data.id);
11587
+ const nodeEnter = node.enter().append("g").classed("dx-tidy-node", true).attr("transform", (d) => `translate(${offsetX + d.y},${(d.x + offsetY) * scaleY})`).attr("opacity", 0).style("cursor", (d) => d.data.children?.length ? "pointer" : "default").on("click", (_, d) => onNodeClick(d.data));
11588
+ nodeEnter.append("circle").attr("r", r);
11589
+ nodeEnter.append("text").attr("dy", "0.32em").attr("x", (d) => d.children ? -(r + 4) : r + 4).attr("text-anchor", (d) => d.children ? "end" : "start").text((d) => label(d.data));
11590
+ const nodeMerge = nodeEnter.merge(node);
11591
+ nodeMerge.transition().duration(TRANSITION_MS4).attr("opacity", 1).attr("transform", (d) => `translate(${offsetX + d.y},${(d.x + offsetY) * scaleY})`);
11592
+ nodeMerge.select("circle").attr("class", (d) => {
11593
+ const collapsedHere = isCollapsed(d.data, collapsed);
11594
+ const leaf = isLeaf(d.data);
11595
+ return [
11596
+ slots.node ?? "",
11597
+ collapsedHere ? "dx-collapsed" : leaf ? "dx-leaf" : "dx-branch"
11598
+ ].filter(Boolean).join(" ");
11599
+ }).attr("r", r);
11600
+ nodeMerge.select("text").attr("class", slots.text ?? "").attr("x", (d) => d.children ? -(r + 4) : r + 4).attr("text-anchor", (d) => d.children ? "end" : "start").text((d) => label(d.data));
11601
+ node.exit().each(function() {
11602
+ select4(this).interrupt();
11603
+ }).transition().duration(TRANSITION_MS4).attr("opacity", 0).remove();
11604
+ };
11325
11605
 
11326
- // src/ExplorerPlugin.tsx
11327
- var ExplorerPlugin = Plugin.define(meta3).pipe(AppPlugin.addMetadataModule({
11328
- metadata: {
11329
- id: Type3.getTypename(Graph2.Graph),
11330
- metadata: {
11331
- icon: Annotation.IconAnnotation.get(Graph2.Graph).pipe(Option.getOrThrow).icon,
11332
- iconHue: Annotation.IconAnnotation.get(Graph2.Graph).pipe(Option.getOrThrow).hue ?? "white",
11333
- inputSchema: ExplorerAction.GraphProps,
11334
- createObject: (props, options) => Effect.gen(function* () {
11335
- const object = yield* Effect.promise(async () => {
11336
- const { view } = await ViewModel.makeFromDatabase({
11337
- db: options.db,
11338
- typename: props.typename
11339
- });
11340
- return Graph2.make({
11341
- name: props.name,
11342
- view
11343
- });
11606
+ // src/components/Tree/Tree.tsx
11607
+ var Tree = ({ classNames, data, edges, variant = "tidy", label, slots, initialCollapsed, onNodeClick, onNodeHover }) => {
11608
+ return useMemo6(() => {
11609
+ switch (variant) {
11610
+ case "tidy":
11611
+ return /* @__PURE__ */ React9.createElement(TidyTree, {
11612
+ classNames,
11613
+ data,
11614
+ label,
11615
+ slots,
11616
+ initialCollapsed,
11617
+ onNodeClick
11344
11618
  });
11345
- return yield* Operation.invoke(SpaceOperation.AddObject, {
11346
- object,
11347
- target: options.target,
11348
- hidden: true,
11349
- targetNodeId: options.targetNodeId
11619
+ case "radial":
11620
+ return /* @__PURE__ */ React9.createElement(RadialTree, {
11621
+ classNames,
11622
+ data,
11623
+ label,
11624
+ slots,
11625
+ initialCollapsed,
11626
+ onNodeClick,
11627
+ onNodeHover
11628
+ });
11629
+ case "edge":
11630
+ return /* @__PURE__ */ React9.createElement(HierarchicalEdgeBundling, {
11631
+ classNames,
11632
+ data,
11633
+ edges: edges ?? [],
11634
+ label,
11635
+ slots,
11636
+ onNodeHover
11350
11637
  });
11351
- })
11352
11638
  }
11353
- }
11354
- }), AppPlugin.addSchemaModule({
11355
- schema: [
11356
- Graph2.Graph
11357
- ]
11358
- }), AppPlugin.addSurfaceModule({
11359
- activate: ReactSurface
11360
- }), AppPlugin.addTranslationsModule({
11361
- translations
11362
- }), Plugin.make);
11639
+ }, [
11640
+ variant,
11641
+ classNames,
11642
+ data,
11643
+ edges,
11644
+ label,
11645
+ slots,
11646
+ initialCollapsed,
11647
+ onNodeClick,
11648
+ onNodeHover
11649
+ ]);
11650
+ };
11363
11651
  export {
11652
+ CanvasForceGraph,
11364
11653
  Chart,
11365
- D3ForceGraph,
11366
- ExplorerPlugin,
11367
11654
  ForceGraph,
11368
11655
  Globe,
11656
+ HierarchicalEdgeBundling,
11657
+ Lattice,
11658
+ RadialTree,
11659
+ TidyTree,
11369
11660
  Tree,
11661
+ buildHierarchy,
11370
11662
  defaultTreeLayoutSlots,
11371
- meta,
11372
- useGraphModel
11663
+ isCollapsed,
11664
+ isLeaf,
11665
+ treeTypeToTreeNode
11373
11666
  };
11374
11667
  //# sourceMappingURL=index.mjs.map