refactor(edge-wrapper): cleanup
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
// Updatable edges have a anchors around their handles to update the edge.
|
// Updatable edges have a anchors around their handles to update the edge.
|
||||||
import { XYHandle, type Connection, getEdgePosition } from '@xyflow/system';
|
import { XYHandle, type Connection, EdgePosition } from '@xyflow/system';
|
||||||
|
|
||||||
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
||||||
import type { EdgeWrapperProps, Edge } from '../../types/edges';
|
import type { EdgeWrapperProps, Edge } from '../../types/edges';
|
||||||
import { useStoreApi } from '../../hooks/useStore';
|
import { useStoreApi } from '../../hooks/useStore';
|
||||||
|
|
||||||
type EdgeUpdateAnchorsProps = {
|
type EdgeUpdateAnchorsProps = {
|
||||||
edgePosition: NonNullable<ReturnType<typeof getEdgePosition>>;
|
|
||||||
edge: Edge;
|
edge: Edge;
|
||||||
isUpdatable: boolean | 'source' | 'target';
|
isUpdatable: boolean | 'source' | 'target';
|
||||||
edgeUpdaterRadius: EdgeWrapperProps['edgeUpdaterRadius'];
|
edgeUpdaterRadius: EdgeWrapperProps['edgeUpdaterRadius'];
|
||||||
@@ -17,20 +16,25 @@ type EdgeUpdateAnchorsProps = {
|
|||||||
onEdgeUpdateEnd: EdgeWrapperProps['onEdgeUpdateEnd'];
|
onEdgeUpdateEnd: EdgeWrapperProps['onEdgeUpdateEnd'];
|
||||||
setUpdateHover: (hover: boolean) => void;
|
setUpdateHover: (hover: boolean) => void;
|
||||||
setUpdating: (updating: boolean) => void;
|
setUpdating: (updating: boolean) => void;
|
||||||
};
|
} & EdgePosition;
|
||||||
|
|
||||||
function EdgeUpdateAnchors({
|
function EdgeUpdateAnchors({
|
||||||
isUpdatable,
|
isUpdatable,
|
||||||
edgeUpdaterRadius,
|
edgeUpdaterRadius,
|
||||||
|
edge,
|
||||||
|
targetHandleId,
|
||||||
|
sourceHandleId,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
sourcePosition,
|
||||||
|
targetPosition,
|
||||||
onEdgeUpdate,
|
onEdgeUpdate,
|
||||||
onEdgeUpdateStart,
|
onEdgeUpdateStart,
|
||||||
onEdgeUpdateEnd,
|
onEdgeUpdateEnd,
|
||||||
setUpdating,
|
setUpdating,
|
||||||
setUpdateHover,
|
setUpdateHover,
|
||||||
edgePosition,
|
|
||||||
edge,
|
|
||||||
targetHandleId,
|
|
||||||
sourceHandleId,
|
|
||||||
}: EdgeUpdateAnchorsProps) {
|
}: EdgeUpdateAnchorsProps) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
@@ -104,9 +108,9 @@ function EdgeUpdateAnchors({
|
|||||||
<>
|
<>
|
||||||
{(isUpdatable === 'source' || isUpdatable === true) && (
|
{(isUpdatable === 'source' || isUpdatable === true) && (
|
||||||
<EdgeAnchor
|
<EdgeAnchor
|
||||||
position={edgePosition.sourcePosition}
|
position={sourcePosition}
|
||||||
centerX={edgePosition.sourceX}
|
centerX={sourceX}
|
||||||
centerY={edgePosition.sourceY}
|
centerY={sourceY}
|
||||||
radius={edgeUpdaterRadius}
|
radius={edgeUpdaterRadius}
|
||||||
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
||||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||||
@@ -116,9 +120,9 @@ function EdgeUpdateAnchors({
|
|||||||
)}
|
)}
|
||||||
{(isUpdatable === 'target' || isUpdatable === true) && (
|
{(isUpdatable === 'target' || isUpdatable === true) && (
|
||||||
<EdgeAnchor
|
<EdgeAnchor
|
||||||
position={edgePosition.targetPosition}
|
position={targetPosition}
|
||||||
centerX={edgePosition.targetX}
|
centerX={targetX}
|
||||||
centerY={edgePosition.targetY}
|
centerY={targetY}
|
||||||
radius={edgeUpdaterRadius}
|
radius={edgeUpdaterRadius}
|
||||||
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
||||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { memo, useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react';
|
import { memo, useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import { getMarkerId, elementSelectionKeys, getEdgePosition, errorMessages, getEdgeZIndex } from '@xyflow/system';
|
import {
|
||||||
|
getMarkerId,
|
||||||
|
elementSelectionKeys,
|
||||||
|
getEdgePosition,
|
||||||
|
errorMessages,
|
||||||
|
getElevatedEdgeZIndex,
|
||||||
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { useStoreApi, useStore } from '../../hooks/useStore';
|
import { useStoreApi, useStore } from '../../hooks/useStore';
|
||||||
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
||||||
import type { EdgeWrapperProps, Node } from '../../types';
|
import type { EdgeWrapperProps } from '../../types';
|
||||||
import { builtinEdgeTypes } from './utils';
|
import { builtinEdgeTypes, nullPosition } from './utils';
|
||||||
import EdgeUpdateAnchors from './EdgeUpdateAnchors';
|
import EdgeUpdateAnchors from './EdgeUpdateAnchors';
|
||||||
|
|
||||||
function EdgeWrapper({
|
function EdgeWrapper({
|
||||||
@@ -53,45 +59,44 @@ function EdgeWrapper({
|
|||||||
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
||||||
const [updating, setUpdating] = useState<boolean>(false);
|
const [updating, setUpdating] = useState<boolean>(false);
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const prevSourceNode = useRef<Node | undefined>();
|
|
||||||
const prevTargetNode = useRef<Node | undefined>();
|
|
||||||
const prevZIndex = useRef<number | undefined>(edge.zIndex);
|
|
||||||
const prevEdgePosition = useRef<ReturnType<typeof getEdgePosition> | null>(null);
|
|
||||||
|
|
||||||
const { edgePosition, zIndex } = useStore(
|
const { zIndex, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition } = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(state) => {
|
(store) => {
|
||||||
const sourceNode = state.nodeLookup.get(edge.source);
|
const sourceNode = store.nodeLookup.get(edge.source);
|
||||||
const targetNode = state.nodeLookup.get(edge.target);
|
const targetNode = store.nodeLookup.get(edge.target);
|
||||||
|
|
||||||
if (!sourceNode || !targetNode) {
|
if (!sourceNode || !targetNode) {
|
||||||
return { edgePosition: null, zIndex: edge.zIndex };
|
return {
|
||||||
|
zIndex: edge.zIndex,
|
||||||
|
...nullPosition,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodesChanged = prevSourceNode.current !== sourceNode || prevTargetNode.current !== targetNode;
|
const edgePosition = getEdgePosition({
|
||||||
|
id,
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
sourceHandle: sourceHandleId || null,
|
||||||
|
targetHandle: targetHandleId || null,
|
||||||
|
connectionMode: store.connectionMode,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|
||||||
prevSourceNode.current = sourceNode;
|
const zIndex = getElevatedEdgeZIndex({
|
||||||
prevTargetNode.current = targetNode;
|
selected: edge.selected,
|
||||||
|
zIndex: edge.zIndex,
|
||||||
prevEdgePosition.current = nodesChanged
|
sourceNode,
|
||||||
? getEdgePosition({
|
targetNode,
|
||||||
id,
|
elevateOnSelect: elevateEdgesOnSelect,
|
||||||
sourceNode,
|
});
|
||||||
targetNode,
|
|
||||||
sourceHandle: sourceHandleId || null,
|
|
||||||
targetHandle: targetHandleId || null,
|
|
||||||
connectionMode: state.connectionMode,
|
|
||||||
onError: state.onError,
|
|
||||||
})
|
|
||||||
: prevEdgePosition.current;
|
|
||||||
prevZIndex.current = getEdgeZIndex(edge.selected, edge.zIndex, sourceNode, targetNode, elevateEdgesOnSelect);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
edgePosition: prevEdgePosition.current,
|
zIndex,
|
||||||
zIndex: prevZIndex.current,
|
...(edgePosition || nullPosition),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[edge.source, edge.target, edge.selected, edge.zIndex]
|
[edge.source, edge.target, edge.selected, edge.zIndex, elevateEdgesOnSelect]
|
||||||
),
|
),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
@@ -100,12 +105,13 @@ function EdgeWrapper({
|
|||||||
() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined),
|
() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined),
|
||||||
[edge.markerStart, rfId]
|
[edge.markerStart, rfId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const markerEndUrl = useMemo(
|
const markerEndUrl = useMemo(
|
||||||
() => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined),
|
() => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined),
|
||||||
[edge.markerEnd, rfId]
|
[edge.markerEnd, rfId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (edge.hidden || !edgePosition) {
|
if (edge.hidden || !sourceX || !sourceY || !targetX || !targetY) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,14 +219,14 @@ function EdgeWrapper({
|
|||||||
labelBgStyle={edge.labelBgStyle}
|
labelBgStyle={edge.labelBgStyle}
|
||||||
labelBgPadding={edge.labelBgPadding}
|
labelBgPadding={edge.labelBgPadding}
|
||||||
labelBgBorderRadius={edge.labelBgBorderRadius}
|
labelBgBorderRadius={edge.labelBgBorderRadius}
|
||||||
|
sourceX={sourceX}
|
||||||
|
sourceY={sourceY}
|
||||||
|
targetX={targetX}
|
||||||
|
targetY={targetY}
|
||||||
|
sourcePosition={sourcePosition}
|
||||||
|
targetPosition={targetPosition}
|
||||||
data={edge.data}
|
data={edge.data}
|
||||||
style={edge.style}
|
style={edge.style}
|
||||||
sourceX={edgePosition.sourceX}
|
|
||||||
sourceY={edgePosition.sourceY}
|
|
||||||
targetX={edgePosition.targetX}
|
|
||||||
targetY={edgePosition.targetY}
|
|
||||||
sourcePosition={edgePosition.sourcePosition}
|
|
||||||
targetPosition={edgePosition.targetPosition}
|
|
||||||
sourceHandleId={sourceHandleId}
|
sourceHandleId={sourceHandleId}
|
||||||
targetHandleId={targetHandleId}
|
targetHandleId={targetHandleId}
|
||||||
markerStart={markerStartUrl}
|
markerStart={markerStartUrl}
|
||||||
@@ -237,7 +243,12 @@ function EdgeWrapper({
|
|||||||
onEdgeUpdate={onEdgeUpdate}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
edgePosition={edgePosition}
|
sourceX={sourceX}
|
||||||
|
sourceY={sourceY}
|
||||||
|
targetX={targetX}
|
||||||
|
targetY={targetY}
|
||||||
|
sourcePosition={sourcePosition}
|
||||||
|
targetPosition={targetPosition}
|
||||||
setUpdateHover={setUpdateHover}
|
setUpdateHover={setUpdateHover}
|
||||||
setUpdating={setUpdating}
|
setUpdating={setUpdating}
|
||||||
sourceHandleId={sourceHandleId}
|
sourceHandleId={sourceHandleId}
|
||||||
|
|||||||
@@ -15,3 +15,12 @@ export const builtinEdgeTypes: EdgeTypes = {
|
|||||||
smoothstep: SmoothStepEdgeInternal as ComponentType<EdgeProps>,
|
smoothstep: SmoothStepEdgeInternal as ComponentType<EdgeProps>,
|
||||||
simplebezier: SimpleBezierEdgeInternal as ComponentType<EdgeProps>,
|
simplebezier: SimpleBezierEdgeInternal as ComponentType<EdgeProps>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const nullPosition = {
|
||||||
|
sourceX: null,
|
||||||
|
sourceY: null,
|
||||||
|
targetX: null,
|
||||||
|
targetY: null,
|
||||||
|
sourcePosition: null,
|
||||||
|
targetPosition: null,
|
||||||
|
};
|
||||||
|
|||||||
@@ -125,16 +125,11 @@ const NodeWrapper = ({
|
|||||||
const isParent = !!node[internalsSymbol]?.isParent;
|
const isParent = !!node[internalsSymbol]?.isParent;
|
||||||
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||||
|
|
||||||
const onMouseEnterHandler =
|
const onMouseEnterHandler = onMouseEnter ? (event: MouseEvent) => onMouseEnter(event, { ...node }) : undefined;
|
||||||
onMouseEnter === undefined ? undefined : (event: MouseEvent) => onMouseEnter(event, { ...node });
|
const onMouseMoveHandler = onMouseMove ? (event: MouseEvent) => onMouseMove(event, { ...node }) : undefined;
|
||||||
const onMouseMoveHandler =
|
const onMouseLeaveHandler = onMouseLeave ? (event: MouseEvent) => onMouseLeave(event, { ...node }) : undefined;
|
||||||
onMouseMove === undefined ? undefined : (event: MouseEvent) => onMouseMove(event, { ...node });
|
const onContextMenuHandler = onContextMenu ? (event: MouseEvent) => onContextMenu(event, { ...node }) : undefined;
|
||||||
const onMouseLeaveHandler =
|
const onDoubleClickHandler = onDoubleClick ? (event: MouseEvent) => onDoubleClick(event, { ...node }) : undefined;
|
||||||
onMouseLeave === undefined ? undefined : (event: MouseEvent) => onMouseLeave(event, { ...node });
|
|
||||||
const onContextMenuHandler =
|
|
||||||
onContextMenu === undefined ? undefined : (event: MouseEvent) => onContextMenu(event, { ...node });
|
|
||||||
const onDoubleClickHandler =
|
|
||||||
onDoubleClick === undefined ? undefined : (event: MouseEvent) => onDoubleClick(event, { ...node });
|
|
||||||
|
|
||||||
const onSelectNodeHandler = (event: MouseEvent) => {
|
const onSelectNodeHandler = (event: MouseEvent) => {
|
||||||
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ import {
|
|||||||
type OnMoveEnd,
|
type OnMoveEnd,
|
||||||
type IsValidConnection,
|
type IsValidConnection,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
EdgeLookup,
|
type EdgeLookup,
|
||||||
ConnectionLookup,
|
type ConnectionLookup,
|
||||||
|
type NodeLookup,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -49,7 +50,7 @@ export type ReactFlowStore = {
|
|||||||
height: number;
|
height: number;
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
nodeLookup: Map<string, Node>;
|
nodeLookup: NodeLookup<Node>;
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
edgeLookup: EdgeLookup<Edge>;
|
edgeLookup: EdgeLookup<Edge>;
|
||||||
connectionLookup: ConnectionLookup;
|
connectionLookup: ConnectionLookup;
|
||||||
|
|||||||
@@ -57,12 +57,11 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
let remainingChanges = changes;
|
let remainingChanges = changes;
|
||||||
const updatedElements: any[] = [];
|
const updatedElements: any[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (const item of elements) {
|
||||||
const nextChanges: any[] = [];
|
const nextChanges: any[] = [];
|
||||||
const _remainingChanges: any[] = [];
|
const _remainingChanges: any[] = [];
|
||||||
const item = elements[i];
|
|
||||||
|
|
||||||
remainingChanges.forEach((c) => {
|
for (const c of remainingChanges) {
|
||||||
if (c.type === 'add') {
|
if (c.type === 'add') {
|
||||||
updatedElements.push(c.item);
|
updatedElements.push(c.item);
|
||||||
} else if (c.id === item.id) {
|
} else if (c.id === item.id) {
|
||||||
@@ -70,7 +69,8 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
} else {
|
} else {
|
||||||
_remainingChanges.push(c);
|
_remainingChanges.push(c);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
remainingChanges = _remainingChanges;
|
remainingChanges = _remainingChanges;
|
||||||
|
|
||||||
if (nextChanges.length === 0) {
|
if (nextChanges.length === 0) {
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import {
|
|||||||
SelectionMode,
|
SelectionMode,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
|
devWarn,
|
||||||
|
adoptUserProvidedNodes,
|
||||||
|
getNodesBounds,
|
||||||
|
getViewportForBounds,
|
||||||
|
updateConnectionLookup,
|
||||||
type SelectionRect,
|
type SelectionRect,
|
||||||
type SnapGrid,
|
type SnapGrid,
|
||||||
type MarkerProps,
|
type MarkerProps,
|
||||||
@@ -12,16 +17,12 @@ import {
|
|||||||
type IsValidConnection,
|
type IsValidConnection,
|
||||||
type NodeOrigin,
|
type NodeOrigin,
|
||||||
type OnError,
|
type OnError,
|
||||||
devWarn,
|
|
||||||
type Viewport,
|
type Viewport,
|
||||||
adoptUserProvidedNodes,
|
|
||||||
getNodesBounds,
|
|
||||||
getViewportForBounds,
|
|
||||||
updateConnectionLookup,
|
|
||||||
type ConnectionLookup,
|
type ConnectionLookup,
|
||||||
type OnConnect,
|
type OnConnect,
|
||||||
type OnConnectStart,
|
type OnConnectStart,
|
||||||
type OnConnectEnd
|
type OnConnectEnd,
|
||||||
|
type NodeLookup
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
@@ -96,7 +97,7 @@ export const getInitialStore = ({
|
|||||||
return {
|
return {
|
||||||
flowId: writable<string | null>(null),
|
flowId: writable<string | null>(null),
|
||||||
nodes: createNodesStore(nextNodes, nodeLookup),
|
nodes: createNodesStore(nextNodes, nodeLookup),
|
||||||
nodeLookup: readable<Map<string, Node>>(nodeLookup),
|
nodeLookup: readable<NodeLookup>(nodeLookup),
|
||||||
visibleNodes: readable<Node[]>([]),
|
visibleNodes: readable<Node[]>([]),
|
||||||
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
||||||
visibleEdges: readable<EdgeLayouted[]>([]),
|
visibleEdges: readable<EdgeLayouted[]>([]),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
writable,
|
writable,
|
||||||
|
get,
|
||||||
type Unsubscriber,
|
type Unsubscriber,
|
||||||
type Subscriber,
|
type Subscriber,
|
||||||
type Updater,
|
type Updater,
|
||||||
type Writable,
|
type Writable
|
||||||
get
|
|
||||||
} from 'svelte/store';
|
} from 'svelte/store';
|
||||||
import {
|
import {
|
||||||
adoptUserProvidedNodes,
|
adoptUserProvidedNodes,
|
||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
type Viewport,
|
type Viewport,
|
||||||
type PanZoomInstance,
|
type PanZoomInstance,
|
||||||
type ConnectionLookup,
|
type ConnectionLookup,
|
||||||
type EdgeLookup
|
type EdgeLookup,
|
||||||
|
type NodeLookup
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||||
@@ -126,7 +127,7 @@ export type NodeStoreOptions = {
|
|||||||
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
||||||
export const createNodesStore = (
|
export const createNodesStore = (
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
nodeLookup: Map<string, Node>
|
nodeLookup: NodeLookup<Node>
|
||||||
): {
|
): {
|
||||||
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
||||||
update: (this: void, updater: Updater<Node[]>) => void;
|
update: (this: void, updater: Updater<Node[]>) => void;
|
||||||
@@ -176,7 +177,7 @@ export const createNodesStore = (
|
|||||||
export const createEdgesStore = (
|
export const createEdgesStore = (
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
connectionLookup: ConnectionLookup,
|
connectionLookup: ConnectionLookup,
|
||||||
edgeLookup: EdgeLookup,
|
edgeLookup: EdgeLookup<Edge>,
|
||||||
defaultOptions?: DefaultEdgeOptions
|
defaultOptions?: DefaultEdgeOptions
|
||||||
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
|
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
|
||||||
const { subscribe, set, update } = writable<Edge[]>([]);
|
const { subscribe, set, update } = writable<Edge[]>([]);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
import { isEdgeVisible, getEdgePosition, getEdgeZIndex } from '@xyflow/system';
|
import { isEdgeVisible, getEdgePosition, getElevatedEdgeZIndex } from '@xyflow/system';
|
||||||
|
|
||||||
import type { EdgeLayouted } from '$lib/types';
|
import type { EdgeLayouted } from '$lib/types';
|
||||||
import type { SvelteFlowStoreState } from './types';
|
import type { SvelteFlowStoreState } from './types';
|
||||||
@@ -64,7 +64,13 @@ export function getVisibleEdges(store: SvelteFlowStoreState) {
|
|||||||
if (edgePosition) {
|
if (edgePosition) {
|
||||||
res.push({
|
res.push({
|
||||||
...edge,
|
...edge,
|
||||||
zIndex: getEdgeZIndex(edge.selected, edge.zIndex, sourceNode, targetNode, false),
|
zIndex: getElevatedEdgeZIndex({
|
||||||
|
selected: edge.selected,
|
||||||
|
zIndex: edge.zIndex,
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
elevateOnSelect: false
|
||||||
|
}),
|
||||||
...edgePosition
|
...edgePosition
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,4 +78,4 @@ export type EdgePosition = {
|
|||||||
targetPosition: Position;
|
targetPosition: Position;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EdgeLookup = Map<string, EdgeBase>;
|
export type EdgeLookup<EdgeType extends EdgeBase = EdgeBase> = Map<string, EdgeType>;
|
||||||
|
|||||||
@@ -107,3 +107,5 @@ export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
|||||||
export type NodeHandle = Optional<HandleElement, 'width' | 'height'>;
|
export type NodeHandle = Optional<HandleElement, 'width' | 'height'>;
|
||||||
|
|
||||||
export type Align = 'center' | 'start' | 'end';
|
export type Align = 'center' | 'start' | 'end';
|
||||||
|
|
||||||
|
export type NodeLookup<NodeType extends NodeBase = NodeBase> = Map<string, NodeType>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Connection, Transform, errorMessages, internalsSymbol, isEdgeBase } from '../..';
|
import { Connection, Transform, errorMessages, internalsSymbol, isEdgeBase } from '../..';
|
||||||
import { EdgeBase, NodeBase } from '../../types';
|
import { EdgeBase, NodeBase } from '../../types';
|
||||||
import { isNumeric, getOverlappingArea, boxToRect, nodeToBox, getBoundsOfBoxes, devWarn } from '../general';
|
import { getOverlappingArea, boxToRect, nodeToBox, getBoundsOfBoxes, devWarn } from '../general';
|
||||||
|
|
||||||
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
|
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
|
||||||
export function getEdgeCenter({
|
export function getEdgeCenter({
|
||||||
@@ -23,23 +23,29 @@ export function getEdgeCenter({
|
|||||||
return [centerX, centerY, xOffset, yOffset];
|
return [centerX, centerY, xOffset, yOffset];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEdgeZIndex(
|
export type GetEdgeZIndexParams = {
|
||||||
selected: boolean | undefined,
|
sourceNode: NodeBase;
|
||||||
zIndex: number | undefined,
|
targetNode: NodeBase;
|
||||||
sourceNode: NodeBase,
|
selected?: boolean;
|
||||||
targetNode: NodeBase,
|
zIndex?: number;
|
||||||
elevateEdgesOnSelect?: boolean
|
elevateOnSelect?: boolean;
|
||||||
) {
|
};
|
||||||
const hasZIndex = isNumeric(zIndex);
|
|
||||||
let nextZIndex = hasZIndex ? zIndex! : 0;
|
|
||||||
|
|
||||||
if (elevateEdgesOnSelect) {
|
export function getElevatedEdgeZIndex({
|
||||||
const edgeOrConnectedNodeSelected = selected || targetNode?.selected || sourceNode?.selected;
|
sourceNode,
|
||||||
const selectedZIndex = Math.max(sourceNode?.[internalsSymbol]?.z || 0, targetNode?.[internalsSymbol]?.z || 0, 1000);
|
targetNode,
|
||||||
nextZIndex = (hasZIndex ? zIndex! : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
selected = false,
|
||||||
|
zIndex = 0,
|
||||||
|
elevateOnSelect = false,
|
||||||
|
}: GetEdgeZIndexParams): number {
|
||||||
|
if (!elevateOnSelect) {
|
||||||
|
return zIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextZIndex;
|
const edgeOrConnectedNodeSelected = selected || targetNode.selected || sourceNode.selected;
|
||||||
|
const selectedZIndex = Math.max(sourceNode[internalsSymbol]?.z || 0, targetNode[internalsSymbol]?.z || 0, 1000);
|
||||||
|
|
||||||
|
return zIndex + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
type IsEdgeVisibleParams = {
|
type IsEdgeVisibleParams = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { EdgePosition } from '../../types/edges';
|
import { EdgePosition } from '../../types/edges';
|
||||||
import { ConnectionMode, OnError } from '../../types/general';
|
import { ConnectionMode, OnError } from '../../types/general';
|
||||||
import { NodeBase, NodeHandle, NodeHandleBounds } from '../../types/nodes';
|
import { NodeBase, NodeHandle } from '../../types/nodes';
|
||||||
import { Position, Rect, XYPosition } from '../../types/utils';
|
import { Position } from '../../types/utils';
|
||||||
import { errorMessages, internalsSymbol } from '../../constants';
|
import { errorMessages, internalsSymbol } from '../../constants';
|
||||||
import { HandleElement } from '../../types';
|
import { HandleElement } from '../../types';
|
||||||
|
|
||||||
@@ -15,21 +15,28 @@ export type GetEdgePositionParams = {
|
|||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null {
|
function isNodeInitialized(node: NodeBase): boolean {
|
||||||
const [sourceNodeRect, sourceHandleBounds, isSourceValid] = getHandleDataByNode(params.sourceNode);
|
return !!node?.[internalsSymbol]?.handleBounds && !!node?.computed?.width;
|
||||||
const [targetNodeRect, targetHandleBounds, isTargetValid] = getHandleDataByNode(params.targetNode);
|
}
|
||||||
|
|
||||||
if (!isSourceValid || !isTargetValid) {
|
export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null {
|
||||||
|
const { sourceNode, targetNode } = params;
|
||||||
|
|
||||||
|
if (!isNodeInitialized(sourceNode) || !isNodeInitialized(targetNode)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when connection type is loose we can define all handles as sources and connect source -> source
|
const sourceHandleBounds = sourceNode[internalsSymbol]?.handleBounds || toHandleBounds(sourceNode.handles);
|
||||||
const targetNodeHandles =
|
const targetHandleBounds = targetNode[internalsSymbol]?.handleBounds || toHandleBounds(targetNode.handles);
|
||||||
|
|
||||||
|
const sourceHandle = getHandle(sourceHandleBounds?.source ?? [], params.sourceHandle);
|
||||||
|
const targetHandle = getHandle(
|
||||||
|
// when connection type is loose we can define all handles as sources and connect source -> source
|
||||||
params.connectionMode === ConnectionMode.Strict
|
params.connectionMode === ConnectionMode.Strict
|
||||||
? targetHandleBounds!.target
|
? targetHandleBounds?.target ?? []
|
||||||
: (targetHandleBounds!.target ?? []).concat(targetHandleBounds!.source ?? []);
|
: (targetHandleBounds?.target ?? []).concat(targetHandleBounds?.source ?? []),
|
||||||
const sourceHandle = getHandle(sourceHandleBounds!.source!, params.sourceHandle);
|
params.targetHandle
|
||||||
const targetHandle = getHandle(targetNodeHandles!, params.targetHandle);
|
);
|
||||||
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
||||||
const targetPosition = targetHandle?.position || Position.Top;
|
const targetPosition = targetHandle?.position || Position.Top;
|
||||||
|
|
||||||
@@ -46,8 +53,8 @@ export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | n
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x: sourceX, y: sourceY } = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
|
const [sourceX, sourceY] = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
|
||||||
const { x: targetX, y: targetY } = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
|
const [targetX, targetY] = getHandlePosition(targetPosition, targetNode, targetHandle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sourceX,
|
sourceX,
|
||||||
@@ -64,79 +71,41 @@ function toHandleBounds(handles?: NodeHandle[]) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return handles.reduce<NodeHandleBounds>(
|
const source = [];
|
||||||
(res, item) => {
|
const target = [];
|
||||||
item.width = item.width || 1;
|
|
||||||
item.height = item.height || 1;
|
|
||||||
|
|
||||||
if (item.type === 'source') {
|
for (const handle of handles) {
|
||||||
res.source?.push(item as HandleElement);
|
handle.width = handle.width || 1;
|
||||||
}
|
handle.height = handle.height || 1;
|
||||||
|
|
||||||
if (item.type === 'target') {
|
if (handle.type === 'source') {
|
||||||
res.target?.push(item as HandleElement);
|
source.push(handle as HandleElement);
|
||||||
}
|
} else if (handle.type === 'target') {
|
||||||
|
target.push(handle as HandleElement);
|
||||||
return res;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: [],
|
|
||||||
target: [],
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHandleDataByNode(node?: NodeBase): [Rect, NodeHandleBounds | null, boolean] {
|
function getHandlePosition(position: Position, node: NodeBase, handle: HandleElement | null = null): number[] {
|
||||||
const handleBounds = node?.[internalsSymbol]?.handleBounds || toHandleBounds(node?.handles) || null;
|
const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0);
|
||||||
const nodeWidth = node?.computed?.width || node?.width;
|
const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0);
|
||||||
const nodeHeight = node?.computed?.height || node?.height;
|
const width = handle?.width || (node?.computed?.width ?? node?.width ?? 0);
|
||||||
|
const height = handle?.height || (node?.computed?.height ?? node?.height ?? 0);
|
||||||
const isValid =
|
|
||||||
handleBounds &&
|
|
||||||
nodeWidth &&
|
|
||||||
nodeHeight &&
|
|
||||||
typeof node?.computed?.positionAbsolute?.x !== 'undefined' &&
|
|
||||||
typeof node?.computed?.positionAbsolute?.y !== 'undefined';
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
x: node?.computed?.positionAbsolute?.x || 0,
|
|
||||||
y: node?.computed?.positionAbsolute?.y || 0,
|
|
||||||
width: nodeWidth || 0,
|
|
||||||
height: nodeHeight || 0,
|
|
||||||
},
|
|
||||||
handleBounds,
|
|
||||||
!!isValid,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHandlePosition(position: Position, nodeRect: Rect, handle: HandleElement | null = null): XYPosition {
|
|
||||||
const x = (handle?.x || 0) + nodeRect.x;
|
|
||||||
const y = (handle?.y || 0) + nodeRect.y;
|
|
||||||
const width = handle?.width || nodeRect.width;
|
|
||||||
const height = handle?.height || nodeRect.height;
|
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case Position.Top:
|
case Position.Top:
|
||||||
return {
|
return [x + width / 2, y];
|
||||||
x: x + width / 2,
|
|
||||||
y,
|
|
||||||
};
|
|
||||||
case Position.Right:
|
case Position.Right:
|
||||||
return {
|
return [x + width, y + height / 2];
|
||||||
x: x + width,
|
|
||||||
y: y + height / 2,
|
|
||||||
};
|
|
||||||
case Position.Bottom:
|
case Position.Bottom:
|
||||||
return {
|
return [x + width / 2, y + height];
|
||||||
x: x + width / 2,
|
|
||||||
y: y + height,
|
|
||||||
};
|
|
||||||
case Position.Left:
|
case Position.Left:
|
||||||
return {
|
return [x, y + height / 2];
|
||||||
x,
|
|
||||||
y: y + height / 2,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user