chore(svelte): run prettier

This commit is contained in:
moklick
2023-02-28 12:48:52 +01:00
parent 62a6278682
commit dda21e1fd2
64 changed files with 1669 additions and 1646 deletions

View File

@@ -1,20 +1,20 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

View File

@@ -1,9 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -1,47 +1,47 @@
{
"name": "svelte",
"version": "0.0.1",
"scripts": {
"dev": "vite dev",
"build": "svelte-kit sync && svelte-package",
"prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"type": "module",
"module": "index.ts",
"exports": {
".": "./index.js"
},
"dependencies": {
"@reactflow/edge-utils": "workspace:*",
"@reactflow/system": "workspace:*",
"@reactflow/utils": "workspace:*",
"@svelte-put/shortcut": "^2.0.0",
"classcat": "^5.0.4",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.6",
"@sveltejs/package": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.55.1",
"svelte-check": "^3.0.1",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.1.1"
}
"name": "svelte",
"version": "0.0.1",
"scripts": {
"dev": "vite dev",
"build": "svelte-kit sync && svelte-package",
"prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"type": "module",
"module": "index.ts",
"exports": {
".": "./index.js"
},
"dependencies": {
"@reactflow/edge-utils": "workspace:*",
"@reactflow/system": "workspace:*",
"@reactflow/utils": "workspace:*",
"@svelte-put/shortcut": "^2.0.0",
"classcat": "^5.0.4",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.6",
"@sveltejs/package": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.55.1",
"svelte-check": "^3.0.1",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.1.1"
}
}

View File

@@ -1,12 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

View File

@@ -1,21 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
html,
body,
#root {
height: 100%;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
</style>
%sveltekit.head%
</head>
<body>
<div id="root">%sveltekit.body%</div>
</body>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
html,
body,
#root {
height: 100%;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
</style>
%sveltekit.head%
</head>
<body>
<div id="root">%sveltekit.body%</div>
</body>
</html>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Handle from '$lib/components/Handle/index.svelte';
import { Position } from '@reactflow/system';
import { Position } from '@reactflow/system';
export let data: { label: string } = { label: 'Node' };
export let xPos: number = 0;
export let yPos: number = 0;
@@ -19,4 +19,4 @@
background: #ffcc00;
padding: 10px;
}
</style>
</style>

View File

@@ -9,113 +9,113 @@ import type { Node } from '$lib/types';
export type UseDragData = { dx: number; dy: number };
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
export type NodeDragItem = {
id: string;
position: XYPosition;
positionAbsolute: XYPosition;
// distance from the mouse cursor to the node when start dragging
distance: XYPosition;
width?: number | null;
height?: number | null;
extent?: 'parent' | CoordinateExtent;
parentNode?: string;
dragging?: boolean;
id: string;
position: XYPosition;
positionAbsolute: XYPosition;
// distance from the mouse cursor to the node when start dragging
distance: XYPosition;
width?: number | null;
height?: number | null;
extent?: 'parent' | CoordinateExtent;
parentNode?: string;
dragging?: boolean;
};
type UseDragParams = {
handleSelector?: string;
nodeId?: string;
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
nodes: Writable<Node[]>;
transform: Writable<Transform>;
handleSelector?: string;
nodeId?: string;
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
nodes: Writable<Node[]>;
transform: Writable<Transform>;
};
export default function drag(
nodeRef: Element,
{ handleSelector, nodeId, updateNodePositions, nodes, transform: transformStore }: UseDragParams
nodeRef: Element,
{ handleSelector, nodeId, updateNodePositions, nodes, transform: transformStore }: UseDragParams
) {
let dragging = false;
let dragItems: NodeDragItem[] = [];
let lastPos: { x: number | null; y: number | null } = { x: null, y: null };
let dragging = false;
let dragItems: NodeDragItem[] = [];
let lastPos: { x: number | null; y: number | null } = { x: null, y: null };
const selection = select(nodeRef);
const selection = select(nodeRef);
const getPointerPosition = ({ sourceEvent }: UseDragEvent) => {
const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
const transform = get(transformStore);
const getPointerPosition = ({ sourceEvent }: UseDragEvent) => {
const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
const transform = get(transformStore);
const pointerPos = {
x: (x - transform[0]) / transform[2],
y: (y - transform[1]) / transform[2]
};
const pointerPos = {
x: (x - transform[0]) / transform[2],
y: (y - transform[1]) / transform[2]
};
// we need the snapped position in order to be able to skip unnecessary drag events
return {
xSnapped: pointerPos.x,
ySnapped: pointerPos.y,
...pointerPos
};
};
// we need the snapped position in order to be able to skip unnecessary drag events
return {
xSnapped: pointerPos.x,
ySnapped: pointerPos.y,
...pointerPos
};
};
const updateNodes = ({ x, y }: XYPosition) => {
let hasChange = false;
const updateNodes = ({ x, y }: XYPosition) => {
let hasChange = false;
dragItems = dragItems.map((n) => {
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
const updatedPos = calcNextPosition(n, nextPosition, get(nodes) as RFNode[]);
dragItems = dragItems.map((n) => {
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
const updatedPos = calcNextPosition(n, nextPosition, get(nodes) as RFNode[]);
// we want to make sure that we only fire a change event when there is a changes
hasChange =
hasChange ||
n.position.x !== updatedPos.position.x ||
n.position.y !== updatedPos.position.y;
// we want to make sure that we only fire a change event when there is a changes
hasChange =
hasChange ||
n.position.x !== updatedPos.position.x ||
n.position.y !== updatedPos.position.y;
n.position = updatedPos.position;
n.positionAbsolute = updatedPos.positionAbsolute;
n.position = updatedPos.position;
n.positionAbsolute = updatedPos.positionAbsolute;
return n;
});
return n;
});
if (!hasChange) {
return;
}
if (!hasChange) {
return;
}
updateNodePositions(dragItems, true, true);
dragging = true;
};
updateNodePositions(dragItems, true, true);
dragging = true;
};
const dragHandler = d3Drag()
.on('start', (event: UseDragEvent) => {
const pointerPos = getPointerPosition(event);
console.log(pointerPos);
lastPos = pointerPos;
dragItems = getDragItems(get(nodes) as RFNode[], pointerPos, nodeId);
})
.on('drag', (event: UseDragEvent) => {
const pointerPos = getPointerPosition(event);
const dragHandler = d3Drag()
.on('start', (event: UseDragEvent) => {
const pointerPos = getPointerPosition(event);
console.log(pointerPos);
lastPos = pointerPos;
dragItems = getDragItems(get(nodes) as RFNode[], pointerPos, nodeId);
})
.on('drag', (event: UseDragEvent) => {
const pointerPos = getPointerPosition(event);
// skip events without movement
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems) {
lastPos = pointerPos;
updateNodes(pointerPos);
}
})
.on('end', (event: UseDragEvent) => {
dragging = false;
// skip events without movement
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems) {
lastPos = pointerPos;
updateNodes(pointerPos);
}
})
.on('end', (event: UseDragEvent) => {
dragging = false;
if (dragItems) {
updateNodePositions(dragItems, false, false);
}
})
.filter((event: MouseEvent) => {
const target = event.target as HTMLDivElement;
const isDraggable =
!event.button &&
!hasSelector(target, '.nodrag', nodeRef) &&
(!handleSelector || hasSelector(target, handleSelector, nodeRef));
if (dragItems) {
updateNodePositions(dragItems, false, false);
}
})
.filter((event: MouseEvent) => {
const target = event.target as HTMLDivElement;
const isDraggable =
!event.button &&
!hasSelector(target, '.nodrag', nodeRef) &&
(!handleSelector || hasSelector(target, handleSelector, nodeRef));
return isDraggable;
});
return isDraggable;
});
selection.call(dragHandler);
selection.call(dragHandler);
}

View File

