@graph-render/react 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -34
- package/README.md +73 -0
- package/dist/index.js +1611 -1910
- package/dist/src/components/Graph.d.ts.map +1 -1
- package/dist/src/hooks/useGraphModel.d.ts.map +1 -1
- package/dist/src/hooks/useGraphSearchState.d.ts.map +1 -1
- package/dist/src/hooks/useGraphViewState.d.ts +29 -0
- package/dist/src/hooks/useGraphViewState.d.ts.map +1 -0
- package/dist/src/utils/columns.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/components/Graph.tsx +25 -90
- package/src/hooks/useGraphCollapse.ts +1 -1
- package/src/hooks/useGraphModel.ts +118 -94
- package/src/hooks/useGraphSearchState.ts +15 -17
- package/src/hooks/useGraphViewState.ts +151 -0
- package/src/utils/columns.ts +4 -2
- package/tsconfig.json +5 -1
|
@@ -115,7 +115,10 @@ const buildFallbackEdges = (
|
|
|
115
115
|
...edge,
|
|
116
116
|
type: edge.type ?? EdgeType.Directed,
|
|
117
117
|
points: [
|
|
118
|
-
{
|
|
118
|
+
{
|
|
119
|
+
x: right - Math.min(sourceWidth * 0.25, 18),
|
|
120
|
+
y: top + Math.min(sourceHeight * 0.35, 18),
|
|
121
|
+
},
|
|
119
122
|
{ x: right + 28, y: top - 20 },
|
|
120
123
|
{ x: right + 36, y: top + sourceHeight / 2 },
|
|
121
124
|
{ x: right - Math.min(sourceWidth * 0.25, 18), y: top + sourceHeight * 0.8 },
|
|
@@ -175,7 +178,13 @@ interface UseGraphModelOptions {
|
|
|
175
178
|
* - `'routing'` — the default `routeEdges` threw
|
|
176
179
|
* - `'routing-override'`— a `routeEdgesOverride` threw (default was used as fallback)
|
|
177
180
|
*/
|
|
178
|
-
onError?: (
|
|
181
|
+
onError?: (
|
|
182
|
+
error: Error,
|
|
183
|
+
context: {
|
|
184
|
+
graph: NxGraphInput;
|
|
185
|
+
phase: 'layout' | 'layout-override' | 'routing' | 'routing-override';
|
|
186
|
+
}
|
|
187
|
+
) => void;
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
export interface GraphModelResult {
|
|
@@ -211,10 +220,15 @@ export const useGraphModel = ({
|
|
|
211
220
|
>({});
|
|
212
221
|
|
|
213
222
|
const { nodes: sourceNodes, edges: sourceEdges } = useMemo(
|
|
214
|
-
() =>
|
|
215
|
-
|
|
223
|
+
() =>
|
|
224
|
+
fromTypedNxGraph(graph, config.defaultEdgeType, {
|
|
225
|
+
inputValidationMode: config.inputValidationMode,
|
|
226
|
+
}),
|
|
227
|
+
[config.defaultEdgeType, config.inputValidationMode, graph]
|
|
216
228
|
);
|
|
217
229
|
|
|
230
|
+
const allowDegradedGraph = config.failureBehavior === 'degrade';
|
|
231
|
+
|
|
218
232
|
const nodesWithMeasuredSize = useMemo(
|
|
219
233
|
() =>
|
|
220
234
|
sourceNodes.map((node) => ({
|
|
@@ -307,65 +321,70 @@ export const useGraphModel = ({
|
|
|
307
321
|
[config.nodeSizing]
|
|
308
322
|
);
|
|
309
323
|
|
|
310
|
-
const positionedNodes: PositionedNode[] = useMemo(
|
|
311
|
-
()
|
|
312
|
-
if (!layoutNodesOverride) {
|
|
313
|
-
try {
|
|
314
|
-
const laidOutNodes = layoutNodes(layoutOptions);
|
|
315
|
-
validatePositionedNodes(laidOutNodes, visibleNodes, 'layout');
|
|
316
|
-
return laidOutNodes;
|
|
317
|
-
} catch (error) {
|
|
318
|
-
onError?.(toError(error), {
|
|
319
|
-
graph,
|
|
320
|
-
phase: 'layout',
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const fallbackNodes = buildFallbackLayout(layoutOptions);
|
|
325
|
-
validatePositionedNodes(fallbackNodes, visibleNodes, 'layout');
|
|
326
|
-
return fallbackNodes;
|
|
327
|
-
} catch (fallbackError) {
|
|
328
|
-
const normalizedFallbackError = toError(fallbackError);
|
|
329
|
-
onError?.(normalizedFallbackError, {
|
|
330
|
-
graph,
|
|
331
|
-
phase: 'layout',
|
|
332
|
-
});
|
|
333
|
-
throw normalizedFallbackError;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
324
|
+
const positionedNodes: PositionedNode[] = useMemo(() => {
|
|
325
|
+
if (!layoutNodesOverride) {
|
|
338
326
|
try {
|
|
339
|
-
const
|
|
340
|
-
validatePositionedNodes(
|
|
341
|
-
return
|
|
327
|
+
const laidOutNodes = layoutNodes(layoutOptions);
|
|
328
|
+
validatePositionedNodes(laidOutNodes, visibleNodes, 'layout');
|
|
329
|
+
return laidOutNodes;
|
|
342
330
|
} catch (error) {
|
|
343
331
|
onError?.(toError(error), {
|
|
344
332
|
graph,
|
|
345
|
-
phase: 'layout
|
|
333
|
+
phase: 'layout',
|
|
346
334
|
});
|
|
347
335
|
|
|
336
|
+
if (!allowDegradedGraph) {
|
|
337
|
+
throw toError(error);
|
|
338
|
+
}
|
|
339
|
+
|
|
348
340
|
try {
|
|
349
|
-
const fallbackNodes =
|
|
341
|
+
const fallbackNodes = buildFallbackLayout(layoutOptions);
|
|
350
342
|
validatePositionedNodes(fallbackNodes, visibleNodes, 'layout');
|
|
351
343
|
return fallbackNodes;
|
|
352
344
|
} catch (fallbackError) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
} catch (finalFallbackError) {
|
|
360
|
-
const normalizedFinalFallbackError = toError(finalFallbackError);
|
|
361
|
-
onError?.(normalizedFinalFallbackError, { graph, phase: 'layout' });
|
|
362
|
-
throw normalizedFinalFallbackError;
|
|
363
|
-
}
|
|
345
|
+
const normalizedFallbackError = toError(fallbackError);
|
|
346
|
+
onError?.(normalizedFallbackError, {
|
|
347
|
+
graph,
|
|
348
|
+
phase: 'layout',
|
|
349
|
+
});
|
|
350
|
+
throw normalizedFallbackError;
|
|
364
351
|
}
|
|
365
352
|
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
const overrideNodes = layoutNodesOverride(layoutOptions);
|
|
357
|
+
validatePositionedNodes(overrideNodes, visibleNodes, 'layout override');
|
|
358
|
+
return overrideNodes;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
onError?.(toError(error), {
|
|
361
|
+
graph,
|
|
362
|
+
phase: 'layout-override',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (!allowDegradedGraph) {
|
|
366
|
+
throw toError(error);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const fallbackNodes = layoutNodes(layoutOptions);
|
|
371
|
+
validatePositionedNodes(fallbackNodes, visibleNodes, 'layout');
|
|
372
|
+
return fallbackNodes;
|
|
373
|
+
} catch (fallbackError) {
|
|
374
|
+
onError?.(toError(fallbackError), { graph, phase: 'layout' });
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const finalFallbackNodes = buildFallbackLayout(layoutOptions);
|
|
378
|
+
validatePositionedNodes(finalFallbackNodes, visibleNodes, 'layout');
|
|
379
|
+
return finalFallbackNodes;
|
|
380
|
+
} catch (finalFallbackError) {
|
|
381
|
+
const normalizedFinalFallbackError = toError(finalFallbackError);
|
|
382
|
+
onError?.(normalizedFinalFallbackError, { graph, phase: 'layout' });
|
|
383
|
+
throw normalizedFinalFallbackError;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}, [graph, layoutNodesOverride, layoutOptions, onError, visibleNodes]);
|
|
369
388
|
|
|
370
389
|
const edgeRoutingOptions = useMemo(
|
|
371
390
|
() => ({
|
|
@@ -380,57 +399,62 @@ export const useGraphModel = ({
|
|
|
380
399
|
[config]
|
|
381
400
|
);
|
|
382
401
|
|
|
383
|
-
const positionedEdges: PositionedEdge[] = useMemo(
|
|
384
|
-
() =>
|
|
385
|
-
const nodeIds = new Set(positionedNodes.map((node) => node.id));
|
|
386
|
-
|
|
387
|
-
if (!routeEdgesOverride) {
|
|
388
|
-
try {
|
|
389
|
-
const routedEdges = routeEdges(positionedNodes, visibleEdges, edgeRoutingOptions);
|
|
390
|
-
validatePositionedEdges(routedEdges, nodeIds, 'routing');
|
|
391
|
-
return routedEdges;
|
|
392
|
-
} catch (error) {
|
|
393
|
-
onError?.(toError(error), {
|
|
394
|
-
graph,
|
|
395
|
-
phase: 'routing',
|
|
396
|
-
});
|
|
397
|
-
const fallbackEdges = buildFallbackEdges(positionedNodes, visibleEdges);
|
|
398
|
-
validatePositionedEdges(fallbackEdges, nodeIds, 'routing');
|
|
399
|
-
return fallbackEdges;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
+
const positionedEdges: PositionedEdge[] = useMemo(() => {
|
|
403
|
+
const nodeIds = new Set(positionedNodes.map((node) => node.id));
|
|
402
404
|
|
|
405
|
+
if (!routeEdgesOverride) {
|
|
403
406
|
try {
|
|
404
|
-
const
|
|
405
|
-
validatePositionedEdges(
|
|
406
|
-
return
|
|
407
|
+
const routedEdges = routeEdges(positionedNodes, visibleEdges, edgeRoutingOptions);
|
|
408
|
+
validatePositionedEdges(routedEdges, nodeIds, 'routing');
|
|
409
|
+
return routedEdges;
|
|
407
410
|
} catch (error) {
|
|
408
411
|
onError?.(toError(error), {
|
|
409
412
|
graph,
|
|
410
|
-
phase: 'routing
|
|
413
|
+
phase: 'routing',
|
|
411
414
|
});
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const fallbackEdges = routeEdges(positionedNodes, visibleEdges, edgeRoutingOptions);
|
|
415
|
-
validatePositionedEdges(fallbackEdges, nodeIds, 'routing');
|
|
416
|
-
return fallbackEdges;
|
|
417
|
-
} catch (fallbackError) {
|
|
418
|
-
onError?.(toError(fallbackError), { graph, phase: 'routing' });
|
|
419
|
-
const finalFallbackEdges = buildFallbackEdges(positionedNodes, visibleEdges);
|
|
420
|
-
validatePositionedEdges(finalFallbackEdges, nodeIds, 'routing');
|
|
421
|
-
return finalFallbackEdges;
|
|
415
|
+
if (!allowDegradedGraph) {
|
|
416
|
+
throw toError(error);
|
|
422
417
|
}
|
|
418
|
+
const fallbackEdges = buildFallbackEdges(positionedNodes, visibleEdges);
|
|
419
|
+
validatePositionedEdges(fallbackEdges, nodeIds, 'routing');
|
|
420
|
+
return fallbackEdges;
|
|
423
421
|
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const overrideEdges = routeEdgesOverride(positionedNodes, visibleEdges, edgeRoutingOptions);
|
|
426
|
+
validatePositionedEdges(overrideEdges, nodeIds, 'routing override');
|
|
427
|
+
return overrideEdges;
|
|
428
|
+
} catch (error) {
|
|
429
|
+
onError?.(toError(error), {
|
|
430
|
+
graph,
|
|
431
|
+
phase: 'routing-override',
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
if (!allowDegradedGraph) {
|
|
435
|
+
throw toError(error);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
const fallbackEdges = routeEdges(positionedNodes, visibleEdges, edgeRoutingOptions);
|
|
440
|
+
validatePositionedEdges(fallbackEdges, nodeIds, 'routing');
|
|
441
|
+
return fallbackEdges;
|
|
442
|
+
} catch (fallbackError) {
|
|
443
|
+
onError?.(toError(fallbackError), { graph, phase: 'routing' });
|
|
444
|
+
const finalFallbackEdges = buildFallbackEdges(positionedNodes, visibleEdges);
|
|
445
|
+
validatePositionedEdges(finalFallbackEdges, nodeIds, 'routing');
|
|
446
|
+
return finalFallbackEdges;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}, [
|
|
450
|
+
edgeRoutingOptions,
|
|
451
|
+
graph,
|
|
452
|
+
onError,
|
|
453
|
+
allowDegradedGraph,
|
|
454
|
+
positionedNodes,
|
|
455
|
+
routeEdgesOverride,
|
|
456
|
+
visibleEdges,
|
|
457
|
+
]);
|
|
434
458
|
|
|
435
459
|
return {
|
|
436
460
|
childNodeIdsByParent,
|
|
@@ -100,23 +100,21 @@ export const useGraphSearchState = <
|
|
|
100
100
|
return { nodeIds: [], edgeIds: [] };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
return (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})()
|
|
119
|
-
);
|
|
103
|
+
return (() => {
|
|
104
|
+
try {
|
|
105
|
+
return (
|
|
106
|
+
highlightStrategy?.({
|
|
107
|
+
nodes,
|
|
108
|
+
edges,
|
|
109
|
+
query: searchQuery,
|
|
110
|
+
matchedNodeIds: searchMatchedNodeIds,
|
|
111
|
+
matchedEdgeIds: searchMatchedEdgeIds,
|
|
112
|
+
}) ?? { nodeIds: [], edgeIds: [] }
|
|
113
|
+
);
|
|
114
|
+
} catch {
|
|
115
|
+
return { nodeIds: [], edgeIds: [] };
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
120
118
|
}, [edges, highlightStrategy, nodes, searchMatchedEdgeIds, searchMatchedNodeIds, searchQuery]);
|
|
121
119
|
|
|
122
120
|
const effectiveHighlightedNodeSet = useMemo(
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
GraphSelection,
|
|
4
|
+
GraphViewport,
|
|
5
|
+
GraphProps,
|
|
6
|
+
GraphHandle,
|
|
7
|
+
NxGraphInput,
|
|
8
|
+
PositionedNode,
|
|
9
|
+
PositionedEdge,
|
|
10
|
+
NodeData,
|
|
11
|
+
EdgeData,
|
|
12
|
+
} from '@graph-render/types';
|
|
13
|
+
import { clampZoom } from '../utils/viewport';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_VIEWPORT: GraphViewport = { x: 0, y: 0, zoom: 1 };
|
|
16
|
+
|
|
17
|
+
const normalizeViewport = (
|
|
18
|
+
viewport: GraphViewport,
|
|
19
|
+
minZoom: number,
|
|
20
|
+
maxZoom: number
|
|
21
|
+
): GraphViewport => ({
|
|
22
|
+
x: Number.isFinite(viewport.x) ? viewport.x : 0,
|
|
23
|
+
y: Number.isFinite(viewport.y) ? viewport.y : 0,
|
|
24
|
+
zoom: clampZoom(Number.isFinite(viewport.zoom) ? viewport.zoom : 1, minZoom, maxZoom),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
interface UseGraphViewStateOptions {
|
|
28
|
+
controlledViewport: GraphViewport | undefined;
|
|
29
|
+
defaultViewport: Partial<GraphViewport> | undefined;
|
|
30
|
+
safeMinZoom: number;
|
|
31
|
+
safeMaxZoom: number;
|
|
32
|
+
onViewportChange: ((viewport: GraphViewport) => void) | undefined;
|
|
33
|
+
selectedNodeIds: string[] | undefined;
|
|
34
|
+
selectedEdgeIds: string[] | undefined;
|
|
35
|
+
defaultSelectedNodeIds: string[] | undefined;
|
|
36
|
+
defaultSelectedEdgeIds: string[] | undefined;
|
|
37
|
+
onSelectionChange: ((selection: GraphSelection) => void) | undefined;
|
|
38
|
+
controlledFocusedNodeId: string | null | undefined;
|
|
39
|
+
defaultFocusedNodeId: string | null;
|
|
40
|
+
onFocusedNodeChange: ((nodeId: string | null) => void) | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface UseGraphViewStateResult {
|
|
44
|
+
viewport: GraphViewport;
|
|
45
|
+
viewportRef: React.MutableRefObject<GraphViewport>;
|
|
46
|
+
selection: GraphSelection;
|
|
47
|
+
selectionRef: React.MutableRefObject<GraphSelection>;
|
|
48
|
+
focusedNodeId: string | null;
|
|
49
|
+
updateViewport: GraphHandle['setViewport'];
|
|
50
|
+
updateSelection: (next: GraphSelection | ((current: GraphSelection) => GraphSelection)) => void;
|
|
51
|
+
updateFocusedNode: (nodeId: string | null) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const useGraphViewState = ({
|
|
55
|
+
controlledViewport,
|
|
56
|
+
defaultViewport,
|
|
57
|
+
safeMinZoom,
|
|
58
|
+
safeMaxZoom,
|
|
59
|
+
onViewportChange,
|
|
60
|
+
selectedNodeIds,
|
|
61
|
+
selectedEdgeIds,
|
|
62
|
+
defaultSelectedNodeIds,
|
|
63
|
+
defaultSelectedEdgeIds,
|
|
64
|
+
onSelectionChange,
|
|
65
|
+
controlledFocusedNodeId,
|
|
66
|
+
defaultFocusedNodeId,
|
|
67
|
+
onFocusedNodeChange,
|
|
68
|
+
}: UseGraphViewStateOptions): UseGraphViewStateResult => {
|
|
69
|
+
const [internalViewport, setInternalViewport] = useState<GraphViewport>(() =>
|
|
70
|
+
normalizeViewport({ ...DEFAULT_VIEWPORT, ...(defaultViewport ?? {}) }, safeMinZoom, safeMaxZoom)
|
|
71
|
+
);
|
|
72
|
+
const [internalSelection, setInternalSelection] = useState<GraphSelection>({
|
|
73
|
+
nodeIds: defaultSelectedNodeIds ?? [],
|
|
74
|
+
edgeIds: defaultSelectedEdgeIds ?? [],
|
|
75
|
+
});
|
|
76
|
+
const [internalFocusedNodeId, setInternalFocusedNodeId] = useState<string | null>(
|
|
77
|
+
defaultFocusedNodeId
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const viewport = useMemo(
|
|
81
|
+
() => normalizeViewport(controlledViewport ?? internalViewport, safeMinZoom, safeMaxZoom),
|
|
82
|
+
[controlledViewport, internalViewport, safeMaxZoom, safeMinZoom]
|
|
83
|
+
);
|
|
84
|
+
const viewportRef = useRef(viewport);
|
|
85
|
+
viewportRef.current = viewport;
|
|
86
|
+
const onViewportChangeRef = useRef(onViewportChange);
|
|
87
|
+
onViewportChangeRef.current = onViewportChange;
|
|
88
|
+
|
|
89
|
+
const selection = useMemo<GraphSelection>(
|
|
90
|
+
() => ({
|
|
91
|
+
nodeIds: selectedNodeIds ?? internalSelection.nodeIds,
|
|
92
|
+
edgeIds: selectedEdgeIds ?? internalSelection.edgeIds,
|
|
93
|
+
}),
|
|
94
|
+
[selectedNodeIds, selectedEdgeIds, internalSelection]
|
|
95
|
+
);
|
|
96
|
+
const selectionRef = useRef(selection);
|
|
97
|
+
selectionRef.current = selection;
|
|
98
|
+
|
|
99
|
+
const focusedNodeId =
|
|
100
|
+
controlledFocusedNodeId !== undefined ? controlledFocusedNodeId : internalFocusedNodeId;
|
|
101
|
+
|
|
102
|
+
const updateViewport = useCallback<GraphHandle['setViewport']>(
|
|
103
|
+
(next) => {
|
|
104
|
+
const current = viewportRef.current;
|
|
105
|
+
const resolved = typeof next === 'function' ? next(current) : next;
|
|
106
|
+
const normalized = normalizeViewport({ ...current, ...resolved }, safeMinZoom, safeMaxZoom);
|
|
107
|
+
|
|
108
|
+
if (!controlledViewport) {
|
|
109
|
+
setInternalViewport(normalized);
|
|
110
|
+
}
|
|
111
|
+
onViewportChangeRef.current?.(normalized);
|
|
112
|
+
},
|
|
113
|
+
[controlledViewport, safeMaxZoom, safeMinZoom]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const updateSelection = useCallback(
|
|
117
|
+
(next: GraphSelection | ((current: GraphSelection) => GraphSelection)) => {
|
|
118
|
+
const current = selectionRef.current;
|
|
119
|
+
const resolved = typeof next === 'function' ? next(current) : next;
|
|
120
|
+
if (selectedNodeIds == null || selectedEdgeIds == null) {
|
|
121
|
+
setInternalSelection((previous) => ({
|
|
122
|
+
nodeIds: selectedNodeIds == null ? resolved.nodeIds : previous.nodeIds,
|
|
123
|
+
edgeIds: selectedEdgeIds == null ? resolved.edgeIds : previous.edgeIds,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
onSelectionChange?.(resolved);
|
|
127
|
+
},
|
|
128
|
+
[onSelectionChange, selectedEdgeIds, selectedNodeIds]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const updateFocusedNode = useCallback(
|
|
132
|
+
(nodeId: string | null) => {
|
|
133
|
+
if (controlledFocusedNodeId === undefined) {
|
|
134
|
+
setInternalFocusedNodeId(nodeId);
|
|
135
|
+
}
|
|
136
|
+
onFocusedNodeChange?.(nodeId);
|
|
137
|
+
},
|
|
138
|
+
[controlledFocusedNodeId, onFocusedNodeChange]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
viewport,
|
|
143
|
+
viewportRef,
|
|
144
|
+
selection,
|
|
145
|
+
selectionRef,
|
|
146
|
+
focusedNodeId,
|
|
147
|
+
updateViewport,
|
|
148
|
+
updateSelection,
|
|
149
|
+
updateFocusedNode,
|
|
150
|
+
};
|
|
151
|
+
};
|
package/src/utils/columns.ts
CHANGED
|
@@ -16,7 +16,9 @@ export const groupPositionedNodesByColumn = <TNode extends PositionedNode = Posi
|
|
|
16
16
|
nodes: TNode[],
|
|
17
17
|
tolerance: number = DEFAULT_COLUMN_TOLERANCE
|
|
18
18
|
): NodeColumn<TNode>[] => {
|
|
19
|
-
const sortedNodes = [...nodes].sort(
|
|
19
|
+
const sortedNodes = [...nodes].sort(
|
|
20
|
+
(left, right) => getNodeCenterX(left) - getNodeCenterX(right)
|
|
21
|
+
);
|
|
20
22
|
const columns: Array<{ centerX: number; avgWidth: number; nodes: TNode[] }> = [];
|
|
21
23
|
|
|
22
24
|
sortedNodes.forEach((node) => {
|
|
@@ -48,4 +50,4 @@ export const groupPositionedNodesByColumn = <TNode extends PositionedNode = Posi
|
|
|
48
50
|
centerX: column.centerX,
|
|
49
51
|
nodes: [...column.nodes].sort((left, right) => left.position.y - right.position.y),
|
|
50
52
|
}));
|
|
51
|
-
};
|
|
53
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -8,5 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"include": ["src"],
|
|
10
10
|
"exclude": ["dist", "node_modules"],
|
|
11
|
-
"references": [
|
|
11
|
+
"references": [
|
|
12
|
+
{ "path": "./tsconfig.node.json" },
|
|
13
|
+
{ "path": "../types" },
|
|
14
|
+
{ "path": "../core-graph-render" }
|
|
15
|
+
]
|
|
12
16
|
}
|