@dxos/plugin-explorer 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934

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 (196) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/neutral/ExplorerArticle-EW2MBCRK.mjs +141 -0
  3. package/dist/lib/neutral/ExplorerArticle-EW2MBCRK.mjs.map +7 -0
  4. package/dist/lib/neutral/ExplorerPlugin.mjs +10 -0
  5. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  6. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  7. package/dist/lib/{browser/types/index.mjs → neutral/chunk-7SPMPHRS.mjs} +10 -8
  8. package/dist/lib/neutral/chunk-7SPMPHRS.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-GRJXLL4Z.mjs +25 -0
  10. package/dist/lib/neutral/chunk-GRJXLL4Z.mjs.map +7 -0
  11. package/dist/lib/{browser/chunk-LSUP47BZ.mjs → neutral/chunk-HPIS2WXY.mjs} +1 -1
  12. package/dist/lib/{browser/chunk-LSUP47BZ.mjs.map → neutral/chunk-HPIS2WXY.mjs.map} +2 -2
  13. package/dist/lib/{browser → neutral/components}/index.mjs +661 -417
  14. package/dist/lib/{node-esm → neutral/components}/index.mjs.map +4 -4
  15. package/dist/lib/neutral/containers/index.mjs +9 -0
  16. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  17. package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
  18. package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
  19. package/dist/lib/neutral/hooks/index.mjs +45 -0
  20. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  21. package/dist/lib/neutral/index.mjs +14 -0
  22. package/dist/lib/neutral/meta.json +1 -0
  23. package/dist/lib/{browser → neutral}/meta.mjs +1 -1
  24. package/dist/lib/neutral/plugin.mjs +12 -0
  25. package/dist/lib/neutral/plugin.mjs.map +7 -0
  26. package/dist/lib/neutral/react-surface-APBW2VQG.mjs +26 -0
  27. package/dist/lib/neutral/react-surface-APBW2VQG.mjs.map +7 -0
  28. package/dist/lib/neutral/testing.mjs +8 -0
  29. package/dist/lib/neutral/testing.mjs.map +7 -0
  30. package/dist/lib/neutral/translations.mjs +33 -0
  31. package/dist/lib/neutral/translations.mjs.map +7 -0
  32. package/dist/lib/neutral/types/index.mjs +10 -0
  33. package/dist/lib/neutral/types/index.mjs.map +7 -0
  34. package/dist/types/data/cities.d.ts +4 -4
  35. package/dist/types/data/cities.d.ts.map +1 -1
  36. package/dist/types/data/countries-110m.d.ts +19 -22
  37. package/dist/types/data/countries-110m.d.ts.map +1 -1
  38. package/dist/types/src/ExplorerPlugin.d.ts +1 -0
  39. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  40. package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
  41. package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  43. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  44. package/dist/types/src/capabilities/index.d.ts +6 -0
  45. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  46. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  47. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  48. package/dist/types/src/components/Chart/Chart.stories.d.ts +4 -1
  49. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  51. package/dist/types/src/components/Globe/Globe.stories.d.ts +5 -2
  52. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  53. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
  54. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
  55. package/dist/types/src/components/Graph/{D3ForceGraph.stories.d.ts → CanvasForceGraph.stories.d.ts} +3 -7
  56. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
  58. package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
  59. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +3 -1
  60. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  61. package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +1 -1
  62. package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
  63. package/dist/types/src/components/Graph/index.d.ts +1 -1
  64. package/dist/types/src/components/Graph/index.d.ts.map +1 -1
  65. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts +21 -0
  66. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts.map +1 -0
  67. package/dist/types/src/components/Tree/Tree.d.ts +20 -23
  68. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  69. package/dist/types/src/components/Tree/Tree.stories.d.ts +5 -12
  70. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  71. package/dist/types/src/components/Tree/index.d.ts +3 -0
  72. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  73. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts +35 -2
  74. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  75. package/dist/types/src/components/Tree/layout/RadialTree.d.ts +35 -2
  76. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  77. package/dist/types/src/components/Tree/layout/TidyTree.d.ts +24 -2
  78. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  79. package/dist/types/src/components/Tree/layout/hierarchy.d.ts +17 -0
  80. package/dist/types/src/components/Tree/layout/hierarchy.d.ts.map +1 -0
  81. package/dist/types/src/components/Tree/layout/index.d.ts +5 -4
  82. package/dist/types/src/components/Tree/layout/index.d.ts.map +1 -1
  83. package/dist/types/src/components/Tree/layout/slots.d.ts +7 -0
  84. package/dist/types/src/components/Tree/layout/slots.d.ts.map +1 -0
  85. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts +15 -0
  86. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts.map +1 -0
  87. package/dist/types/src/components/Tree/space-graph-adapter.d.ts +32 -0
  88. package/dist/types/src/components/Tree/space-graph-adapter.d.ts.map +1 -0
  89. package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -1
  90. package/dist/types/src/components/Tree/testing/index.d.ts +1 -0
  91. package/dist/types/src/components/Tree/testing/index.d.ts.map +1 -1
  92. package/dist/types/src/components/Tree/testing/relations.d.ts +47 -0
  93. package/dist/types/src/components/Tree/testing/relations.d.ts.map +1 -0
  94. package/dist/types/src/components/Tree/types/tree.d.ts +6 -6
  95. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  96. package/dist/types/src/components/Tree/types/types.d.ts +14 -4
  97. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  98. package/dist/types/src/components/plot.d.ts.map +1 -1
  99. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts +8 -0
  100. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts.map +1 -0
  101. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts +24 -0
  102. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts.map +1 -0
  103. package/dist/types/src/containers/ExplorerArticle/index.d.ts +2 -0
  104. package/dist/types/src/containers/ExplorerArticle/index.d.ts.map +1 -0
  105. package/dist/types/src/containers/index.d.ts +1 -1
  106. package/dist/types/src/containers/index.d.ts.map +1 -1
  107. package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
  108. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  109. package/dist/types/src/index.d.ts +1 -3
  110. package/dist/types/src/index.d.ts.map +1 -1
  111. package/dist/types/src/plugin.d.ts +3 -0
  112. package/dist/types/src/plugin.d.ts.map +1 -0
  113. package/dist/types/src/testing.d.ts +2 -0
  114. package/dist/types/src/testing.d.ts.map +1 -0
  115. package/dist/types/src/translations.d.ts +17 -17
  116. package/dist/types/src/translations.d.ts.map +1 -1
  117. package/dist/types/src/types/Graph.d.ts +3 -4
  118. package/dist/types/src/types/Graph.d.ts.map +1 -1
  119. package/dist/types/tsconfig.tsbuildinfo +1 -1
  120. package/package.json +100 -62
  121. package/src/ExplorerPlugin.test.ts +26 -0
  122. package/src/ExplorerPlugin.tsx +6 -35
  123. package/src/capabilities/create-object.ts +36 -0
  124. package/src/capabilities/index.ts +1 -0
  125. package/src/capabilities/react-surface.tsx +6 -4
  126. package/src/components/Chart/Chart.stories.tsx +14 -20
  127. package/src/components/Globe/Globe.stories.tsx +17 -19
  128. package/src/components/Graph/CanvasForceGraph.stories.tsx +83 -0
  129. package/src/components/Graph/CanvasForceGraph.tsx +124 -0
  130. package/src/components/Graph/ForceGraph.stories.tsx +69 -37
  131. package/src/components/Graph/ForceGraph.tsx +104 -85
  132. package/src/components/Graph/index.ts +1 -1
  133. package/src/components/Tree/EdgeBundling.stories.tsx +144 -0
  134. package/src/components/Tree/Tree.stories.tsx +17 -38
  135. package/src/components/Tree/Tree.tsx +69 -100
  136. package/src/components/Tree/index.ts +3 -0
  137. package/src/components/Tree/layout/HierarchicalEdgeBundling.tsx +277 -0
  138. package/src/components/Tree/layout/RadialTree.tsx +237 -0
  139. package/src/components/Tree/layout/TidyTree.tsx +246 -0
  140. package/src/components/Tree/layout/hierarchy.ts +32 -0
  141. package/src/components/Tree/layout/index.ts +5 -5
  142. package/src/components/Tree/layout/slots.ts +19 -0
  143. package/src/components/Tree/layout/useContainerSize.ts +43 -0
  144. package/src/components/Tree/space-graph-adapter.ts +96 -0
  145. package/src/components/Tree/testing/generator.ts +1 -1
  146. package/src/components/Tree/testing/index.ts +1 -0
  147. package/src/components/Tree/testing/relations.ts +182 -0
  148. package/src/components/Tree/types/tree.test.ts +1 -1
  149. package/src/components/Tree/types/tree.ts +9 -9
  150. package/src/components/Tree/types/types.ts +38 -29
  151. package/src/containers/ExplorerArticle/ExplorerArticle.stories.tsx +119 -0
  152. package/src/containers/ExplorerArticle/ExplorerArticle.tsx +153 -0
  153. package/src/containers/ExplorerArticle/index.ts +5 -0
  154. package/src/containers/index.ts +1 -1
  155. package/src/hooks/useGraphModel.ts +10 -6
  156. package/src/index.ts +1 -4
  157. package/src/plugin.ts +9 -0
  158. package/src/testing.ts +7 -0
  159. package/src/translations.ts +1 -1
  160. package/src/types/ExplorerAction.ts +1 -1
  161. package/src/types/Graph.ts +2 -3
  162. package/dist/lib/browser/index.mjs.map +0 -7
  163. package/dist/lib/browser/meta.json +0 -1
  164. package/dist/lib/browser/types/index.mjs.map +0 -7
  165. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs +0 -26
  166. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs.map +0 -7
  167. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  168. package/dist/lib/node-esm/index.mjs +0 -11375
  169. package/dist/lib/node-esm/meta.json +0 -1
  170. package/dist/lib/node-esm/meta.mjs +0 -9
  171. package/dist/lib/node-esm/types/index.mjs +0 -71
  172. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  173. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -19
  174. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
  175. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
  176. package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
  177. package/dist/types/src/components/Graph/testing.d.ts +0 -14
  178. package/dist/types/src/components/Graph/testing.d.ts.map +0 -1
  179. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +0 -6
  180. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +0 -1
  181. package/dist/types/src/containers/ExplorerContainer/index.d.ts +0 -2
  182. package/dist/types/src/containers/ExplorerContainer/index.d.ts.map +0 -1
  183. package/src/components/Graph/D3ForceGraph.stories.tsx +0 -83
  184. package/src/components/Graph/D3ForceGraph.tsx +0 -108
  185. package/src/components/Graph/testing.ts +0 -58
  186. package/src/components/Tree/layout/HierarchicalEdgeBundling.ts +0 -162
  187. package/src/components/Tree/layout/RadialTree.ts +0 -94
  188. package/src/components/Tree/layout/TidyTree.ts +0 -101
  189. package/src/containers/ExplorerContainer/ExplorerContainer.tsx +0 -53
  190. package/src/containers/ExplorerContainer/index.ts +0 -5
  191. /package/dist/lib/{browser/chunk-J5LGTIGS.mjs.map → neutral/ExplorerPlugin.mjs.map} +0 -0
  192. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  193. /package/dist/lib/{browser/meta.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  194. /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/index.mjs.map} +0 -0
  195. /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
  196. /package/src/components/Graph/{adapter.ts → graph-adapter.ts} +0 -0