@@ -1,152 +1,152 @@
import type {
CoordinateExtent,
Node,
NodeDragItem,
NodeInternals,
NodeOrigin,
XYPosition
CoordinateExtent,
Node,
NodeDragItem,
NodeInternals,
NodeOrigin,
XYPosition
} from '@reactflow/system';
import { clampPosition, isNumeric } from '../../../utils';
export function isParentSelected(node: Node, nodes: Node[]): boolean {
if (!node.parentNode) {
return false;
}
if (!node.parentNode) {
return false;
}
const parentNode = nodes.find((n) => n.id === node.parentNode);
const parentNode = nodes.find((n) => n.id === node.parentNode);
if (!parentNode) {
return false;
}
if (!parentNode) {
return false;
}
if (parentNode.selected) {
return true;
}
if (parentNode.selected) {
return true;
}
return isParentSelected(parentNode, nodes);
return isParentSelected(parentNode, nodes);
}
export function hasSelector(target: Element, selector: string, domNode: Element): boolean {
let current = target;
let current = target;
do {
if (current?.matches(selector)) return true;
if (current === domNode) return false;
current = current.parentElement as Element;
} while (current);
do {
if (current?.matches(selector)) return true;
if (current === domNode) return false;
current = current.parentElement as Element;
} while (current);
return false;
return false;
}
// looks for all selected nodes and created a NodeDragItem for each of them
export function getDragItems(nodes: Node[], mousePos: XYPosition, nodeId?: string): NodeDragItem[] {
return nodes
.filter(
(n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, nodes))
)
.map((n) => ({
id: n.id,
position: n.position ? { ...n.position } : { x: 0, y: 0 },
positionAbsolute: n.positionAbsolute ? { ...n.positionAbsolute } : { x: 0, y: 0 },
distance: {
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
y: mousePos.y - (n.positionAbsolute?.y ?? 0)
},
delta: {
x: 0,
y: 0
},
extent: n.extent,
parentNode: n.parentNode,
width: n.width,
height: n.height
}));
return nodes
.filter(
(n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, nodes))
)
.map((n) => ({
id: n.id,
position: n.position ? { ...n.position } : { x: 0, y: 0 },
positionAbsolute: n.positionAbsolute ? { ...n.positionAbsolute } : { x: 0, y: 0 },
distance: {
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
y: mousePos.y - (n.positionAbsolute?.y ?? 0)
},
delta: {
x: 0,
y: 0
},
extent: n.extent,
parentNode: n.parentNode,
width: n.width,
height: n.height
}));
}
export function calcNextPosition(
node: NodeDragItem | Node,
nextPosition: XYPosition,
nodes: Node[],
nodeExtent?: CoordinateExtent,
nodeOrigin: NodeOrigin = [0, 0]
node: NodeDragItem | Node,
nextPosition: XYPosition,
nodes: Node[],
nodeExtent?: CoordinateExtent,
nodeOrigin: NodeOrigin = [0, 0]
): { position: XYPosition; positionAbsolute: XYPosition } {
let currentExtent = node.extent || nodeExtent;
let currentExtent = node.extent || nodeExtent;
if (node.extent === 'parent') {
if (node.parentNode && node.width && node.height) {
const parent = nodes.find((n) => n.id === node.parentNode)!;
const { x: parentX, y: parentY } = parent.positionAbsolute!;
currentExtent =
parent &&
isNumeric(parentX) &&
isNumeric(parentY) &&
isNumeric(parent.width) &&
isNumeric(parent.height)
? [
[parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
[
parentX + parent.width! - node.width + node.width * nodeOrigin[0],
parentY + parent.height! - node.height + node.height * nodeOrigin[1]
]
]
: currentExtent;
} else {
currentExtent = nodeExtent;
}
} else if (node.extent && node.parentNode) {
const parent = nodes.find((n) => n.id === node.parentNode)!;
const { x: parentX, y: parentY } = parent.positionAbsolute!;
currentExtent = [
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
[node.extent[1][0] + parentX, node.extent[1][1] + parentY]
];
}
if (node.extent === 'parent') {
if (node.parentNode && node.width && node.height) {
const parent = nodes.find((n) => n.id === node.parentNode)!;
const { x: parentX, y: parentY } = parent.positionAbsolute!;
currentExtent =
parent &&
isNumeric(parentX) &&
isNumeric(parentY) &&
isNumeric(parent.width) &&
isNumeric(parent.height)
? [
[parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
[
parentX + parent.width! - node.width + node.width * nodeOrigin[0],
parentY + parent.height! - node.height + node.height * nodeOrigin[1]
]
]
: currentExtent;
} else {
currentExtent = nodeExtent;
}
} else if (node.extent && node.parentNode) {
const parent = nodes.find((n) => n.id === node.parentNode)!;
const { x: parentX, y: parentY } = parent.positionAbsolute!;
currentExtent = [
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
[node.extent[1][0] + parentX, node.extent[1][1] + parentY]
];
}
let parentPosition = { x: 0, y: 0 };
let parentPosition = { x: 0, y: 0 };
if (node.parentNode) {
const parent = nodes.find((n) => n.id === node.parentNode)!;
parentPosition = parent.positionAbsolute!;
}
if (node.parentNode) {
const parent = nodes.find((n) => n.id === node.parentNode)!;
parentPosition = parent.positionAbsolute!;
}
const positionAbsolute = currentExtent
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
: nextPosition;
const positionAbsolute = currentExtent
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
: nextPosition;
return {
position: {
x: positionAbsolute.x - parentPosition.x,
y: positionAbsolute.y - parentPosition.y
},
positionAbsolute
};
return {
position: {
x: positionAbsolute.x - parentPosition.x,
y: positionAbsolute.y - parentPosition.y
},
positionAbsolute
};
}
// returns two params:
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
// 2. array of selected nodes (for multi selections)
export function getEventHandlerParams({
nodeId,
dragItems,
nodeInternals
nodeId,
dragItems,
nodeInternals
}: {
nodeId?: string;
dragItems: NodeDragItem[];
nodeInternals: NodeInternals;
nodeId?: string;
dragItems: NodeDragItem[];
nodeInternals: NodeInternals;
}): [Node, Node[]] {
const extentedDragItems: Node[] = dragItems.map((n) => {
const node = nodeInternals.get(n.id)!;
const extentedDragItems: Node[] = dragItems.map((n) => {
const node = nodeInternals.get(n.id)!;
return {
...node,
position: n.position,
positionAbsolute: n.positionAbsolute
};
});
return {
...node,
position: n.position,
positionAbsolute: n.positionAbsolute
};
});
return [
nodeId ? extentedDragItems.find((n) => n.id === nodeId)! : extentedDragItems[0],
extentedDragItems
];
return [
nodeId ? extentedDragItems.find((n) => n.id === nodeId)! : extentedDragItems[0],
extentedDragItems
];
}

View File

@@ -1,15 +1,15 @@
export default function (node: Element, target = 'body') {
const targetEl = document.querySelector(target);
const targetEl = document.querySelector(target);
if (targetEl) {
targetEl.appendChild(node);
}
if (targetEl) {
targetEl.appendChild(node);
}
return {
destroy() {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
};
return {
destroy() {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
};
}

View File

@@ -6,114 +6,114 @@ import type { D3ZoomEvent } from 'd3-zoom';
import type { D3SelectionInstance, D3ZoomInstance, Transform } from '@reactflow/system';
const isWrappedWithClass = (event: any, className: string | undefined) =>
event.target.closest(`.${className}`);
event.target.closest(`.${className}`);
function filter(event: any, params: ZoomParams): boolean {
const zoomScroll = true;
const pinchZoom = true;
const zoomScroll = true;
const pinchZoom = true;
if (
event.button === 1 &&
event.type === 'mousedown' &&
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))
) {
return true;
}
if (
event.button === 1 &&
event.type === 'mousedown' &&
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))
) {
return true;
}
// if all interactions are disabled, we prevent all zoom events
// if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
// return false;
// }
// // during a selection we prevent all other interactions
if (params.selecting) {
return false;
}
// if all interactions are disabled, we prevent all zoom events
// if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
// return false;
// }
// // during a selection we prevent all other interactions
if (params.selecting) {
return false;
}
// // if zoom on double click is disabled, we prevent the double click event
// if (!zoomOnDoubleClick && event.type === 'dblclick') {
// return false;
// }
// // if zoom on double click is disabled, we prevent the double click event
// if (!zoomOnDoubleClick && event.type === 'dblclick') {
// return false;
// }
// // if the target element is inside an element with the nowheel class, we prevent zooming
// if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
// return false;
// }
// // if the target element is inside an element with the nowheel class, we prevent zooming
// if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
// return false;
// }
// // if the target element is inside an element with the nopan class, we prevent panning
if (isWrappedWithClass(event, 'nopan') && event.type !== 'wheel') {
return false;
}
// // if the target element is inside an element with the nopan class, we prevent panning
if (isWrappedWithClass(event, 'nopan') && event.type !== 'wheel') {
return false;
}
// if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
// return false;
// }
// if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
// return false;
// }
// // when there is no scroll handling enabled, we prevent all wheel events
// if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
// return false;
// }
// // when there is no scroll handling enabled, we prevent all wheel events
// if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
// return false;
// }
// // if the pane is not movable, we prevent dragging it with mousestart or touchstart
// if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
// return false;
// }
// // if the pane is not movable, we prevent dragging it with mousestart or touchstart
// if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
// return false;
// }
// // if the pane is only movable using allowed clicks
// if (
// Array.isArray(panOnDrag) &&
// !panOnDrag.includes(event.button) &&
// (event.type === 'mousedown' || event.type === 'touchstart')
// ) {
// return false;
// }
// // if the pane is only movable using allowed clicks
// if (
// Array.isArray(panOnDrag) &&
// !panOnDrag.includes(event.button) &&
// (event.type === 'mousedown' || event.type === 'touchstart')
// ) {
// return false;
// }
// // We only allow right clicks if pan on drag is set to right click
// const buttonAllowed =
// (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) ||
// !event.button ||
// event.button <= 1;
// // We only allow right clicks if pan on drag is set to right click
// const buttonAllowed =
// (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) ||
// !event.button ||
// event.button <= 1;
// default filter for d3-zoom
return ((!event.ctrlKey || event.type === 'wheel') && !event.button) || event.button <= 1;
// default filter for d3-zoom
return ((!event.ctrlKey || event.type === 'wheel') && !event.button) || event.button <= 1;
}
type ZoomParams = {
transform: Writable<Transform>;
selecting: boolean;
d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
transform: Writable<Transform>;
selecting: boolean;
d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
};
export default function zoom(domNode: Element, params: ZoomParams) {
const { transform, d3 } = params;
const d3ZoomInstance = d3Zoom();
const selection = select(domNode).call(d3ZoomInstance);
const d3ZoomHandler = selection.on('wheel.zoom');
const { transform, d3 } = params;
const d3ZoomInstance = d3Zoom();
const selection = select(domNode).call(d3ZoomInstance);
const d3ZoomHandler = selection.on('wheel.zoom');
d3ZoomInstance.transform(selection, zoomIdentity);
d3ZoomInstance.transform(selection, zoomIdentity);
selection.on('wheel.zoom', function (event: any, d: any) {
if (isWrappedWithClass(event, 'nowheel')) {
return null;
}
selection.on('wheel.zoom', function (event: any, d: any) {
if (isWrappedWithClass(event, 'nowheel')) {
return null;
}
event.preventDefault();
d3ZoomHandler!.call(this, event, d);
});
event.preventDefault();
d3ZoomHandler!.call(this, event, d);
});
d3.set({
zoom: d3ZoomInstance,
selection
});
d3.set({
zoom: d3ZoomInstance,
selection
});
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
transform.set([event.transform.x, event.transform.y, event.transform.k]);
});
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
transform.set([event.transform.x, event.transform.y, event.transform.k]);
});
d3ZoomInstance.filter((event: any) => filter(event, params));
d3ZoomInstance.filter((event: any) => filter(event, params));
return {
update(params: ZoomParams) {
d3ZoomInstance.filter((event: any) => filter(event, params));
}
};
return {
update(params: ZoomParams) {
d3ZoomInstance.filter((event: any) => filter(event, params));
}
};
}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import cc from 'classcat';
import { useStore } from '$lib/store';
import type { ConnectionLineProps } from '$lib/types';
import { useStore } from '$lib/store';
import type { ConnectionLineProps } from '$lib/types';
type $$Props = ConnectionLineProps;
@@ -10,11 +10,7 @@
</script>
{#if $connectionPath}
<svg
width={$width}
height={$height}
class="react-flow__connectionline"
>
<svg width={$width} height={$height} class="react-flow__connectionline">
<g class={cc(['react-flow__connection', $connection.status])}>
<path d={$connectionPath} fill="none" class="react-flow__connection-path" />
</g>
@@ -43,4 +39,4 @@
stroke-width: 1;
fill: none;
}
</style>
</style>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import portal from '$lib/actions/portal';
type $$Props = {}
type $$Props = {};
</script>
<div use:portal={'.react-flow__edgelabel-renderer'}>
<slot />
</div>
</div>

View File

