@dxos/plugin-explorer 0.9.0 → 0.9.1-staging.ee54ba693a

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 (162) hide show
  1. package/dist/lib/neutral/ExplorerArticle-2WPXYTXV.mjs +101 -0
  2. package/dist/lib/neutral/ExplorerArticle-2WPXYTXV.mjs.map +7 -0
  3. package/dist/lib/neutral/ExplorerPlugin.mjs +1 -1
  4. package/dist/lib/neutral/NeighborhoodCompanion-WC6E3YE6.mjs +90 -0
  5. package/dist/lib/neutral/NeighborhoodCompanion-WC6E3YE6.mjs.map +7 -0
  6. package/dist/lib/neutral/app-graph-builder-L2TJ6GNR.mjs +37 -0
  7. package/dist/lib/neutral/app-graph-builder-L2TJ6GNR.mjs.map +7 -0
  8. package/dist/lib/neutral/capabilities/index.mjs +4 -2
  9. package/dist/lib/neutral/capabilities/index.mjs.map +3 -3
  10. package/dist/lib/neutral/{chunk-3D7BYXOR.mjs → chunk-4F6AT7RA.mjs} +6 -4
  11. package/dist/lib/neutral/chunk-4F6AT7RA.mjs.map +7 -0
  12. package/dist/lib/neutral/chunk-C56B464A.mjs +53 -0
  13. package/dist/lib/neutral/chunk-C56B464A.mjs.map +7 -0
  14. package/dist/lib/neutral/{chunk-7XUDLV6E.mjs → chunk-EZ52JY7X.mjs} +34 -31
  15. package/dist/lib/neutral/chunk-EZ52JY7X.mjs.map +7 -0
  16. package/dist/lib/neutral/{chunk-YBCHBVCJ.mjs → chunk-TTFPTHAQ.mjs} +8 -8
  17. package/dist/lib/neutral/chunk-TTFPTHAQ.mjs.map +7 -0
  18. package/dist/lib/neutral/components/index.mjs +430 -506
  19. package/dist/lib/neutral/components/index.mjs.map +4 -4
  20. package/dist/lib/neutral/containers/index.mjs +4 -2
  21. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  22. package/dist/lib/neutral/{create-object-F6TKVAGV.mjs → create-object-WKIN3OB6.mjs} +3 -4
  23. package/dist/lib/neutral/{create-object-F6TKVAGV.mjs.map → create-object-WKIN3OB6.mjs.map} +3 -3
  24. package/dist/lib/neutral/hooks/index.mjs +64 -1
  25. package/dist/lib/neutral/hooks/index.mjs.map +4 -4
  26. package/dist/lib/neutral/index.mjs +2 -2
  27. package/dist/lib/neutral/meta.json +1 -1
  28. package/dist/lib/neutral/meta.mjs +1 -1
  29. package/dist/lib/neutral/plugin.mjs +1 -1
  30. package/dist/lib/neutral/react-surface-EWPG5PUQ.mjs +39 -0
  31. package/dist/lib/neutral/react-surface-EWPG5PUQ.mjs.map +7 -0
  32. package/dist/lib/neutral/testing/index.mjs +2 -2
  33. package/dist/lib/neutral/testing/index.mjs.map +2 -2
  34. package/dist/lib/neutral/translations.mjs +5 -2
  35. package/dist/lib/neutral/translations.mjs.map +3 -3
  36. package/dist/lib/neutral/types/index.mjs +1 -1
  37. package/dist/types/dx.config.d.ts +28 -0
  38. package/dist/types/dx.config.d.ts.map +1 -0
  39. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  40. package/dist/types/src/capabilities/app-graph-builder.d.ts +9 -0
  41. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/create-object.d.ts +1 -7
  43. package/dist/types/src/capabilities/create-object.d.ts.map +1 -1
  44. package/dist/types/src/capabilities/index.d.ts +3 -7
  45. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  46. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  47. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  48. package/dist/types/src/components/Lattice/Lattice.d.ts +1 -1
  49. package/dist/types/src/components/Lattice/Lattice.d.ts.map +1 -1
  50. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts +1 -1
  51. package/dist/types/src/components/Tree/EdgeBundling.stories.d.ts.map +1 -1
  52. package/dist/types/src/components/Tree/Tree.stories.d.ts +1 -1
  53. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/Tree/index.d.ts +1 -3
  55. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  56. package/dist/types/src/components/Tree/types/tree.d.ts +10 -27
  57. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  58. package/dist/types/src/components/Tree/types/types.d.ts +1 -10
  59. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  60. package/dist/types/src/components/Visualization/Visualization.d.ts +39 -0
  61. package/dist/types/src/components/Visualization/Visualization.d.ts.map +1 -0
  62. package/dist/types/src/components/Visualization/Visualization.stories.d.ts +13 -0
  63. package/dist/types/src/components/Visualization/Visualization.stories.d.ts.map +1 -0
  64. package/dist/types/src/components/Visualization/index.d.ts +3 -0
  65. package/dist/types/src/components/Visualization/index.d.ts.map +1 -0
  66. package/dist/types/src/components/Visualization/variants.d.ts +53 -0
  67. package/dist/types/src/components/Visualization/variants.d.ts.map +1 -0
  68. package/dist/types/src/components/index.d.ts +1 -0
  69. package/dist/types/src/components/index.d.ts.map +1 -1
  70. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts +2 -2
  71. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.d.ts.map +1 -1
  72. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts +2 -2
  73. package/dist/types/src/containers/ExplorerArticle/ExplorerArticle.stories.d.ts.map +1 -1
  74. package/dist/types/src/containers/NeighborhoodCompanion/NeighborhoodCompanion.d.ts +14 -0
  75. package/dist/types/src/containers/NeighborhoodCompanion/NeighborhoodCompanion.d.ts.map +1 -0
  76. package/dist/types/src/containers/NeighborhoodCompanion/index.d.ts +2 -0
  77. package/dist/types/src/containers/NeighborhoodCompanion/index.d.ts.map +1 -0
  78. package/dist/types/src/containers/index.d.ts +1 -0
  79. package/dist/types/src/containers/index.d.ts.map +1 -1
  80. package/dist/types/src/hooks/index.d.ts +1 -0
  81. package/dist/types/src/hooks/index.d.ts.map +1 -1
  82. package/dist/types/src/hooks/useNeighborhoodModel.d.ts +14 -0
  83. package/dist/types/src/hooks/useNeighborhoodModel.d.ts.map +1 -0
  84. package/dist/types/src/meta.d.ts +32 -2
  85. package/dist/types/src/meta.d.ts.map +1 -1
  86. package/dist/types/src/testing/relations.d.ts +1 -2
  87. package/dist/types/src/testing/relations.d.ts.map +1 -1
  88. package/dist/types/src/translations.d.ts +6 -0
  89. package/dist/types/src/translations.d.ts.map +1 -1
  90. package/dist/types/src/types/Graph.d.ts +6 -8
  91. package/dist/types/src/types/Graph.d.ts.map +1 -1
  92. package/dist/types/tsconfig.tsbuildinfo +1 -1
  93. package/{dist/lib/neutral/chunk-42BYLQQA.mjs → dx.config.ts} +18 -21
  94. package/package.json +42 -37
  95. package/src/ExplorerPlugin.test.ts +1 -1
  96. package/src/ExplorerPlugin.tsx +3 -2
  97. package/src/capabilities/app-graph-builder.ts +40 -0
  98. package/src/capabilities/create-object.ts +3 -2
  99. package/src/capabilities/index.ts +1 -0
  100. package/src/capabilities/react-surface.tsx +14 -4
  101. package/src/components/Lattice/Lattice.tsx +1 -2
  102. package/src/components/Tree/EdgeBundling.stories.tsx +5 -3
  103. package/src/components/Tree/Tree.stories.tsx +7 -2
  104. package/src/components/Tree/index.ts +3 -3
  105. package/src/components/Tree/types/tree.ts +6 -6
  106. package/src/components/Tree/types/types.ts +1 -11
  107. package/src/components/Visualization/Visualization.stories.tsx +104 -0
  108. package/src/components/Visualization/Visualization.tsx +209 -0
  109. package/src/components/Visualization/index.ts +6 -0
  110. package/src/components/Visualization/variants.ts +508 -0
  111. package/src/components/index.ts +1 -0
  112. package/src/containers/ExplorerArticle/ExplorerArticle.stories.tsx +3 -3
  113. package/src/containers/ExplorerArticle/ExplorerArticle.tsx +9 -10
  114. package/src/containers/NeighborhoodCompanion/NeighborhoodCompanion.tsx +102 -0
  115. package/src/containers/NeighborhoodCompanion/index.ts +5 -0
  116. package/src/containers/index.ts +1 -0
  117. package/src/hooks/index.ts +1 -0
  118. package/src/hooks/useNeighborhoodModel.ts +81 -0
  119. package/src/meta.ts +2 -32
  120. package/src/testing/relations.ts +1 -3
  121. package/src/translations.ts +4 -1
  122. package/src/types/Graph.ts +18 -20
  123. package/dist/lib/neutral/ExplorerArticle-4I7PNGDC.mjs +0 -459
  124. package/dist/lib/neutral/ExplorerArticle-4I7PNGDC.mjs.map +0 -7
  125. package/dist/lib/neutral/chunk-3D7BYXOR.mjs.map +0 -7
  126. package/dist/lib/neutral/chunk-42BYLQQA.mjs.map +0 -7
  127. package/dist/lib/neutral/chunk-7XUDLV6E.mjs.map +0 -7
  128. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs +0 -20
  129. package/dist/lib/neutral/chunk-IKHJV3Q4.mjs.map +0 -7
  130. package/dist/lib/neutral/chunk-YBCHBVCJ.mjs.map +0 -7
  131. package/dist/lib/neutral/react-surface-APBW2VQG.mjs +0 -26
  132. package/dist/lib/neutral/react-surface-APBW2VQG.mjs.map +0 -7
  133. package/dist/types/src/components/Tree/Tree.d.ts +0 -24
  134. package/dist/types/src/components/Tree/Tree.d.ts.map +0 -1
  135. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts +0 -40
  136. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +0 -1
  137. package/dist/types/src/components/Tree/layout/RadialTree.d.ts +0 -37
  138. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +0 -1
  139. package/dist/types/src/components/Tree/layout/TidyTree.d.ts +0 -26
  140. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +0 -1
  141. package/dist/types/src/components/Tree/layout/hierarchy.d.ts +0 -17
  142. package/dist/types/src/components/Tree/layout/hierarchy.d.ts.map +0 -1
  143. package/dist/types/src/components/Tree/layout/index.d.ts +0 -6
  144. package/dist/types/src/components/Tree/layout/index.d.ts.map +0 -1
  145. package/dist/types/src/components/Tree/layout/slots.d.ts +0 -7
  146. package/dist/types/src/components/Tree/layout/slots.d.ts.map +0 -1
  147. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts +0 -15
  148. package/dist/types/src/components/Tree/layout/useContainerSize.d.ts.map +0 -1
  149. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts +0 -18
  150. package/dist/types/src/containers/ExplorerArticle/Visualization.d.ts.map +0 -1
  151. package/dist/types/src/containers/ExplorerArticle/variants.d.ts +0 -9
  152. package/dist/types/src/containers/ExplorerArticle/variants.d.ts.map +0 -1
  153. package/src/components/Tree/Tree.tsx +0 -81
  154. package/src/components/Tree/layout/HierarchicalEdgeBundling.tsx +0 -335
  155. package/src/components/Tree/layout/RadialTree.tsx +0 -242
  156. package/src/components/Tree/layout/TidyTree.tsx +0 -246
  157. package/src/components/Tree/layout/hierarchy.ts +0 -32
  158. package/src/components/Tree/layout/index.ts +0 -9
  159. package/src/components/Tree/layout/slots.ts +0 -19
  160. package/src/components/Tree/layout/useContainerSize.ts +0 -43
  161. package/src/containers/ExplorerArticle/Visualization.tsx +0 -523
  162. package/src/containers/ExplorerArticle/variants.ts +0 -47