@@ -1,7 +1,4 @@
1
- import {
2
- meta
3
- } from "./chunk-LSUP47BZ.mjs";
4
- import "./chunk-J5LGTIGS.mjs";
1
+ import "../chunk-J5LGTIGS.mjs";
5
2
 
6
3
  // src/components/Chart/Chart.tsx
7
4
  import * as Plot from "@observablehq/plot";
@@ -10825,45 +10822,158 @@ 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/ui-theme";
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";
10833
10939
  import { GraphForceProjector, SVG } from "@dxos/react-ui-graph";
10834
- import { composable, composableProps, getHashStyles } from "@dxos/ui-theme";
10940
+ import { composable as composable2, composableProps as composableProps2, getHashStyles } from "@dxos/ui-theme";
10835
10941
  import "@dxos/react-ui-graph/styles/graph.css";
10836
10942
  var EMPTY_ATOM = Atom.make({
10837
10943
  nodes: [],
10838
10944
  edges: []
10839
10945
  });
10840
- var D3ForceGraph = composable(({ model, selection: _selection, grid, drag, ...props }, forwardedRef) => {
10946
+ var ForceGraph = composable2(({ model, selection: selectionProp, grid, drag, onInspect, ...props }, forwardedRef) => {
10841
10947
  useAtomValue(model?.graphAtom ?? EMPTY_ATOM);
10842
- const svgRef = useRef(null);
10843
- const projector = useMemo(() => {
10948
+ const graph = useRef2(null);
10949
+ const selection = useMemo(() => selectionProp ?? new SelectionModel(), [
10950
+ selectionProp
10951
+ ]);
10952
+ useEffect4(() => {
10953
+ const unsubscribe = selection.subscribe(() => graph.current?.repaint());
10954
+ return unsubscribe;
10955
+ }, [
10956
+ selection
10957
+ ]);
10958
+ const svgRef = useRef2(null);
10959
+ const [projector, setProjector] = useState2();
10960
+ useEffect4(() => {
10844
10961
  if (svgRef.current) {
10845
- return new GraphForceProjector(svgRef.current, {
10962
+ setProjector(new GraphForceProjector(svgRef.current, {
10846
10963
  attributes: {
10847
- linkForce: (edge) => {
10848
- return edge.data?.object?.active !== false;
10849
- }
10964
+ // TODO(burdon): Check type (currently assumes Employee property).
10965
+ // Edge shouldn't contribute to force if it's not active.
10966
+ linkForce: (edge) => edge.data?.object?.active !== false
10850
10967
  },
10851
10968
  forces: {
10852
10969
  point: {
10853
10970
  strength: 0.01
10854
10971
  }
10855
10972
  }
10856
- });
10973
+ }));
10857
10974
  }
10858
10975
  }, []);
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) => {
10976
+ const handleSelect = useCallback2((node) => {
10867
10977
  if (selection.contains(node.id)) {
10868
10978
  selection.remove(node.id);
10869
10979
  } else {
@@ -10872,29 +10982,27 @@ var D3ForceGraph = composable(({ model, selection: _selection, grid, drag, ...pr
10872
10982
  }, [
10873
10983
  selection
10874
10984
  ]);
10875
- return /* @__PURE__ */ React3.createElement("div", {
10876
- ...composableProps(props, {
10985
+ return /* @__PURE__ */ React4.createElement("div", {
10986
+ ...composableProps2(props, {
10877
10987
  classNames: "dx-container"
10878
10988
  }),
10879
10989
  ref: forwardedRef
10880
- }, /* @__PURE__ */ React3.createElement(SVG.Root, {
10990
+ }, /* @__PURE__ */ React4.createElement(SVG.Root, {
10881
10991
  ref: svgRef
10882
- }, /* @__PURE__ */ React3.createElement(SVG.Markers, null), grid && /* @__PURE__ */ React3.createElement(SVG.Grid, {
10992
+ }, /* @__PURE__ */ React4.createElement(SVG.Markers, null), grid && /* @__PURE__ */ React4.createElement(SVG.Grid, {
10883
10993
  axis: true
10884
- }), /* @__PURE__ */ React3.createElement(SVG.Zoom, {
10994
+ }), /* @__PURE__ */ React4.createElement(SVG.Zoom, {
10885
10995
  extent: [
10886
10996
  1 / 2,
10887
10997
  2
10888
10998
  ]
10889
- }, /* @__PURE__ */ React3.createElement(SVG.Graph, {
10890
- drag,
10999
+ }, /* @__PURE__ */ React4.createElement(SVG.Graph, {
10891
11000
  ref: graph,
11001
+ drag,
10892
11002
  model,
10893
11003
  projector,
10894
11004
  labels: {
10895
- text: (node) => {
10896
- return node.data?.data.label ?? node.id;
10897
- }
11005
+ text: (node) => node.data?.data.label ?? node.id
10898
11006
  },
10899
11007
  attributes: {
10900
11008
  node: (node) => {
@@ -10909,217 +11017,402 @@ var D3ForceGraph = composable(({ model, selection: _selection, grid, drag, ...pr
10909
11017
  };
10910
11018
  }
10911
11019
  },
10912
- onSelect: handleSelect
11020
+ onSelect: handleSelect,
11021
+ onInspect
10913
11022
  }))));
10914
11023
  });
10915
11024
 
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";
11025
+ // src/components/Tree/Tree.tsx
11026
+ import React8, { useMemo as useMemo5 } from "react";
10922
11027
 
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
- }
11028
+ // src/components/Tree/layout/HierarchicalEdgeBundling.tsx
11029
+ import { cluster, curveBundle, hierarchy, lineRadial, select } from "d3";
11030
+ import React5, { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3 } from "react";
11031
+ import { mx } from "@dxos/ui-theme";
11032
+
11033
+ // src/components/Tree/layout/slots.ts
11034
+ var defaultTreeLayoutSlots = {
11035
+ // Cursor + transition so the hover swap reads clearly; SVG circles support the `:hover` pseudo-class
11036
+ // via Tailwind variants exactly like HTML elements.
11037
+ node: "fill-blue-600 hover:fill-orange-500 cursor-pointer transition-colors",
11038
+ // 0.5px is fine on a white background, but on a dark Storybook background the lines disappear.
11039
+ // Use stroke-1 with opacity 50% so they read in both themes; dx-bundle-dim/out/in further tune on hover.
11040
+ path: "fill-none stroke-blue-500/50 stroke-[1px] dark:stroke-blue-400/60",
11041
+ text: "fill-neutral-700 dark:fill-neutral-300 text-xs hover:fill-orange-500 cursor-pointer transition-colors"
10949
11042
  };
10950
11043
 
10951
- // src/components/Graph/ForceGraph.tsx
10952
- var ForceGraph = ({ model, match }) => {
10953
- const { ref, width, height } = useResizeDetector3({
10954
- refreshRate: 200
11044
+ // src/components/Tree/layout/useContainerSize.ts
11045
+ import { useEffect as useEffect5, useState as useState3 } from "react";
11046
+ var useContainerSize = () => {
11047
+ const [el, setEl] = useState3(null);
11048
+ const [size, setSize] = useState3({
11049
+ width: 0,
11050
+ height: 0
10955
11051
  });
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));
11052
+ useEffect5(() => {
11053
+ if (!el) {
11054
+ return;
11055
+ }
11056
+ const rect = el.getBoundingClientRect();
11057
+ setSize({
11058
+ width: rect.width,
11059
+ height: rect.height
10964
11060
  });
11061
+ const observer = new ResizeObserver((entries) => {
11062
+ const entry = entries[0];
11063
+ if (!entry) {
11064
+ return;
11065
+ }
11066
+ const { width, height } = entry.contentRect;
11067
+ setSize((prev) => prev.width === width && prev.height === height ? prev : {
11068
+ width,
11069
+ height
11070
+ });
11071
+ });
11072
+ observer.observe(el);
11073
+ return () => observer.disconnect();
10965
11074
  }, [
10966
- model
11075
+ el
10967
11076
  ]);
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);
10971
- }
10972
- return () => {
10973
- forceGraph.current?.pauseAnimation().graphData({
10974
- nodes: [],
10975
- links: []
10976
- });
10977
- forceGraph.current = null;
10978
- };
10979
- }, []);
10980
- useEffect4(() => {
10981
- if (!data || !width || !height || !forceGraph.current) {
11077
+ return {
11078
+ setRef: setEl,
11079
+ width: size.width,
11080
+ height: size.height
11081
+ };
11082
+ };
11083
+
11084
+ // src/components/Tree/layout/HierarchicalEdgeBundling.tsx
11085
+ var TRANSITION_MS = 350;
11086
+ var HierarchicalEdgeBundling = ({ classNames, data, edges = [], label = (d) => d.label ?? d.id, padding = 120, tension = 0.85, slots = defaultTreeLayoutSlots, onNodeHover }) => {
11087
+ const svgRef = useRef3(null);
11088
+ const { setRef, width, height } = useContainerSize();
11089
+ const root = useMemo2(() => buildBundleHierarchy(data, edges), [
11090
+ data,
11091
+ edges
11092
+ ]);
11093
+ const handleHoverRef = useRef3(() => {
11094
+ });
11095
+ handleHoverRef.current = (node, event) => onNodeHover?.(node, event);
11096
+ useEffect6(() => {
11097
+ if (!svgRef.current || !width || !height) {
10982
11098
  return;
10983
11099
  }
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();
11100
+ const radius = Math.max(0, Math.min(width, height) / 2 - padding);
11101
+ renderBundling(svgRef.current, root, {
11102
+ radius,
11103
+ label,
11104
+ slots,
11105
+ tension,
11106
+ onNodeHover: (n, e) => handleHoverRef.current(n, e)
11107
+ });
10989
11108
  }, [
10990
- data,
11109
+ root,
10991
11110
  width,
10992
11111
  height,
10993
- forceGraph.current
11112
+ padding,
11113
+ tension,
11114
+ label,
11115
+ slots
10994
11116
  ]);
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"
11117
+ return /* @__PURE__ */ React5.createElement("div", {
11118
+ ref: setRef,
11119
+ className: mx("dx-expander relative", classNames)
11120
+ }, width > 0 && height > 0 && /* @__PURE__ */ React5.createElement("svg", {
11121
+ ref: svgRef,
11122
+ xmlns: "http://www.w3.org/2000/svg",
11123
+ width,
11124
+ height,
11125
+ viewBox: `${-width / 2} ${-height / 2} ${width} ${height}`
11005
11126
  }));
11006
11127
  };
11007
-
11008
- // 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";
11014
-
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([
11128
+ var buildBundleHierarchy = (data, edges) => {
11129
+ const root = hierarchy(data);
11130
+ const byId = /* @__PURE__ */ new Map();
11131
+ for (const node of root.descendants()) {
11132
+ byId.set(node.data.id, node);
11133
+ node.outgoing = [];
11134
+ node.incoming = [];
11135
+ }
11136
+ for (const edge of edges) {
11137
+ const source = byId.get(edge.source);
11138
+ const target = byId.get(edge.target);
11139
+ if (!source || !target || source === target) {
11140
+ continue;
11141
+ }
11142
+ source.outgoing.push([
11143
+ source,
11144
+ target,
11145
+ edge
11146
+ ]);
11147
+ target.incoming.push([
11148
+ source,
11149
+ target,
11150
+ edge
11151
+ ]);
11152
+ }
11153
+ return root;
11154
+ };
11155
+ var renderBundling = (svgElement, root, options) => {
11156
+ const { radius, tension, label, slots, onNodeHover } = options;
11157
+ const svg = select(svgElement);
11158
+ if (!root.children?.length) {
11159
+ svg.selectAll("g.dx-bundle-root").remove();
11160
+ return;
11161
+ }
11162
+ cluster().size([
11023
11163
  2 * Math.PI,
11024
- radius - padding
11025
- ]);
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;
11164
+ radius
11165
+ ])(root);
11166
+ const g = svg.selectAll("g.dx-bundle-root").data([
11167
+ null
11168
+ ]).join("g").classed("dx-bundle-root", true);
11169
+ const linksLayer = g.selectAll("g.dx-bundle-links").data([
11170
+ null
11171
+ ]).join("g").classed("dx-bundle-links", true);
11172
+ const nodesLayer = g.selectAll("g.dx-bundle-nodes").data([
11173
+ null
11174
+ ]).join("g").classed("dx-bundle-nodes", true);
11175
+ const line = lineRadial().curve(curveBundle.beta(tension)).radius((d) => d.y).angle((d) => d.x);
11176
+ const leaves = root.leaves();
11177
+ const flatEdges = leaves.flatMap((leaf) => leaf.outgoing ?? []);
11178
+ 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() {
11179
+ select(this).interrupt();
11180
+ }).transition().duration(TRANSITION_MS).attr("opacity", 0).remove());
11181
+ paths.each(function(d) {
11182
+ d[0].pathEl = this;
11183
+ }).transition().duration(TRANSITION_MS).attr("opacity", 1).attr("d", ([s, t]) => line(s.path(t)));
11184
+ const labels = nodesLayer.selectAll("g.dx-bundle-leaf").data(leaves, (d) => d.data.id).join((enter) => {
11185
+ const ge = enter.append("g").classed("dx-bundle-leaf", true).attr("opacity", 0);
11186
+ ge.append("text").attr("dy", "0.32em").attr("paint-order", "stroke").style("cursor", "pointer");
11187
+ return ge;
11188
+ }, (update) => update, (exit) => exit.each(function() {
11189
+ select(this).interrupt();
11190
+ }).transition().duration(TRANSITION_MS).attr("opacity", 0).remove());
11191
+ labels.transition().duration(TRANSITION_MS).attr("opacity", 1).attr("transform", (d) => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`);
11192
+ labels.select("text").attr("class", slots.text ?? "").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).each(function(d) {
11193
+ d.text = this;
11194
+ }).text((d) => label(d.data)).on("pointerenter", function(event, d) {
11195
+ onNodeHover(d.data, event);
11196
+ hover(linksLayer, leaves, d, true);
11197
+ }).on("pointerleave", function(event, d) {
11198
+ onNodeHover(null);
11199
+ hover(linksLayer, leaves, d, false);
11033
11200
  });
11034
11201
  };
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);
11043
- }
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 = [];
11202
+ var hover = (linksLayer, leaves, focused, on) => {
11203
+ const outgoing = new Set((focused.outgoing ?? []).map(([, t]) => t));
11204
+ const incoming = new Set((focused.incoming ?? []).map(([s]) => s));
11205
+ linksLayer.selectAll("path").classed("dx-bundle-out", (d) => on && d[0] === focused).classed("dx-bundle-in", (d) => on && d[1] === focused).classed("dx-bundle-dim", (d) => on && d[0] !== focused && d[1] !== focused);
11206
+ for (const leaf of leaves) {
11207
+ if (!leaf.text) {
11208
+ continue;
11057
11209
  }
11210
+ select(leaf.text).classed("dx-bundle-focused", on && leaf === focused).classed("dx-bundle-out-text", on && outgoing.has(leaf)).classed("dx-bundle-in-text", on && incoming.has(leaf));
11058
11211
  }
11059
- return root;
11060
11212
  };
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
- ];
11073
- }
11074
- return clone;
11213
+
11214
+ // src/components/Tree/layout/RadialTree.tsx
11215
+ import { cluster as d3Cluster, linkRadial, select as select2, tree as d3Tree } from "d3";
11216
+ import React6, { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
11217
+ import { mx as mx2 } from "@dxos/ui-theme";
11218
+
11219
+ // src/components/Tree/layout/hierarchy.ts
11220
+ import { hierarchy as d3Hierarchy } from "d3";
11221
+ var buildHierarchy = (data, collapsed = /* @__PURE__ */ new Set()) => {
11222
+ return d3Hierarchy(data, (d) => {
11223
+ if (!d.children?.length) {
11224
+ return void 0;
11225
+ }
11226
+ return collapsed.has(d.id) ? void 0 : d.children;
11227
+ });
11075
11228
  };
11076
- var HierarchicalEdgeBundling_default = HierarchicalEdgeBundling;
11229
+ var isCollapsed = (data, collapsed) => Boolean(data.children?.length) && collapsed.has(data.id);
11230
+ var isLeaf = (data) => !data.children?.length;
11077
11231
 
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,
11232
+ // src/components/Tree/layout/RadialTree.tsx
11233
+ var TRANSITION_MS2 = 350;
11234
+ var RadialTree = ({ classNames, data, label = (d) => d.label ?? d.id, slots = defaultTreeLayoutSlots, r = 4, padding = 80, initialCollapsed, cluster: cluster2 = false, onNodeClick, onNodeHover }) => {
11235
+ const svgRef = useRef4(null);
11236
+ const { setRef, width, height } = useContainerSize();
11237
+ const [collapsed, setCollapsed] = useState4(() => new Set(initialCollapsed ?? []));
11238
+ const toggle = useCallback3((id) => {
11239
+ setCollapsed((prev) => {
11240
+ const next = new Set(prev);
11241
+ if (next.has(id)) {
11242
+ next.delete(id);
11243
+ } else {
11244
+ next.add(id);
11245
+ }
11246
+ return next;
11247
+ });
11248
+ }, []);
11249
+ const handleClickRef = useRef4(() => {
11250
+ });
11251
+ handleClickRef.current = (node) => {
11252
+ onNodeClick?.(node);
11253
+ if (node.children?.length) {
11254
+ toggle(node.id);
11255
+ }
11256
+ };
11257
+ const handleHoverRef = useRef4(() => {
11258
+ });
11259
+ handleHoverRef.current = (node, event) => onNodeHover?.(node, event);
11260
+ const root = useMemo3(() => buildHierarchy(data, collapsed), [
11261
+ data,
11262
+ collapsed
11263
+ ]);
11264
+ useEffect7(() => {
11265
+ if (!svgRef.current || !width || !height) {
11266
+ return;
11267
+ }
11268
+ const radius = Math.max(0, Math.min(width, height) / 2 - padding);
11269
+ renderRadialTree(svgRef.current, root, {
11270
+ radius,
11271
+ r,
11272
+ label,
11273
+ slots,
11274
+ collapsed,
11275
+ cluster: cluster2,
11276
+ onNodeClick: (n) => handleClickRef.current(n),
11277
+ onNodeHover: (n, e) => handleHoverRef.current(n, e)
11278
+ });
11279
+ }, [
11280
+ root,
11281
+ width,
11282
+ height,
11283
+ r,
11284
+ padding,
11285
+ label,
11286
+ slots,
11287
+ collapsed,
11288
+ cluster2
11289
+ ]);
11290
+ return /* @__PURE__ */ React6.createElement("div", {
11291
+ ref: setRef,
11292
+ className: mx2("dx-expander relative", classNames)
11293
+ }, width > 0 && height > 0 && /* @__PURE__ */ React6.createElement("svg", {
11294
+ ref: svgRef,
11295
+ xmlns: "http://www.w3.org/2000/svg",
11296
+ width,
11297
+ height,
11298
+ viewBox: `${-width / 2} ${-height / 2} ${width} ${height}`
11299
+ }));
11300
+ };
11301
+ var renderRadialTree = (svgElement, root, options) => {
11302
+ const { radius, r, label, slots, collapsed, cluster: cluster2, onNodeClick, onNodeHover } = options;
11303
+ const svg = select2(svgElement);
11304
+ const layout = cluster2 ? d3Cluster() : d3Tree();
11305
+ layout.size([
11306
+ 2 * Math.PI,
11090
11307
  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();
11308
+ ]).separation((a, b) => (a.parent === b.parent ? 1 : 2) / Math.max(1, a.depth))(root);
11309
+ const g = svg.selectAll("g.dx-radial-root").data([
11310
+ null
11311
+ ]).join("g").classed("dx-radial-root", true);
11312
+ const linksLayer = g.selectAll("g.dx-radial-links").data([
11313
+ null
11314
+ ]).join("g").classed("dx-radial-links", true);
11315
+ const nodesLayer = g.selectAll("g.dx-radial-nodes").data([
11316
+ null
11317
+ ]).join("g").classed("dx-radial-nodes", true);
11318
+ const linkPath = linkRadial().angle((d) => d.x).radius((d) => d.y);
11319
+ 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_MS2).attr("opacity", 0).remove()).transition().duration(TRANSITION_MS2).attr("opacity", 1).attr("d", linkPath);
11320
+ const node = nodesLayer.selectAll("g.dx-radial-node").data(root.descendants(), (d) => d.data.id);
11321
+ 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));
11322
+ nodeEnter.append("circle").attr("r", r).on("pointerenter", (event, d) => onNodeHover(d.data, event)).on("pointerleave", (event) => onNodeHover(null, event));
11323
+ nodeEnter.append("text").attr("dy", "0.32em").attr("paint-order", "stroke").text((d) => label(d.data));
11324
+ const nodeMerge = nodeEnter.merge(node);
11325
+ nodeMerge.transition().duration(TRANSITION_MS2).attr("opacity", 1).attr("transform", (d) => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`);
11326
+ nodeMerge.select("circle").attr("class", (d) => {
11327
+ const collapsedHere = isCollapsed(d.data, collapsed);
11328
+ const leaf = isLeaf(d.data);
11329
+ return [
11330
+ slots.node ?? "",
11331
+ collapsedHere ? "dx-collapsed" : leaf ? "dx-leaf" : "dx-branch"
11332
+ ].filter(Boolean).join(" ");
11333
+ }).attr("r", r);
11334
+ 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));
11335
+ node.exit().transition().duration(TRANSITION_MS2).attr("opacity", 0).remove();
11100
11336
  };
11101
- var RadialTree_default = RadialTree;
11102
11337
 
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([
11338
+ // src/components/Tree/layout/TidyTree.tsx
11339
+ import { curveBumpX, link as d3Link, select as select3, tree as d3Tree2 } from "d3";
11340
+ import React7, { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
11341
+ import { mx as mx3 } from "@dxos/ui-theme";
11342
+ var TRANSITION_MS3 = 350;
11343
+ var TidyTree = ({ classNames, data, label = (d) => d.label ?? d.id, slots = defaultTreeLayoutSlots, r = 4, margin = 24, initialCollapsed, onNodeClick }) => {
11344
+ const svgRef = useRef5(null);
11345
+ const { setRef, width, height } = useContainerSize();
11346
+ const [collapsed, setCollapsed] = useState5(() => new Set(initialCollapsed ?? []));
11347
+ const toggle = useCallback4((id) => {
11348
+ setCollapsed((prev) => {
11349
+ const next = new Set(prev);
11350
+ if (next.has(id)) {
11351
+ next.delete(id);
11352
+ } else {
11353
+ next.add(id);
11354
+ }
11355
+ return next;
11356
+ });
11357
+ }, []);
11358
+ const handleClickRef = useRef5(() => {
11359
+ });
11360
+ handleClickRef.current = (node) => {
11361
+ onNodeClick?.(node);
11362
+ if (node.children?.length) {
11363
+ toggle(node.id);
11364
+ }
11365
+ };
11366
+ const root = useMemo4(() => buildHierarchy(data, collapsed), [
11367
+ data,
11368
+ collapsed
11369
+ ]);
11370
+ useEffect8(() => {
11371
+ if (!svgRef.current || !width || !height) {
11372
+ return;
11373
+ }
11374
+ renderTidyTree(svgRef.current, root, {
11375
+ width,
11376
+ height,
11377
+ r,
11378
+ margin,
11379
+ label,
11380
+ slots,
11381
+ collapsed,
11382
+ onNodeClick: (n) => handleClickRef.current(n)
11383
+ });
11384
+ }, [
11385
+ root,
11386
+ width,
11387
+ height,
11388
+ r,
11389
+ margin,
11390
+ label,
11391
+ slots,
11392
+ collapsed
11393
+ ]);
11394
+ return /* @__PURE__ */ React7.createElement("div", {
11395
+ ref: setRef,
11396
+ className: mx3("dx-expander relative", classNames)
11397
+ }, width > 0 && height > 0 && /* @__PURE__ */ React7.createElement("svg", {
11398
+ ref: svgRef,
11399
+ xmlns: "http://www.w3.org/2000/svg",
11400
+ width,
11401
+ height,
11402
+ viewBox: `${-width / 2} ${-height / 2} ${width} ${height}`
11403
+ }));
11404
+ };
11405
+ var renderTidyTree = (svgElement, root, options) => {
11406
+ const { width, height, r, margin, label, slots, collapsed, onNodeClick } = options;
11407
+ const svg = select3(svgElement);
11408
+ const dx = 18;
11409
+ const dy = Math.max(60, (width - margin * 2) / Math.max(1, root.height + 1));
11410
+ d3Tree2().nodeSize([
11115
11411
  dx,
11116
11412
  dy
11117
- ]);
11118
- layout(root);
11413
+ ])(root);
11119
11414
  let x0 = Infinity;
11120
11415
  let x1 = -x0;
11121
- let y0 = Infinity;
11122
- let y1 = -y0;
11123
11416
  root.each((d) => {
11124
11417
  if (d.x > x1) {
11125
11418
  x1 = d.x;
@@ -11127,27 +11420,139 @@ var TidyTree = (s, data, options) => {
11127
11420
  if (d.x < x0) {
11128
11421
  x0 = d.x;
11129
11422
  }
11130
- if (d.y > y1) {
11131
- y1 = d.y;
11132
- }
11133
- if (d.y < y0) {
11134
- y0 = d.y;
11135
- }
11136
11423
  });
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]);
11424
+ const treeWidth = width - margin * 2;
11425
+ const treeHeight = x1 - x0;
11426
+ const scaleY = treeHeight > 0 ? Math.min(1, (height - margin * 2) / treeHeight) : 1;
11427
+ const offsetX = -treeWidth / 2;
11428
+ const offsetY = -(x0 + x1) / 2;
11429
+ const g = svg.selectAll("g.dx-tidy-root").data([
11430
+ null
11431
+ ]).join("g").classed("dx-tidy-root", true);
11432
+ const linksLayer = g.selectAll("g.dx-tidy-links").data([
11433
+ null
11434
+ ]).join("g").classed("dx-tidy-links", true);
11435
+ const nodesLayer = g.selectAll("g.dx-tidy-nodes").data([
11436
+ null
11437
+ ]).join("g").classed("dx-tidy-nodes", true);
11438
+ const linkPath = d3Link(curveBumpX).x((d) => offsetX + d.y).y((d) => (d.x + offsetY) * scaleY);
11439
+ 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);
11440
+ const node = nodesLayer.selectAll("g.dx-tidy-node").data(root.descendants(), (d) => d.data.id);
11441
+ 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));
11442
+ nodeEnter.append("circle").attr("r", r);
11443
+ 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));
11444
+ const nodeMerge = nodeEnter.merge(node);
11445
+ nodeMerge.transition().duration(TRANSITION_MS3).attr("opacity", 1).attr("transform", (d) => `translate(${offsetX + d.y},${(d.x + offsetY) * scaleY})`);
11446
+ nodeMerge.select("circle").attr("class", (d) => {
11447
+ const collapsedHere = isCollapsed(d.data, collapsed);
11448
+ const leaf = isLeaf(d.data);
11449
+ return [
11450
+ slots.node ?? "",
11451
+ collapsedHere ? "dx-collapsed" : leaf ? "dx-leaf" : "dx-branch"
11452
+ ].filter(Boolean).join(" ");
11453
+ }).attr("r", r);
11454
+ 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));
11455
+ node.exit().each(function() {
11456
+ select3(this).interrupt();
11457
+ }).transition().duration(TRANSITION_MS3).attr("opacity", 0).remove();
11458
+ };
11459
+
11460
+ // src/components/Tree/Tree.tsx
11461
+ var Tree = ({ classNames, data, edges, variant = "tidy", label, slots, initialCollapsed, onNodeClick, onNodeHover }) => {
11462
+ return useMemo5(() => {
11463
+ switch (variant) {
11464
+ case "tidy":
11465
+ return /* @__PURE__ */ React8.createElement(TidyTree, {
11466
+ classNames,
11467
+ data,
11468
+ label,
11469
+ slots,
11470
+ initialCollapsed,
11471
+ onNodeClick
11472
+ });
11473
+ case "radial":
11474
+ return /* @__PURE__ */ React8.createElement(RadialTree, {
11475
+ classNames,
11476
+ data,
11477
+ label,
11478
+ slots,
11479
+ initialCollapsed,
11480
+ onNodeClick,
11481
+ onNodeHover
11482
+ });
11483
+ case "edge":
11484
+ return /* @__PURE__ */ React8.createElement(HierarchicalEdgeBundling, {
11485
+ classNames,
11486
+ data,
11487
+ edges: edges ?? [],
11488
+ label,
11489
+ slots,
11490
+ onNodeHover
11491
+ });
11492
+ }
11493
+ }, [
11494
+ variant,
11495
+ classNames,
11496
+ data,
11497
+ edges,
11498
+ label,
11499
+ slots,
11500
+ initialCollapsed,
11501
+ onNodeClick,
11502
+ onNodeHover
11503
+ ]);
11504
+ };
11505
+
11506
+ // src/components/Tree/space-graph-adapter.ts
11507
+ import { Obj as Obj2 } from "@dxos/echo";
11508
+ var ROOT_ID = "db:root";
11509
+ var SCHEMA_PREFIX = "schema:";
11510
+ var truncate = (id) => `${id.slice(0, 4)}\u2026${id.slice(-4)}`;
11511
+ var labelOf = (node) => node.data?.label ?? truncate(node.id);
11512
+ var spaceGraphToHierarchy = (model, { rootLabel = "Database", rootId = ROOT_ID } = {}) => {
11513
+ const graph = model.graph;
11514
+ const objectNodes = graph.nodes.filter((node) => node.type === "object");
11515
+ const byTypename = /* @__PURE__ */ new Map();
11516
+ for (const node of objectNodes) {
11517
+ const obj = node.data?.object;
11518
+ const typename = (obj && Obj2.getTypename(obj)) ?? "(untyped)";
11519
+ const bucket = byTypename.get(typename) ?? [];
11520
+ bucket.push(node);
11521
+ byTypename.set(typename, bucket);
11144
11522
  }
11523
+ const tree = {
11524
+ id: rootId,
11525
+ label: rootLabel,
11526
+ children: Array.from(byTypename.entries()).map(([typename, nodes]) => ({
11527
+ id: `${SCHEMA_PREFIX}${typename}`,
11528
+ label: shortTypename(typename),
11529
+ children: nodes.map((node) => ({
11530
+ id: node.id,
11531
+ label: labelOf(node),
11532
+ // Preserve the ECHO object on the leaf so layouts can fire hover/inspect callbacks with it.
11533
+ data: node.data?.object
11534
+ }))
11535
+ }))
11536
+ };
11537
+ const objectIds = new Set(objectNodes.map((n) => n.id));
11538
+ const edges = graph.edges.filter((edge) => objectIds.has(edge.source) && objectIds.has(edge.target)).filter((edge) => edge.source !== edge.target).map((edge) => ({
11539
+ source: edge.source,
11540
+ target: edge.target,
11541
+ kind: edge.type
11542
+ }));
11543
+ return {
11544
+ tree,
11545
+ edges
11546
+ };
11547
+ };
11548
+ var shortTypename = (typename) => {
11549
+ const last = typename.split(".").pop() ?? typename;
11550
+ return last.charAt(0).toUpperCase() + last.slice(1);
11145
11551
  };
11146
- var TidyTree_default = TidyTree;
11147
11552
 
11148
11553
  // src/components/Tree/types/tree.ts
11149
11554
  import * as Schema from "effect/Schema";
11150
- import { Key, Obj as Obj2, Ref, Type } from "@dxos/echo";
11555
+ import { Key, Obj as Obj3, Ref, Type } from "@dxos/echo";
11151
11556
  import { TestSchema } from "@dxos/echo/testing";
11152
11557
  import { invariant } from "@dxos/invariant";
11153
11558
  var TreeNodeType = Schema.Struct({
@@ -11171,204 +11576,43 @@ var TreeType = Schema.Struct({
11171
11576
  }));
11172
11577
 
11173
11578
  // src/components/Tree/types/types.ts
11174
- var mapGraphToTreeData = (model, maxDepth = 8) => {
11175
- let data;
11176
- return data;
11177
- };
11178
-
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);
11236
- }
11237
- }
11238
- }, [
11239
- context.current,
11240
- tree3
11241
- ]);
11242
- return /* @__PURE__ */ React5.createElement("div", {
11243
- className: "grow",
11244
- onClick: () => onNodeClick?.()
11245
- }, /* @__PURE__ */ React5.createElement(SVG2.Root, {
11246
- ref: context
11247
- }));
11248
- };
11249
-
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;
11262
- }
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();
11579
+ var treeTypeToTreeNode = (tree, rootId = tree.root, visited = /* @__PURE__ */ new Set()) => {
11580
+ const node = tree.nodes[rootId];
11581
+ if (!node) {
11582
+ return void 0;
11583
+ }
11584
+ if (visited.has(rootId)) {
11585
+ return {
11586
+ id: rootId,
11587
+ label: labelOf2(node),
11588
+ data: node.data
11269
11589
  };
11270
- }, [
11271
- space,
11272
- registry,
11273
- queue
11274
- ]);
11275
- useEffect6(() => {
11276
- model?.setFilter(filter).setOptions(options);
11277
- }, [
11278
- model,
11279
- filter,
11280
- options
11281
- ]);
11282
- return model;
11283
- };
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
- }
11321
- }
11322
- },
11323
- ...componentsTranslations
11324
- ];
11325
-
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
- });
11344
- });
11345
- return yield* Operation.invoke(SpaceOperation.AddObject, {
11346
- object,
11347
- target: options.target,
11348
- hidden: true,
11349
- targetNodeId: options.targetNodeId
11350
- });
11351
- })
11352
- }
11353
11590
  }