@@ -1,210 +1,210 @@
import {
getHostForElement,
calcAutoPan,
getEventPosition,
pointToRendererPoint,
rendererPointToPoint
getHostForElement,
calcAutoPan,
getEventPosition,
pointToRendererPoint,
rendererPointToPoint
} from '@reactflow/utils';
import type {
OnConnect,
HandleType,
Connection,
ConnectionMode,
Node,
XYPosition,
Transform
OnConnect,
HandleType,
Connection,
ConnectionMode,
Node,
XYPosition,
Transform
} from '@reactflow/system';
import {
getClosestHandle,
getConnectionStatus,
getHandleLookup,
getHandleType,
isValidHandle,
resetRecentHandle,
type ConnectionHandle,
type ValidConnectionFunc
getClosestHandle,
getConnectionStatus,
getHandleLookup,
getHandleType,
isValidHandle,
resetRecentHandle,
type ConnectionHandle,
type ValidConnectionFunc
} from './utils';
import { get, type Writable } from 'svelte/store';
import type { ConnectionData } from '$lib/types';
export function handlePointerDown({
event,
handleId,
nodeId,
onConnect,
domNode,
nodes,
connectionMode,
connectionRadius,
isTarget,
transform: transformStore,
panBy,
updateConnection,
cancelConnection,
isValidConnection,
edgeUpdaterType,
onEdgeUpdateEnd
event,
handleId,
nodeId,
onConnect,
domNode,
nodes,
connectionMode,
connectionRadius,
isTarget,
transform: transformStore,
panBy,
updateConnection,
cancelConnection,
isValidConnection,
edgeUpdaterType,
onEdgeUpdateEnd
}: {
event: MouseEvent | TouchEvent;
handleId: string | null;
nodeId: string;
onConnect: OnConnect;
isTarget: boolean;
connectionMode: ConnectionMode;
domNode: HTMLDivElement | null;
nodes: Node[];
connectionRadius: number;
isValidConnection: ValidConnectionFunc;
transform: Writable<Transform>;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
panBy: (delta: XYPosition) => void;
edgeUpdaterType?: HandleType;
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
event: MouseEvent | TouchEvent;
handleId: string | null;
nodeId: string;
onConnect: OnConnect;
isTarget: boolean;
connectionMode: ConnectionMode;
domNode: HTMLDivElement | null;
nodes: Node[];
connectionRadius: number;
isValidConnection: ValidConnectionFunc;
transform: Writable<Transform>;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
panBy: (delta: XYPosition) => void;
edgeUpdaterType?: HandleType;
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
}): void {
// when react-flow is used inside a shadow root we can't use document
const doc = getHostForElement(event.target as HTMLElement);
let autoPanId = 0;
let prevClosestHandle: ConnectionHandle | null;
// when react-flow is used inside a shadow root we can't use document
const doc = getHostForElement(event.target as HTMLElement);
let autoPanId = 0;
let prevClosestHandle: ConnectionHandle | null;
const { x, y } = getEventPosition(event);
const clickedHandle = doc?.elementFromPoint(x, y);
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
const containerBounds = domNode?.getBoundingClientRect();
const { x, y } = getEventPosition(event);
const clickedHandle = doc?.elementFromPoint(x, y);
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
const containerBounds = domNode?.getBoundingClientRect();
if (!containerBounds || !handleType) {
return;
}
if (!containerBounds || !handleType) {
return;
}
let prevActiveHandle: Element;
let connectionPosition = getEventPosition(event, containerBounds);
let autoPanStarted = false;
let connection: Connection | null = null;
let isValid = false;
let handleDomNode: Element | null = null;
let prevActiveHandle: Element;
let connectionPosition = getEventPosition(event, containerBounds);
let autoPanStarted = false;
let connection: Connection | null = null;
let isValid = false;
let handleDomNode: Element | null = null;
const autoPanOnConnect = true;
const autoPanOnConnect = true;
const handleLookup = getHandleLookup({
nodes,
nodeId,
handleId,
handleType
});
const handleLookup = getHandleLookup({
nodes,
nodeId,
handleId,
handleType
});
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
const autoPan = (): void => {
// @todd add prop
if (!autoPanOnConnect) {
return;
}
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
const autoPan = (): void => {
// @todd add prop
if (!autoPanOnConnect) {
return;
}
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
panBy({ x: xMovement, y: yMovement });
autoPanId = requestAnimationFrame(autoPan);
};
panBy({ x: xMovement, y: yMovement });
autoPanId = requestAnimationFrame(autoPan);
};
updateConnection({
position: connectionPosition,
nodeId,
handleId,
handleType,
status: null
});
updateConnection({
position: connectionPosition,
nodeId,
handleId,
handleType,
status: null
});
// @todo add prop
// onConnectStart?.(event, { nodeId, handleId, handleType });
// @todo add prop
// onConnectStart?.(event, { nodeId, handleId, handleType });
function onPointerMove(event: MouseEvent | TouchEvent) {
const transform = get(transformStore);
connectionPosition = getEventPosition(event, containerBounds);
function onPointerMove(event: MouseEvent | TouchEvent) {
const transform = get(transformStore);
connectionPosition = getEventPosition(event, containerBounds);
prevClosestHandle = getClosestHandle(
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
connectionRadius,
handleLookup
);
prevClosestHandle = getClosestHandle(
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
connectionRadius,
handleLookup
);
if (!autoPanStarted) {
autoPan();
autoPanStarted = true;
}
if (!autoPanStarted) {
autoPan();
autoPanStarted = true;
}
const result = isValidHandle(
event,
prevClosestHandle,
connectionMode,
nodeId,
handleId,
isTarget ? 'target' : 'source',
isValidConnection,
doc
);
const result = isValidHandle(
event,
prevClosestHandle,
connectionMode,
nodeId,
handleId,
isTarget ? 'target' : 'source',
isValidConnection,
doc
);
handleDomNode = result.handleDomNode;
connection = result.connection;
isValid = result.isValid;
handleDomNode = result.handleDomNode;
connection = result.connection;
isValid = result.isValid;
updateConnection({
position:
prevClosestHandle && isValid
? rendererPointToPoint(
{
x: prevClosestHandle.x,
y: prevClosestHandle.y
},
transform
)
: connectionPosition,
status: getConnectionStatus(!!prevClosestHandle, isValid)
});
updateConnection({
position:
prevClosestHandle && isValid
? rendererPointToPoint(
{
x: prevClosestHandle.x,
y: prevClosestHandle.y
},
transform
)
: connectionPosition,
status: getConnectionStatus(!!prevClosestHandle, isValid)
});
if (!prevClosestHandle && !isValid && !handleDomNode) {
return resetRecentHandle(prevActiveHandle);
}
if (!prevClosestHandle && !isValid && !handleDomNode) {
return resetRecentHandle(prevActiveHandle);
}
if (connection.source !== connection.target && handleDomNode) {
resetRecentHandle(prevActiveHandle);
prevActiveHandle = handleDomNode;
// @todo: remove the old class names "react-flow__handle-" in the next major version
handleDomNode.classList.add('connecting');
handleDomNode.classList.toggle('valid', isValid);
}
}
if (connection.source !== connection.target && handleDomNode) {
resetRecentHandle(prevActiveHandle);
prevActiveHandle = handleDomNode;
// @todo: remove the old class names "react-flow__handle-" in the next major version
handleDomNode.classList.add('connecting');
handleDomNode.classList.toggle('valid', isValid);
}
}
function onPointerUp(event: MouseEvent | TouchEvent) {
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
onConnect?.(connection);
}
function onPointerUp(event: MouseEvent | TouchEvent) {
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
onConnect?.(connection);
}
// it's important to get a fresh reference from the store here
// in order to get the latest state of onConnectEnd
// @todo add onConnectEnd prop
// getState().onConnectEnd?.(event);
// it's important to get a fresh reference from the store here
// in order to get the latest state of onConnectEnd
// @todo add onConnectEnd prop
// getState().onConnectEnd?.(event);
if (edgeUpdaterType) {
onEdgeUpdateEnd?.(event);
}
if (edgeUpdaterType) {
onEdgeUpdateEnd?.(event);
}
resetRecentHandle(prevActiveHandle);
cancelConnection();
cancelAnimationFrame(autoPanId);
autoPanStarted = false;
isValid = false;
connection = null;
handleDomNode = null;
resetRecentHandle(prevActiveHandle);
cancelConnection();
cancelAnimationFrame(autoPanId);
autoPanStarted = false;
isValid = false;
connection = null;
handleDomNode = null;
doc.removeEventListener('mousemove', onPointerMove as EventListener);
doc.removeEventListener('mouseup', onPointerUp as EventListener);
doc.removeEventListener('mousemove', onPointerMove as EventListener);
doc.removeEventListener('mouseup', onPointerUp as EventListener);
doc.removeEventListener('touchmove', onPointerMove as EventListener);
doc.removeEventListener('touchend', onPointerUp as EventListener);
}
doc.removeEventListener('touchmove', onPointerMove as EventListener);
doc.removeEventListener('touchend', onPointerUp as EventListener);
}
doc.addEventListener('mousemove', onPointerMove as EventListener);
doc.addEventListener('mouseup', onPointerUp as EventListener);
doc.addEventListener('mousemove', onPointerMove as EventListener);
doc.addEventListener('mouseup', onPointerUp as EventListener);
doc.addEventListener('touchmove', onPointerMove as EventListener);
doc.addEventListener('touchend', onPointerUp as EventListener);
doc.addEventListener('touchmove', onPointerMove as EventListener);
doc.addEventListener('touchend', onPointerUp as EventListener);
}

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { getContext } from 'svelte';
import { getContext } from 'svelte';
import cc from 'classcat';
import { Position, type Connection, type HandleProps } from '@reactflow/system';
import { isMouseEvent } from '@reactflow/utils';
import { Position, type Connection, type HandleProps } from '@reactflow/system';
import { isMouseEvent } from '@reactflow/utils';
import { handlePointerDown } from './handler';
import { useStore } from '$lib/store';
import { handlePointerDown } from './handler';
import { useStore } from '$lib/store';
type $$Props = HandleProps;
@@ -34,40 +34,40 @@
} = useStore();
function onConnectExtended(params: Connection) {
addEdge(params);
// @todo add props
// onConnectAction?.(edgeParams);
// onConnect?.(edgeParams);
};
addEdge(params);
// @todo add props
// onConnectAction?.(edgeParams);
// onConnect?.(edgeParams);
}
function onPointerDown(event: MouseEvent | TouchEvent) {
const isMouseTriggered = isMouseEvent(event);
function onPointerDown(event: MouseEvent | TouchEvent) {
const isMouseTriggered = isMouseEvent(event);
if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) {
handlePointerDown({
event,
handleId,
nodeId,
isTarget,
connectionRadius: $connectionRadius,
domNode: $domNode,
nodes: $nodes,
connectionMode: $connectionMode,
transform,
isValidConnection: isValidConnection!,
onConnect: onConnectExtended,
updateConnection,
cancelConnection,
panBy,
});
}
if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) {
handlePointerDown({
event,
handleId,
nodeId,
isTarget,
connectionRadius: $connectionRadius,
domNode: $domNode,
nodes: $nodes,
connectionMode: $connectionMode,
transform,
isValidConnection: isValidConnection!,
onConnect: onConnectExtended,
updateConnection,
cancelConnection,
panBy
});
}
// if (isMouseTriggered) {
// onMouseDown?.(event);
// } else {
// onTouchStart?.(event);
// }
};
// if (isMouseTriggered) {
// onMouseDown?.(event);
// } else {
// onTouchStart?.(event);
// }
}
</script>
<div
@@ -92,7 +92,6 @@
<slot />
</div>
<style>
.react-flow__handle {
position: absolute;
@@ -134,4 +133,4 @@
left: 100%;
transform: translate(-50%, -50%);
}
</style>
</style>

View File

@@ -3,11 +3,11 @@ import type { Connection, HandleType, XYPosition, Node, NodeHandleBounds } from
import { getEventPosition } from '@reactflow/utils';
export type ConnectionHandle = {
id: string | null;
type: HandleType;
nodeId: string;
x: number;
y: number;
id: string | null;
type: HandleType;
nodeId: string;
x: number;
y: number;
};
export type ValidConnectionFunc = (connection: Connection) => boolean;
@@ -15,174 +15,174 @@ export type ValidConnectionFunc = (connection: Connection) => boolean;
// this functions collects all handles and adds an absolute position
// so that we can later find the closest handle to the mouse position
export function getHandles(
node: Node,
handleBounds: NodeHandleBounds,
type: HandleType,
currentHandle: string
node: Node,
handleBounds: NodeHandleBounds,
type: HandleType,
currentHandle: string
): ConnectionHandle[] {
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
res.push({
id: h.id || null,
type,
nodeId: node.id,
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2
});
}
return res;
}, []);
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
res.push({
id: h.id || null,
type,
nodeId: node.id,
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2
});
}
return res;
}, []);
}
export function getClosestHandle(
pos: XYPosition,
connectionRadius: number,
handles: ConnectionHandle[]
pos: XYPosition,
connectionRadius: number,
handles: ConnectionHandle[]
): ConnectionHandle | null {
let closestHandle: ConnectionHandle | null = null;
let minDistance = Infinity;
let closestHandle: ConnectionHandle | null = null;
let minDistance = Infinity;
handles.forEach((handle) => {
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
if (distance <= connectionRadius && distance < minDistance) {
minDistance = distance;
closestHandle = handle;
}
});
handles.forEach((handle) => {
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
if (distance <= connectionRadius && distance < minDistance) {
minDistance = distance;
closestHandle = handle;
}
});
return closestHandle;
return closestHandle;
}
type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection;
handleDomNode: Element | null;
isValid: boolean;
connection: Connection;
};
const nullConnection: Connection = {
source: null,
target: null,
sourceHandle: null,
targetHandle: null
source: null,
target: null,
sourceHandle: null,
targetHandle: null
};
// checks if and returns connection in fom of an object { source: 123, target: 312 }
export function isValidHandle(
event: MouseEvent | TouchEvent,
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null,
connectionMode: ConnectionMode,
fromNodeId: string,
fromHandleId: string | null,
fromType: string,
isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot
event: MouseEvent | TouchEvent,
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null,
connectionMode: ConnectionMode,
fromNodeId: string,
fromHandleId: string | null,
fromType: string,
isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot
) {
const isTarget = fromType === 'target';
const handleDomNode = doc.querySelector(
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
);
const { x, y } = getEventPosition(event);
const handleBelow = doc.elementFromPoint(x, y);
const handleToCheck = handleBelow?.classList.contains('react-flow__handle')
? handleBelow
: handleDomNode;
const isTarget = fromType === 'target';
const handleDomNode = doc.querySelector(
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
);
const { x, y } = getEventPosition(event);
const handleBelow = doc.elementFromPoint(x, y);
const handleToCheck = handleBelow?.classList.contains('react-flow__handle')
? handleBelow
: handleDomNode;
const result: Result = {
handleDomNode: handleToCheck,
isValid: false,
connection: nullConnection
};
const result: Result = {
handleDomNode: handleToCheck,
isValid: false,
connection: nullConnection
};
if (handleToCheck) {
const handleType = getHandleType(undefined, handleToCheck);
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
const handleId = handleToCheck.getAttribute('data-handleid');
if (handleToCheck) {
const handleType = getHandleType(undefined, handleToCheck);
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
const handleId = handleToCheck.getAttribute('data-handleid');
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
sourceHandle: isTarget ? handleId : fromHandleId,
target: isTarget ? fromNodeId : handleNodeId,
targetHandle: isTarget ? fromHandleId : handleId
};
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
sourceHandle: isTarget ? handleId : fromHandleId,
target: isTarget ? fromNodeId : handleNodeId,
targetHandle: isTarget ? fromHandleId : handleId
};
result.connection = connection;
result.connection = connection;
// in strict mode we don't allow target to target or source to source connections
const isValid =
connectionMode === ConnectionMode.Strict
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
// in strict mode we don't allow target to target or source to source connections
const isValid =
connectionMode === ConnectionMode.Strict
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
if (isValid) {
result.isValid = isValidConnection(connection);
}
}
if (isValid) {
result.isValid = isValidConnection(connection);
}
}
return result;
return result;
}
type GetHandleLookupParams = {
nodes: Node[];
nodeId: string;
handleId: string | null;
handleType: string;
nodes: Node[];
nodeId: string;
handleId: string | null;
handleType: string;
};
export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
return nodes.reduce<ConnectionHandle[]>((res, node) => {
if (node[internalsSymbol]) {
const { handleBounds } = node[internalsSymbol];
let sourceHandles: ConnectionHandle[] = [];
let targetHandles: ConnectionHandle[] = [];
return nodes.reduce<ConnectionHandle[]>((res, node) => {
if (node[internalsSymbol]) {
const { handleBounds } = node[internalsSymbol];
let sourceHandles: ConnectionHandle[] = [];
let targetHandles: ConnectionHandle[] = [];
if (handleBounds) {
sourceHandles = getHandles(
node,
handleBounds,
'source',
`${nodeId}-${handleId}-${handleType}`
);
targetHandles = getHandles(
node,
handleBounds,
'target',
`${nodeId}-${handleId}-${handleType}`
);
}
if (handleBounds) {
sourceHandles = getHandles(
node,
handleBounds,
'source',
`${nodeId}-${handleId}-${handleType}`
);
targetHandles = getHandles(
node,
handleBounds,
'target',
`${nodeId}-${handleId}-${handleType}`
);
}
res.push(...sourceHandles, ...targetHandles);
}
return res;
}, []);
res.push(...sourceHandles, ...targetHandles);
}
return res;
}, []);
}
export function getHandleType(
edgeUpdaterType: HandleType | undefined,
handleDomNode: Element | null
edgeUpdaterType: HandleType | undefined,
handleDomNode: Element | null
): HandleType | null {
if (edgeUpdaterType) {
return edgeUpdaterType;
} else if (handleDomNode?.classList.contains('target')) {
return 'target';
} else if (handleDomNode?.classList.contains('source')) {
return 'source';
}
if (edgeUpdaterType) {
return edgeUpdaterType;
} else if (handleDomNode?.classList.contains('target')) {
return 'target';
} else if (handleDomNode?.classList.contains('source')) {
return 'source';
}
return null;
return null;
}
export function resetRecentHandle(handleDomNode: Element): void {
handleDomNode?.classList.remove('valid', 'connecting');
handleDomNode?.classList.remove('valid', 'connecting');
}
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
let connectionStatus = null;
let connectionStatus = null;
if (isHandleValid) {
connectionStatus = 'valid';
} else if (isInsideConnectionRadius && !isHandleValid) {
connectionStatus = 'invalid';
}
if (isHandleValid) {
connectionStatus = 'valid';
} else if (isInsideConnectionRadius && !isHandleValid) {
connectionStatus = 'invalid';
}
return connectionStatus as ConnectionStatus;
return connectionStatus as ConnectionStatus;
}

