Merge branch 'xyflow' into feat/ssr
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system';
|
||||
import {
|
||||
getElementsToRemove,
|
||||
getIncomersBase,
|
||||
getOutgoersBase,
|
||||
getOverlappingArea,
|
||||
isRectObject,
|
||||
nodeToRect,
|
||||
type Rect,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import useViewportHelper from './useViewportHelper';
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
@@ -164,6 +172,8 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
|
||||
}, []);
|
||||
|
||||
const getNodeRect = useCallback(
|
||||
@@ -223,6 +233,41 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
[]
|
||||
);
|
||||
|
||||
const getConnectedEdges = useCallback<Instance.getConnectedEdges>((node) => {
|
||||
const { edges } = store.getState();
|
||||
|
||||
const nodeIds = new Set();
|
||||
if (typeof node === 'string') {
|
||||
nodeIds.add(node);
|
||||
} else if (node.length >= 1) {
|
||||
node.forEach((n) => {
|
||||
nodeIds.add(n.id);
|
||||
});
|
||||
}
|
||||
|
||||
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
||||
}, []);
|
||||
|
||||
const getIncomers = useCallback<Instance.getIncomers>((node) => {
|
||||
const { nodes, edges } = store.getState();
|
||||
|
||||
if (typeof node === 'string') {
|
||||
return getIncomersBase({ id: node }, nodes, edges);
|
||||
}
|
||||
|
||||
return getIncomersBase(node, nodes, edges);
|
||||
}, []);
|
||||
|
||||
const getOutgoers = useCallback<Instance.getOutgoers>((node) => {
|
||||
const { nodes, edges } = store.getState();
|
||||
|
||||
if (typeof node == 'string') {
|
||||
return getOutgoersBase({ id: node }, nodes, edges);
|
||||
}
|
||||
|
||||
return getOutgoersBase(node, nodes, edges);
|
||||
}, []);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
...viewportHelper,
|
||||
@@ -238,6 +283,9 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
deleteElements,
|
||||
getIntersectingNodes,
|
||||
isNodeIntersecting,
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
};
|
||||
}, [
|
||||
viewportHelper,
|
||||
@@ -253,5 +301,8 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
deleteElements,
|
||||
getIntersectingNodes,
|
||||
isNodeIntersecting,
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { pointToRendererPoint, getTransformForBounds, fitView, type XYPosition } from '@xyflow/system';
|
||||
import {
|
||||
pointToRendererPoint,
|
||||
getTransformForBounds,
|
||||
fitView,
|
||||
type XYPosition,
|
||||
rendererPointToPoint,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStoreApi, useStore } from '../hooks/useStore';
|
||||
import type { ViewportHelperFunctions, ReactFlowState } from '../types';
|
||||
@@ -85,6 +91,36 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
||||
const { transform, snapToGrid, snapGrid } = store.getState();
|
||||
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
|
||||
},
|
||||
screenToFlowCoordinate: (position: XYPosition) => {
|
||||
const { transform, snapToGrid, snapGrid, domNode } = store.getState();
|
||||
if (domNode) {
|
||||
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
||||
|
||||
const correctedPosition = {
|
||||
x: position.x - domX,
|
||||
y: position.y - domY,
|
||||
};
|
||||
|
||||
return pointToRendererPoint(correctedPosition, transform, snapToGrid, snapGrid || [1, 1]);
|
||||
}
|
||||
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
flowToScreenCoordinate: (position: XYPosition) => {
|
||||
const { transform, domNode } = store.getState();
|
||||
if (domNode) {
|
||||
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
||||
|
||||
const rendererPosition = rendererPointToPoint(position, transform);
|
||||
|
||||
return {
|
||||
x: rendererPosition.x + domX,
|
||||
y: rendererPosition.y + domY,
|
||||
};
|
||||
}
|
||||
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
viewportInitialized: panZoomInitialized,
|
||||
};
|
||||
}, [panZoomInitialized]);
|
||||
|
||||
@@ -55,5 +55,7 @@ export type ViewportHelperFunctions = {
|
||||
setCenter: SetCenter;
|
||||
fitBounds: FitBounds;
|
||||
project: Project;
|
||||
screenToFlowCoordinate: Project;
|
||||
flowToScreenCoordinate: Project;
|
||||
viewportInitialized: boolean;
|
||||
};
|
||||
|
||||
@@ -28,7 +28,10 @@ export namespace Instance {
|
||||
export type GetEdge<EdgeData> = (id: string) => Edge<EdgeData> | undefined;
|
||||
export type AddEdges<EdgeData> = (payload: Edge<EdgeData>[] | Edge<EdgeData>) => void;
|
||||
export type ToObject<NodeData = any, EdgeData = any> = () => ReactFlowJsonObject<NodeData, EdgeData>;
|
||||
export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => void;
|
||||
export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => {
|
||||
deletedNodes: Node[];
|
||||
deletedEdges: Edge[];
|
||||
};
|
||||
export type GetIntersectingNodes<NodeData> = (
|
||||
node: (Partial<Node<NodeData>> & { id: Node['id'] }) | Rect,
|
||||
partially?: boolean,
|
||||
@@ -39,6 +42,9 @@ export namespace Instance {
|
||||
area: Rect,
|
||||
partially?: boolean
|
||||
) => boolean;
|
||||
export type getConnectedEdges = (id: string | (Partial<Node> & { id: Node['id'] })[]) => Edge[];
|
||||
export type getIncomers = (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
||||
export type getOutgoers = (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
||||
}
|
||||
|
||||
export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
{#if $connection.path}
|
||||
<svg width={$width} height={$height} class="svelte-flow__connectionline" style={containerStyle}>
|
||||
<g class={cc(['svelte-flow__connection', $connection.status])} {style}>
|
||||
<g class={cc(['svelte-flow__connection', $connection.status])}>
|
||||
<slot name="connectionLine" />
|
||||
<!-- slot fallbacks do not work if slots are forwarded in parent -->
|
||||
{#if !isCustomComponent}
|
||||
<path d={$connection.path} fill="none" class="svelte-flow__connection-path" />
|
||||
<path d={$connection.path} {style} fill="none" class="svelte-flow__connection-path" />
|
||||
{/if}
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<svelte:options immutable />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { getMarkerId } from '@xyflow/system';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<svelte:options immutable />
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
createEventDispatcher,
|
||||
|
||||
@@ -74,15 +74,16 @@
|
||||
export { className as class };
|
||||
|
||||
let domNode: HTMLDivElement;
|
||||
let clientWidth: number;
|
||||
let clientHeight: number;
|
||||
|
||||
const store = hasContext(key)
|
||||
? useStore()
|
||||
: createStoreContext({ nodes: get(nodes), edges: get(edges), width, height });
|
||||
|
||||
onMount(() => {
|
||||
const { width, height } = domNode.getBoundingClientRect();
|
||||
store.width.set(width);
|
||||
store.height.set(height);
|
||||
store.width.set(clientWidth);
|
||||
store.height.set(clientHeight);
|
||||
store.domNode.set(domNode);
|
||||
|
||||
store.syncNodeStores(nodes);
|
||||
@@ -146,6 +147,8 @@
|
||||
|
||||
<div
|
||||
bind:this={domNode}
|
||||
bind:clientWidth
|
||||
bind:clientHeight
|
||||
{style}
|
||||
class={cc(['svelte-flow', className])}
|
||||
data-testid="svelte-flow__wrapper"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
export function useNodes() {
|
||||
const { nodes } = useStore();
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export function useEdges() {
|
||||
const { edges } = useStore();
|
||||
return edges;
|
||||
}
|
||||
@@ -1,17 +1,25 @@
|
||||
import { get, type Writable } from 'svelte/store';
|
||||
import {
|
||||
getIncomersBase,
|
||||
getOutgoersBase,
|
||||
getOverlappingArea,
|
||||
isRectObject,
|
||||
nodeToRect,
|
||||
pointToRendererPoint,
|
||||
type Project,
|
||||
type FitBoundsOptions,
|
||||
type SetCenterOptions,
|
||||
type Viewport,
|
||||
type ViewportHelperFunctionOptions,
|
||||
type XYPosition,
|
||||
type ZoomInOut
|
||||
type ZoomInOut,
|
||||
type Rect,
|
||||
getTransformForBounds,
|
||||
getElementsToRemove,
|
||||
rendererPointToPoint
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
import type { FitViewOptions } from '$lib/types';
|
||||
import type { SvelteFlowStore } from '$lib/store/types';
|
||||
import type { Edge, FitViewOptions, Node } from '$lib/types';
|
||||
|
||||
export function useSvelteFlow(): {
|
||||
zoomIn: ZoomInOut;
|
||||
@@ -22,10 +30,27 @@ export function useSvelteFlow(): {
|
||||
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
||||
getViewport: () => Viewport;
|
||||
fitView: (options?: FitViewOptions) => void;
|
||||
project: Project;
|
||||
getIntersectingNodes: (
|
||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
||||
partially?: boolean,
|
||||
nodesToIntersect?: Node[]
|
||||
) => Node[];
|
||||
isNodeIntersecting: (
|
||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
||||
area: Rect,
|
||||
partially?: boolean
|
||||
) => boolean;
|
||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
||||
deleteElements: (
|
||||
nodesToRemove?: Partial<Node> & { id: string }[],
|
||||
edgesToRemove?: Partial<Edge> & { id: string }[]
|
||||
) => { deletedNodes: Node[]; deletedEdges: Edge[] };
|
||||
screenToFlowCoordinate: (position: XYPosition) => XYPosition;
|
||||
flowToScreenCoordinate: (position: XYPosition) => XYPosition;
|
||||
viewport: Writable<Viewport>;
|
||||
nodes: SvelteFlowStore['nodes'];
|
||||
edges: SvelteFlowStore['edges'];
|
||||
getConnectedEdges: (id: string | (Partial<Node> & { id: Node['id'] })[]) => Edge[];
|
||||
getIncomers: (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
||||
getOutgoers: (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
||||
} {
|
||||
const {
|
||||
zoomIn,
|
||||
@@ -35,12 +60,29 @@ export function useSvelteFlow(): {
|
||||
viewport,
|
||||
width,
|
||||
height,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
panZoom,
|
||||
nodes,
|
||||
edges
|
||||
edges,
|
||||
domNode
|
||||
} = useStore();
|
||||
|
||||
const getNodeRect = (
|
||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect
|
||||
): [Rect | null, Node | null | undefined, boolean] => {
|
||||
const isRect = isRectObject(nodeOrRect);
|
||||
const node = isRect ? null : get(nodes).find((n) => n.id === nodeOrRect.id);
|
||||
|
||||
if (!isRect && !node) {
|
||||
return [null, null, isRect];
|
||||
}
|
||||
|
||||
const nodeRect = isRect ? nodeOrRect : nodeToRect(node!);
|
||||
|
||||
return [nodeRect, node, isRect];
|
||||
};
|
||||
|
||||
return {
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
@@ -62,30 +104,165 @@ export function useSvelteFlow(): {
|
||||
},
|
||||
getViewport: () => get(viewport),
|
||||
setCenter: (x, y, options) => {
|
||||
const _width = get(width);
|
||||
const _height = get(height);
|
||||
const _maxZoom = get(maxZoom);
|
||||
|
||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : _maxZoom;
|
||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : get(maxZoom);
|
||||
|
||||
get(panZoom)?.setViewport(
|
||||
{
|
||||
x: _width / 2 - x * nextZoom,
|
||||
y: _height / 2 - y * nextZoom,
|
||||
x: get(width) / 2 - x * nextZoom,
|
||||
y: get(height) / 2 - y * nextZoom,
|
||||
zoom: nextZoom
|
||||
},
|
||||
{ duration: options?.duration }
|
||||
);
|
||||
},
|
||||
fitView,
|
||||
project: (position: XYPosition) => {
|
||||
const _snapGrid = get(snapGrid);
|
||||
const { x, y, zoom } = get(viewport);
|
||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => {
|
||||
const [x, y, zoom] = getTransformForBounds(
|
||||
bounds,
|
||||
get(width),
|
||||
get(height),
|
||||
get(minZoom),
|
||||
get(maxZoom),
|
||||
options?.padding ?? 0.1
|
||||
);
|
||||
|
||||
return pointToRendererPoint(position, [x, y, zoom], _snapGrid !== null, _snapGrid || [1, 1]);
|
||||
get(panZoom)?.setViewport(
|
||||
{
|
||||
x,
|
||||
y,
|
||||
zoom
|
||||
},
|
||||
{ duration: options?.duration }
|
||||
);
|
||||
},
|
||||
nodes,
|
||||
edges,
|
||||
viewport: viewport
|
||||
getIntersectingNodes: (
|
||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
||||
partially = true,
|
||||
nodesToIntersect?: Node[]
|
||||
) => {
|
||||
const [nodeRect, node, isRect] = getNodeRect(nodeOrRect);
|
||||
|
||||
if (!nodeRect || !node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (nodesToIntersect || get(nodes)).filter((n) => {
|
||||
if (!isRect && (n.id === node.id || !n.positionAbsolute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currNodeRect = nodeToRect(n);
|
||||
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
||||
const partiallyVisible = partially && overlappingArea > 0;
|
||||
|
||||
return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
|
||||
});
|
||||
},
|
||||
isNodeIntersecting: (
|
||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
||||
area: Rect,
|
||||
partially = true
|
||||
) => {
|
||||
const [nodeRect] = getNodeRect(nodeOrRect);
|
||||
|
||||
if (!nodeRect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const overlappingArea = getOverlappingArea(nodeRect, area);
|
||||
const partiallyVisible = partially && overlappingArea > 0;
|
||||
|
||||
return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
|
||||
},
|
||||
deleteElements: (
|
||||
nodesToRemove: Partial<Node> & { id: string }[] = [],
|
||||
edgesToRemove: Partial<Edge> & { id: string }[] = []
|
||||
) => {
|
||||
const _nodes = get(nodes);
|
||||
const _edges = get(edges);
|
||||
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({
|
||||
nodesToRemove,
|
||||
edgesToRemove,
|
||||
nodes: _nodes,
|
||||
edges: _edges
|
||||
});
|
||||
|
||||
if (matchingNodes) {
|
||||
nodes.set(_nodes.filter((node) => !matchingNodes.some(({ id }) => id === node.id)));
|
||||
}
|
||||
|
||||
if (matchingEdges) {
|
||||
edges.set(_edges.filter((edge) => !matchingEdges.some(({ id }) => id === edge.id)));
|
||||
}
|
||||
|
||||
return {
|
||||
deletedNodes: matchingNodes,
|
||||
deletedEdges: matchingEdges
|
||||
};
|
||||
},
|
||||
screenToFlowCoordinate: (position: XYPosition) => {
|
||||
const _domNode = get(domNode);
|
||||
|
||||
if (_domNode) {
|
||||
const _snapGrid = get(snapGrid);
|
||||
const { x, y, zoom } = get(viewport);
|
||||
const { x: domX, y: domY } = _domNode.getBoundingClientRect();
|
||||
|
||||
const correctedPosition = {
|
||||
x: position.x - domX,
|
||||
y: position.y - domY
|
||||
};
|
||||
|
||||
return pointToRendererPoint(
|
||||
correctedPosition,
|
||||
[x, y, zoom],
|
||||
_snapGrid !== null,
|
||||
_snapGrid || [1, 1]
|
||||
);
|
||||
}
|
||||
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
flowToScreenCoordinate: (position: XYPosition) => {
|
||||
const _domNode = get(domNode);
|
||||
|
||||
if (_domNode) {
|
||||
const { x, y, zoom } = get(viewport);
|
||||
const { x: domX, y: domY } = _domNode.getBoundingClientRect();
|
||||
|
||||
const rendererPosition = rendererPointToPoint(position, [x, y, zoom]);
|
||||
|
||||
return {
|
||||
x: rendererPosition.x + domX,
|
||||
y: rendererPosition.y + domY
|
||||
};
|
||||
}
|
||||
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
getConnectedEdges: (node) => {
|
||||
const nodeIds = new Set();
|
||||
|
||||
if (typeof node === 'string') {
|
||||
nodeIds.add(node);
|
||||
} else if (node.length >= 1) {
|
||||
node.forEach((n) => {
|
||||
nodeIds.add(n.id);
|
||||
});
|
||||
}
|
||||
|
||||
return get(edges).filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
||||
},
|
||||
getIncomers: (node) => {
|
||||
const _node = typeof node === 'string' ? { id: node } : node;
|
||||
|
||||
return getIncomersBase(_node, get(nodes), get(edges));
|
||||
},
|
||||
getOutgoers: (node) => {
|
||||
const _node = typeof node === 'string' ? { id: node } : node;
|
||||
|
||||
return getOutgoersBase(_node, get(nodes), get(edges));
|
||||
},
|
||||
viewport
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,9 +19,12 @@ export { useStore } from '$lib/store';
|
||||
|
||||
// utils
|
||||
export * from '$lib/utils';
|
||||
|
||||
//hooks
|
||||
export * from '$lib/hooks/useSvelteFlow';
|
||||
export * from '$lib/hooks/useUpdateNodeInternals';
|
||||
export * from '$lib/hooks/useConnection';
|
||||
export * from '$lib/hooks/useNodesEdges';
|
||||
|
||||
// types
|
||||
export type { Edge, EdgeProps, EdgeTypes, DefaultEdgeOptions } from '$lib/types/edges';
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
.svelte-flow__edge-label {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@@ -35,29 +35,40 @@ export const isNodeBase = <NodeType extends NodeBase = NodeBase, EdgeType extend
|
||||
): element is NodeType => 'id' in element && !('source' in element) && !('target' in element);
|
||||
|
||||
export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||
node: NodeType,
|
||||
node: Partial<NodeType> & { id: string },
|
||||
nodes: NodeType[],
|
||||
edges: EdgeType[]
|
||||
): NodeType[] => {
|
||||
if (!isNodeBase(node)) {
|
||||
if (!node.id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const outgoerIds = edges.filter((e) => e.source === node.id).map((e) => e.target);
|
||||
return nodes.filter((n) => outgoerIds.includes(n.id));
|
||||
const outgoerIds = new Set();
|
||||
edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
outgoerIds.add(edge.target);
|
||||
}
|
||||
});
|
||||
|
||||
return nodes.filter((n) => outgoerIds.has(n.id));
|
||||
};
|
||||
|
||||
export const getIncomersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||
node: NodeType,
|
||||
node: Partial<NodeType> & { id: string },
|
||||
nodes: NodeType[],
|
||||
edges: EdgeType[]
|
||||
): NodeType[] => {
|
||||
if (!isNodeBase(node)) {
|
||||
if (!node.id) {
|
||||
return [];
|
||||
}
|
||||
const incomersIds = new Set();
|
||||
edges.forEach((edge) => {
|
||||
if (edge.target === node.id) {
|
||||
incomersIds.add(edge.source);
|
||||
}
|
||||
});
|
||||
|
||||
const incomersIds = edges.filter((e) => e.target === node.id).map((e) => e.source);
|
||||
return nodes.filter((n) => incomersIds.includes(n.id));
|
||||
return nodes.filter((n) => incomersIds.has(n.id));
|
||||
};
|
||||
|
||||
export const getNodePositionWithOrigin = (
|
||||
@@ -161,9 +172,12 @@ export const getConnectedEdgesBase = <NodeType extends NodeBase = NodeBase, Edge
|
||||
nodes: NodeType[],
|
||||
edges: EdgeType[]
|
||||
): EdgeType[] => {
|
||||
const nodeIds = nodes.map((node) => node.id);
|
||||
const nodeIds = new Set();
|
||||
nodes.forEach((node) => {
|
||||
nodeIds.add(node.id);
|
||||
});
|
||||
|
||||
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target));
|
||||
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
||||
};
|
||||
|
||||
export function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
||||
|
||||
Reference in New Issue
Block a user