11354
- }), AppPlugin.addSchemaModule({
11355
- schema: [
11356
- Graph2.Graph
11357
- ]
11358
- }), AppPlugin.addSurfaceModule({
11359
- activate: ReactSurface
11360
- }), AppPlugin.addTranslationsModule({
11361
- translations
11362
- }), Plugin.make);
11591
+ visited.add(rootId);
11592
+ return {
11593
+ id: rootId,
11594
+ label: labelOf2(node),
11595
+ data: node.data,
11596
+ children: node.children.map((childId) => treeTypeToTreeNode(tree, childId, visited)).filter((c) => Boolean(c))
11597
+ };
11598
+ };
11599
+ var labelOf2 = (node) => {
11600
+ return typeof node.data?.text === "string" ? node.data.text : void 0;
11601
+ };
11363
11602
  export {
11603
+ CanvasForceGraph,
11364
11604
  Chart,
11365
- D3ForceGraph,
11366
- ExplorerPlugin,
11367
11605
  ForceGraph,
11368
11606
  Globe,
11607
+ HierarchicalEdgeBundling,
11608
+ RadialTree,
11609
+ TidyTree,
11369
11610
  Tree,
11611
+ buildHierarchy,
11370
11612
  defaultTreeLayoutSlots,
11371
- meta,
11372
- useGraphModel
11613
+ isCollapsed,
11614
+ isLeaf,
11615
+ spaceGraphToHierarchy,
11616
+ treeTypeToTreeNode
11373
11617
  };
11374
11618
  //# sourceMappingURL=index.mjs.map