View File

@@ -1,6 +1,6 @@
import { KeyDefinition } from '$lib/types';
export type KeyHandlerProps = {
selectionKey?: KeyDefinition;
deleteKey?: KeyDefinition;
selectionKey?: KeyDefinition;
deleteKey?: KeyDefinition;
};

View File

@@ -2,19 +2,19 @@
import { shortcut } from '@svelte-put/shortcut';
import { useStore } from '$lib/store';
import type { KeyHandlerProps } from './KeyHandler'
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
import type { KeyHandlerProps } from './KeyHandler';
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
const { selectionKeyPressed, deleteKeyPressed } = useStore();
type $$Props = KeyHandlerProps;
export let selectionKey: $$Props['selectionKey'] = 'Shift';
export let deleteKey: $$Props['deleteKey'] = 'Backspace';
export let selectionKey: $$Props['selectionKey'] = 'Shift';
export let deleteKey: $$Props['deleteKey'] = 'Backspace';
function isKeyObject(key?: KeyDefinition): key is KeyDefinitionObject {
return typeof key === 'object';
}
}
$: selectionKeyString = typeof selectionKey === 'string' ? selectionKey : selectionKey!.key;
$: selectionKeyModifier = isKeyObject(selectionKey) ? selectionKey?.modifier : [];
@@ -24,20 +24,44 @@
</script>
<svelte:window
use:shortcut={{
trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressed.set(true) }],
use:shortcut={{
trigger: [
{
key: selectionKeyString,
modifier: selectionKeyModifier,
callback: () => selectionKeyPressed.set(true)
}
],
type: 'keydown'
}}
use:shortcut={{
trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressed.set(false) }],
use:shortcut={{
trigger: [
{
key: selectionKeyString,
modifier: selectionKeyModifier,
callback: () => selectionKeyPressed.set(false)
}
],
type: 'keyup'
}}
use:shortcut={{
trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressed.set(true) }],
use:shortcut={{
trigger: [
{
key: deleteKeyString,
modifier: deleteKeyModifier,
callback: () => deleteKeyPressed.set(true)
}
],
type: 'keydown'
}}
use:shortcut={{
trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressed.set(false) }],
use:shortcut={{
trigger: [
{
key: deleteKeyString,
modifier: deleteKeyModifier,
callback: () => deleteKeyPressed.set(false)
}
],
type: 'keyup'
}}
/>

View File

@@ -1,15 +1,15 @@
<script lang="ts">
import { getRectOfNodes } from '@reactflow/utils';
import { getRectOfNodes } from '@reactflow/utils';
import { useStore } from '$lib/store';
import Selection from '$lib/components/Selection/index.svelte';
import drag from '$lib/actions/drag'
import drag from '$lib/actions/drag';
const { selectionRectMode, nodes, nodeOrigin, transform, updateNodePositions } = useStore();
$: selectedNodes = $nodes.filter(n => n.selected);
$: selectedNodes = $nodes.filter((n) => n.selected);
$: rect = getRectOfNodes(selectedNodes, $nodeOrigin);
</script>
</script>
{#if selectedNodes}
<div
@@ -34,4 +34,4 @@
z-index: 7;
pointer-events: all;
}
</style>
</style>

View File

@@ -4,7 +4,7 @@
export let width: number | null = 0;
export let height: number | null = 0;
export let isVisible: boolean = true;
</script>
</script>
{#if isVisible}
<div

View File

@@ -3,7 +3,7 @@
import Selection from '$lib/components/Selection/index.svelte';
const { selectionRect, selectionRectMode } = useStore();
</script>
</script>
<Selection
isVisible={!!($selectionRect && $selectionRectMode === 'user')}

View File

@@ -11,11 +11,7 @@
export let interactionWidth: $$Props['interactionWidth'] = 20;
</script>
<path
d={path}
fill="none"
class="react-flow__edge-path"
/>
<path d={path} fill="none" class="react-flow__edge-path" />
{#if interactionWidth}
<path
@@ -33,7 +29,7 @@
class="react-flow__edge-label"
style:transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
>
{label}
{label}
</div>
</EdgeLabelRenderer>
{/if}
@@ -43,4 +39,4 @@
position: absolute;
background: white;
}
</style>
</style>

View File

@@ -2,7 +2,7 @@
import { getBezierPath } from '@reactflow/edge-utils';
import type { EdgeProps } from '$lib/types';
import BaseEdge from './BaseEdge.svelte';
import BaseEdge from './BaseEdge.svelte';
type $$Props = EdgeProps;
@@ -12,13 +12,8 @@
targetX: $$props.targetX,
targetY: $$props.targetY,
sourcePosition: $$props.sourcePosition,
targetPosition: $$props.targetPosition,
targetPosition: $$props.targetPosition
});
</script>
<BaseEdge
{path}
{labelX}
{labelY}
{...$$props}
/>
<BaseEdge {path} {labelX} {labelY} {...$$props} />

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import type { SvelteComponentTyped } from 'svelte';
import type { SvelteComponentTyped } from 'svelte';
import { Position } from '@reactflow/system';
import { useStore } from '$lib/store';
import { useStore } from '$lib/store';
import BezierEdge from '$lib/components/edges/StraightEdge.svelte';
import type { EdgeProps, WrapEdgeProps } from '$lib/types';
import type { EdgeProps, WrapEdgeProps } from '$lib/types';
type $$Props = WrapEdgeProps;
@@ -17,7 +17,7 @@
export let targetX: $$Props['targetX'] = 0;
export let targetY: $$Props['targetY'] = 0;
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
export let targetPosition: $$Props['targetPosition'] = Position.Top;
export let targetPosition: $$Props['targetPosition'] = Position.Top;
export let animated: $$Props['animated'] = false;
export let selected: $$Props['selected'] = false;
export let label: $$Props['label'] = undefined;
@@ -26,11 +26,7 @@
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
</script>
<g
class="react-flow__edge"
class:animated
data-id={id}
>
<g class="react-flow__edge" class:animated data-id={id}>
<svelte:component
this={edgeComponent}
{id}

View File

@@ -6,19 +6,14 @@
type $$Props = EdgeProps;
$: [path, labelX, labelY] = getSmoothStepPath({
$: [path, labelX, labelY] = getSmoothStepPath({
sourceX: $$props.sourceX,
sourceY: $$props.sourceY,
targetX: $$props.targetX,
targetY: $$props.targetY,
sourcePosition: $$props.sourcePosition,
targetPosition: $$props.targetPosition,
targetPosition: $$props.targetPosition
});
</script>
<BaseEdge
{path}
{labelX}
{labelY}
{...$$props}
/>
<BaseEdge {path} {labelX} {labelY} {...$$props} />

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import { getStraightPath } from '@reactflow/edge-utils';
import type { EdgeProps } from '$lib/types';
import BaseEdge from './BaseEdge.svelte';
import type { EdgeProps } from '$lib/types';
import BaseEdge from './BaseEdge.svelte';
type $$Props = EdgeProps;
@@ -10,13 +10,8 @@
sourceX: $$props.sourceX,
sourceY: $$props.sourceY,
targetX: $$props.targetX,
targetY: $$props.targetY,
targetY: $$props.targetY
});
</script>
<BaseEdge
{path}
{labelX}
{labelY}
{...$$props}
/>
<BaseEdge {path} {labelX} {labelY} {...$$props} />

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { Position } from '@reactflow/system';
import Handle from '$lib/components/Handle/index.svelte';
import type { NodeProps } from '$lib/types';
interface $$Props extends NodeProps<{ label: string }>{}
import Handle from '$lib/components/Handle/index.svelte';
import type { NodeProps } from '$lib/types';
interface $$Props extends NodeProps<{ label: string }> {}
export let id: $$Props['id'];
export let data: $$Props['data'] = { label: 'Node' };
@@ -18,4 +18,4 @@
<Handle type="target" position={targetPosition} />
{data?.label}
<Handle type="source" position={sourcePosition} />
<Handle type="source" position={sourcePosition} />

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { Position } from '@reactflow/system';
import Handle from '$lib/components/Handle/index.svelte';
export let data: { label: string } = { label: 'Node' };
@@ -10,4 +10,3 @@
{data?.label}
<Handle type="source" position={sourcePosition} />

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import { onMount, setContext, SvelteComponentTyped } from 'svelte';
import { onMount, setContext, SvelteComponentTyped } from 'svelte';
import cc from 'classcat';
import { type XYPosition, Position } from '@reactflow/system';
import drag from '$lib/actions/drag'
import { useStore } from '$lib/store';
import DefaultNode from './DefaultNode.svelte';
import type { WrapNodeProps, NodeProps } from '$lib/types';
import drag from '$lib/actions/drag';
import { useStore } from '$lib/store';
import DefaultNode from './DefaultNode.svelte';
import type { WrapNodeProps, NodeProps } from '$lib/types';
interface $$Props extends WrapNodeProps {}
@@ -29,7 +29,8 @@
let nodeRef: HTMLDivElement;
const { nodes, transform, nodeTypes, updateNodePositions, addSelectedNodes } = useStore();
const nodeComponent: typeof SvelteComponentTyped<Partial<NodeProps>> = $nodeTypes[type] || DefaultNode;
const nodeComponent: typeof SvelteComponentTyped<Partial<NodeProps>> =
$nodeTypes[type] || DefaultNode;
const isSelectable = true;
const selectNodesOnDrag = false;
const isDraggable = true;
@@ -41,7 +42,7 @@
return () => {
resizeObserver?.unobserve(nodeRef);
}
};
});
function onSelectNodeHandler(event: MouseEvent) {
@@ -54,15 +55,15 @@
// const node = store.getState().nodeInternals.get(id)!;
// onClick(event, { ...node });
// }
};
}
</script>
<div
use:drag={{ nodeId: id, nodes, transform, updateNodePositions }}
class={cc(['react-flow__node', `react-flow__node-${type}`, className])}
class:initializing={!width && !height}
class:dragging={dragging}
class:selected={selected}
class:dragging
class:selected
bind:this={nodeRef}
on:click={onSelectNodeHandler}
style:transform={`translate(${positionAbsolute.x}px, ${positionAbsolute.y}px)`}
@@ -92,7 +93,7 @@
border-color: #1a192b;
background-color: white;
position: absolute;
cursor: grab;
cursor: grab;
pointer-events: all;
user-select: none;
}
@@ -109,4 +110,4 @@
.initializing {
visibility: hidden;
}
</style>
</style>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { Position } from '@reactflow/system';
import Handle from '$lib/components/Handle/index.svelte';
export let data: { label: string } = { label: 'Node' };
@@ -10,4 +10,3 @@
{data?.label}
<Handle type="target" position={targetPosition} />