@@ -1,16 +1,32 @@
1
1
  import {
2
+ Tree,
3
+ TreeNodeType,
4
+ TreeType,
2
5
  treeTypeToTreeNode
3
- } from "../chunk-7XUDLV6E.mjs";
4
- import {
5
- createAdapter,
6
- getNodeFillForObject
7
- } from "../chunk-IKHJV3Q4.mjs";
6
+ } from "../chunk-EZ52JY7X.mjs";
8
7
  import "../chunk-J5LGTIGS.mjs";
9
8
 
10
9
  // src/components/Chart/Chart.tsx
11
10
  import * as Plot from "@observablehq/plot";
12
11
  import React, { useEffect } from "react";
13
12
  import { useResizeDetector } from "react-resize-detector";
13
+
14
+ // src/util/node-color.ts
15
+ import { Obj } from "@dxos/echo";
16
+ import { getHashHue } from "@dxos/ui-theme";
17
+ var getNodeFillForTypename = (typename) => {
18
+ return `var(--color-${getHashHue(typename)}-400)`;
19
+ };
20
+ var getNodeFillForObject = (object) => {
21
+ return getNodeFillForTypename(object && Obj.getTypename(object));
22
+ };
23
+
24
+ // src/util/plot.ts
25
+ var createAdapter = (prop, accessor) => accessor ? {
26
+ transform: (values) => values.map((value) => accessor(value)[prop])
27
+ } : prop;
28
+
29
+ // src/components/Chart/Chart.tsx
14
30
  var defaultOptions = {
15
31
  r: 4,
16
32
  stroke: "gray",
@@ -10934,7 +10950,7 @@ var assignRef = (ref, value) => {
10934
10950
  // src/components/Graph/ForceGraph.tsx
10935
10951
  import { Atom, useAtomValue } from "@effect-atom/atom-react";
10936
10952
  import React4, { useCallback as useCallback2, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState2 } from "react";
10937
- import { Obj } from "@dxos/echo";
10953
+ import { Obj as Obj2 } from "@dxos/echo";
10938
10954
  import { SelectionModel } from "@dxos/graph";
10939
10955
  import { composable as composable2, composableProps as composableProps2 } from "@dxos/react-ui";
10940
10956
  import { GraphForceProjector, SVG } from "@dxos/react-ui-graph";
@@ -11010,7 +11026,7 @@ var ForceGraph = composable2(({ model, selection: selectionProp, grid, drag, onI
11010
11026
  const obj = node.data?.data.object;
11011
11027
  return {
11012
11028
  data: {
11013
- color: getHashStyles(obj && Obj.getTypename(obj))?.hue
11029
+ color: getHashStyles(obj && Obj2.getTypename(obj))?.hue
11014
11030
  },
11015
11031
  classes: {
11016
11032
  "dx-selected": selection.contains(node.id)
@@ -11025,50 +11041,9 @@ var ForceGraph = composable2(({ model, selection: selectionProp, grid, drag, onI
11025
11041
 
11026
11042
  // src/components/Lattice/Lattice.tsx
11027
11043
  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";
11030
-
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
11038
- });
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
- });
11058
- });
11059
- observer.observe(el);
11060
- return () => observer.disconnect();
11061
- }, [
11062
- el
11063
- ]);
11064
- return {
11065
- setRef: setEl,
11066
- width: size.width,
11067
- height: size.height
11068
- };
11069
- };
11070
-
11071
- // src/components/Lattice/Lattice.tsx
11044
+ import React5, { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef3 } from "react";
11045
+ import { Obj as Obj3 } from "@dxos/echo";
11046
+ import { useContainerSize } from "@dxos/react-ui-graph";
11072
11047
  var TRANSITION_MS = 350;
11073
11048
  var Lattice = ({ nodes, padding = 16, onNodeHover }) => {
11074
11049
  const svgRef = useRef3(null);
@@ -11079,8 +11054,8 @@ var Lattice = ({ nodes, padding = 16, onNodeHover }) => {
11079
11054
  if (!object) {
11080
11055
  return void 0;
11081
11056
  }
11082
- const label = node.data?.label ?? Obj2.getLabel(object) ?? node.id;
11083
- const typename = Obj2.getTypename(object) ?? "(untyped)";
11057
+ const label = node.data?.label ?? Obj3.getLabel(object) ?? node.id;
11058
+ const typename = Obj3.getTypename(object) ?? "(untyped)";
11084
11059
  return {
11085
11060
  id: node.id,
11086
11061
  label,
@@ -11093,7 +11068,7 @@ var Lattice = ({ nodes, padding = 16, onNodeHover }) => {
11093
11068
  ]);
11094
11069
  const handleHoverRef = useRef3(void 0);
11095
11070
  handleHoverRef.current = onNodeHover;
11096
- useEffect6(() => {
11071
+ useEffect5(() => {
11097
11072
  if (!svgRef.current || !width || !height) {
11098
11073
  return;
11099
11074
  }
@@ -11167,501 +11142,450 @@ var renderLattice = (svgElement, cells, options) => {
11167
11142
  }, event)).on("pointerleave", () => onNodeHover(null));
11168
11143
  };
11169
11144
 
11170
- // src/components/Tree/Tree.tsx
11171
- import React9, { useMemo as useMemo6 } from "react";
11145
+ // src/components/Visualization/Visualization.tsx
11146
+ import { createContext } from "@radix-ui/react-context";
11147
+ import { Slot } from "@radix-ui/react-slot";
11148
+ import React6, { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef4, useState as useState3 } from "react";
11149
+ import { composableProps as composableProps3, slottable } from "@dxos/react-ui";
11150
+ import { GraphSwarmProjector as GraphSwarmProjector2, SVG as SVG2 } from "@dxos/react-ui-graph";
11172
11151
 
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
11197
- ]);
11198
- const handleHoverRef = useRef4(() => {
11199
- });
11200
- handleHoverRef.current = (node, event) => onNodeHover?.(node, event);
11201
- useEffect7(() => {
11202
- if (!svgRef.current || !width || !height) {
11203
- return;
11204
- }
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 = [];
11152
+ // src/components/Visualization/variants.ts
11153
+ import { curveCatmullRom, line as d3Line } from "d3";
11154
+ import { Obj as Obj4 } from "@dxos/echo";
11155
+ import { CLUSTER_NODE_TYPE_LEAF, CLUSTER_NODE_TYPE_ROOT, GraphBundleProjector, GraphClusterProjector, GraphForceProjector as GraphForceProjector2, GraphLatticeProjector, GraphPlexusProjector, GraphSwarmProjector, GraphTreeProjector, PLEXUS_NODE_TYPE_FOCUS, PLEXUS_NODE_TYPE_RELATION, TREE_NODE_TYPE_INBOUND, TREE_NODE_TYPE_ROOT, appendRadialGroupLabel, appendRadialLeafLabel, appendRootLabel } from "@dxos/react-ui-graph";
11156
+ var TWEEN_MS = 500;
11157
+ var LABEL_FADE_MS = 200;
11158
+ var PLEXUS_LEAF_RADIUS = 5;
11159
+ var PLEXUS_FOCUS_RADIUS = PLEXUS_LEAF_RADIUS * 2;
11160
+ var TREE_NODE_RADIUS = 6;
11161
+ var TREE_ROOT_RADIUS = TREE_NODE_RADIUS * 2;
11162
+ var swarmTrailLine = d3Line().curve(curveCatmullRom.alpha(0.5));
11163
+ var applyNodeSwarm = (group, node) => {
11164
+ const swarm = node;
11165
+ const path = group.querySelector("path.dx-swarm-tail");
11166
+ const grad = group.querySelector("linearGradient");
11167
+ if (!path || !grad) {
11168
+ return;
11242
11169
  }
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;
11248
- }
11249
- source.outgoing.push([
11250
- source,
11251
- target,
11252
- edge
11253
- ]);
11254
- target.incoming.push([
11255
- source,
11256
- target,
11257
- edge
11170
+ const history = swarm.history ?? [];
11171
+ if (history.length === 0) {
11172
+ path.setAttribute("d", "");
11173
+ return;
11174
+ }
11175
+ const hx = swarm.x ?? 0;
11176
+ const hy = swarm.y ?? 0;
11177
+ const points = [
11178
+ [
11179
+ 0,
11180
+ 0
11181
+ ]
11182
+ ];
11183
+ for (let i = history.length - 1; i >= 0; i--) {
11184
+ points.push([
11185
+ history[i].x - hx,
11186
+ history[i].y - hy
11258
11187
  ]);
11259
11188
  }
11260
- return root;
11189
+ path.setAttribute("d", swarmTrailLine(points) ?? "");
11190
+ const oldest = history[0];
11191
+ grad.setAttribute("x2", String(oldest.x - hx));
11192
+ grad.setAttribute("y2", String(oldest.y - hy));
11261
11193
  };
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;
11194
+ var plexusRelationOf = (edge, focusId) => {
11195
+ const outgoing = edge.source.id === focusId;
11196
+ const incoming = edge.target.id === focusId;
11197
+ if (!outgoing && !incoming) {
11198
+ return void 0;
11268
11199
  }
11269
- cluster().size([
11270
- 2 * Math.PI,
11271
- radius
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);
11200
+ const direction = outgoing ? "out" : "in";
11201
+ const arrow = outgoing ? "\u2192" : "\u2190";
11202
+ if (edge.type === "relation") {
11203
+ const relation = edge.data?.object;
11204
+ const typename = relation ? Obj4.getTypename(relation) : void 0;
11205
+ const name = typename ? shortTypename(typename) : "Relation";
11206
+ return {
11207
+ key: `relation:${direction}:${typename ?? "?"}`,
11208
+ label: `${name} ${arrow}`
11209
+ };
11210
+ }
11211
+ if (edge.type === "ref") {
11212
+ const property = edge.data?.property;
11213
+ const name = property ?? "References";
11214
+ return {
11215
+ key: `ref:${direction}:${property ?? "?"}`,
11216
+ label: `${name} ${arrow}`
11217
+ };
11218
+ }
11219
+ return void 0;
11317
11220
  };
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;
11324
- }
11325
- if (d[0] === focused) {
11326
- return "var(--color-orange-500)";
11327
- }
11328
- if (d[1] === focused) {
11329
- return "var(--color-sky-500)";
11221
+ var typenameGroupOf = (node) => {
11222
+ const obj = node.data?.data?.object;
11223
+ return obj ? Obj4.getTypename(obj) ?? "(untyped)" : void 0;
11224
+ };
11225
+ var shortTypename = (typename) => {
11226
+ const last = typename.split(".").pop() ?? typename;
11227
+ return last.charAt(0).toUpperCase() + last.slice(1);
11228
+ };
11229
+ var labelForLeaf = (node, obj) => (obj && Obj4.getLabel(obj)) ?? node.data?.data?.label ?? node.id;
11230
+ var appendTreeLabel = (group, text, r, placement) => {
11231
+ const offset = r + 4;
11232
+ const label = group.append("text").attr("class", "dx-cluster-label").attr("opacity", 0).style("cursor", "pointer");
11233
+ if (placement === "above") {
11234
+ label.attr("x", 0).attr("y", -offset).attr("text-anchor", "middle");
11235
+ } else {
11236
+ const toLeft = placement === "left";
11237
+ label.attr("dy", "0.32em").attr("x", toLeft ? -offset : offset).attr("text-anchor", toLeft ? "end" : "start");
11238
+ }
11239
+ label.text(text).transition().delay(TWEEN_MS).duration(LABEL_FADE_MS).attr("opacity", 1);
11240
+ };
11241
+ var DEFINITIONS = {
11242
+ force: {
11243
+ value: "force",
11244
+ icon: "ph--graph--regular",
11245
+ label: "Force-directed",
11246
+ drag: true,
11247
+ emitsHover: true,
11248
+ // Force has no `duration` — its own simulation drives motion via ticks.
11249
+ createProjector: (ctx, { prev }) => new GraphForceProjector2(ctx, void 0, void 0, prev),
11250
+ renderNode: (group, node) => {
11251
+ const r = node.r ?? 6;
11252
+ group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", getNodeFillForObject(node.data?.data?.object));
11330
11253
  }
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;
11254
+ },
11255
+ cluster: {
11256
+ value: "cluster",
11257
+ icon: "ph--asterisk-simple--regular",
11258
+ label: "Radial",
11259
+ emitsHover: true,
11260
+ createProjector: (ctx, { prev }) => new GraphClusterProjector(ctx, {
11261
+ duration: TWEEN_MS,
11262
+ groupOf: typenameGroupOf,
11263
+ rootLabel: "Database",
11264
+ groupLabel: shortTypename,
11265
+ // All three node kinds share the same radius — leaves, groups, and root read
11266
+ // as members of the same circle rather than ranked by size.
11267
+ rootRadius: 4,
11268
+ groupRadius: 4
11269
+ }, void 0, prev),
11270
+ renderNode: (group, node) => {
11271
+ const obj = node.data?.data?.object;
11272
+ const r = node.r ?? 4;
11273
+ group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
11274
+ const labelOptions = {
11275
+ delay: TWEEN_MS,
11276
+ duration: LABEL_FADE_MS
11277
+ };
11278
+ if (node.type === CLUSTER_NODE_TYPE_LEAF) {
11279
+ const text = labelForLeaf(node, obj);
11280
+ if (text) {
11281
+ appendRadialLeafLabel(group, node, text, r, labelOptions);
11341
11282
  }
11342
- if (leaf === focused) {
11343
- return "var(--color-neutral-900)";
11283
+ } else if (node.type === CLUSTER_NODE_TYPE_ROOT) {
11284
+ if (node.label) {
11285
+ appendRootLabel(group, node.label, r, labelOptions);
11344
11286
  }
11345
- if (isOut) {
11346
- return "var(--color-orange-500)";
11287
+ } else {
11288
+ if (node.label) {
11289
+ appendRadialGroupLabel(group, node, node.label, r, labelOptions);
11290
+ }
11291
+ }
11292
+ }
11293
+ },
11294
+ bundle: {
11295
+ value: "bundle",
11296
+ icon: "ph--circles-three-plus--regular",
11297
+ label: "Connections",
11298
+ highlightOnHover: true,
11299
+ emitsHover: true,
11300
+ createProjector: (ctx, { prev }) => new GraphBundleProjector(ctx, {
11301
+ duration: TWEEN_MS,
11302
+ groupOf: typenameGroupOf
11303
+ }, void 0, prev),
11304
+ // Bundle layout renders ONLY leaves (root/group are invisible routing anchors).
11305
+ renderNode: (group, node) => {
11306
+ const obj = node.data?.data?.object;
11307
+ const r = node.r ?? 4;
11308
+ group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
11309
+ const text = labelForLeaf(node, obj);
11310
+ if (text) {
11311
+ appendRadialLeafLabel(group, node, text, r, {
11312
+ delay: TWEEN_MS,
11313
+ duration: LABEL_FADE_MS
11314
+ });
11315
+ }
11316
+ }
11317
+ },
11318
+ plexus: {
11319
+ value: "plexus",
11320
+ icon: "ph--share-network--regular",
11321
+ label: "Plexus",
11322
+ focusable: true,
11323
+ // No focus → dispatch to the bundle projector (the unfocused overview). Once a node is
11324
+ // focused, switch to the focus-centric plexus layout; both receive `prev` so the swap
11325
+ // (and every re-focus) animates from the previous node positions.
11326
+ createProjector: (ctx, { focusId, prev }) => {
11327
+ if (!focusId) {
11328
+ return new GraphBundleProjector(ctx, {
11329
+ duration: TWEEN_MS,
11330
+ groupOf: typenameGroupOf
11331
+ }, void 0, prev);
11332
+ }
11333
+ return new GraphPlexusProjector(ctx, {
11334
+ duration: TWEEN_MS,
11335
+ focus: focusId,
11336
+ relationOf: plexusRelationOf,
11337
+ leafRadius: PLEXUS_LEAF_RADIUS,
11338
+ relationRadius: PLEXUS_LEAF_RADIUS,
11339
+ focusRadius: PLEXUS_FOCUS_RADIUS
11340
+ }, void 0, prev);
11341
+ },
11342
+ // Three node kinds: the focus at the centre (larger, object fill), synthetic relation
11343
+ // nodes on the inner ring (neutral, labelled by relation/property), and object leaves on
11344
+ // the outer ring (object fill). Type tags are set by the projector.
11345
+ renderNode: (group, node) => {
11346
+ const obj = node.data?.data?.object;
11347
+ const labelOptions = {
11348
+ delay: TWEEN_MS,
11349
+ duration: LABEL_FADE_MS
11350
+ };
11351
+ if (node.type === PLEXUS_NODE_TYPE_RELATION) {
11352
+ const r2 = PLEXUS_LEAF_RADIUS;
11353
+ group.append("circle").attr("r", r2).style("cursor", "default").style("fill", "var(--color-neutral-500)");
11354
+ if (node.label) {
11355
+ appendRadialGroupLabel(group, node, node.label, r2, labelOptions);
11347
11356
  }
11348
- if (isIn) {
11349
- return "var(--color-sky-500)";
11357
+ return;
11358
+ }
11359
+ const isFocus = node.type === PLEXUS_NODE_TYPE_FOCUS;
11360
+ const r = isFocus ? PLEXUS_FOCUS_RADIUS : PLEXUS_LEAF_RADIUS;
11361
+ group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
11362
+ const text = labelForLeaf(node, obj);
11363
+ if (text) {
11364
+ if (isFocus) {
11365
+ appendRootLabel(group, text, r, labelOptions);
11366
+ } else {
11367
+ appendRadialLeafLabel(group, node, text, r, labelOptions);
11350
11368
  }
11351
- return null;
11352
- }).style("font-weight", () => on && leaf === focused ? "600" : null);
11369
+ }
11353
11370
  }
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);
11371
+ },
11372
+ lattice: {
11373
+ value: "lattice",
11374
+ icon: "ph--grid-four--regular",
11375
+ label: "Lattice",
11376
+ emitsHover: true,
11377
+ createProjector: (ctx, { prev }) => new GraphLatticeProjector(ctx, {
11378
+ duration: TWEEN_MS,
11379
+ // Plugin-explorer overrides the projector's force-matched default (6)
11380
+ // with a smaller node so the lattice reads as a dense matrix.
11381
+ radius: 4,
11382
+ // Cluster by typename first so same-type rects sit together; break ties by label.
11383
+ sortBy: (node) => {
11384
+ const obj = node.data?.data?.object;
11385
+ const typename = obj ? Obj4.getTypename(obj) ?? "(untyped)" : "(untyped)";
11386
+ const label = (obj && Obj4.getLabel(obj)) ?? node.data?.data?.label ?? node.id;
11387
+ return `${typename} ${label}`;
11388
+ }
11389
+ }, void 0, prev),
11390
+ renderNode: (group, node) => {
11391
+ const r = node.r ?? 6;
11392
+ const sz = r * 2;
11393
+ group.append("rect").attr("x", -r).attr("y", -r).attr("width", sz).attr("height", sz).attr("rx", r * 0.3).attr("ry", r * 0.3).style("cursor", "pointer").style("fill", getNodeFillForObject(node.data?.data?.object));
11356
11394
  }
11357
- }
11358
- };
11359
-
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";
11364
-
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;
11395
+ },
11396
+ swarm: {
11397
+ value: "swarm",
11398
+ icon: "ph--microscope--regular",
11399
+ label: "Swarm",
11400
+ edgeOpacity: 0.3,
11401
+ trackPointer: true,
11402
+ emitsHover: true,
11403
+ applyNode: applyNodeSwarm,
11404
+ // Swarm in SVG: a per-tick projector mirroring force's emit-positions pattern.
11405
+ createProjector: (ctx, { prev }) => new GraphSwarmProjector(ctx, void 0, void 0, prev),
11406
+ // Match the force variant's shape so identity-by-id transitions read continuously.
11407
+ // The tail is a SINGLE `<path>` traced through head + history points; its stroke
11408
+ // uses a per-boid `<linearGradient>` that fades head → tail. Done this way (rather
11409
+ // than as N overlapping `<line>` segments) so coincident segment endpoints don't
11410
+ // compound their alpha and read as a striped trail.
11411
+ renderNode: (group, node) => {
11412
+ const fill = getNodeFillForObject(node.data?.data?.object);
11413
+ const r = node.r ?? 6;
11414
+ const strokeWidth = Math.max(1, r * 0.6);
11415
+ const gradId = `dx-swarm-grad-${String(node.id).replace(/[^\w-]/g, "_")}`;
11416
+ const grad = group.append("defs").append("linearGradient").attr("id", gradId).attr("gradientUnits", "userSpaceOnUse").attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", 0);
11417
+ grad.append("stop").attr("offset", 0).attr("stop-color", fill).attr("stop-opacity", 0.7);
11418
+ grad.append("stop").attr("offset", 1).attr("stop-color", fill).attr("stop-opacity", 0);
11419
+ group.append("path").classed("dx-swarm-tail", true).attr("fill", "none").attr("stroke", `url(#${gradId})`).attr("stroke-width", strokeWidth).attr("stroke-linecap", "round").attr("stroke-linejoin", "round").attr("pointer-events", "none");
11420
+ group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", fill);
11371
11421
  }
11372
- return collapsed.has(d.id) ? void 0 : d.children;
11373
- });
11374
- };
11375
- var isCollapsed = (data, collapsed) => Boolean(data.children?.length) && collapsed.has(data.id);
11376
- var isLeaf = (data) => !data.children?.length;
11377
-
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);
11422
+ },
11423
+ neighborhood: {
11424
+ value: "neighborhood",
11425
+ icon: "ph--tree-structure--regular",
11426
+ label: "Neighborhood",
11427
+ emitsHover: true,
11428
+ // Tidy tree: the active node (passed as `focusId`) is the root, drawn on the left and vertically
11429
+ // centred; the pre-traversed n-hop neighbourhood fans out to the right. No focus → plain ring.
11430
+ createProjector: (ctx, { focusId, prev }) => new GraphTreeProjector(ctx, {
11431
+ duration: TWEEN_MS,
11432
+ focus: focusId,
11433
+ nodeRadius: TREE_NODE_RADIUS,
11434
+ rootRadius: TREE_ROOT_RADIUS
11435
+ }, void 0, prev),
11436
+ renderNode: (group, node) => {
11437
+ const obj = node.data?.data?.object;
11438
+ const isRoot = node.type === TREE_NODE_TYPE_ROOT;
11439
+ const r = isRoot ? TREE_ROOT_RADIUS : TREE_NODE_RADIUS;
11440
+ group.append("circle").attr("r", r).style("cursor", "pointer").style("fill", obj ? getNodeFillForObject(obj) : "var(--color-neutral-500)");
11441
+ const text = labelForLeaf(node, obj);
11442
+ if (text) {
11443
+ const placement = isRoot ? "above" : node.type === TREE_NODE_TYPE_INBOUND ? "left" : "right";
11444
+ appendTreeLabel(group, text, r, placement);
11391
11445
  }
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
11446
  }
11402
- };
11403
- const handleHoverRef = useRef5(() => {
11447
+ }
11448
+ };
11449
+ var VARIANTS = [
11450
+ "force",
11451
+ "cluster",
11452
+ "bundle",
11453
+ "plexus",
11454
+ "lattice",
11455
+ "swarm"
11456
+ ].map((id) => DEFINITIONS[id]);
11457
+ var getVariant = (value) => DEFINITIONS[value];
11458
+ var isVariant = (value) => VARIANTS.some((variant) => variant.value === value);
11459
+
11460
+ // src/components/Visualization/Visualization.tsx
11461
+ var [VisualizationProvider, useVisualizationContext] = createContext("Visualization.Root");
11462
+ var VisualizationRoot = slottable(({ children, asChild, model, variant, focus, onSurfaceClick, ...props }, forwardedRef) => {
11463
+ const { className, ...rest } = composableProps3(props, {
11464
+ classNames: "dx-expander relative"
11404
11465
  });
11405
- handleHoverRef.current = (node, event) => onNodeHover?.(node, event);
11406
- const root = useMemo4(() => buildHierarchy(data, collapsed), [
11407
- data,
11408
- collapsed
11466
+ const Comp = asChild ? Slot : "div";
11467
+ return /* @__PURE__ */ React6.createElement(VisualizationProvider, {
11468
+ model,
11469
+ variant: getVariant(variant),
11470
+ focus
11471
+ }, /* @__PURE__ */ React6.createElement(Comp, {
11472
+ ...rest,
11473
+ className,
11474
+ onClick: onSurfaceClick,
11475
+ ref: forwardedRef
11476
+ }, children));
11477
+ });
11478
+ VisualizationRoot.displayName = "Visualization.Root";
11479
+ var VisualizationGraph = ({ debug = false, onNodeHover }) => {
11480
+ const { model, variant, focus } = useVisualizationContext("Visualization.Graph");
11481
+ const svgRef = useRef4(null);
11482
+ const [projector, setProjector] = useState3();
11483
+ const projectorRef = useRef4(void 0);
11484
+ projectorRef.current = projector;
11485
+ const [clickFocusId, setClickFocusId] = useState3(void 0);
11486
+ const focusId = focus ?? clickFocusId;
11487
+ const lastLayoutRef = useRef4(void 0);
11488
+ useEffect6(() => model?.subscribe(() => void 0), [
11489
+ model
11409
11490
  ]);
11410
- useEffect8(() => {
11411
- if (!svgRef.current || !width || !height) {
11412
- return;
11491
+ useEffect6(() => {
11492
+ if (!variant.focusable) {
11493
+ setClickFocusId(void 0);
11413
11494
  }
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
- });
11425
11495
  }, [
11426
- root,
11427
- width,
11428
- height,
11429
- r,
11430
- padding,
11431
- label,
11432
- slots,
11433
- collapsed,
11434
- cluster2
11496
+ variant
11435
11497
  ]);
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}`
11445
- }));
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
- };
11483
-
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);
11498
+ useEffect6(() => {
11499
+ if (projectorRef.current?.layout) {
11500
+ lastLayoutRef.current = projectorRef.current.layout;
11510
11501
  }
11511
- };
11512
- const root = useMemo5(() => buildHierarchy(data, collapsed), [
11513
- data,
11514
- collapsed
11502
+ if (!svgRef.current) {
11503
+ return;
11504
+ }
11505
+ setProjector(variant.createProjector(svgRef.current, {
11506
+ focusId,
11507
+ prev: lastLayoutRef.current
11508
+ }));
11509
+ }, [
11510
+ variant,
11511
+ focusId
11515
11512
  ]);
11516
- useEffect9(() => {
11517
- if (!svgRef.current || !width || !height) {
11513
+ const handleSelect = useCallback3((node) => {
11514
+ if (!variant.focusable || !node.data?.data?.object) {
11518
11515
  return;
11519
11516
  }
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
- });
11517
+ setClickFocusId((current) => current === node.id ? void 0 : node.id);
11530
11518
  }, [
11531
- root,
11532
- width,
11533
- height,
11534
- r,
11535
- margin,
11536
- label,
11537
- slots,
11538
- collapsed
11519
+ variant
11539
11520
  ]);
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
- }));
11550
- };
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;
11521
+ const handlePointerMove = useCallback3((point) => {
11522
+ if (projector instanceof GraphSwarmProjector2) {
11523
+ projector.setCursor(point.x, point.y);
11565
11524
  }
11566
- if (d.x < x0) {
11567
- x0 = d.x;
11525
+ }, [
11526
+ projector
11527
+ ]);
11528
+ const handlePointerLeave = useCallback3(() => {
11529
+ if (projector instanceof GraphSwarmProjector2) {
11530
+ projector.setCursor(null);
11568
11531
  }
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
- };
11605
-
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
11618
- });
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
11637
- });
11532
+ }, [
11533
+ projector
11534
+ ]);
11535
+ const handleInspect = useCallback3((node, event) => {
11536
+ if (!variant.emitsHover) {
11537
+ return;
11638
11538
  }
11539
+ onNodeHover?.(node ? {
11540
+ id: node.id,
11541
+ data: node.data?.data?.object
11542
+ } : null, event);
11639
11543
  }, [
11640
11544
  variant,
11641
- classNames,
11642
- data,
11643
- edges,
11644
- label,
11645
- slots,
11646
- initialCollapsed,
11647
- onNodeClick,
11648
11545
  onNodeHover
11649
11546
  ]);
11547
+ const pointerProps = variant.trackPointer ? {
11548
+ onPointerMove: handlePointerMove,
11549
+ onPointerLeave: handlePointerLeave
11550
+ } : void 0;
11551
+ return /* @__PURE__ */ React6.createElement(SVG2.Root, {
11552
+ ref: svgRef
11553
+ }, /* @__PURE__ */ React6.createElement(SVG2.Zoom, {
11554
+ extent: [
11555
+ 1 / 2,
11556
+ 2
11557
+ ]
11558
+ }, projector && /* @__PURE__ */ React6.createElement(SVG2.Graph, {
11559
+ model,
11560
+ projector,
11561
+ renderNode: variant.renderNode,
11562
+ applyNode: variant.applyNode,
11563
+ edgeOpacity: variant.edgeOpacity,
11564
+ drag: variant.drag,
11565
+ highlightOnHover: variant.highlightOnHover,
11566
+ onSelect: handleSelect,
11567
+ onInspect: handleInspect,
11568
+ ...pointerProps
11569
+ })), debug && /* @__PURE__ */ React6.createElement(SVG2.FPS, null));
11570
+ };
11571
+ VisualizationGraph.displayName = "Visualization.Graph";
11572
+ var Visualization = {
11573
+ Root: VisualizationRoot,
11574
+ Graph: VisualizationGraph
11650
11575
  };
11651
11576
  export {
11652
11577
  CanvasForceGraph,
11653
11578
  Chart,
11654
11579
  ForceGraph,
11655
11580
  Globe,
11656
- HierarchicalEdgeBundling,
11657
11581
  Lattice,
11658
- RadialTree,
11659
- TidyTree,
11660
11582
  Tree,
11661
- buildHierarchy,
11662
- defaultTreeLayoutSlots,
11663
- isCollapsed,
11664
- isLeaf,
11583
+ TreeNodeType,
11584
+ TreeType,
11585
+ VARIANTS,
11586
+ Visualization,
11587
+ getVariant,
11588
+ isVariant,
11665
11589
  treeTypeToTreeNode
11666
11590
  };
11667
11591
  //# sourceMappingURL=index.mjs.map