feat(svelte): add subflows
This commit is contained in:
@@ -89,14 +89,16 @@ export function calcNextPosition(
|
|||||||
if (node.parentNode && node.width && node.height) {
|
if (node.parentNode && node.width && node.height) {
|
||||||
const parent = nodeInternals.get(node.parentNode);
|
const parent = nodeInternals.get(node.parentNode);
|
||||||
const parentOrigin = parent?.origin || nodeOrigin;
|
const parentOrigin = parent?.origin || nodeOrigin;
|
||||||
|
const currNodeOrigin = node.origin || nodeOrigin;
|
||||||
|
|
||||||
const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, parentOrigin).positionAbsolute;
|
const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, parentOrigin).positionAbsolute;
|
||||||
currentExtent =
|
currentExtent =
|
||||||
parent && isNumeric(parentX) && isNumeric(parentY) && isNumeric(parent.width) && isNumeric(parent.height)
|
parent && isNumeric(parentX) && isNumeric(parentY) && isNumeric(parent.width) && isNumeric(parent.height)
|
||||||
? [
|
? [
|
||||||
[parentX + node.width * parentOrigin[0], parentY + node.height * parentOrigin[1]],
|
[parentX + node.width * currNodeOrigin[0], parentY + node.height * currNodeOrigin[1]],
|
||||||
[
|
[
|
||||||
parentX + parent.width - node.width + node.width * parentOrigin[0],
|
parentX + parent.width - node.width + node.width * currNodeOrigin[0],
|
||||||
parentY + parent.height - node.height + node.height * parentOrigin[1],
|
parentY + parent.height - node.height + node.height * currNodeOrigin[1],
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
: currentExtent;
|
: currentExtent;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
'drag-n-drop',
|
'drag-n-drop',
|
||||||
'overview',
|
'overview',
|
||||||
'stress',
|
'stress',
|
||||||
|
'subflows',
|
||||||
'usesvelteflow',
|
'usesvelteflow',
|
||||||
'validation'
|
'validation'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { get, type Writable } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { drag as d3Drag, type D3DragEvent, type SubjectPosition } from 'd3-drag';
|
import { drag as d3Drag, type D3DragEvent, type SubjectPosition } from 'd3-drag';
|
||||||
import { select } from 'd3-selection';
|
import { select } from 'd3-selection';
|
||||||
import type { XYPosition, CoordinateExtent, Transform } from '@reactflow/system';
|
import type { XYPosition, CoordinateExtent } from '@reactflow/system';
|
||||||
|
|
||||||
import { getDragItems, hasSelector, calcNextPosition } from './utils';
|
import { getDragItems, hasSelector, calcNextPosition } from './utils';
|
||||||
import type { Node } from '$lib/types';
|
|
||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
export type UseDragData = { dx: number; dy: number };
|
export type UseDragData = { dx: number; dy: number };
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import type { CoordinateExtent, NodeDragItem, NodeOrigin, XYPosition } from '@reactflow/system';
|
import {
|
||||||
import { clampPosition, isNumeric } from '@reactflow/utils';
|
errorMessages,
|
||||||
|
type CoordinateExtent,
|
||||||
|
type NodeDragItem,
|
||||||
|
type NodeOrigin,
|
||||||
|
type XYPosition
|
||||||
|
} from '@reactflow/system';
|
||||||
|
import { clampPosition, devWarn, getNodePositionWithOrigin, isNumeric } from '@reactflow/utils';
|
||||||
|
|
||||||
import type { Node } from '$lib/types';
|
import type { Node } from '$lib/types';
|
||||||
|
|
||||||
@@ -65,16 +71,28 @@ export function calcNextPosition(
|
|||||||
node: NodeDragItem | Node,
|
node: NodeDragItem | Node,
|
||||||
nextPosition: XYPosition,
|
nextPosition: XYPosition,
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
nodeExtent?: CoordinateExtent,
|
nodeExtent?: CoordinateExtent
|
||||||
nodeOrigin: NodeOrigin = [0, 0]
|
|
||||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||||
let currentExtent = node.extent || nodeExtent;
|
let currentExtent = node.extent || nodeExtent;
|
||||||
|
|
||||||
if (node.extent === 'parent') {
|
if (node.extent === 'parent') {
|
||||||
if (node.parentNode && node.width && node.height) {
|
if (node.parentNode && node.width && node.height) {
|
||||||
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
const parent = nodes.find((n) => n.id === node.parentNode);
|
||||||
const parentOrigin = parent.origin || nodeOrigin;
|
const parentOrigin = parent?.origin || [0, 0];
|
||||||
const { x: parentX, y: parentY } = parent.positionAbsolute!;
|
const nodeOrigin = node.origin || [0, 0];
|
||||||
|
const { x: parentX, y: parentY } = getNodePositionWithOrigin(
|
||||||
|
parent,
|
||||||
|
parentOrigin
|
||||||
|
).positionAbsolute;
|
||||||
|
console.log({
|
||||||
|
parentX,
|
||||||
|
parentY,
|
||||||
|
parentW: parent?.width,
|
||||||
|
parentH: parent?.height,
|
||||||
|
nodeW: node.width,
|
||||||
|
nodeH: node.height,
|
||||||
|
parentOrigin: parentOrigin[0]
|
||||||
|
});
|
||||||
currentExtent =
|
currentExtent =
|
||||||
parent &&
|
parent &&
|
||||||
isNumeric(parentX) &&
|
isNumeric(parentX) &&
|
||||||
@@ -82,19 +100,23 @@ export function calcNextPosition(
|
|||||||
isNumeric(parent.width) &&
|
isNumeric(parent.width) &&
|
||||||
isNumeric(parent.height)
|
isNumeric(parent.height)
|
||||||
? [
|
? [
|
||||||
[parentX + node.width * parentOrigin[0], parentY + node.height * parentOrigin[1]],
|
[parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
|
||||||
[
|
[
|
||||||
parentX + parent.width! - node.width + node.width * parentOrigin[0],
|
parentX + parent.width - node.width + node.width * nodeOrigin[0],
|
||||||
parentY + parent.height! - node.height + node.height * parentOrigin[1]
|
parentY + parent.height - node.height + node.height * nodeOrigin[1]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
: currentExtent;
|
: currentExtent;
|
||||||
} else {
|
} else {
|
||||||
|
devWarn('005', errorMessages['005']());
|
||||||
currentExtent = nodeExtent;
|
currentExtent = nodeExtent;
|
||||||
}
|
}
|
||||||
} else if (node.extent && node.parentNode) {
|
} else if (node.extent && node.parentNode) {
|
||||||
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
const parent = nodes.find((n) => n.id === node.parentNode);
|
||||||
const { x: parentX, y: parentY } = parent.positionAbsolute!;
|
const { x: parentX, y: parentY } = getNodePositionWithOrigin(
|
||||||
|
parent,
|
||||||
|
parent?.origin || [0, 0]
|
||||||
|
).positionAbsolute;
|
||||||
currentExtent = [
|
currentExtent = [
|
||||||
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
||||||
[node.extent[1][0] + parentX, node.extent[1][1] + parentY]
|
[node.extent[1][0] + parentX, node.extent[1][1] + parentY]
|
||||||
@@ -104,8 +126,8 @@ export function calcNextPosition(
|
|||||||
let parentPosition = { x: 0, y: 0 };
|
let parentPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
if (node.parentNode) {
|
if (node.parentNode) {
|
||||||
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
const parent = nodes.find((n) => n.id === node.parentNode);
|
||||||
parentPosition = parent.positionAbsolute!;
|
parentPosition = getNodePositionWithOrigin(parent, parent?.origin || [0, 0]).positionAbsolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionAbsolute = currentExtent
|
const positionAbsolute = currentExtent
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
export let width: NodeWrapperProps['width'] = undefined;
|
export let width: NodeWrapperProps['width'] = undefined;
|
||||||
export let height: NodeWrapperProps['height'] = undefined;
|
export let height: NodeWrapperProps['height'] = undefined;
|
||||||
export let type: NodeWrapperProps['type'] = 'default';
|
export let type: NodeWrapperProps['type'] = 'default';
|
||||||
|
export let isParent: NodeWrapperProps['isParent'] = false;
|
||||||
export let positionAbsolute: NodeWrapperProps['positionAbsolute'] = undefined;
|
export let positionAbsolute: NodeWrapperProps['positionAbsolute'] = undefined;
|
||||||
export let positionOrigin: NodeWrapperProps['positionOrigin'] = undefined;
|
export let positionOrigin: NodeWrapperProps['positionOrigin'] = undefined;
|
||||||
export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined;
|
export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined;
|
||||||
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
|
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
|
||||||
|
|
||||||
let className: string = '';
|
let className: string = '';
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
@@ -77,12 +77,13 @@
|
|||||||
use:drag={{ nodeId: id, nodes, snapGrid, transform, updateNodePositions }}
|
use:drag={{ nodeId: id, nodes, snapGrid, transform, updateNodePositions }}
|
||||||
bind:this={nodeRef}
|
bind:this={nodeRef}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
class={cc(['svelte-flow__node', `svelte-flow__node-${type}`, className])}
|
class={cc(['svelte-flow__node', `svelte-flow__node-${type || 'default'}`, className])}
|
||||||
class:initializing={!width && !height}
|
class:initializing={!width && !height}
|
||||||
class:dragging
|
class:dragging
|
||||||
class:selected
|
class:selected
|
||||||
class:draggable
|
class:draggable
|
||||||
class:connectable
|
class:connectable
|
||||||
|
class:parent={isParent}
|
||||||
style:transform={`translate(${positionOrigin?.x ?? 0}px, ${positionOrigin?.y ?? 0}px)`}
|
style:transform={`translate(${positionOrigin?.x ?? 0}px, ${positionOrigin?.y ?? 0}px)`}
|
||||||
{style}
|
{style}
|
||||||
on:click={onSelectNodeHandler}
|
on:click={onSelectNodeHandler}
|
||||||
|
|||||||
@@ -21,4 +21,5 @@ export type NodeWrapperProps = Pick<
|
|||||||
positionOrigin?: XYPosition;
|
positionOrigin?: XYPosition;
|
||||||
'on:nodeclick'?: (event: MouseEvent) => void;
|
'on:nodeclick'?: (event: MouseEvent) => void;
|
||||||
resizeObserver?: ResizeObserver | null;
|
resizeObserver?: ResizeObserver | null;
|
||||||
|
isParent?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import { internalsSymbol } from '@reactflow/system';
|
||||||
import { getPositionWithOrigin } from '@reactflow/utils';
|
import { getPositionWithOrigin } from '@reactflow/utils';
|
||||||
|
|
||||||
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
import { NodeWrapper } from '$lib/components/NodeWrapper';
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
connectable={node.connectable || node.connectable === undefined}
|
connectable={node.connectable || node.connectable === undefined}
|
||||||
positionAbsolute={node.positionAbsolute}
|
positionAbsolute={node.positionAbsolute}
|
||||||
positionOrigin={posOrigin}
|
positionOrigin={posOrigin}
|
||||||
|
isParent={!!node[internalsSymbol]?.isParent}
|
||||||
width={node.width}
|
width={node.width}
|
||||||
height={node.height}
|
height={node.height}
|
||||||
style={node.style}
|
style={node.style}
|
||||||
|
|||||||
@@ -8,55 +8,55 @@ import type { SvelteFlowStoreState } from './types';
|
|||||||
|
|
||||||
export function getEdgesLayouted(store: SvelteFlowStoreState) {
|
export function getEdgesLayouted(store: SvelteFlowStoreState) {
|
||||||
return derived([store.edges, store.nodes], ([edges, nodes]) => {
|
return derived([store.edges, store.nodes], ([edges, nodes]) => {
|
||||||
return edges
|
return edges.reduce<EdgeLayouted[]>((res, edge) => {
|
||||||
.map((edge) => {
|
const sourceNode = nodes.find((node) => node.id === edge.source);
|
||||||
const sourceNode = nodes.find((node) => node.id === edge.source);
|
const targetNode = nodes.find((node) => node.id === edge.target);
|
||||||
const targetNode = nodes.find((node) => node.id === edge.target);
|
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
|
||||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
|
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
|
||||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
|
|
||||||
|
|
||||||
if (!sourceIsValid || !targetIsValid) {
|
if (!sourceIsValid || !targetIsValid) {
|
||||||
return null;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const edgeType = edge.type || 'default';
|
const edgeType = edge.type || 'default';
|
||||||
|
|
||||||
const targetNodeHandles = targetHandleBounds!.target;
|
const targetNodeHandles = targetHandleBounds!.target;
|
||||||
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
|
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
|
||||||
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
|
const targetHandle = getHandle(targetNodeHandles!, edge.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;
|
||||||
|
|
||||||
if (!sourceHandle || !targetHandle) {
|
if (!sourceHandle || !targetHandle) {
|
||||||
return null;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
||||||
sourceNodeRect,
|
sourceNodeRect,
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetNodeRect,
|
targetNodeRect,
|
||||||
targetHandle,
|
targetHandle,
|
||||||
targetPosition
|
targetPosition
|
||||||
);
|
);
|
||||||
|
|
||||||
// we nee to do this to match the types
|
// we nee to do this to match the types
|
||||||
const sourceHandleId = edge.sourceHandle;
|
const sourceHandleId = edge.sourceHandle;
|
||||||
const targetHandleId = edge.targetHandle;
|
const targetHandleId = edge.targetHandle;
|
||||||
|
|
||||||
return {
|
res.push({
|
||||||
...edge,
|
...edge,
|
||||||
type: edgeType,
|
type: edgeType,
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
sourceHandleId,
|
sourceHandleId,
|
||||||
targetHandleId
|
targetHandleId
|
||||||
};
|
} as EdgeLayouted);
|
||||||
})
|
|
||||||
.filter((e) => e !== null) as EdgeLayouted[];
|
return res;
|
||||||
|
}, []);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
} from '@reactflow/utils';
|
} from '@reactflow/utils';
|
||||||
|
|
||||||
import { addEdge as addEdgeUtil } from '$lib/utils';
|
import { addEdge as addEdgeUtil } from '$lib/utils';
|
||||||
import type { EdgeTypes, NodeTypes, Node, Edge, ConnectionData } from '$lib/types';
|
import type { EdgeTypes, NodeTypes, Node, Edge, ConnectionData, FitViewOptions } from '$lib/types';
|
||||||
import { getEdgesLayouted } from './edges-layouted';
|
import { getEdgesLayouted } from './edges-layouted';
|
||||||
import { getConnectionPath } from './connection-path';
|
import { getConnectionPath } from './connection-path';
|
||||||
import {
|
import {
|
||||||
@@ -102,15 +102,20 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
node[internalsSymbol] = {
|
const newNode = {
|
||||||
...node[internalsSymbol],
|
...node,
|
||||||
handleBounds: {
|
width: dimensions.width,
|
||||||
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin),
|
height: dimensions.height,
|
||||||
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin)
|
[internalsSymbol]: {
|
||||||
|
...node[internalsSymbol],
|
||||||
|
handleBounds: {
|
||||||
|
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin),
|
||||||
|
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
node.width = dimensions.width;
|
|
||||||
node.height = dimensions.height;
|
return newNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +126,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
|
|||||||
|
|
||||||
const fitViewOnInitDone =
|
const fitViewOnInitDone =
|
||||||
get(store.fitViewOnInitDone) ||
|
get(store.fitViewOnInitDone) ||
|
||||||
(get(store.fitViewOnInit) && !!d3Zoom && !!d3Selection && fitView());
|
(get(store.fitViewOnInit) && !!d3Zoom && !!d3Selection && fitView({ nodes: nextNodes }));
|
||||||
|
|
||||||
store.fitViewOnInitDone.set(fitViewOnInitDone);
|
store.fitViewOnInitDone.set(fitViewOnInitDone);
|
||||||
store.nodes.set(nextNodes);
|
store.nodes.set(nextNodes);
|
||||||
@@ -162,16 +167,18 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitView() {
|
function fitView(options?: FitViewOptions) {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||||
|
|
||||||
if (!d3Zoom || !d3Selection) {
|
if (!d3Zoom || !d3Selection) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fitViewNodes = options?.nodes || get(store.nodes);
|
||||||
|
|
||||||
return fitViewUtil(
|
return fitViewUtil(
|
||||||
{
|
{
|
||||||
nodes: get(store.nodes),
|
nodes: fitViewNodes as Node[],
|
||||||
width: get(store.width),
|
width: get(store.width),
|
||||||
height: get(store.height),
|
height: get(store.height),
|
||||||
minZoom: 0.2,
|
minZoom: 0.2,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
import type { initialStoreState } from './initial-store';
|
import type { initialStoreState } from './initial-store';
|
||||||
import type { Node, Edge, ConnectionData, NodeTypes, EdgeTypes } from '$lib/types';
|
import type { Node, Edge, ConnectionData, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export type SvelteFlowStoreActions = {
|
export type SvelteFlowStoreActions = {
|
||||||
@@ -18,7 +18,7 @@ export type SvelteFlowStoreActions = {
|
|||||||
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
|
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
|
||||||
setMinZoom: (minZoom: number) => void;
|
setMinZoom: (minZoom: number) => void;
|
||||||
setMaxZoom: (maxZoom: number) => void;
|
setMaxZoom: (maxZoom: number) => void;
|
||||||
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
|
fitView: (options?: FitViewOptions) => boolean;
|
||||||
updateNodePositions: (
|
updateNodePositions: (
|
||||||
nodeDragItems: NodeDragItem[],
|
nodeDragItems: NodeDragItem[],
|
||||||
positionChanged?: boolean,
|
positionChanged?: boolean,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { writable, type Writable } from 'svelte/store';
|
||||||
import {
|
import {
|
||||||
isNodeBase,
|
isNodeBase,
|
||||||
isEdgeBase,
|
isEdgeBase,
|
||||||
@@ -5,10 +6,12 @@ import {
|
|||||||
getOutgoersBase,
|
getOutgoersBase,
|
||||||
getIncomersBase,
|
getIncomersBase,
|
||||||
updateEdgeBase,
|
updateEdgeBase,
|
||||||
getConnectedEdgesBase
|
getConnectedEdgesBase,
|
||||||
|
isNumeric,
|
||||||
|
getNodePositionWithOrigin
|
||||||
} from '@reactflow/utils';
|
} from '@reactflow/utils';
|
||||||
|
import { internalsSymbol, type XYZPosition } from '@reactflow/system';
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||||
import { writable, type Writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export const isNode = isNodeBase<Node, Edge>;
|
export const isNode = isNodeBase<Node, Edge>;
|
||||||
export const isEdge = isEdgeBase<Node, Edge>;
|
export const isEdge = isEdgeBase<Node, Edge>;
|
||||||
@@ -27,11 +30,53 @@ export const createNodes = (
|
|||||||
let defaults = defaultOptions || {};
|
let defaults = defaultOptions || {};
|
||||||
|
|
||||||
const _set: typeof set = (nds: Node[]) => {
|
const _set: typeof set = (nds: Node[]) => {
|
||||||
const nextNodes = defaults ? nds.map((node) => ({ ...defaults, ...node })) : nds;
|
const parentNodes: Record<string, boolean> = {};
|
||||||
// @todo calculate absolute position based on parent / child relation
|
|
||||||
const nextNodesLayouted = nextNodes.map((n) => ({ ...n, positionAbsolute: n.position }));
|
|
||||||
|
|
||||||
value = nextNodesLayouted;
|
const nextNodes = nds.map((n) => {
|
||||||
|
const node: Node = { ...defaults, ...n, positionAbsolute: n.position };
|
||||||
|
const z = (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? 1 : 0);
|
||||||
|
|
||||||
|
if (node.parentNode) {
|
||||||
|
parentNodes[node.parentNode] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(node, internalsSymbol, {
|
||||||
|
value: {
|
||||||
|
handleBounds: node?.[internalsSymbol]?.handleBounds,
|
||||||
|
z
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodesWithPositions = nextNodes.map((node) => {
|
||||||
|
if (node.parentNode && !parentNodes[node.parentNode]) {
|
||||||
|
throw new Error(`Parent node ${node.parentNode} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.parentNode || parentNodes?.[node.id]) {
|
||||||
|
const { x, y, z } = calculateXYZPosition(node, nextNodes, {
|
||||||
|
...node.position,
|
||||||
|
z: node[internalsSymbol]?.z ?? 0
|
||||||
|
});
|
||||||
|
|
||||||
|
node.positionAbsolute = {
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
};
|
||||||
|
|
||||||
|
node[internalsSymbol]!.z = z;
|
||||||
|
|
||||||
|
if (parentNodes?.[node.id]) {
|
||||||
|
node[internalsSymbol]!.isParent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
value = nodesWithPositions;
|
||||||
|
|
||||||
set(value);
|
set(value);
|
||||||
};
|
};
|
||||||
@@ -81,3 +126,20 @@ export const createEdges = (
|
|||||||
setDefaultOptions
|
setDefaultOptions
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function calculateXYZPosition(node: Node, nodes: Node[], result: XYZPosition): XYZPosition {
|
||||||
|
if (!node.parentNode) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const parentNode = nodes.find((n) => n.id === node.parentNode)!;
|
||||||
|
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin);
|
||||||
|
|
||||||
|
return calculateXYZPosition(parentNode, nodes, {
|
||||||
|
x: (result.x ?? 0) + parentNodePosition.x,
|
||||||
|
y: (result.y ?? 0) + parentNodePosition.y,
|
||||||
|
z:
|
||||||
|
(parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0)
|
||||||
|
? parentNode[internalsSymbol]?.z ?? 0
|
||||||
|
: result.z ?? 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SvelteFlow, {
|
||||||
|
SvelteFlowProvider,
|
||||||
|
Controls,
|
||||||
|
Background,
|
||||||
|
BackgroundVariant,
|
||||||
|
Minimap,
|
||||||
|
createNodes,
|
||||||
|
createEdges,
|
||||||
|
type NodeTypes,
|
||||||
|
} from '../../lib/index';
|
||||||
|
import { DebugNode } from './DebugNode';
|
||||||
|
|
||||||
|
const nodeTypes: NodeTypes = {
|
||||||
|
default: DebugNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodes = createNodes([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'input',
|
||||||
|
data: { label: 'Node 1' },
|
||||||
|
position: { x: 250, y: 5 },
|
||||||
|
origin: [0.5, 0.5],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
data: { label: 'Node 4' },
|
||||||
|
position: { x: 100, y: 200 },
|
||||||
|
style: "width:500px; height:300px;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4a',
|
||||||
|
data: { label: 'Node 4a' },
|
||||||
|
position: { x: 15, y: 15 },
|
||||||
|
parentNode: '4',
|
||||||
|
extent: [
|
||||||
|
[0, 0],
|
||||||
|
[100, 100],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4b',
|
||||||
|
data: { label: 'Node 4b' },
|
||||||
|
position: { x: 100, y: 60 },
|
||||||
|
style: "width: 300px; height: 200px;",
|
||||||
|
parentNode: '4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4b1',
|
||||||
|
data: { label: 'Node 4b1' },
|
||||||
|
position: { x: 40, y: 20 },
|
||||||
|
parentNode: '4b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4b2',
|
||||||
|
data: { label: 'Node 4b2' },
|
||||||
|
position: { x: 20, y: 100 },
|
||||||
|
parentNode: '4b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
type: 'group',
|
||||||
|
data: { label: 'Node 5' },
|
||||||
|
position: { x: 650, y: 250 },
|
||||||
|
style: "width: 400px; height: 150px",
|
||||||
|
zIndex: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5a',
|
||||||
|
data: { label: 'Node 5a' },
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
parentNode: '5',
|
||||||
|
extent: 'parent',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5b',
|
||||||
|
data: { label: 'Node 5b' },
|
||||||
|
position: { x: 225, y: 50 },
|
||||||
|
parentNode: '5',
|
||||||
|
expandParent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
data: { label: 'Node 2' },
|
||||||
|
position: { x: 100, y: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
data: { label: 'Node 3' },
|
||||||
|
position: { x: 400, y: 100 },
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const edges = createEdges([
|
||||||
|
{
|
||||||
|
id: 'e1-2',
|
||||||
|
source: '1',
|
||||||
|
target: '2',
|
||||||
|
// markerEnd: {
|
||||||
|
// type: MarkerType.Arrow,
|
||||||
|
// strokeWidth: 2,
|
||||||
|
// width: 15,
|
||||||
|
// height: 15,
|
||||||
|
// color: '#f00',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
{ id: 'e1-3', source: '1', target: '3' },
|
||||||
|
{ id: 'e3-4', source: '3', target: '4', zIndex: 100 },
|
||||||
|
{ id: 'e3-4b', source: '3', target: '4b' },
|
||||||
|
{ id: 'e4a-4b1', source: '4a', target: '4b1' },
|
||||||
|
{ id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 },
|
||||||
|
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
||||||
|
], { animated: true });
|
||||||
|
|
||||||
|
$: {
|
||||||
|
console.log($nodes)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SvelteFlowProvider
|
||||||
|
{nodes}
|
||||||
|
{edges}
|
||||||
|
>
|
||||||
|
<SvelteFlow
|
||||||
|
{nodeTypes}
|
||||||
|
fitView
|
||||||
|
minZoom={0.1}
|
||||||
|
maxZoom={2.5}
|
||||||
|
>
|
||||||
|
<Controls />
|
||||||
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
|
<Minimap />
|
||||||
|
</SvelteFlow>
|
||||||
|
</SvelteFlowProvider>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.svelte-flow .svelte-flow__node.parent) {
|
||||||
|
background-color: rgba(220,220,255,0.4)
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Handle, Position } from '../../../lib/index';
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
export let xPos: number = 0;
|
||||||
|
export let yPos: number = 0;
|
||||||
|
export let zIndex: number = 0;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Handle type="target" position={Position.Top} />
|
||||||
|
<div>{id}</div>
|
||||||
|
<div>
|
||||||
|
x:{Math.round(xPos || 0)} y:{Math.round(yPos || 0)} z:{zIndex}
|
||||||
|
</div>
|
||||||
|
<Handle type="source" position={Position.Bottom} />
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom {
|
||||||
|
background: #ffcc00;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as DebugNode } from './DebugNode.svelte';
|
||||||
@@ -80,6 +80,7 @@ export type NodeDragItem = {
|
|||||||
extent?: 'parent' | CoordinateExtent;
|
extent?: 'parent' | CoordinateExtent;
|
||||||
parentNode?: string;
|
parentNode?: string;
|
||||||
dragging?: boolean;
|
dragging?: boolean;
|
||||||
|
origin?: NodeOrigin;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeOrigin = [number, number];
|
export type NodeOrigin = [number, number];
|
||||||
|
|||||||
Reference in New Issue
Block a user