View File

@@ -1,15 +1,11 @@
<script lang="ts">
<script lang="ts">
import EdgeWrapper from '$lib/components/edges/EdgeWrapper.svelte';
import { useStore } from '$lib/store';
const { edgesLayouted, width, height } = useStore();
</script>
<svg
width={$width}
height={$height}
class="react-flow__edges"
<svg width={$width} height={$height} class="react-flow__edges">
{#each $edgesLayouted as edge (edge.id)}
<EdgeWrapper {...edge} />
{/each}
@@ -25,4 +21,4 @@
pointer-events: none;
overflow: visible;
}
}
</style>

View File

@@ -1,105 +1,105 @@
import {
type NodeHandleBounds,
type Rect,
type Node,
type HandleElement,
type XYPosition,
Position,
internalsSymbol
type NodeHandleBounds,
type Rect,
type Node,
type HandleElement,
type XYPosition,
Position,
internalsSymbol
} from '@reactflow/system';
export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
const isValid =
handleBounds &&
node?.width &&
node?.height &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
const isValid =
handleBounds &&
node?.width &&
node?.height &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
return [
{
x: node?.positionAbsolute?.x || 0,
y: node?.positionAbsolute?.y || 0,
width: node?.width || 0,
height: node?.height || 0
},
handleBounds,
!!isValid
];
return [
{
x: node?.positionAbsolute?.x || 0,
y: node?.positionAbsolute?.y || 0,
width: node?.width || 0,
height: node?.height || 0
},
handleBounds,
!!isValid
];
}
export function getHandlePosition(
position: Position,
nodeRect: Rect,
handle: HandleElement | null = null
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;
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) {
case Position.Top:
return {
x: x + width / 2,
y
};
case Position.Right:
return {
x: x + width,
y: y + height / 2
};
case Position.Bottom:
return {
x: x + width / 2,
y: y + height
};
case Position.Left:
return {
x,
y: y + height / 2
};
}
switch (position) {
case Position.Top:
return {
x: x + width / 2,
y
};
case Position.Right:
return {
x: x + width,
y: y + height / 2
};
case Position.Bottom:
return {
x: x + width / 2,
y: y + height
};
case Position.Left:
return {
x,
y: y + height / 2
};
}
}
export function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null {
if (!bounds) {
return null;
}
if (!bounds) {
return null;
}
if (handleId) {
return bounds.find((d) => d.id === handleId)!;
} else if (bounds.length === 1) {
return bounds[0];
}
if (handleId) {
return bounds.find((d) => d.id === handleId)!;
} else if (bounds.length === 1) {
return bounds[0];
}
return null;
return null;
}
export type EdgePosition = {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
};
export function getEdgePositions(
sourceNodeRect: Rect,
sourceHandle: HandleElement,
sourcePosition: Position,
targetNodeRect: Rect,
targetHandle: HandleElement,
targetPosition: Position
sourceNodeRect: Rect,
sourceHandle: HandleElement,
sourcePosition: Position,
targetNodeRect: Rect,
targetHandle: HandleElement,
targetPosition: Position
): EdgePosition {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
return {
sourceX: sourceHandlePos.x,
sourceY: sourceHandlePos.y,
targetX: targetHandlePos.x,
targetY: targetHandlePos.y
};
return {
sourceX: sourceHandlePos.x,
sourceY: sourceHandlePos.y,
targetX: targetHandlePos.x,
targetY: targetHandlePos.y
};
}

View File

@@ -5,15 +5,18 @@
import { useStore } from '$lib/store';
const { nodes, updateNodeDimensions } = useStore();
const resizeObserver: ResizeObserver | null = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = entries.map((entry: ResizeObserverEntry) => ({
id: entry.target.getAttribute('data-id') as string,
nodeElement: entry.target as HTMLDivElement,
forceUpdate: true,
}))
const resizeObserver: ResizeObserver | null =
typeof ResizeObserver === 'undefined'
? null
: new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = entries.map((entry: ResizeObserverEntry) => ({
id: entry.target.getAttribute('data-id') as string,
nodeElement: entry.target as HTMLDivElement,
forceUpdate: true
}));
updateNodeDimensions(updates);
});
updateNodeDimensions(updates);
});
onDestroy(() => {
resizeObserver?.disconnect();
@@ -33,4 +36,4 @@
pointer-events: none;
transform-origin: 0 0;
}
</style>
</style>

View File

@@ -10,7 +10,7 @@
handler?.(event);
};
};
}
export function toggleSelected<Item extends Node | Edge>(ids: string[]) {
return (item: Item) => {
@@ -19,39 +19,48 @@
if (item.selected !== isSelected) {
return {
...item,
selected: isSelected,
selected: isSelected
};
}
return item;
}
};
}
</script>
<script lang="ts">
import { useStore } from '$lib/store';
import { SelectionMode, type Node, type Edge } from '@reactflow/system';
import { getConnectedEdges, getEventPosition, getNodesInside } from '@reactflow/utils';
import { SelectionMode, type Node, type Edge } from '@reactflow/system';
import { getConnectedEdges, getEventPosition, getNodesInside } from '@reactflow/utils';
const { nodes, edges, transform, nodeOrigin, dragging, selectionRect, selectionRectMode, selectionKeyPressed, resetSelectedElements } = useStore();
const {
nodes,
edges,
transform,
nodeOrigin,
dragging,
selectionRect,
selectionRectMode,
selectionKeyPressed,
resetSelectedElements
} = useStore();
// @todo take from props
const elementsSelectable = true;
const selectionMode = SelectionMode.Partial
const selectionMode = SelectionMode.Partial;
let container: HTMLDivElement;
let containerBounds: DOMRect | null = null
let container: HTMLDivElement;
let containerBounds: DOMRect | null = null;
let selectedNodes: Node[] = [];
$: isSelecting = $selectionKeyPressed;
$: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectMode === 'user');
function onClick (event: MouseEvent) {
function onClick(event: MouseEvent) {
// onPaneClick?.(event);
resetSelectedElements();
selectionRectMode.set(null)
selectionRectMode.set(null);
}
function onMouseDown(event: MouseEvent) {
@@ -80,11 +89,8 @@
y
});
// onSelectionStart?.(event);
};
}
function onMouseMove(event: MouseEvent) {
if (!isSelecting || !containerBounds || !$selectionRect) {
@@ -98,11 +104,11 @@
x: mousePos.x < startX ? mousePos.x : startX,
y: mousePos.y < startY ? mousePos.y : startY,
width: Math.abs(mousePos.x - startX),
height: Math.abs(mousePos.y - startY),
height: Math.abs(mousePos.y - startY)
};
selectedNodes = getNodesInside(
new Map($nodes.map(node => [node.id, node])),
new Map($nodes.map((node) => [node.id, node])),
nextUserSelectRect,
$transform,
selectionMode === SelectionMode.Partial,
@@ -112,8 +118,8 @@
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
const selectedNodeIds = selectedNodes.map((n) => n.id);
nodes.update(nodes => nodes.map(toggleSelected(selectedNodeIds)));
edges.update(edges => edges.map(toggleSelected(selectedEdgeIds)));
nodes.update((nodes) => nodes.map(toggleSelected(selectedNodeIds)));
edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds)));
selectionRectMode.set('user');
selectionRect.set(nextUserSelectRect);
@@ -135,7 +141,6 @@
selectionRectMode.set('nodes');
}
// onSelectionEnd?.(event);
}
@@ -156,7 +161,7 @@
class:selection={isSelecting}
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
on:mousedown={hasActiveSelection ? onMouseDown : undefined}
on:mousemove={hasActiveSelection? onMouseMove : undefined}
on:mousemove={hasActiveSelection ? onMouseMove : undefined}
on:mouseup={hasActiveSelection ? onMouseUp : undefined}
on:mouseleave={hasActiveSelection ? onMouseLeave : undefined}
>
@@ -181,4 +186,4 @@
.dragging {
cursor: grabbing;
}
</style>
</style>

View File

@@ -1,7 +1,7 @@
import type { PanelPosition } from '@reactflow/system';
export type PanelProps = {
position?: PanelPosition;
style?: string;
class?: string;
position?: PanelPosition;
style?: string;
class?: string;
};

View File

@@ -7,15 +7,12 @@
export let position: $$Props['position'] = 'top-right';
export let style: $$Props['style'] = '';
let className: $$Props['class'] = undefined;
export { className as class }
export { className as class };
$: positionClasses = `${position}`.split('-');
</script>
<div
class={cc(['react-flow__panel', className, ...positionClasses])}
style={style}
>
<div class={cc(['react-flow__panel', className, ...positionClasses])} {style}>
<slot />
</div>
@@ -46,4 +43,4 @@
left: 50%;
transform: translateX(-50%);
}
</style>
</style>

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import { setContext, onMount } from 'svelte'
import { setContext, onMount } from 'svelte';
import cc from 'classcat';
import { key, createStore } from '$lib/store';
import { key, createStore } from '$lib/store';
import Zoom from '$lib/container/Zoom/index.svelte';
import Pane from '$lib/container/Pane/index.svelte';
import Viewport from '$lib/container/Viewport.svelte';
@@ -12,7 +12,7 @@
import NodeSelection from '$lib/components/NodeSelection/index.svelte';
import { KeyHandler } from '$lib/components/KeyHandler';
import { ConnectionLine } from '$lib/components/ConnectionLine';
import type { SvelteFlowProps } from '$lib/types';
import type { SvelteFlowProps } from '$lib/types';
type $$Props = SvelteFlowProps;
@@ -51,7 +51,6 @@
});
});
// $: {
// const updatableProps = {
// defaultEdgeOptions,
@@ -69,7 +68,7 @@
// ...state,
// [key]: valuesToUpdate[key],
// }));
// });
// }
@@ -86,7 +85,7 @@
{...$$restProps}
class={cc(['react-flow', className])}
data-testid="rf__wrapper"
id={id}
{id}
bind:this={domNode}
>
<KeyHandler {selectionKey} {deleteKey} />
@@ -127,4 +126,4 @@
pointer-events: none;
user-select: none;
}
</style>
</style>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { useStore } from '$lib/store';
import { useStore } from '$lib/store';
const { transform } = useStore();
</script>
<div
class="react-flow__viewport"
class="react-flow__viewport"
style="transform: translate({$transform[0]}px, {$transform[1]}px) scale({$transform[2]})"
>
<slot />
@@ -22,4 +22,4 @@
z-index: 2;
pointer-events: none;
}
</style>
</style>

View File

@@ -20,4 +20,4 @@
left: 0;
z-index: 4;
}
</style>
</style>

View File

@@ -4,7 +4,7 @@
import DotPattern from './DotPattern.svelte';
import LinePattern from './LinePattern.svelte';
import { useStore } from '$lib/store';
import { BackgroundVariant } from './types';
import { BackgroundVariant } from './types';
export let variant: BackgroundVariant = BackgroundVariant.Dots;
export let gap = 20;
@@ -18,13 +18,13 @@
const defaultColor = {
[BackgroundVariant.Dots]: '#91919a',
[BackgroundVariant.Lines]: '#eee',
[BackgroundVariant.Cross]: '#e2e2e2',
[BackgroundVariant.Cross]: '#e2e2e2'
};
const defaultSize = {
[BackgroundVariant.Dots]: 1,
[BackgroundVariant.Lines]: 1,
[BackgroundVariant.Cross]: 6,
[BackgroundVariant.Cross]: 6
};
const { transform, id } = useStore();
@@ -43,7 +43,7 @@
: [patternDimensions[0] / 2, patternDimensions[1] / 2];
</script>
<svg class={cc(['react-flow__background', className])} >
<svg class={cc(['react-flow__background', className])}>
<pattern
id={patternId}
x={$transform[0] % scaledGap[0]}
@@ -56,7 +56,7 @@
{#if isDots}
<DotPattern color={patternColor} radius={scaledSize / 2} />
{:else}
<LinePattern dimensions={patternDimensions } color={patternColor} lineWidth={lineWidth} />
<LinePattern dimensions={patternDimensions} color={patternColor} {lineWidth} />
{/if}
</pattern>
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
@@ -72,4 +72,4 @@
pointer-events: none;
z-index: -1;
}
</style>
</style>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
export let radius = 5;
export let color = "#000";
export let color = '#000';
</script>
<circle cx={radius} cy={radius} r={radius} fill={color} />
<circle cx={radius} cy={radius} r={radius} fill={color} />

View File

@@ -1,13 +1,11 @@
<script lang="ts">
export let lineWidth = 1;
export let color = "#777";
export let color = '#777';
export let dimensions: [number, number];
</script>
<path
stroke={color}
stroke-width={lineWidth}
d={`M${dimensions[0] / 2} 0 V${dimensions[1]} M0 ${dimensions[1] / 2} H${
dimensions[0]
}`}
/>
d={`M${dimensions[0] / 2} 0 V${dimensions[1]} M0 ${dimensions[1] / 2} H${dimensions[0]}`}
/>

View File

@@ -1,5 +1,5 @@
export enum BackgroundVariant {
Lines = 'lines',
Dots = 'dots',
Cross = 'cross'
Lines = 'lines',
Dots = 'dots',
Cross = 'cross'
}

View File

@@ -3,10 +3,15 @@
let className: string;
export { className as class }
export { className as class };
</script>
<button type="button" on:click class={cc(['react-flow__controls-button', className])} {...$$restProps} >
<button
type="button"
on:click
class={cc(['react-flow__controls-button', className])}
{...$$restProps}
>
<slot class="button-svg" />
</button>
@@ -31,4 +36,4 @@
max-width: 12px;
max-height: 12px;
}
</style>
</style>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { PanelPosition } from '@reactflow/system';
import type { PanelPosition } from '@reactflow/system';
import Panel from '$lib/container/Panel/index.svelte';
import { useStore } from '$lib/store';
@@ -37,13 +37,11 @@
// nodesConnectable: !isInteractive,
// elementsSelectable: !isInteractive,
// });
// onInteractiveChange?.(!isInteractive);
};
</script>
<Panel class="react-flow__controls" position={position}>
<Panel class="react-flow__controls" {position}>
{#if showZoom}
<ControlButton
on:click={onZoomInHandler}
@@ -85,4 +83,4 @@
{#if isInteractive}<UnlockIcon />{:else}<LockIcon />{/if}
</svelte:component>
{/if}
</Panel>
</Panel>

View File

@@ -1,3 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
<path d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z" />
</svg>
<path
d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 469 B

View File

@@ -1,3 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z" />
</svg>
<path
d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 529 B

After

Width:  |  Height:  |  Size: 536 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 5">
<path d="M0 0h32v4.2H0z" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 96 B

After

Width:  |  Height:  |  Size: 97 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path d="M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 153 B

View File

@@ -1,3 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z" />
</svg>
<path
d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 478 B

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import cc from 'classcat';
import type { PanelPosition, Rect, Node } from '@reactflow/system';
import { getBoundsOfRects, getNodePositionWithOrigin, getRectOfNodes } from '@reactflow/utils';
import type { PanelPosition, Rect, Node } from '@reactflow/system';
import { getBoundsOfRects, getNodePositionWithOrigin, getRectOfNodes } from '@reactflow/utils';
import { useStore } from '$lib/store';
import Panel from '$lib/container/Panel/index.svelte';
import Panel from '$lib/container/Panel/index.svelte';
import MinimapNode from './MinimapNode.svelte';
let position: PanelPosition = 'bottom-right';
@@ -27,22 +27,25 @@
const { nodes, transform, width, height, nodeOrigin, id } = useStore();
type GetMiniMapNodeAttribute = (node: Node) => string;
const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func);
const getAttrFunction = (func: any): GetMiniMapNodeAttribute =>
func instanceof Function ? func : () => func;
const nodeColorFunc = getAttrFunction(nodeColor);
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
const nodeClassNameFunc = getAttrFunction(nodeClassName);
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
const shapeRendering =
typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
const labelledBy = `react-flow__minimap-desc-${$id}`;
$: viewBB = {
x: -$transform[0] / $transform[2],
y: -$transform[1] / $transform[2],
width: $width / $transform[2],
height: $height / $transform[2],
height: $height / $transform[2]
} as Rect;
$: boundingRect = $nodes.length > 0 ? getBoundsOfRects(getRectOfNodes($nodes, $nodeOrigin), viewBB) : viewBB
$: boundingRect =
$nodes.length > 0 ? getBoundsOfRects(getRectOfNodes($nodes, $nodeOrigin), viewBB) : viewBB;
$: elementWidth = (style?.width as number) ?? defaultWidth;
$: elementHeight = (style?.height as number) ?? defaultHeight;
$: elementHeight = (style?.height as number) ?? defaultHeight;
$: scaledWidth = boundingRect.width / elementWidth;
$: scaledHeight = boundingRect.height / elementHeight;
$: viewScale = Math.max(scaledWidth, scaledHeight);
@@ -55,7 +58,11 @@
$: viewboxHeight = viewHeight + offset * 2;
</script>
<Panel position={position} class={cc(['react-flow__minimap', className])} style={`background-color: ${bgColor};`}>
<Panel
{position}
class={cc(['react-flow__minimap', className])}
style={`background-color: ${bgColor};`}
>
<svg
width={elementWidth}
height={elementHeight}
@@ -65,7 +72,7 @@
>
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
{#each $nodes as node(node.id)}
{#each $nodes as node (node.id)}
{#if node.width && node.height}
{@const pos = getNodePositionWithOrigin(node, $nodeOrigin).positionAbsolute}
<MinimapNode
@@ -77,7 +84,7 @@
borderRadius={nodeBorderRadius}
strokeColor={nodeStrokeColorFunc(node)}
strokeWidth={nodeStrokeWidth}
shapeRendering={shapeRendering}
{shapeRendering}
class={nodeClassNameFunc(node)}
style={{}}
/>
@@ -85,7 +92,9 @@
{/each}
<path
class="react-flow__minimap-mask"
d={`M${x - offset},${y - offset}h${viewboxWidth + offset * 2}v${viewboxHeight + offset * 2}h${-viewboxWidth - offset * 2}z
d={`M${x - offset},${y - offset}h${viewboxWidth + offset * 2}v${viewboxHeight + offset * 2}h${
-viewboxWidth - offset * 2
}z
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`}
fill={maskColor}
fill-rule="evenodd"
@@ -94,4 +103,4 @@
pointer-events="none"
/>
</svg>
</Panel>
</Panel>

View File

@@ -20,14 +20,14 @@
<rect
class={cc(['react-flow__minimap-node', className])}
x={x}
y={y}
{x}
{y}
rx={borderRadius}
ry={borderRadius}
width={width}
height={height}
fill={fill}
{width}
{height}
{fill}
stroke={strokeColor}
stroke-width={strokeWidth}
shape-rendering={shapeRendering}
/>
/>

View File

@@ -5,72 +5,72 @@ import type { SvelteFlowStoreState } from './types';
import { derived } from 'svelte/store';
const oppositePosition = {
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
};
export function getConnectionPath(store: SvelteFlowStoreState) {
return derived(
[
store.connection,
store.connectionLineType,
store.connectionMode,
store.nodes,
store.transform
],
([$connection, $connectionLineType, $connectionMode, $nodes, $transform]) => {
if (!$connection.nodeId) {
return null;
}
return derived(
[
store.connection,
store.connectionLineType,
store.connectionMode,
store.nodes,
store.transform
],
([$connection, $connectionLineType, $connectionMode, $nodes, $transform]) => {
if (!$connection.nodeId) {
return null;
}
const fromNode = $nodes.find((n) => n.id === $connection.nodeId);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const handleBoundsStrict = fromHandleBounds?.[$connection.handleType || 'source'] || [];
const handleBoundsLoose = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[$connection.handleType === 'source' ? 'target' : 'source']!;
const handleBounds =
$connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = $connection.handleId
? handleBounds.find((d) => d.id === $connection.handleId)
: handleBounds[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
const fromNode = $nodes.find((n) => n.id === $connection.nodeId);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const handleBoundsStrict = fromHandleBounds?.[$connection.handleType || 'source'] || [];
const handleBoundsLoose = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[$connection.handleType === 'source' ? 'target' : 'source']!;
const handleBounds =
$connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = $connection.handleId
? handleBounds.find((d) => d.id === $connection.handleId)
: handleBounds[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: (($connection.position?.x ?? 0) - $transform[0]) / $transform[2],
targetY: (($connection.position?.y ?? 0) - $transform[1]) / $transform[2],
targetPosition: toPosition
};
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: (($connection.position?.x ?? 0) - $transform[0]) / $transform[2],
targetY: (($connection.position?.y ?? 0) - $transform[1]) / $transform[2],
targetPosition: toPosition
};
let path = '';
let path = '';
if ($connectionLineType === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if ($connectionLineType === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if ($connectionLineType === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
if ($connectionLineType === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if ($connectionLineType === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if ($connectionLineType === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
return path;
}
);
return path;
}
);
}

View File

@@ -6,56 +6,56 @@ import type { WrapEdgeProps } from '$lib/types';
import type { SvelteFlowStoreState } from './types';
export function getEdgesLayouted(store: SvelteFlowStoreState) {
return derived([store.edges, store.nodes], ([$edges, $nodes]) => {
return $edges
.map((edge) => {
const sourceNode = $nodes.find((node) => node.id === edge.source);
const targetNode = $nodes.find((node) => node.id === edge.target);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
return derived([store.edges, store.nodes], ([$edges, $nodes]) => {
return $edges
.map((edge) => {
const sourceNode = $nodes.find((node) => node.id === edge.source);
const targetNode = $nodes.find((node) => node.id === edge.target);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
if (!sourceIsValid || !targetIsValid) {
return null;
}
if (!sourceIsValid || !targetIsValid) {
return null;
}
const edgeType = edge.type || 'default';
const edgeType = edge.type || 'default';
const targetNodeHandles = targetHandleBounds!.target;
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const targetNodeHandles = targetHandleBounds!.target;
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
if (!sourceHandle || !targetHandle) {
return null;
}
if (!sourceHandle || !targetHandle) {
return null;
}
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNodeRect,
sourceHandle,
sourcePosition,
targetNodeRect,
targetHandle,
targetPosition
);
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNodeRect,
sourceHandle,
sourcePosition,
targetNodeRect,
targetHandle,
targetPosition
);
// we nee to do this to match the types
const sourceHandleId = edge.sourceHandle;
const targetHandleId = edge.targetHandle;
// we nee to do this to match the types
const sourceHandleId = edge.sourceHandle;
const targetHandleId = edge.targetHandle;
return {
...edge,
type: edgeType,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
sourceHandleId,
targetHandleId
};
})
.filter((e) => e !== null) as WrapEdgeProps[];
});
return {
...edge,
type: edgeType,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
sourceHandleId,
targetHandleId
};
})
.filter((e) => e !== null) as WrapEdgeProps[];
});
}

View File

@@ -1,23 +1,23 @@
import { getContext } from 'svelte';
import { get } from 'svelte/store';
import {
type Transform,
type NodeDragItem,
type NodeDimensionUpdate,
internalsSymbol,
type NodeOrigin,
type ViewportHelperFunctionOptions,
type Node as RFNode,
type Connection,
type XYPosition,
type CoordinateExtent
type Transform,
type NodeDragItem,
type NodeDimensionUpdate,
internalsSymbol,
type NodeOrigin,
type ViewportHelperFunctionOptions,
type Node as RFNode,
type Connection,
type XYPosition,
type CoordinateExtent
} from '@reactflow/system';
import {
fitView as fitViewUtil,
getConnectedEdges,
getD3Transition,
getDimensions,
addEdge as addEdgeUtil
fitView as fitViewUtil,
getConnectedEdges,
getD3Transition,
getDimensions,
addEdge as addEdgeUtil
} from '@reactflow/utils';
import { zoomIdentity } from 'd3-zoom';
@@ -32,307 +32,307 @@ import type { SvelteFlowStore } from './types';
export const key = Symbol();
type CreateStoreProps = {
fitView?: boolean;
nodeOrigin?: NodeOrigin;
transform?: Transform;
nodeTypes?: NodeTypes;
edgeTypes?: EdgeTypes;
id?: string;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
transform?: Transform;
nodeTypes?: NodeTypes;
edgeTypes?: EdgeTypes;
id?: string;
};
export function createStore({
transform = [0, 0, 1],
nodeOrigin = [0, 0],
fitView: fitViewOnInit = false,
nodeTypes = {},
edgeTypes = {},
id = '1'
transform = [0, 0, 1],
nodeOrigin = [0, 0],
fitView: fitViewOnInit = false,
nodeTypes = {},
edgeTypes = {},
id = '1'
}: CreateStoreProps): SvelteFlowStore {
const store = {
...initialStoreState
};
const store = {
...initialStoreState
};
let fitViewOnInitDone = false;
let fitViewOnInitDone = false;
function setEdges(edges: Edge[]) {
store.edges.set(edges);
}
function setEdges(edges: Edge[]) {
store.edges.set(edges);
}
function addEdge(edgeParams: Edge | Connection) {
const edges = get(store.edges);
store.edges.set(addEdgeUtil(edgeParams, edges));
}
function addEdge(edgeParams: Edge | Connection) {
const edges = get(store.edges);
store.edges.set(addEdgeUtil(edgeParams, edges));
}
function setNodes(nodes: Node[]) {
store.nodes.update((currentNodes) => {
const nextNodes = nodes.map((n) => {
const currentNode = currentNodes.find((cn) => cn.id === n.id) || {};
function setNodes(nodes: Node[]) {
store.nodes.update((currentNodes) => {
const nextNodes = nodes.map((n) => {
const currentNode = currentNodes.find((cn) => cn.id === n.id) || {};
return {
...currentNode,
...n,
positionAbsolute: n.position
};
});
return {
...currentNode,
...n,
positionAbsolute: n.position
};
});
return nextNodes;
});
}
return nextNodes;
});
}
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
store.nodes.update((nds) => {
return nds.map((n) => {
const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id);
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
store.nodes.update((nds) => {
return nds.map((n) => {
const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id);
if (nodeDragItem) {
return {
...n,
dragging,
positionAbsolute: nodeDragItem.positionAbsolute,
position: nodeDragItem.position
};
}
if (nodeDragItem) {
return {
...n,
dragging,
positionAbsolute: nodeDragItem.positionAbsolute,
position: nodeDragItem.position
};
}
return n;
});
});
}
return n;
});
});
}
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
const viewportNode = document?.querySelector('.react-flow__viewport');
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
const viewportNode = document?.querySelector('.react-flow__viewport');
if (!viewportNode) {
return;
}
if (!viewportNode) {
return;
}
const style = window.getComputedStyle(viewportNode);
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const style = window.getComputedStyle(viewportNode);
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const nextNodes = get(store.nodes).map((node) => {
const update = updates.find((u) => u.id === node.id);
const nextNodes = get(store.nodes).map((node) => {
const update = updates.find((u) => u.id === node.id);
if (update) {
const dimensions = getDimensions(update.nodeElement);
if (update) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate = !!(
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width ||
node.height !== dimensions.height ||
update.forceUpdate)
);
const doUpdate = !!(
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width ||
node.height !== dimensions.height ||
update.forceUpdate)
);
if (doUpdate) {
node[internalsSymbol] = {
...node[internalsSymbol],
handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom),
target: getHandleBounds('.target', update.nodeElement, zoom)
}
};
node.width = dimensions.width;
node.height = dimensions.height;
}
}
if (doUpdate) {
node[internalsSymbol] = {
...node[internalsSymbol],
handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom),
target: getHandleBounds('.target', update.nodeElement, zoom)
}
};
node.width = dimensions.width;
node.height = dimensions.height;
}
}
return node;
});
return node;
});
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
fitViewOnInitDone =
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
fitViewOnInitDone =
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
store.nodes.set(nextNodes);
}
store.nodes.set(nextNodes);
}
function zoomIn(options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
function zoomIn(options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2);
}
}
if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2);
}
}
function zoomOut(options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2);
}
}
function zoomOut(options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2);
}
}
function fitView() {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
function fitView() {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (!d3Zoom || !d3Selection) {
return false;
}
if (!d3Zoom || !d3Selection) {
return false;
}
return fitViewUtil(
{
nodes: get(store.nodes) as RFNode[],
width: get(store.width),
height: get(store.height),
minZoom: 0.2,
maxZoom: 2,
d3Selection,
d3Zoom,
nodeOrigin: get(store.nodeOrigin)
},
{}
);
}
return fitViewUtil(
{
nodes: get(store.nodes) as RFNode[],
width: get(store.width),
height: get(store.height),
minZoom: 0.2,
maxZoom: 2,
d3Selection,
d3Zoom,
nodeOrigin: get(store.nodeOrigin)
},
{}
);
}
function resetSelectedItem<T extends Node | Edge>(item: T) {
if (item.selected) {
return {
...item,
selected: false
};
}
function resetSelectedItem<T extends Node | Edge>(item: T) {
if (item.selected) {
return {
...item,
selected: false
};
}
return item;
}
return item;
}
function resetSelectedElements() {
store.nodes.update((ns) => ns.map(resetSelectedItem));
store.edges.update((es) => es.map(resetSelectedItem));
}
function resetSelectedElements() {
store.nodes.update((ns) => ns.map(resetSelectedItem));
store.edges.update((es) => es.map(resetSelectedItem));
}
store.deleteKeyPressed.subscribe((deleteKeyPressed) => {
if (deleteKeyPressed) {
const nodes = get(store.nodes);
const edges = get(store.edges);
const selectedNodes = nodes.filter((node) => node.selected);
const selectedEdges = edges.filter((edge) => edge.selected);
store.deleteKeyPressed.subscribe((deleteKeyPressed) => {
if (deleteKeyPressed) {
const nodes = get(store.nodes);
const edges = get(store.edges);
const selectedNodes = nodes.filter((node) => node.selected);
const selectedEdges = edges.filter((edge) => edge.selected);
// @todo can we put this stuff into @reactflow/utils?
const nodeIds = selectedNodes.map((node) => node.id);
const edgeIds = selectedEdges.map((edge) => edge.id);
const nodesToRemove = nodes.reduce<Node[]>((res, node) => {
const parentHit =
!nodeIds.includes(node.id) &&
node.parentNode &&
res.find((n) => n.id === node.parentNode);
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
if (deletable && (nodeIds.includes(node.id) || parentHit)) {
res.push(node);
}
// @todo can we put this stuff into @reactflow/utils?
const nodeIds = selectedNodes.map((node) => node.id);
const edgeIds = selectedEdges.map((edge) => edge.id);
const nodesToRemove = nodes.reduce<Node[]>((res, node) => {
const parentHit =
!nodeIds.includes(node.id) &&
node.parentNode &&
res.find((n) => n.id === node.parentNode);
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
if (deletable && (nodeIds.includes(node.id) || parentHit)) {
res.push(node);
}
return res;
}, []);
const deletableEdges = edges.filter((e) =>
typeof e.deletable === 'boolean' ? e.deletable : true
);
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
return res;
}, []);
const deletableEdges = edges.filter((e) =>
typeof e.deletable === 'boolean' ? e.deletable : true
);
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
if (nodesToRemove || initialHitEdges) {
const connectedEdges = getConnectedEdges(nodesToRemove as RFNode[], deletableEdges);
const edgesToRemove = [...initialHitEdges, ...connectedEdges];
const edgeIdsToRemove = edgesToRemove.reduce<string[]>((res, edge) => {
if (!res.includes(edge.id)) {
res.push(edge.id);
}
return res;
}, []);
if (nodesToRemove || initialHitEdges) {
const connectedEdges = getConnectedEdges(nodesToRemove as RFNode[], deletableEdges);
const edgesToRemove = [...initialHitEdges, ...connectedEdges];
const edgeIdsToRemove = edgesToRemove.reduce<string[]>((res, edge) => {
if (!res.includes(edge.id)) {
res.push(edge.id);
}
return res;
}, []);
store.nodes.update((nds) => nds.filter((node) => !nodeIds.includes(node.id)));
store.edges.update((eds) => eds.filter((edge) => !edgeIdsToRemove.includes(edge.id)));
}
}
});
store.nodes.update((nds) => nds.filter((node) => !nodeIds.includes(node.id)));
store.edges.update((eds) => eds.filter((edge) => !edgeIdsToRemove.includes(edge.id)));
}
}
});
function addSelectedNodes(ids: string[]) {
store.selectionRect.set(null);
store.selectionRectMode.set(null);
function addSelectedNodes(ids: string[]) {
store.selectionRect.set(null);
store.selectionRectMode.set(null);
if (get(store.multiselectionKeyPressed)) {
// @todo handle multiselection key
}
if (get(store.multiselectionKeyPressed)) {
// @todo handle multiselection key
}
store.nodes.update((ns) =>
ns.map((node) => {
return {
...node,
selected: ids.includes(node.id)
};
})
);
}
store.nodes.update((ns) =>
ns.map((node) => {
return {
...node,
selected: ids.includes(node.id)
};
})
);
}
function panBy(delta: XYPosition) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const transform = get(store.transform);
const width = get(store.width);
const height = get(store.height);
function panBy(delta: XYPosition) {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const transform = get(store.transform);
const width = get(store.width);
const height = get(store.height);
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
return;
}
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
return;
}
const nextTransform = zoomIdentity
.translate(transform[0] + delta.x, transform[1] + delta.y)
.scale(transform[2]);
const nextTransform = zoomIdentity
.translate(transform[0] + delta.x, transform[1] + delta.y)
.scale(transform[2]);
const extent: CoordinateExtent = [
[0, 0],
[width, height]
];
const extent: CoordinateExtent = [
[0, 0],
[width, height]
];
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]
]);
d3Zoom.transform(d3Selection, constrainedTransform);
}
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]
]);
d3Zoom.transform(d3Selection, constrainedTransform);
}
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
const currentConnectionData = get(store.connection);
const nextConnectionData = currentConnectionData
? {
...initConnectionData,
...currentConnectionData,
...connectionUpdate
}
: {
...initConnectionData,
...connectionUpdate
};
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
const currentConnectionData = get(store.connection);
const nextConnectionData = currentConnectionData
? {
...initConnectionData,
...currentConnectionData,
...connectionUpdate
}
: {
...initConnectionData,
...connectionUpdate
};
store.connection.set(nextConnectionData);
}
store.connection.set(nextConnectionData);
}
function cancelConnection() {
updateConnection(initConnectionData);
}
function cancelConnection() {
updateConnection(initConnectionData);
}
return {
// state
...store,
return {
// state
...store,
// derived state
edgesLayouted: getEdgesLayouted(store),
connectionPath: getConnectionPath(store),
// derived state
edgesLayouted: getEdgesLayouted(store),
connectionPath: getConnectionPath(store),
// actions
setNodes,
setEdges,
addEdge,
updateNodePositions,
updateNodeDimensions,
zoomIn,
zoomOut,
fitView,
resetSelectedElements,
addSelectedNodes,
panBy,
updateConnection,
cancelConnection
};
// actions
setNodes,
setEdges,
addEdge,
updateNodePositions,
updateNodeDimensions,
zoomIn,
zoomOut,
fitView,
resetSelectedElements,
addSelectedNodes,
panBy,
updateConnection,
cancelConnection
};
}
export function useStore(): SvelteFlowStore {
const { getStore } = getContext<{ getStore: () => SvelteFlowStore }>(key);
const { getStore } = getContext<{ getStore: () => SvelteFlowStore }>(key);
return getStore();
return getStore();
}

View File

@@ -1,13 +1,13 @@
import { readable, writable } from 'svelte/store';
import {
SelectionMode,
type D3ZoomInstance,
type D3SelectionInstance,
ConnectionMode,
ConnectionLineType,
type Transform,
type NodeOrigin,
type Rect
SelectionMode,
type D3ZoomInstance,
type D3SelectionInstance,
ConnectionMode,
ConnectionLineType,
type Transform,
type NodeOrigin,
type Rect
} from '@reactflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -19,47 +19,47 @@ import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
export const initConnectionData = {
nodeId: null,
handleId: null,
handleType: null,
position: null,
status: null
nodeId: null,
handleId: null,
handleType: null,
position: null,
status: null
};
export const initialStoreState = {
nodes: writable<Node[]>([]),
edges: writable<Edge[]>([]),
edgesLayouted: readable<Edge[]>([]),
height: writable<number>(500),
width: writable<number>(500),
nodeOrigin: writable<NodeOrigin>([0.5, 0.5]),
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
zoom: null,
selection: null
}),
id: writable<string | null>(null),
dragging: writable<boolean>(false),
selectionRect: writable<(Rect & { startX: number; startY: number }) | null>(null),
selectionKeyPressed: writable<boolean>(false),
multiselectionKeyPressed: writable<boolean>(false),
deleteKeyPressed: writable<boolean>(false),
selectionRectMode: writable<string | null>(null),
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
nodeTypes: writable<NodeTypes>({
input: InputNode,
output: OutputNode,
default: DefaultNode
}),
edgeTypes: writable<EdgeTypes>({
straight: StraightEdge,
smoothstep: SmoothStepEdge,
default: BezierEdge
}),
transform: writable<Transform>([0, 0, 1]),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connectionPath: readable<string | null>(null),
connection: writable<ConnectionData>(initConnectionData),
connectionRadius: writable<number>(25),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier)
nodes: writable<Node[]>([]),
edges: writable<Edge[]>([]),
edgesLayouted: readable<Edge[]>([]),
height: writable<number>(500),
width: writable<number>(500),
nodeOrigin: writable<NodeOrigin>([0.5, 0.5]),
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
zoom: null,
selection: null
}),
id: writable<string | null>(null),
dragging: writable<boolean>(false),
selectionRect: writable<(Rect & { startX: number; startY: number }) | null>(null),
selectionKeyPressed: writable<boolean>(false),
multiselectionKeyPressed: writable<boolean>(false),
deleteKeyPressed: writable<boolean>(false),
selectionRectMode: writable<string | null>(null),
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
nodeTypes: writable<NodeTypes>({
input: InputNode,
output: OutputNode,
default: DefaultNode
}),
edgeTypes: writable<EdgeTypes>({
straight: StraightEdge,
smoothstep: SmoothStepEdge,
default: BezierEdge
}),
transform: writable<Transform>([0, 0, 1]),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connectionPath: readable<string | null>(null),
connection: writable<ConnectionData>(initConnectionData),
connectionRadius: writable<number>(25),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier)
};

View File

@@ -1,32 +1,32 @@
import type {
NodeDimensionUpdate,
XYPosition,
ViewportHelperFunctionOptions,
Connection,
NodeDragItem
NodeDimensionUpdate,
XYPosition,
ViewportHelperFunctionOptions,
Connection,
NodeDragItem
} from '@reactflow/system';
import { initialStoreState } from './initial-store';
import type { Node, Edge, ConnectionData } from '$lib/types';
export type SvelteFlowStoreActions = {
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
addEdge: (edge: Edge | Connection) => void;
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
updateNodePositions: (
nodeDragItems: NodeDragItem[],
positionChanged?: boolean,
dragging?: boolean
) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
resetSelectedElements: () => void;
addSelectedNodes: (ids: string[]) => void;
panBy: (delta: XYPosition) => void;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
addEdge: (edge: Edge | Connection) => void;
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
updateNodePositions: (
nodeDragItems: NodeDragItem[],
positionChanged?: boolean,
dragging?: boolean
) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
resetSelectedElements: () => void;
addSelectedNodes: (ids: string[]) => void;
panBy: (delta: XYPosition) => void;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
};
export type SvelteFlowStoreState = typeof initialStoreState;

View File

@@ -7,25 +7,25 @@ export type KeyDefinitionObject = { key: string; modifier?: KeyModifier };
export type KeyDefinition = string | KeyDefinitionObject;
export type ConnectionData = {
position: XYPosition | null;
nodeId: string | null;
handleId: string | null;
handleType: HandleType | null;
status: string | null;
position: XYPosition | null;
nodeId: string | null;
handleId: string | null;
handleType: HandleType | null;
status: string | null;
};
export type ConnectionLineProps = {};
export type SvelteFlowProps = {
nodes: Node[];
edges: Edge[];
nodes: Node[];
edges: Edge[];
connectionLineType?: ConnectionLineType;
selectionKey?: KeyDefinition;
deleteKey?: KeyDefinition;
nodeTypes?: NodeTypes;
fitView?: boolean;
class?: string;
style?: string;
id?: string;
connectionLineType?: ConnectionLineType;
selectionKey?: KeyDefinition;
deleteKey?: KeyDefinition;
nodeTypes?: NodeTypes;
fitView?: boolean;
class?: string;
style?: string;
id?: string;
};

View File

@@ -1,10 +1,10 @@
export type {
Position,
XYPosition,
Edge,
BaseEdgeProps,
WrapEdgeProps,
EdgeProps
Position,
XYPosition,
Edge,
BaseEdgeProps,
WrapEdgeProps,
EdgeProps
} from '@reactflow/system';
export * from './nodes';

View File

@@ -5,43 +5,43 @@ import type { internalsSymbol, NodeHandleBounds, Position, XYPosition } from '@r
// we need a base node type or helpes that accept Node like types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Node<NodeData = any> = {
id: string;
type: string;
data: NodeData;
position: XYPosition;
sourcePosition?: Position;
targetPosition?: Position;
positionAbsolute?: XYPosition;
id: string;
type: string;
data: NodeData;
position: XYPosition;
sourcePosition?: Position;
targetPosition?: Position;
positionAbsolute?: XYPosition;
width?: number;
height?: number;
selected?: boolean;
class?: string;
style?: string;
deletable?: boolean;
// not supported yet
parentNode?: string;
width?: number;
height?: number;
selected?: boolean;
class?: string;
style?: string;
deletable?: boolean;
// not supported yet
parentNode?: string;
// only used internally
[internalsSymbol]?: {
z?: number;
handleBounds?: NodeHandleBounds;
isParent?: boolean;
};
// only used internally
[internalsSymbol]?: {
z?: number;
handleBounds?: NodeHandleBounds;
isParent?: boolean;
};
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type NodeProps<NodeData = any> = Pick<
Node<NodeData>,
'id' | 'data' | 'selected' | 'sourcePosition' | 'targetPosition'
Node<NodeData>,
'id' | 'data' | 'selected' | 'sourcePosition' | 'targetPosition'
> & {
xPos: number;
yPos: number;
isConnectable?: boolean;
xPos: number;
yPos: number;
isConnectable?: boolean;
};
export type WrapNodeProps = Node & {
resizeObserver?: ResizeObserver | null;
resizeObserver?: ResizeObserver | null;
};
export type NodeTypes = Record<string, typeof SvelteComponentTyped<Partial<NodeProps>>>;

View File

@@ -1,10 +1,16 @@
<script lang="ts">
import type { Node, Edge, NodeTypes } from '../lib/types';
import SvelteFlow, { Controls, Background, BackgroundVariant, Minimap, Panel } from '../lib/index';
import SvelteFlow, {
Controls,
Background,
BackgroundVariant,
Minimap,
Panel
} from '../lib/index';
import CustomNode from '../customnodes/Custom.svelte';
const nodeTypes: NodeTypes = {
custom: CustomNode,
custom: CustomNode
};
// const yNodes = 10;
@@ -13,8 +19,6 @@
// const nodes: Node[] = [];
// const edges: Edge[] = [];
// let source = null;
// for (let y = 0; y < yNodes; y++) {
@@ -43,61 +47,70 @@
// }
// }
let nodes: Node<{ label: string }>[] = [{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 150, y: 5 }
}, {
id: '2',
type: 'default',
data: { label: 'Node' },
position: { x: 0, y: 150 },
}, {
id: 'A',
type: 'default',
data: { label: 'Styled with class' },
class: 'custom-style',
position: { x: 150, y: 150 },
}, {
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 300, y: 150 },
},
{
id: 'B',
type: 'default',
data: { label: 'Styled with style' },
style: 'border: 2px solid #ff5050;',
position: { x: 450, y: 150 },
},
{
id: '4',
type: 'custom',
data: { label: 'Custom Node' },
position: { x: 150, y: 300 },
}];
let nodes: Node<{ label: string }>[] = [
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 150, y: 5 }
},
{
id: '2',
type: 'default',
data: { label: 'Node' },
position: { x: 0, y: 150 }
},
{
id: 'A',
type: 'default',
data: { label: 'Styled with class' },
class: 'custom-style',
position: { x: 150, y: 150 }
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 300, y: 150 }
},
{
id: 'B',
type: 'default',
data: { label: 'Styled with style' },
style: 'border: 2px solid #ff5050;',
position: { x: 450, y: 150 }
},
{
id: '4',
type: 'custom',
data: { label: 'Custom Node' },
position: { x: 150, y: 300 }
}
];
let edges: Edge[] = [
{
id: '1-2',
type: 'default',
source: '1',
target: '2',
label: 'Edge Text'
},
{
id: '1-3',
type: 'smoothstep',
source: '1',
target: '3'
},
{
id: '2-4',
type: 'default',
source: '2',
target: '4',
animated: true
}
];
let edges: Edge[] = [{
id: '1-2',
type: 'default',
source: '1',
target: '2',
label: 'Edge Text'
}, {
id: '1-3',
type: 'smoothstep',
source: '1',
target: '3'
}, {
id: '2-4',
type: 'default',
source: '2',
target: '4',
animated: true
}];
function updateNode() {
nodes[0].position = { x: nodes[0].position.x + 20, y: nodes[0].position.y };
}

View File

@@ -1,97 +1,97 @@
import {
type CoordinateExtent,
type Dimensions,
type XYPosition,
type Node,
type XYZPosition,
type HandleElement,
internalsSymbol,
Position
type CoordinateExtent,
type Dimensions,
type XYPosition,
type Node,
type XYZPosition,
type HandleElement,
internalsSymbol,
Position
} from '@reactflow/system';
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max);
export const clampPosition = (position: XYPosition = { x: 0, y: 0 }, extent: CoordinateExtent) => ({
x: clamp(position.x, extent[0][0], extent[1][0]),
y: clamp(position.y, extent[0][1], extent[1][1])
x: clamp(position.x, extent[0][0], extent[1][0]),
y: clamp(position.y, extent[0][1], extent[1][1])
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isNumeric = (n: any): n is number => !isNaN(n) && isFinite(n);
export const getDimensions = (node: HTMLDivElement): Dimensions => ({
width: node.offsetWidth,
height: node.offsetHeight
width: node.offsetWidth,
height: node.offsetHeight
});
type ParentNodes = Record<string, boolean>;
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 = parentNode.positionAbsolute!;
if (!node.parentNode) {
return result;
}
const parentNode = nodes.find((n) => n.id === node.parentNode)!;
const parentNodePosition = parentNode.positionAbsolute!;
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
});
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
});
}
export function updateAbsoluteNodePositions(nodes: Node[], parentNodes?: ParentNodes) {
nodes.forEach((node) => {
if (node.parentNode) {
throw new Error(`Parent node ${node.parentNode} not found`);
}
nodes.forEach((node) => {
if (node.parentNode) {
throw new Error(`Parent node ${node.parentNode} not found`);
}
if (node.parentNode || parentNodes?.[node.id]) {
const { x, y, z } = calculateXYZPosition(node, nodes, {
...node.position,
z: node[internalsSymbol]?.z ?? 0
});
if (node.parentNode || parentNodes?.[node.id]) {
const { x, y, z } = calculateXYZPosition(node, nodes, {
...node.position,
z: node[internalsSymbol]?.z ?? 0
});
node.positionAbsolute = {
x,
y
};
node.positionAbsolute = {
x,
y
};
node[internalsSymbol]!.z = z;
node[internalsSymbol]!.z = z;
if (parentNodes?.[node.id]) {
node[internalsSymbol]!.isParent = true;
}
}
});
if (parentNodes?.[node.id]) {
node[internalsSymbol]!.isParent = true;
}
}
});
}
export const getHandleBounds = (
selector: string,
nodeElement: HTMLDivElement,
zoom: number
selector: string,
nodeElement: HTMLDivElement,
zoom: number
): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector);
const handles = nodeElement.querySelectorAll(selector);
if (!handles || !handles.length) {
return null;
}
if (!handles || !handles.length) {
return null;
}
const handlesArray = Array.from(handles) as HTMLDivElement[];
const nodeBounds = nodeElement.getBoundingClientRect();
const handlesArray = Array.from(handles) as HTMLDivElement[];
const nodeBounds = nodeElement.getBoundingClientRect();
return handlesArray.map((handle): HandleElement => {
const handleBounds = handle.getBoundingClientRect();
return handlesArray.map((handle): HandleElement => {
const handleBounds = handle.getBoundingClientRect();
return {
id: handle.getAttribute('data-handleid'),
position: handle.getAttribute('data-handlepos') as unknown as Position,
x: (handleBounds.left - nodeBounds.left) / zoom,
y: (handleBounds.top - nodeBounds.top) / zoom,
...getDimensions(handle)
};
});
return {
id: handle.getAttribute('data-handleid'),
position: handle.getAttribute('data-handlepos') as unknown as Position,
x: (handleBounds.left - nodeBounds.left) / zoom,
y: (handleBounds.top - nodeBounds.top) / zoom,
...getDimensions(handle)
};
});
};

View File

@@ -3,13 +3,13 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter()
}
kit: {
adapter: adapter()
}
};
export default config;

View File

@@ -1,17 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -2,7 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
plugins: [sveltekit()]
plugins: [sveltekit()]
};
export default config;