chore(svelte): run prettier
This commit is contained in:
@@ -1,20 +1,20 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||||
plugins: ['svelte3', '@typescript-eslint'],
|
plugins: ['svelte3', '@typescript-eslint'],
|
||||||
ignorePatterns: ['*.cjs'],
|
ignorePatterns: ['*.cjs'],
|
||||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||||
settings: {
|
settings: {
|
||||||
'svelte3/typescript': () => require('typescript')
|
'svelte3/typescript': () => require('typescript')
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
ecmaVersion: 2020
|
ecmaVersion: 2020
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2017: true,
|
es2017: true,
|
||||||
node: true
|
node: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"useTabs": true,
|
"singleQuote": true,
|
||||||
"singleQuote": true,
|
"trailingComma": "none",
|
||||||
"trailingComma": "none",
|
"printWidth": 100,
|
||||||
"printWidth": 100,
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"pluginSearchDirs": ["."],
|
||||||
"pluginSearchDirs": ["."],
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "svelte",
|
"name": "svelte",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "svelte-kit sync && svelte-package",
|
"build": "svelte-kit sync && svelte-package",
|
||||||
"prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1",
|
"prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
"format": "prettier --plugin-search-dir . --write ."
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.js"
|
".": "./index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reactflow/edge-utils": "workspace:*",
|
"@reactflow/edge-utils": "workspace:*",
|
||||||
"@reactflow/system": "workspace:*",
|
"@reactflow/system": "workspace:*",
|
||||||
"@reactflow/utils": "workspace:*",
|
"@reactflow/utils": "workspace:*",
|
||||||
"@svelte-put/shortcut": "^2.0.0",
|
"@svelte-put/shortcut": "^2.0.0",
|
||||||
"classcat": "^5.0.4",
|
"classcat": "^5.0.4",
|
||||||
"d3-drag": "^3.0.0",
|
"d3-drag": "^3.0.0",
|
||||||
"d3-selection": "^3.0.0",
|
"d3-selection": "^3.0.0",
|
||||||
"d3-zoom": "^3.0.0",
|
"d3-zoom": "^3.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^2.0.0",
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
"@sveltejs/kit": "^1.5.6",
|
"@sveltejs/kit": "^1.5.6",
|
||||||
"@sveltejs/package": "^1.0.2",
|
"@sveltejs/package": "^1.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||||
"@typescript-eslint/parser": "^5.45.0",
|
"@typescript-eslint/parser": "^5.45.0",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.28.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
"prettier-plugin-svelte": "^2.8.1",
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
"svelte": "^3.55.1",
|
"svelte": "^3.55.1",
|
||||||
"svelte-check": "^3.0.1",
|
"svelte-check": "^3.0.1",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"vite": "^4.1.1"
|
"vite": "^4.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
// interface Locals {}
|
// interface Locals {}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">%sveltekit.body%</div>
|
<div id="root">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Handle from '$lib/components/Handle/index.svelte';
|
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 data: { label: string } = { label: 'Node' };
|
||||||
export let xPos: number = 0;
|
export let xPos: number = 0;
|
||||||
|
|||||||
@@ -9,113 +9,113 @@ import type { Node } from '$lib/types';
|
|||||||
export type UseDragData = { dx: number; dy: number };
|
export type UseDragData = { dx: number; dy: number };
|
||||||
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
||||||
export type NodeDragItem = {
|
export type NodeDragItem = {
|
||||||
id: string;
|
id: string;
|
||||||
position: XYPosition;
|
position: XYPosition;
|
||||||
positionAbsolute: XYPosition;
|
positionAbsolute: XYPosition;
|
||||||
// distance from the mouse cursor to the node when start dragging
|
// distance from the mouse cursor to the node when start dragging
|
||||||
distance: XYPosition;
|
distance: XYPosition;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
extent?: 'parent' | CoordinateExtent;
|
extent?: 'parent' | CoordinateExtent;
|
||||||
parentNode?: string;
|
parentNode?: string;
|
||||||
dragging?: boolean;
|
dragging?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseDragParams = {
|
type UseDragParams = {
|
||||||
handleSelector?: string;
|
handleSelector?: string;
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
|
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
|
||||||
nodes: Writable<Node[]>;
|
nodes: Writable<Node[]>;
|
||||||
transform: Writable<Transform>;
|
transform: Writable<Transform>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function drag(
|
export default function drag(
|
||||||
nodeRef: Element,
|
nodeRef: Element,
|
||||||
{ handleSelector, nodeId, updateNodePositions, nodes, transform: transformStore }: UseDragParams
|
{ handleSelector, nodeId, updateNodePositions, nodes, transform: transformStore }: UseDragParams
|
||||||
) {
|
) {
|
||||||
let dragging = false;
|
let dragging = false;
|
||||||
let dragItems: NodeDragItem[] = [];
|
let dragItems: NodeDragItem[] = [];
|
||||||
let lastPos: { x: number | null; y: number | null } = { x: null, y: null };
|
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 getPointerPosition = ({ sourceEvent }: UseDragEvent) => {
|
||||||
const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
|
const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
|
||||||
const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
|
const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
|
||||||
const transform = get(transformStore);
|
const transform = get(transformStore);
|
||||||
|
|
||||||
const pointerPos = {
|
const pointerPos = {
|
||||||
x: (x - transform[0]) / transform[2],
|
x: (x - transform[0]) / transform[2],
|
||||||
y: (y - transform[1]) / transform[2]
|
y: (y - transform[1]) / transform[2]
|
||||||
};
|
};
|
||||||
|
|
||||||
// we need the snapped position in order to be able to skip unnecessary drag events
|
// we need the snapped position in order to be able to skip unnecessary drag events
|
||||||
return {
|
return {
|
||||||
xSnapped: pointerPos.x,
|
xSnapped: pointerPos.x,
|
||||||
ySnapped: pointerPos.y,
|
ySnapped: pointerPos.y,
|
||||||
...pointerPos
|
...pointerPos
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNodes = ({ x, y }: XYPosition) => {
|
const updateNodes = ({ x, y }: XYPosition) => {
|
||||||
let hasChange = false;
|
let hasChange = false;
|
||||||
|
|
||||||
dragItems = dragItems.map((n) => {
|
dragItems = dragItems.map((n) => {
|
||||||
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
|
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
|
||||||
const updatedPos = calcNextPosition(n, nextPosition, get(nodes) as RFNode[]);
|
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
|
// we want to make sure that we only fire a change event when there is a changes
|
||||||
hasChange =
|
hasChange =
|
||||||
hasChange ||
|
hasChange ||
|
||||||
n.position.x !== updatedPos.position.x ||
|
n.position.x !== updatedPos.position.x ||
|
||||||
n.position.y !== updatedPos.position.y;
|
n.position.y !== updatedPos.position.y;
|
||||||
|
|
||||||
n.position = updatedPos.position;
|
n.position = updatedPos.position;
|
||||||
n.positionAbsolute = updatedPos.positionAbsolute;
|
n.positionAbsolute = updatedPos.positionAbsolute;
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasChange) {
|
if (!hasChange) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNodePositions(dragItems, true, true);
|
updateNodePositions(dragItems, true, true);
|
||||||
dragging = true;
|
dragging = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dragHandler = d3Drag()
|
const dragHandler = d3Drag()
|
||||||
.on('start', (event: UseDragEvent) => {
|
.on('start', (event: UseDragEvent) => {
|
||||||
const pointerPos = getPointerPosition(event);
|
const pointerPos = getPointerPosition(event);
|
||||||
console.log(pointerPos);
|
console.log(pointerPos);
|
||||||
lastPos = pointerPos;
|
lastPos = pointerPos;
|
||||||
dragItems = getDragItems(get(nodes) as RFNode[], pointerPos, nodeId);
|
dragItems = getDragItems(get(nodes) as RFNode[], pointerPos, nodeId);
|
||||||
})
|
})
|
||||||
.on('drag', (event: UseDragEvent) => {
|
.on('drag', (event: UseDragEvent) => {
|
||||||
const pointerPos = getPointerPosition(event);
|
const pointerPos = getPointerPosition(event);
|
||||||
|
|
||||||
// skip events without movement
|
// skip events without movement
|
||||||
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems) {
|
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems) {
|
||||||
lastPos = pointerPos;
|
lastPos = pointerPos;
|
||||||
updateNodes(pointerPos);
|
updateNodes(pointerPos);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('end', (event: UseDragEvent) => {
|
.on('end', (event: UseDragEvent) => {
|
||||||
dragging = false;
|
dragging = false;
|
||||||
|
|
||||||
if (dragItems) {
|
if (dragItems) {
|
||||||
updateNodePositions(dragItems, false, false);
|
updateNodePositions(dragItems, false, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((event: MouseEvent) => {
|
.filter((event: MouseEvent) => {
|
||||||
const target = event.target as HTMLDivElement;
|
const target = event.target as HTMLDivElement;
|
||||||
const isDraggable =
|
const isDraggable =
|
||||||
!event.button &&
|
!event.button &&
|
||||||
!hasSelector(target, '.nodrag', nodeRef) &&
|
!hasSelector(target, '.nodrag', nodeRef) &&
|
||||||
(!handleSelector || hasSelector(target, handleSelector, nodeRef));
|
(!handleSelector || hasSelector(target, handleSelector, nodeRef));
|
||||||
|
|
||||||
return isDraggable;
|
return isDraggable;
|
||||||
});
|
});
|
||||||
|
|
||||||
selection.call(dragHandler);
|
selection.call(dragHandler);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,152 +1,152 @@
|
|||||||
import type {
|
import type {
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
Node,
|
Node,
|
||||||
NodeDragItem,
|
NodeDragItem,
|
||||||
NodeInternals,
|
NodeInternals,
|
||||||
NodeOrigin,
|
NodeOrigin,
|
||||||
XYPosition
|
XYPosition
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
import { clampPosition, isNumeric } from '../../../utils';
|
import { clampPosition, isNumeric } from '../../../utils';
|
||||||
|
|
||||||
export function isParentSelected(node: Node, nodes: Node[]): boolean {
|
export function isParentSelected(node: Node, nodes: Node[]): boolean {
|
||||||
if (!node.parentNode) {
|
if (!node.parentNode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNode = nodes.find((n) => n.id === node.parentNode);
|
const parentNode = nodes.find((n) => n.id === node.parentNode);
|
||||||
|
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentNode.selected) {
|
if (parentNode.selected) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isParentSelected(parentNode, nodes);
|
return isParentSelected(parentNode, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSelector(target: Element, selector: string, domNode: Element): boolean {
|
export function hasSelector(target: Element, selector: string, domNode: Element): boolean {
|
||||||
let current = target;
|
let current = target;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (current?.matches(selector)) return true;
|
if (current?.matches(selector)) return true;
|
||||||
if (current === domNode) return false;
|
if (current === domNode) return false;
|
||||||
current = current.parentElement as Element;
|
current = current.parentElement as Element;
|
||||||
} while (current);
|
} while (current);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// looks for all selected nodes and created a NodeDragItem for each of them
|
// looks for all selected nodes and created a NodeDragItem for each of them
|
||||||
export function getDragItems(nodes: Node[], mousePos: XYPosition, nodeId?: string): NodeDragItem[] {
|
export function getDragItems(nodes: Node[], mousePos: XYPosition, nodeId?: string): NodeDragItem[] {
|
||||||
return nodes
|
return nodes
|
||||||
.filter(
|
.filter(
|
||||||
(n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, nodes))
|
(n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, nodes))
|
||||||
)
|
)
|
||||||
.map((n) => ({
|
.map((n) => ({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
position: n.position ? { ...n.position } : { x: 0, y: 0 },
|
position: n.position ? { ...n.position } : { x: 0, y: 0 },
|
||||||
positionAbsolute: n.positionAbsolute ? { ...n.positionAbsolute } : { x: 0, y: 0 },
|
positionAbsolute: n.positionAbsolute ? { ...n.positionAbsolute } : { x: 0, y: 0 },
|
||||||
distance: {
|
distance: {
|
||||||
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
|
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
|
||||||
y: mousePos.y - (n.positionAbsolute?.y ?? 0)
|
y: mousePos.y - (n.positionAbsolute?.y ?? 0)
|
||||||
},
|
},
|
||||||
delta: {
|
delta: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
},
|
},
|
||||||
extent: n.extent,
|
extent: n.extent,
|
||||||
parentNode: n.parentNode,
|
parentNode: n.parentNode,
|
||||||
width: n.width,
|
width: n.width,
|
||||||
height: n.height
|
height: n.height
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcNextPosition(
|
export function calcNextPosition(
|
||||||
node: NodeDragItem | Node,
|
node: NodeDragItem | Node,
|
||||||
nextPosition: XYPosition,
|
nextPosition: XYPosition,
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
nodeExtent?: CoordinateExtent,
|
nodeExtent?: CoordinateExtent,
|
||||||
nodeOrigin: NodeOrigin = [0, 0]
|
nodeOrigin: NodeOrigin = [0, 0]
|
||||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||||
let currentExtent = node.extent || nodeExtent;
|
let currentExtent = node.extent || nodeExtent;
|
||||||
|
|
||||||
if (node.extent === 'parent') {
|
if (node.extent === 'parent') {
|
||||||
if (node.parentNode && node.width && node.height) {
|
if (node.parentNode && node.width && node.height) {
|
||||||
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
||||||
const { x: parentX, y: parentY } = parent.positionAbsolute!;
|
const { x: parentX, y: parentY } = parent.positionAbsolute!;
|
||||||
currentExtent =
|
currentExtent =
|
||||||
parent &&
|
parent &&
|
||||||
isNumeric(parentX) &&
|
isNumeric(parentX) &&
|
||||||
isNumeric(parentY) &&
|
isNumeric(parentY) &&
|
||||||
isNumeric(parent.width) &&
|
isNumeric(parent.width) &&
|
||||||
isNumeric(parent.height)
|
isNumeric(parent.height)
|
||||||
? [
|
? [
|
||||||
[parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
|
[parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
|
||||||
[
|
[
|
||||||
parentX + parent.width! - node.width + node.width * nodeOrigin[0],
|
parentX + parent.width! - node.width + node.width * nodeOrigin[0],
|
||||||
parentY + parent.height! - node.height + node.height * nodeOrigin[1]
|
parentY + parent.height! - node.height + node.height * nodeOrigin[1]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
: currentExtent;
|
: currentExtent;
|
||||||
} else {
|
} else {
|
||||||
currentExtent = nodeExtent;
|
currentExtent = nodeExtent;
|
||||||
}
|
}
|
||||||
} else if (node.extent && node.parentNode) {
|
} else if (node.extent && node.parentNode) {
|
||||||
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
||||||
const { x: parentX, y: parentY } = parent.positionAbsolute!;
|
const { x: parentX, y: parentY } = parent.positionAbsolute!;
|
||||||
currentExtent = [
|
currentExtent = [
|
||||||
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
||||||
[node.extent[1][0] + parentX, node.extent[1][1] + parentY]
|
[node.extent[1][0] + parentX, node.extent[1][1] + parentY]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
let parentPosition = { x: 0, y: 0 };
|
let parentPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
if (node.parentNode) {
|
if (node.parentNode) {
|
||||||
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
const parent = nodes.find((n) => n.id === node.parentNode)!;
|
||||||
parentPosition = parent.positionAbsolute!;
|
parentPosition = parent.positionAbsolute!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionAbsolute = currentExtent
|
const positionAbsolute = currentExtent
|
||||||
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
|
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
|
||||||
: nextPosition;
|
: nextPosition;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: {
|
position: {
|
||||||
x: positionAbsolute.x - parentPosition.x,
|
x: positionAbsolute.x - parentPosition.x,
|
||||||
y: positionAbsolute.y - parentPosition.y
|
y: positionAbsolute.y - parentPosition.y
|
||||||
},
|
},
|
||||||
positionAbsolute
|
positionAbsolute
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns two params:
|
// returns two params:
|
||||||
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
// 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)
|
// 2. array of selected nodes (for multi selections)
|
||||||
export function getEventHandlerParams({
|
export function getEventHandlerParams({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
nodeInternals
|
nodeInternals
|
||||||
}: {
|
}: {
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
dragItems: NodeDragItem[];
|
dragItems: NodeDragItem[];
|
||||||
nodeInternals: NodeInternals;
|
nodeInternals: NodeInternals;
|
||||||
}): [Node, Node[]] {
|
}): [Node, Node[]] {
|
||||||
const extentedDragItems: Node[] = dragItems.map((n) => {
|
const extentedDragItems: Node[] = dragItems.map((n) => {
|
||||||
const node = nodeInternals.get(n.id)!;
|
const node = nodeInternals.get(n.id)!;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
position: n.position,
|
position: n.position,
|
||||||
positionAbsolute: n.positionAbsolute
|
positionAbsolute: n.positionAbsolute
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
nodeId ? extentedDragItems.find((n) => n.id === nodeId)! : extentedDragItems[0],
|
nodeId ? extentedDragItems.find((n) => n.id === nodeId)! : extentedDragItems[0],
|
||||||
extentedDragItems
|
extentedDragItems
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
export default function (node: Element, target = 'body') {
|
export default function (node: Element, target = 'body') {
|
||||||
const targetEl = document.querySelector(target);
|
const targetEl = document.querySelector(target);
|
||||||
|
|
||||||
if (targetEl) {
|
if (targetEl) {
|
||||||
targetEl.appendChild(node);
|
targetEl.appendChild(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destroy() {
|
destroy() {
|
||||||
if (node.parentNode) {
|
if (node.parentNode) {
|
||||||
node.parentNode.removeChild(node);
|
node.parentNode.removeChild(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,114 +6,114 @@ import type { D3ZoomEvent } from 'd3-zoom';
|
|||||||
import type { D3SelectionInstance, D3ZoomInstance, Transform } from '@reactflow/system';
|
import type { D3SelectionInstance, D3ZoomInstance, Transform } from '@reactflow/system';
|
||||||
|
|
||||||
const isWrappedWithClass = (event: any, className: string | undefined) =>
|
const isWrappedWithClass = (event: any, className: string | undefined) =>
|
||||||
event.target.closest(`.${className}`);
|
event.target.closest(`.${className}`);
|
||||||
|
|
||||||
function filter(event: any, params: ZoomParams): boolean {
|
function filter(event: any, params: ZoomParams): boolean {
|
||||||
const zoomScroll = true;
|
const zoomScroll = true;
|
||||||
const pinchZoom = true;
|
const pinchZoom = true;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.button === 1 &&
|
event.button === 1 &&
|
||||||
event.type === 'mousedown' &&
|
event.type === 'mousedown' &&
|
||||||
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))
|
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if all interactions are disabled, we prevent all zoom events
|
// if all interactions are disabled, we prevent all zoom events
|
||||||
// if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
|
// if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
// // during a selection we prevent all other interactions
|
// // during a selection we prevent all other interactions
|
||||||
if (params.selecting) {
|
if (params.selecting) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // if zoom on double click is disabled, we prevent the double click event
|
// // if zoom on double click is disabled, we prevent the double click event
|
||||||
// if (!zoomOnDoubleClick && event.type === 'dblclick') {
|
// if (!zoomOnDoubleClick && event.type === 'dblclick') {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // if the target element is inside an element with the nowheel class, we prevent zooming
|
// // if the target element is inside an element with the nowheel class, we prevent zooming
|
||||||
// if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
|
// if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // if the target element is inside an element with the nopan class, we prevent panning
|
// // if the target element is inside an element with the nopan class, we prevent panning
|
||||||
if (isWrappedWithClass(event, 'nopan') && event.type !== 'wheel') {
|
if (isWrappedWithClass(event, 'nopan') && event.type !== 'wheel') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
|
// if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // when there is no scroll handling enabled, we prevent all wheel events
|
// // when there is no scroll handling enabled, we prevent all wheel events
|
||||||
// if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
|
// if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
// // if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
||||||
// if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
|
// if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // if the pane is only movable using allowed clicks
|
// // if the pane is only movable using allowed clicks
|
||||||
// if (
|
// if (
|
||||||
// Array.isArray(panOnDrag) &&
|
// Array.isArray(panOnDrag) &&
|
||||||
// !panOnDrag.includes(event.button) &&
|
// !panOnDrag.includes(event.button) &&
|
||||||
// (event.type === 'mousedown' || event.type === 'touchstart')
|
// (event.type === 'mousedown' || event.type === 'touchstart')
|
||||||
// ) {
|
// ) {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // We only allow right clicks if pan on drag is set to right click
|
// // We only allow right clicks if pan on drag is set to right click
|
||||||
// const buttonAllowed =
|
// const buttonAllowed =
|
||||||
// (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) ||
|
// (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) ||
|
||||||
// !event.button ||
|
// !event.button ||
|
||||||
// event.button <= 1;
|
// event.button <= 1;
|
||||||
|
|
||||||
// default filter for d3-zoom
|
// default filter for d3-zoom
|
||||||
return ((!event.ctrlKey || event.type === 'wheel') && !event.button) || event.button <= 1;
|
return ((!event.ctrlKey || event.type === 'wheel') && !event.button) || event.button <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ZoomParams = {
|
type ZoomParams = {
|
||||||
transform: Writable<Transform>;
|
transform: Writable<Transform>;
|
||||||
selecting: boolean;
|
selecting: boolean;
|
||||||
d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
|
d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function zoom(domNode: Element, params: ZoomParams) {
|
export default function zoom(domNode: Element, params: ZoomParams) {
|
||||||
const { transform, d3 } = params;
|
const { transform, d3 } = params;
|
||||||
const d3ZoomInstance = d3Zoom();
|
const d3ZoomInstance = d3Zoom();
|
||||||
const selection = select(domNode).call(d3ZoomInstance);
|
const selection = select(domNode).call(d3ZoomInstance);
|
||||||
const d3ZoomHandler = selection.on('wheel.zoom');
|
const d3ZoomHandler = selection.on('wheel.zoom');
|
||||||
|
|
||||||
d3ZoomInstance.transform(selection, zoomIdentity);
|
d3ZoomInstance.transform(selection, zoomIdentity);
|
||||||
|
|
||||||
selection.on('wheel.zoom', function (event: any, d: any) {
|
selection.on('wheel.zoom', function (event: any, d: any) {
|
||||||
if (isWrappedWithClass(event, 'nowheel')) {
|
if (isWrappedWithClass(event, 'nowheel')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
d3ZoomHandler!.call(this, event, d);
|
d3ZoomHandler!.call(this, event, d);
|
||||||
});
|
});
|
||||||
|
|
||||||
d3.set({
|
d3.set({
|
||||||
zoom: d3ZoomInstance,
|
zoom: d3ZoomInstance,
|
||||||
selection
|
selection
|
||||||
});
|
});
|
||||||
|
|
||||||
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
transform.set([event.transform.x, event.transform.y, event.transform.k]);
|
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 {
|
return {
|
||||||
update(params: ZoomParams) {
|
update(params: ZoomParams) {
|
||||||
d3ZoomInstance.filter((event: any) => filter(event, params));
|
d3ZoomInstance.filter((event: any) => filter(event, params));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import type { ConnectionLineProps } from '$lib/types';
|
import type { ConnectionLineProps } from '$lib/types';
|
||||||
|
|
||||||
type $$Props = ConnectionLineProps;
|
type $$Props = ConnectionLineProps;
|
||||||
|
|
||||||
@@ -10,11 +10,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $connectionPath}
|
{#if $connectionPath}
|
||||||
<svg
|
<svg width={$width} height={$height} class="react-flow__connectionline">
|
||||||
width={$width}
|
|
||||||
height={$height}
|
|
||||||
class="react-flow__connectionline"
|
|
||||||
>
|
|
||||||
<g class={cc(['react-flow__connection', $connection.status])}>
|
<g class={cc(['react-flow__connection', $connection.status])}>
|
||||||
<path d={$connectionPath} fill="none" class="react-flow__connection-path" />
|
<path d={$connectionPath} fill="none" class="react-flow__connection-path" />
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import portal from '$lib/actions/portal';
|
import portal from '$lib/actions/portal';
|
||||||
|
|
||||||
type $$Props = {}
|
type $$Props = {};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:portal={'.react-flow__edgelabel-renderer'}>
|
<div use:portal={'.react-flow__edgelabel-renderer'}>
|
||||||
|
|||||||
@@ -1,210 +1,210 @@
|
|||||||
import {
|
import {
|
||||||
getHostForElement,
|
getHostForElement,
|
||||||
calcAutoPan,
|
calcAutoPan,
|
||||||
getEventPosition,
|
getEventPosition,
|
||||||
pointToRendererPoint,
|
pointToRendererPoint,
|
||||||
rendererPointToPoint
|
rendererPointToPoint
|
||||||
} from '@reactflow/utils';
|
} from '@reactflow/utils';
|
||||||
import type {
|
import type {
|
||||||
OnConnect,
|
OnConnect,
|
||||||
HandleType,
|
HandleType,
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
Node,
|
Node,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
Transform
|
Transform
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getClosestHandle,
|
getClosestHandle,
|
||||||
getConnectionStatus,
|
getConnectionStatus,
|
||||||
getHandleLookup,
|
getHandleLookup,
|
||||||
getHandleType,
|
getHandleType,
|
||||||
isValidHandle,
|
isValidHandle,
|
||||||
resetRecentHandle,
|
resetRecentHandle,
|
||||||
type ConnectionHandle,
|
type ConnectionHandle,
|
||||||
type ValidConnectionFunc
|
type ValidConnectionFunc
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { get, type Writable } from 'svelte/store';
|
import { get, type Writable } from 'svelte/store';
|
||||||
import type { ConnectionData } from '$lib/types';
|
import type { ConnectionData } from '$lib/types';
|
||||||
|
|
||||||
export function handlePointerDown({
|
export function handlePointerDown({
|
||||||
event,
|
event,
|
||||||
handleId,
|
handleId,
|
||||||
nodeId,
|
nodeId,
|
||||||
onConnect,
|
onConnect,
|
||||||
domNode,
|
domNode,
|
||||||
nodes,
|
nodes,
|
||||||
connectionMode,
|
connectionMode,
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
isTarget,
|
isTarget,
|
||||||
transform: transformStore,
|
transform: transformStore,
|
||||||
panBy,
|
panBy,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
edgeUpdaterType,
|
edgeUpdaterType,
|
||||||
onEdgeUpdateEnd
|
onEdgeUpdateEnd
|
||||||
}: {
|
}: {
|
||||||
event: MouseEvent | TouchEvent;
|
event: MouseEvent | TouchEvent;
|
||||||
handleId: string | null;
|
handleId: string | null;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
onConnect: OnConnect;
|
onConnect: OnConnect;
|
||||||
isTarget: boolean;
|
isTarget: boolean;
|
||||||
connectionMode: ConnectionMode;
|
connectionMode: ConnectionMode;
|
||||||
domNode: HTMLDivElement | null;
|
domNode: HTMLDivElement | null;
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
connectionRadius: number;
|
connectionRadius: number;
|
||||||
isValidConnection: ValidConnectionFunc;
|
isValidConnection: ValidConnectionFunc;
|
||||||
transform: Writable<Transform>;
|
transform: Writable<Transform>;
|
||||||
updateConnection: (connection: Partial<ConnectionData>) => void;
|
updateConnection: (connection: Partial<ConnectionData>) => void;
|
||||||
cancelConnection: () => void;
|
cancelConnection: () => void;
|
||||||
panBy: (delta: XYPosition) => void;
|
panBy: (delta: XYPosition) => void;
|
||||||
edgeUpdaterType?: HandleType;
|
edgeUpdaterType?: HandleType;
|
||||||
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
|
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
|
||||||
}): void {
|
}): void {
|
||||||
// when react-flow is used inside a shadow root we can't use document
|
// when react-flow is used inside a shadow root we can't use document
|
||||||
const doc = getHostForElement(event.target as HTMLElement);
|
const doc = getHostForElement(event.target as HTMLElement);
|
||||||
let autoPanId = 0;
|
let autoPanId = 0;
|
||||||
let prevClosestHandle: ConnectionHandle | null;
|
let prevClosestHandle: ConnectionHandle | null;
|
||||||
|
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const clickedHandle = doc?.elementFromPoint(x, y);
|
const clickedHandle = doc?.elementFromPoint(x, y);
|
||||||
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
|
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
|
||||||
const containerBounds = domNode?.getBoundingClientRect();
|
const containerBounds = domNode?.getBoundingClientRect();
|
||||||
|
|
||||||
if (!containerBounds || !handleType) {
|
if (!containerBounds || !handleType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let prevActiveHandle: Element;
|
let prevActiveHandle: Element;
|
||||||
let connectionPosition = getEventPosition(event, containerBounds);
|
let connectionPosition = getEventPosition(event, containerBounds);
|
||||||
let autoPanStarted = false;
|
let autoPanStarted = false;
|
||||||
let connection: Connection | null = null;
|
let connection: Connection | null = null;
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
let handleDomNode: Element | null = null;
|
let handleDomNode: Element | null = null;
|
||||||
|
|
||||||
const autoPanOnConnect = true;
|
const autoPanOnConnect = true;
|
||||||
|
|
||||||
const handleLookup = getHandleLookup({
|
const handleLookup = getHandleLookup({
|
||||||
nodes,
|
nodes,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
handleType
|
handleType
|
||||||
});
|
});
|
||||||
|
|
||||||
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
|
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
|
||||||
const autoPan = (): void => {
|
const autoPan = (): void => {
|
||||||
// @todd add prop
|
// @todd add prop
|
||||||
if (!autoPanOnConnect) {
|
if (!autoPanOnConnect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
|
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
|
||||||
|
|
||||||
panBy({ x: xMovement, y: yMovement });
|
panBy({ x: xMovement, y: yMovement });
|
||||||
autoPanId = requestAnimationFrame(autoPan);
|
autoPanId = requestAnimationFrame(autoPan);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateConnection({
|
updateConnection({
|
||||||
position: connectionPosition,
|
position: connectionPosition,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
handleType,
|
handleType,
|
||||||
status: null
|
status: null
|
||||||
});
|
});
|
||||||
|
|
||||||
// @todo add prop
|
// @todo add prop
|
||||||
// onConnectStart?.(event, { nodeId, handleId, handleType });
|
// onConnectStart?.(event, { nodeId, handleId, handleType });
|
||||||
|
|
||||||
function onPointerMove(event: MouseEvent | TouchEvent) {
|
function onPointerMove(event: MouseEvent | TouchEvent) {
|
||||||
const transform = get(transformStore);
|
const transform = get(transformStore);
|
||||||
connectionPosition = getEventPosition(event, containerBounds);
|
connectionPosition = getEventPosition(event, containerBounds);
|
||||||
|
|
||||||
prevClosestHandle = getClosestHandle(
|
prevClosestHandle = getClosestHandle(
|
||||||
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
|
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
handleLookup
|
handleLookup
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!autoPanStarted) {
|
if (!autoPanStarted) {
|
||||||
autoPan();
|
autoPan();
|
||||||
autoPanStarted = true;
|
autoPanStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = isValidHandle(
|
const result = isValidHandle(
|
||||||
event,
|
event,
|
||||||
prevClosestHandle,
|
prevClosestHandle,
|
||||||
connectionMode,
|
connectionMode,
|
||||||
nodeId,
|
nodeId,
|
||||||
handleId,
|
handleId,
|
||||||
isTarget ? 'target' : 'source',
|
isTarget ? 'target' : 'source',
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
doc
|
doc
|
||||||
);
|
);
|
||||||
|
|
||||||
handleDomNode = result.handleDomNode;
|
handleDomNode = result.handleDomNode;
|
||||||
connection = result.connection;
|
connection = result.connection;
|
||||||
isValid = result.isValid;
|
isValid = result.isValid;
|
||||||
|
|
||||||
updateConnection({
|
updateConnection({
|
||||||
position:
|
position:
|
||||||
prevClosestHandle && isValid
|
prevClosestHandle && isValid
|
||||||
? rendererPointToPoint(
|
? rendererPointToPoint(
|
||||||
{
|
{
|
||||||
x: prevClosestHandle.x,
|
x: prevClosestHandle.x,
|
||||||
y: prevClosestHandle.y
|
y: prevClosestHandle.y
|
||||||
},
|
},
|
||||||
transform
|
transform
|
||||||
)
|
)
|
||||||
: connectionPosition,
|
: connectionPosition,
|
||||||
status: getConnectionStatus(!!prevClosestHandle, isValid)
|
status: getConnectionStatus(!!prevClosestHandle, isValid)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!prevClosestHandle && !isValid && !handleDomNode) {
|
if (!prevClosestHandle && !isValid && !handleDomNode) {
|
||||||
return resetRecentHandle(prevActiveHandle);
|
return resetRecentHandle(prevActiveHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection.source !== connection.target && handleDomNode) {
|
if (connection.source !== connection.target && handleDomNode) {
|
||||||
resetRecentHandle(prevActiveHandle);
|
resetRecentHandle(prevActiveHandle);
|
||||||
prevActiveHandle = handleDomNode;
|
prevActiveHandle = handleDomNode;
|
||||||
// @todo: remove the old class names "react-flow__handle-" in the next major version
|
// @todo: remove the old class names "react-flow__handle-" in the next major version
|
||||||
handleDomNode.classList.add('connecting');
|
handleDomNode.classList.add('connecting');
|
||||||
handleDomNode.classList.toggle('valid', isValid);
|
handleDomNode.classList.toggle('valid', isValid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerUp(event: MouseEvent | TouchEvent) {
|
function onPointerUp(event: MouseEvent | TouchEvent) {
|
||||||
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
|
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
|
||||||
onConnect?.(connection);
|
onConnect?.(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's important to get a fresh reference from the store here
|
// it's important to get a fresh reference from the store here
|
||||||
// in order to get the latest state of onConnectEnd
|
// in order to get the latest state of onConnectEnd
|
||||||
// @todo add onConnectEnd prop
|
// @todo add onConnectEnd prop
|
||||||
// getState().onConnectEnd?.(event);
|
// getState().onConnectEnd?.(event);
|
||||||
|
|
||||||
if (edgeUpdaterType) {
|
if (edgeUpdaterType) {
|
||||||
onEdgeUpdateEnd?.(event);
|
onEdgeUpdateEnd?.(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetRecentHandle(prevActiveHandle);
|
resetRecentHandle(prevActiveHandle);
|
||||||
cancelConnection();
|
cancelConnection();
|
||||||
cancelAnimationFrame(autoPanId);
|
cancelAnimationFrame(autoPanId);
|
||||||
autoPanStarted = false;
|
autoPanStarted = false;
|
||||||
isValid = false;
|
isValid = false;
|
||||||
connection = null;
|
connection = null;
|
||||||
handleDomNode = null;
|
handleDomNode = null;
|
||||||
|
|
||||||
doc.removeEventListener('mousemove', onPointerMove as EventListener);
|
doc.removeEventListener('mousemove', onPointerMove as EventListener);
|
||||||
doc.removeEventListener('mouseup', onPointerUp as EventListener);
|
doc.removeEventListener('mouseup', onPointerUp as EventListener);
|
||||||
|
|
||||||
doc.removeEventListener('touchmove', onPointerMove as EventListener);
|
doc.removeEventListener('touchmove', onPointerMove as EventListener);
|
||||||
doc.removeEventListener('touchend', onPointerUp as EventListener);
|
doc.removeEventListener('touchend', onPointerUp as EventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.addEventListener('mousemove', onPointerMove as EventListener);
|
doc.addEventListener('mousemove', onPointerMove as EventListener);
|
||||||
doc.addEventListener('mouseup', onPointerUp as EventListener);
|
doc.addEventListener('mouseup', onPointerUp as EventListener);
|
||||||
|
|
||||||
doc.addEventListener('touchmove', onPointerMove as EventListener);
|
doc.addEventListener('touchmove', onPointerMove as EventListener);
|
||||||
doc.addEventListener('touchend', onPointerUp as EventListener);
|
doc.addEventListener('touchend', onPointerUp as EventListener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { Position, type Connection, type HandleProps } from '@reactflow/system';
|
import { Position, type Connection, type HandleProps } from '@reactflow/system';
|
||||||
import { isMouseEvent } from '@reactflow/utils';
|
import { isMouseEvent } from '@reactflow/utils';
|
||||||
|
|
||||||
import { handlePointerDown } from './handler';
|
import { handlePointerDown } from './handler';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
type $$Props = HandleProps;
|
type $$Props = HandleProps;
|
||||||
|
|
||||||
@@ -34,40 +34,40 @@
|
|||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
function onConnectExtended(params: Connection) {
|
function onConnectExtended(params: Connection) {
|
||||||
addEdge(params);
|
addEdge(params);
|
||||||
// @todo add props
|
// @todo add props
|
||||||
// onConnectAction?.(edgeParams);
|
// onConnectAction?.(edgeParams);
|
||||||
// onConnect?.(edgeParams);
|
// onConnect?.(edgeParams);
|
||||||
};
|
}
|
||||||
|
|
||||||
function onPointerDown(event: MouseEvent | TouchEvent) {
|
function onPointerDown(event: MouseEvent | TouchEvent) {
|
||||||
const isMouseTriggered = isMouseEvent(event);
|
const isMouseTriggered = isMouseEvent(event);
|
||||||
|
|
||||||
if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) {
|
if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) {
|
||||||
handlePointerDown({
|
handlePointerDown({
|
||||||
event,
|
event,
|
||||||
handleId,
|
handleId,
|
||||||
nodeId,
|
nodeId,
|
||||||
isTarget,
|
isTarget,
|
||||||
connectionRadius: $connectionRadius,
|
connectionRadius: $connectionRadius,
|
||||||
domNode: $domNode,
|
domNode: $domNode,
|
||||||
nodes: $nodes,
|
nodes: $nodes,
|
||||||
connectionMode: $connectionMode,
|
connectionMode: $connectionMode,
|
||||||
transform,
|
transform,
|
||||||
isValidConnection: isValidConnection!,
|
isValidConnection: isValidConnection!,
|
||||||
onConnect: onConnectExtended,
|
onConnect: onConnectExtended,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
panBy,
|
panBy
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (isMouseTriggered) {
|
// if (isMouseTriggered) {
|
||||||
// onMouseDown?.(event);
|
// onMouseDown?.(event);
|
||||||
// } else {
|
// } else {
|
||||||
// onTouchStart?.(event);
|
// onTouchStart?.(event);
|
||||||
// }
|
// }
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -92,7 +92,6 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.react-flow__handle {
|
.react-flow__handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { Connection, HandleType, XYPosition, Node, NodeHandleBounds } from
|
|||||||
import { getEventPosition } from '@reactflow/utils';
|
import { getEventPosition } from '@reactflow/utils';
|
||||||
|
|
||||||
export type ConnectionHandle = {
|
export type ConnectionHandle = {
|
||||||
id: string | null;
|
id: string | null;
|
||||||
type: HandleType;
|
type: HandleType;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ValidConnectionFunc = (connection: Connection) => boolean;
|
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
|
// this functions collects all handles and adds an absolute position
|
||||||
// so that we can later find the closest handle to the mouse position
|
// so that we can later find the closest handle to the mouse position
|
||||||
export function getHandles(
|
export function getHandles(
|
||||||
node: Node,
|
node: Node,
|
||||||
handleBounds: NodeHandleBounds,
|
handleBounds: NodeHandleBounds,
|
||||||
type: HandleType,
|
type: HandleType,
|
||||||
currentHandle: string
|
currentHandle: string
|
||||||
): ConnectionHandle[] {
|
): ConnectionHandle[] {
|
||||||
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
|
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
|
||||||
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
|
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
|
||||||
res.push({
|
res.push({
|
||||||
id: h.id || null,
|
id: h.id || null,
|
||||||
type,
|
type,
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
|
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
|
||||||
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2
|
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClosestHandle(
|
export function getClosestHandle(
|
||||||
pos: XYPosition,
|
pos: XYPosition,
|
||||||
connectionRadius: number,
|
connectionRadius: number,
|
||||||
handles: ConnectionHandle[]
|
handles: ConnectionHandle[]
|
||||||
): ConnectionHandle | null {
|
): ConnectionHandle | null {
|
||||||
let closestHandle: ConnectionHandle | null = null;
|
let closestHandle: ConnectionHandle | null = null;
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
|
|
||||||
handles.forEach((handle) => {
|
handles.forEach((handle) => {
|
||||||
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
|
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
|
||||||
if (distance <= connectionRadius && distance < minDistance) {
|
if (distance <= connectionRadius && distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
closestHandle = handle;
|
closestHandle = handle;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return closestHandle;
|
return closestHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
handleDomNode: Element | null;
|
handleDomNode: Element | null;
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
connection: Connection;
|
connection: Connection;
|
||||||
};
|
};
|
||||||
|
|
||||||
const nullConnection: Connection = {
|
const nullConnection: Connection = {
|
||||||
source: null,
|
source: null,
|
||||||
target: null,
|
target: null,
|
||||||
sourceHandle: null,
|
sourceHandle: null,
|
||||||
targetHandle: null
|
targetHandle: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// checks if and returns connection in fom of an object { source: 123, target: 312 }
|
// checks if and returns connection in fom of an object { source: 123, target: 312 }
|
||||||
export function isValidHandle(
|
export function isValidHandle(
|
||||||
event: MouseEvent | TouchEvent,
|
event: MouseEvent | TouchEvent,
|
||||||
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null,
|
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null,
|
||||||
connectionMode: ConnectionMode,
|
connectionMode: ConnectionMode,
|
||||||
fromNodeId: string,
|
fromNodeId: string,
|
||||||
fromHandleId: string | null,
|
fromHandleId: string | null,
|
||||||
fromType: string,
|
fromType: string,
|
||||||
isValidConnection: ValidConnectionFunc,
|
isValidConnection: ValidConnectionFunc,
|
||||||
doc: Document | ShadowRoot
|
doc: Document | ShadowRoot
|
||||||
) {
|
) {
|
||||||
const isTarget = fromType === 'target';
|
const isTarget = fromType === 'target';
|
||||||
const handleDomNode = doc.querySelector(
|
const handleDomNode = doc.querySelector(
|
||||||
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
|
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
|
||||||
);
|
);
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const handleBelow = doc.elementFromPoint(x, y);
|
const handleBelow = doc.elementFromPoint(x, y);
|
||||||
const handleToCheck = handleBelow?.classList.contains('react-flow__handle')
|
const handleToCheck = handleBelow?.classList.contains('react-flow__handle')
|
||||||
? handleBelow
|
? handleBelow
|
||||||
: handleDomNode;
|
: handleDomNode;
|
||||||
|
|
||||||
const result: Result = {
|
const result: Result = {
|
||||||
handleDomNode: handleToCheck,
|
handleDomNode: handleToCheck,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
connection: nullConnection
|
connection: nullConnection
|
||||||
};
|
};
|
||||||
|
|
||||||
if (handleToCheck) {
|
if (handleToCheck) {
|
||||||
const handleType = getHandleType(undefined, handleToCheck);
|
const handleType = getHandleType(undefined, handleToCheck);
|
||||||
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
|
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
|
||||||
const handleId = handleToCheck.getAttribute('data-handleid');
|
const handleId = handleToCheck.getAttribute('data-handleid');
|
||||||
|
|
||||||
const connection: Connection = {
|
const connection: Connection = {
|
||||||
source: isTarget ? handleNodeId : fromNodeId,
|
source: isTarget ? handleNodeId : fromNodeId,
|
||||||
sourceHandle: isTarget ? handleId : fromHandleId,
|
sourceHandle: isTarget ? handleId : fromHandleId,
|
||||||
target: isTarget ? fromNodeId : handleNodeId,
|
target: isTarget ? fromNodeId : handleNodeId,
|
||||||
targetHandle: isTarget ? fromHandleId : handleId
|
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
|
// in strict mode we don't allow target to target or source to source connections
|
||||||
const isValid =
|
const isValid =
|
||||||
connectionMode === ConnectionMode.Strict
|
connectionMode === ConnectionMode.Strict
|
||||||
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
|
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
|
||||||
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
|
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
result.isValid = isValidConnection(connection);
|
result.isValid = isValidConnection(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetHandleLookupParams = {
|
type GetHandleLookupParams = {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
handleId: string | null;
|
handleId: string | null;
|
||||||
handleType: string;
|
handleType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
|
export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
|
||||||
return nodes.reduce<ConnectionHandle[]>((res, node) => {
|
return nodes.reduce<ConnectionHandle[]>((res, node) => {
|
||||||
if (node[internalsSymbol]) {
|
if (node[internalsSymbol]) {
|
||||||
const { handleBounds } = node[internalsSymbol];
|
const { handleBounds } = node[internalsSymbol];
|
||||||
let sourceHandles: ConnectionHandle[] = [];
|
let sourceHandles: ConnectionHandle[] = [];
|
||||||
let targetHandles: ConnectionHandle[] = [];
|
let targetHandles: ConnectionHandle[] = [];
|
||||||
|
|
||||||
if (handleBounds) {
|
if (handleBounds) {
|
||||||
sourceHandles = getHandles(
|
sourceHandles = getHandles(
|
||||||
node,
|
node,
|
||||||
handleBounds,
|
handleBounds,
|
||||||
'source',
|
'source',
|
||||||
`${nodeId}-${handleId}-${handleType}`
|
`${nodeId}-${handleId}-${handleType}`
|
||||||
);
|
);
|
||||||
targetHandles = getHandles(
|
targetHandles = getHandles(
|
||||||
node,
|
node,
|
||||||
handleBounds,
|
handleBounds,
|
||||||
'target',
|
'target',
|
||||||
`${nodeId}-${handleId}-${handleType}`
|
`${nodeId}-${handleId}-${handleType}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.push(...sourceHandles, ...targetHandles);
|
res.push(...sourceHandles, ...targetHandles);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHandleType(
|
export function getHandleType(
|
||||||
edgeUpdaterType: HandleType | undefined,
|
edgeUpdaterType: HandleType | undefined,
|
||||||
handleDomNode: Element | null
|
handleDomNode: Element | null
|
||||||
): HandleType | null {
|
): HandleType | null {
|
||||||
if (edgeUpdaterType) {
|
if (edgeUpdaterType) {
|
||||||
return edgeUpdaterType;
|
return edgeUpdaterType;
|
||||||
} else if (handleDomNode?.classList.contains('target')) {
|
} else if (handleDomNode?.classList.contains('target')) {
|
||||||
return 'target';
|
return 'target';
|
||||||
} else if (handleDomNode?.classList.contains('source')) {
|
} else if (handleDomNode?.classList.contains('source')) {
|
||||||
return 'source';
|
return 'source';
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetRecentHandle(handleDomNode: Element): void {
|
export function resetRecentHandle(handleDomNode: Element): void {
|
||||||
handleDomNode?.classList.remove('valid', 'connecting');
|
handleDomNode?.classList.remove('valid', 'connecting');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
|
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
|
||||||
let connectionStatus = null;
|
let connectionStatus = null;
|
||||||
|
|
||||||
if (isHandleValid) {
|
if (isHandleValid) {
|
||||||
connectionStatus = 'valid';
|
connectionStatus = 'valid';
|
||||||
} else if (isInsideConnectionRadius && !isHandleValid) {
|
} else if (isInsideConnectionRadius && !isHandleValid) {
|
||||||
connectionStatus = 'invalid';
|
connectionStatus = 'invalid';
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionStatus as ConnectionStatus;
|
return connectionStatus as ConnectionStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { KeyDefinition } from '$lib/types';
|
import { KeyDefinition } from '$lib/types';
|
||||||
|
|
||||||
export type KeyHandlerProps = {
|
export type KeyHandlerProps = {
|
||||||
selectionKey?: KeyDefinition;
|
selectionKey?: KeyDefinition;
|
||||||
deleteKey?: KeyDefinition;
|
deleteKey?: KeyDefinition;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { shortcut } from '@svelte-put/shortcut';
|
import { shortcut } from '@svelte-put/shortcut';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import type { KeyHandlerProps } from './KeyHandler'
|
import type { KeyHandlerProps } from './KeyHandler';
|
||||||
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
|
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
|
||||||
|
|
||||||
const { selectionKeyPressed, deleteKeyPressed } = useStore();
|
const { selectionKeyPressed, deleteKeyPressed } = useStore();
|
||||||
|
|
||||||
@@ -25,19 +25,43 @@
|
|||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressed.set(true) }],
|
trigger: [
|
||||||
|
{
|
||||||
|
key: selectionKeyString,
|
||||||
|
modifier: selectionKeyModifier,
|
||||||
|
callback: () => selectionKeyPressed.set(true)
|
||||||
|
}
|
||||||
|
],
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressed.set(false) }],
|
trigger: [
|
||||||
|
{
|
||||||
|
key: selectionKeyString,
|
||||||
|
modifier: selectionKeyModifier,
|
||||||
|
callback: () => selectionKeyPressed.set(false)
|
||||||
|
}
|
||||||
|
],
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressed.set(true) }],
|
trigger: [
|
||||||
|
{
|
||||||
|
key: deleteKeyString,
|
||||||
|
modifier: deleteKeyModifier,
|
||||||
|
callback: () => deleteKeyPressed.set(true)
|
||||||
|
}
|
||||||
|
],
|
||||||
type: 'keydown'
|
type: 'keydown'
|
||||||
}}
|
}}
|
||||||
use:shortcut={{
|
use:shortcut={{
|
||||||
trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressed.set(false) }],
|
trigger: [
|
||||||
|
{
|
||||||
|
key: deleteKeyString,
|
||||||
|
modifier: deleteKeyModifier,
|
||||||
|
callback: () => deleteKeyPressed.set(false)
|
||||||
|
}
|
||||||
|
],
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getRectOfNodes } from '@reactflow/utils';
|
import { getRectOfNodes } from '@reactflow/utils';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import Selection from '$lib/components/Selection/index.svelte';
|
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();
|
const { selectionRectMode, nodes, nodeOrigin, transform, updateNodePositions } = useStore();
|
||||||
|
|
||||||
$: selectedNodes = $nodes.filter(n => n.selected);
|
$: selectedNodes = $nodes.filter((n) => n.selected);
|
||||||
$: rect = getRectOfNodes(selectedNodes, $nodeOrigin);
|
$: rect = getRectOfNodes(selectedNodes, $nodeOrigin);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,7 @@
|
|||||||
export let interactionWidth: $$Props['interactionWidth'] = 20;
|
export let interactionWidth: $$Props['interactionWidth'] = 20;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<path
|
<path d={path} fill="none" class="react-flow__edge-path" />
|
||||||
d={path}
|
|
||||||
fill="none"
|
|
||||||
class="react-flow__edge-path"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if interactionWidth}
|
{#if interactionWidth}
|
||||||
<path
|
<path
|
||||||
@@ -33,7 +29,7 @@
|
|||||||
class="react-flow__edge-label"
|
class="react-flow__edge-label"
|
||||||
style:transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
|
style:transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
</EdgeLabelRenderer>
|
</EdgeLabelRenderer>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { getBezierPath } from '@reactflow/edge-utils';
|
import { getBezierPath } from '@reactflow/edge-utils';
|
||||||
|
|
||||||
import type { EdgeProps } from '$lib/types';
|
import type { EdgeProps } from '$lib/types';
|
||||||
import BaseEdge from './BaseEdge.svelte';
|
import BaseEdge from './BaseEdge.svelte';
|
||||||
|
|
||||||
type $$Props = EdgeProps;
|
type $$Props = EdgeProps;
|
||||||
|
|
||||||
@@ -12,13 +12,8 @@
|
|||||||
targetX: $$props.targetX,
|
targetX: $$props.targetX,
|
||||||
targetY: $$props.targetY,
|
targetY: $$props.targetY,
|
||||||
sourcePosition: $$props.sourcePosition,
|
sourcePosition: $$props.sourcePosition,
|
||||||
targetPosition: $$props.targetPosition,
|
targetPosition: $$props.targetPosition
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BaseEdge
|
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||||
{path}
|
|
||||||
{labelX}
|
|
||||||
{labelY}
|
|
||||||
{...$$props}
|
|
||||||
/>
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SvelteComponentTyped } from 'svelte';
|
import type { SvelteComponentTyped } from 'svelte';
|
||||||
import { Position } from '@reactflow/system';
|
import { Position } from '@reactflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import BezierEdge from '$lib/components/edges/StraightEdge.svelte';
|
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;
|
type $$Props = WrapEdgeProps;
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
export let targetX: $$Props['targetX'] = 0;
|
export let targetX: $$Props['targetX'] = 0;
|
||||||
export let targetY: $$Props['targetY'] = 0;
|
export let targetY: $$Props['targetY'] = 0;
|
||||||
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
|
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 animated: $$Props['animated'] = false;
|
||||||
export let selected: $$Props['selected'] = false;
|
export let selected: $$Props['selected'] = false;
|
||||||
export let label: $$Props['label'] = undefined;
|
export let label: $$Props['label'] = undefined;
|
||||||
@@ -26,11 +26,7 @@
|
|||||||
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
|
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<g
|
<g class="react-flow__edge" class:animated data-id={id}>
|
||||||
class="react-flow__edge"
|
|
||||||
class:animated
|
|
||||||
data-id={id}
|
|
||||||
>
|
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={edgeComponent}
|
this={edgeComponent}
|
||||||
{id}
|
{id}
|
||||||
|
|||||||
@@ -12,13 +12,8 @@
|
|||||||
targetX: $$props.targetX,
|
targetX: $$props.targetX,
|
||||||
targetY: $$props.targetY,
|
targetY: $$props.targetY,
|
||||||
sourcePosition: $$props.sourcePosition,
|
sourcePosition: $$props.sourcePosition,
|
||||||
targetPosition: $$props.targetPosition,
|
targetPosition: $$props.targetPosition
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BaseEdge
|
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||||
{path}
|
|
||||||
{labelX}
|
|
||||||
{labelY}
|
|
||||||
{...$$props}
|
|
||||||
/>
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getStraightPath } from '@reactflow/edge-utils';
|
import { getStraightPath } from '@reactflow/edge-utils';
|
||||||
|
|
||||||
import type { EdgeProps } from '$lib/types';
|
import type { EdgeProps } from '$lib/types';
|
||||||
import BaseEdge from './BaseEdge.svelte';
|
import BaseEdge from './BaseEdge.svelte';
|
||||||
|
|
||||||
type $$Props = EdgeProps;
|
type $$Props = EdgeProps;
|
||||||
|
|
||||||
@@ -10,13 +10,8 @@
|
|||||||
sourceX: $$props.sourceX,
|
sourceX: $$props.sourceX,
|
||||||
sourceY: $$props.sourceY,
|
sourceY: $$props.sourceY,
|
||||||
targetX: $$props.targetX,
|
targetX: $$props.targetX,
|
||||||
targetY: $$props.targetY,
|
targetY: $$props.targetY
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BaseEdge
|
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||||
{path}
|
|
||||||
{labelX}
|
|
||||||
{labelY}
|
|
||||||
{...$$props}
|
|
||||||
/>
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { Position } from '@reactflow/system';
|
import { Position } from '@reactflow/system';
|
||||||
|
|
||||||
import Handle from '$lib/components/Handle/index.svelte';
|
import Handle from '$lib/components/Handle/index.svelte';
|
||||||
import type { NodeProps } from '$lib/types';
|
import type { NodeProps } from '$lib/types';
|
||||||
|
|
||||||
interface $$Props extends NodeProps<{ label: string }>{}
|
interface $$Props extends NodeProps<{ label: string }> {}
|
||||||
|
|
||||||
export let id: $$Props['id'];
|
export let id: $$Props['id'];
|
||||||
export let data: $$Props['data'] = { label: 'Node' };
|
export let data: $$Props['data'] = { label: 'Node' };
|
||||||
|
|||||||
@@ -10,4 +10,3 @@
|
|||||||
|
|
||||||
{data?.label}
|
{data?.label}
|
||||||
<Handle type="source" position={sourcePosition} />
|
<Handle type="source" position={sourcePosition} />
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, setContext, SvelteComponentTyped } from 'svelte';
|
import { onMount, setContext, SvelteComponentTyped } from 'svelte';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { type XYPosition, Position } from '@reactflow/system';
|
import { type XYPosition, Position } from '@reactflow/system';
|
||||||
|
|
||||||
import drag from '$lib/actions/drag'
|
import drag from '$lib/actions/drag';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import DefaultNode from './DefaultNode.svelte';
|
import DefaultNode from './DefaultNode.svelte';
|
||||||
import type { WrapNodeProps, NodeProps } from '$lib/types';
|
import type { WrapNodeProps, NodeProps } from '$lib/types';
|
||||||
|
|
||||||
interface $$Props extends WrapNodeProps {}
|
interface $$Props extends WrapNodeProps {}
|
||||||
|
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
let nodeRef: HTMLDivElement;
|
let nodeRef: HTMLDivElement;
|
||||||
|
|
||||||
const { nodes, transform, nodeTypes, updateNodePositions, addSelectedNodes } = useStore();
|
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 isSelectable = true;
|
||||||
const selectNodesOnDrag = false;
|
const selectNodesOnDrag = false;
|
||||||
const isDraggable = true;
|
const isDraggable = true;
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resizeObserver?.unobserve(nodeRef);
|
resizeObserver?.unobserve(nodeRef);
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function onSelectNodeHandler(event: MouseEvent) {
|
function onSelectNodeHandler(event: MouseEvent) {
|
||||||
@@ -54,15 +55,15 @@
|
|||||||
// const node = store.getState().nodeInternals.get(id)!;
|
// const node = store.getState().nodeInternals.get(id)!;
|
||||||
// onClick(event, { ...node });
|
// onClick(event, { ...node });
|
||||||
// }
|
// }
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
use:drag={{ nodeId: id, nodes, transform, updateNodePositions }}
|
use:drag={{ nodeId: id, nodes, transform, updateNodePositions }}
|
||||||
class={cc(['react-flow__node', `react-flow__node-${type}`, className])}
|
class={cc(['react-flow__node', `react-flow__node-${type}`, className])}
|
||||||
class:initializing={!width && !height}
|
class:initializing={!width && !height}
|
||||||
class:dragging={dragging}
|
class:dragging
|
||||||
class:selected={selected}
|
class:selected
|
||||||
bind:this={nodeRef}
|
bind:this={nodeRef}
|
||||||
on:click={onSelectNodeHandler}
|
on:click={onSelectNodeHandler}
|
||||||
style:transform={`translate(${positionAbsolute.x}px, ${positionAbsolute.y}px)`}
|
style:transform={`translate(${positionAbsolute.x}px, ${positionAbsolute.y}px)`}
|
||||||
|
|||||||
@@ -10,4 +10,3 @@
|
|||||||
|
|
||||||
{data?.label}
|
{data?.label}
|
||||||
<Handle type="target" position={targetPosition} />
|
<Handle type="target" position={targetPosition} />
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,9 @@
|
|||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
const { edgesLayouted, width, height } = useStore();
|
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)}
|
{#each $edgesLayouted as edge (edge.id)}
|
||||||
<EdgeWrapper {...edge} />
|
<EdgeWrapper {...edge} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,105 +1,105 @@
|
|||||||
import {
|
import {
|
||||||
type NodeHandleBounds,
|
type NodeHandleBounds,
|
||||||
type Rect,
|
type Rect,
|
||||||
type Node,
|
type Node,
|
||||||
type HandleElement,
|
type HandleElement,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
Position,
|
Position,
|
||||||
internalsSymbol
|
internalsSymbol
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
|
export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
|
||||||
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
|
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
|
||||||
|
|
||||||
const isValid =
|
const isValid =
|
||||||
handleBounds &&
|
handleBounds &&
|
||||||
node?.width &&
|
node?.width &&
|
||||||
node?.height &&
|
node?.height &&
|
||||||
typeof node?.positionAbsolute?.x !== 'undefined' &&
|
typeof node?.positionAbsolute?.x !== 'undefined' &&
|
||||||
typeof node?.positionAbsolute?.y !== 'undefined';
|
typeof node?.positionAbsolute?.y !== 'undefined';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
x: node?.positionAbsolute?.x || 0,
|
x: node?.positionAbsolute?.x || 0,
|
||||||
y: node?.positionAbsolute?.y || 0,
|
y: node?.positionAbsolute?.y || 0,
|
||||||
width: node?.width || 0,
|
width: node?.width || 0,
|
||||||
height: node?.height || 0
|
height: node?.height || 0
|
||||||
},
|
},
|
||||||
handleBounds,
|
handleBounds,
|
||||||
!!isValid
|
!!isValid
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHandlePosition(
|
export function getHandlePosition(
|
||||||
position: Position,
|
position: Position,
|
||||||
nodeRect: Rect,
|
nodeRect: Rect,
|
||||||
handle: HandleElement | null = null
|
handle: HandleElement | null = null
|
||||||
): XYPosition {
|
): XYPosition {
|
||||||
const x = (handle?.x || 0) + nodeRect.x;
|
const x = (handle?.x || 0) + nodeRect.x;
|
||||||
const y = (handle?.y || 0) + nodeRect.y;
|
const y = (handle?.y || 0) + nodeRect.y;
|
||||||
const width = handle?.width || nodeRect.width;
|
const width = handle?.width || nodeRect.width;
|
||||||
const height = handle?.height || nodeRect.height;
|
const height = handle?.height || nodeRect.height;
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case Position.Top:
|
case Position.Top:
|
||||||
return {
|
return {
|
||||||
x: x + width / 2,
|
x: x + width / 2,
|
||||||
y
|
y
|
||||||
};
|
};
|
||||||
case Position.Right:
|
case Position.Right:
|
||||||
return {
|
return {
|
||||||
x: x + width,
|
x: x + width,
|
||||||
y: y + height / 2
|
y: y + height / 2
|
||||||
};
|
};
|
||||||
case Position.Bottom:
|
case Position.Bottom:
|
||||||
return {
|
return {
|
||||||
x: x + width / 2,
|
x: x + width / 2,
|
||||||
y: y + height
|
y: y + height
|
||||||
};
|
};
|
||||||
case Position.Left:
|
case Position.Left:
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y: y + height / 2
|
y: y + height / 2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null {
|
export function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null {
|
||||||
if (!bounds) {
|
if (!bounds) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleId) {
|
if (handleId) {
|
||||||
return bounds.find((d) => d.id === handleId)!;
|
return bounds.find((d) => d.id === handleId)!;
|
||||||
} else if (bounds.length === 1) {
|
} else if (bounds.length === 1) {
|
||||||
return bounds[0];
|
return bounds[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EdgePosition = {
|
export type EdgePosition = {
|
||||||
sourceX: number;
|
sourceX: number;
|
||||||
sourceY: number;
|
sourceY: number;
|
||||||
targetX: number;
|
targetX: number;
|
||||||
targetY: number;
|
targetY: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getEdgePositions(
|
export function getEdgePositions(
|
||||||
sourceNodeRect: Rect,
|
sourceNodeRect: Rect,
|
||||||
sourceHandle: HandleElement,
|
sourceHandle: HandleElement,
|
||||||
sourcePosition: Position,
|
sourcePosition: Position,
|
||||||
targetNodeRect: Rect,
|
targetNodeRect: Rect,
|
||||||
targetHandle: HandleElement,
|
targetHandle: HandleElement,
|
||||||
targetPosition: Position
|
targetPosition: Position
|
||||||
): EdgePosition {
|
): EdgePosition {
|
||||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
|
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
|
||||||
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
|
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sourceX: sourceHandlePos.x,
|
sourceX: sourceHandlePos.x,
|
||||||
sourceY: sourceHandlePos.y,
|
sourceY: sourceHandlePos.y,
|
||||||
targetX: targetHandlePos.x,
|
targetX: targetHandlePos.x,
|
||||||
targetY: targetHandlePos.y
|
targetY: targetHandlePos.y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,18 @@
|
|||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
const { nodes, updateNodeDimensions } = useStore();
|
const { nodes, updateNodeDimensions } = useStore();
|
||||||
const resizeObserver: ResizeObserver | null = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
const resizeObserver: ResizeObserver | null =
|
||||||
const updates = entries.map((entry: ResizeObserverEntry) => ({
|
typeof ResizeObserver === 'undefined'
|
||||||
id: entry.target.getAttribute('data-id') as string,
|
? null
|
||||||
nodeElement: entry.target as HTMLDivElement,
|
: new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||||
forceUpdate: true,
|
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(() => {
|
onDestroy(() => {
|
||||||
resizeObserver?.disconnect();
|
resizeObserver?.disconnect();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
handler?.(event);
|
handler?.(event);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export function toggleSelected<Item extends Node | Edge>(ids: string[]) {
|
export function toggleSelected<Item extends Node | Edge>(ids: string[]) {
|
||||||
return (item: Item) => {
|
return (item: Item) => {
|
||||||
@@ -19,39 +19,48 @@
|
|||||||
if (item.selected !== isSelected) {
|
if (item.selected !== isSelected) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
selected: isSelected,
|
selected: isSelected
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { SelectionMode, type Node, type Edge } from '@reactflow/system';
|
import { SelectionMode, type Node, type Edge } from '@reactflow/system';
|
||||||
import { getConnectedEdges, getEventPosition, getNodesInside } from '@reactflow/utils';
|
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
|
// @todo take from props
|
||||||
const elementsSelectable = true;
|
const elementsSelectable = true;
|
||||||
const selectionMode = SelectionMode.Partial
|
const selectionMode = SelectionMode.Partial;
|
||||||
|
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
let containerBounds: DOMRect | null = null
|
let containerBounds: DOMRect | null = null;
|
||||||
let selectedNodes: Node[] = [];
|
let selectedNodes: Node[] = [];
|
||||||
|
|
||||||
$: isSelecting = $selectionKeyPressed;
|
$: isSelecting = $selectionKeyPressed;
|
||||||
$: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectMode === 'user');
|
$: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectMode === 'user');
|
||||||
|
|
||||||
|
function onClick(event: MouseEvent) {
|
||||||
function onClick (event: MouseEvent) {
|
|
||||||
// onPaneClick?.(event);
|
// onPaneClick?.(event);
|
||||||
|
|
||||||
resetSelectedElements();
|
resetSelectedElements();
|
||||||
selectionRectMode.set(null)
|
selectionRectMode.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseDown(event: MouseEvent) {
|
function onMouseDown(event: MouseEvent) {
|
||||||
@@ -80,11 +89,8 @@
|
|||||||
y
|
y
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// onSelectionStart?.(event);
|
// onSelectionStart?.(event);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function onMouseMove(event: MouseEvent) {
|
function onMouseMove(event: MouseEvent) {
|
||||||
if (!isSelecting || !containerBounds || !$selectionRect) {
|
if (!isSelecting || !containerBounds || !$selectionRect) {
|
||||||
@@ -98,11 +104,11 @@
|
|||||||
x: mousePos.x < startX ? mousePos.x : startX,
|
x: mousePos.x < startX ? mousePos.x : startX,
|
||||||
y: mousePos.y < startY ? mousePos.y : startY,
|
y: mousePos.y < startY ? mousePos.y : startY,
|
||||||
width: Math.abs(mousePos.x - startX),
|
width: Math.abs(mousePos.x - startX),
|
||||||
height: Math.abs(mousePos.y - startY),
|
height: Math.abs(mousePos.y - startY)
|
||||||
};
|
};
|
||||||
|
|
||||||
selectedNodes = getNodesInside(
|
selectedNodes = getNodesInside(
|
||||||
new Map($nodes.map(node => [node.id, node])),
|
new Map($nodes.map((node) => [node.id, node])),
|
||||||
nextUserSelectRect,
|
nextUserSelectRect,
|
||||||
$transform,
|
$transform,
|
||||||
selectionMode === SelectionMode.Partial,
|
selectionMode === SelectionMode.Partial,
|
||||||
@@ -112,8 +118,8 @@
|
|||||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
||||||
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
||||||
|
|
||||||
nodes.update(nodes => nodes.map(toggleSelected(selectedNodeIds)));
|
nodes.update((nodes) => nodes.map(toggleSelected(selectedNodeIds)));
|
||||||
edges.update(edges => edges.map(toggleSelected(selectedEdgeIds)));
|
edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds)));
|
||||||
|
|
||||||
selectionRectMode.set('user');
|
selectionRectMode.set('user');
|
||||||
selectionRect.set(nextUserSelectRect);
|
selectionRect.set(nextUserSelectRect);
|
||||||
@@ -135,7 +141,6 @@
|
|||||||
selectionRectMode.set('nodes');
|
selectionRectMode.set('nodes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// onSelectionEnd?.(event);
|
// onSelectionEnd?.(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +161,7 @@
|
|||||||
class:selection={isSelecting}
|
class:selection={isSelecting}
|
||||||
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
|
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
|
||||||
on:mousedown={hasActiveSelection ? onMouseDown : undefined}
|
on:mousedown={hasActiveSelection ? onMouseDown : undefined}
|
||||||
on:mousemove={hasActiveSelection? onMouseMove : undefined}
|
on:mousemove={hasActiveSelection ? onMouseMove : undefined}
|
||||||
on:mouseup={hasActiveSelection ? onMouseUp : undefined}
|
on:mouseup={hasActiveSelection ? onMouseUp : undefined}
|
||||||
on:mouseleave={hasActiveSelection ? onMouseLeave : undefined}
|
on:mouseleave={hasActiveSelection ? onMouseLeave : undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import type { PanelPosition } from '@reactflow/system';
|
import type { PanelPosition } from '@reactflow/system';
|
||||||
|
|
||||||
export type PanelProps = {
|
export type PanelProps = {
|
||||||
position?: PanelPosition;
|
position?: PanelPosition;
|
||||||
style?: string;
|
style?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,15 +7,12 @@
|
|||||||
export let position: $$Props['position'] = 'top-right';
|
export let position: $$Props['position'] = 'top-right';
|
||||||
export let style: $$Props['style'] = '';
|
export let style: $$Props['style'] = '';
|
||||||
let className: $$Props['class'] = undefined;
|
let className: $$Props['class'] = undefined;
|
||||||
export { className as class }
|
export { className as class };
|
||||||
|
|
||||||
$: positionClasses = `${position}`.split('-');
|
$: positionClasses = `${position}`.split('-');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class={cc(['react-flow__panel', className, ...positionClasses])} {style}>
|
||||||
class={cc(['react-flow__panel', className, ...positionClasses])}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext, onMount } from 'svelte'
|
import { setContext, onMount } from 'svelte';
|
||||||
import cc from 'classcat';
|
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 Zoom from '$lib/container/Zoom/index.svelte';
|
||||||
import Pane from '$lib/container/Pane/index.svelte';
|
import Pane from '$lib/container/Pane/index.svelte';
|
||||||
import Viewport from '$lib/container/Viewport.svelte';
|
import Viewport from '$lib/container/Viewport.svelte';
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
import NodeSelection from '$lib/components/NodeSelection/index.svelte';
|
import NodeSelection from '$lib/components/NodeSelection/index.svelte';
|
||||||
import { KeyHandler } from '$lib/components/KeyHandler';
|
import { KeyHandler } from '$lib/components/KeyHandler';
|
||||||
import { ConnectionLine } from '$lib/components/ConnectionLine';
|
import { ConnectionLine } from '$lib/components/ConnectionLine';
|
||||||
import type { SvelteFlowProps } from '$lib/types';
|
import type { SvelteFlowProps } from '$lib/types';
|
||||||
|
|
||||||
type $$Props = SvelteFlowProps;
|
type $$Props = SvelteFlowProps;
|
||||||
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// $: {
|
// $: {
|
||||||
// const updatableProps = {
|
// const updatableProps = {
|
||||||
// defaultEdgeOptions,
|
// defaultEdgeOptions,
|
||||||
@@ -86,7 +85,7 @@
|
|||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
class={cc(['react-flow', className])}
|
class={cc(['react-flow', className])}
|
||||||
data-testid="rf__wrapper"
|
data-testid="rf__wrapper"
|
||||||
id={id}
|
{id}
|
||||||
bind:this={domNode}
|
bind:this={domNode}
|
||||||
>
|
>
|
||||||
<KeyHandler {selectionKey} {deleteKey} />
|
<KeyHandler {selectionKey} {deleteKey} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
const { transform } = useStore();
|
const { transform } = useStore();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import DotPattern from './DotPattern.svelte';
|
import DotPattern from './DotPattern.svelte';
|
||||||
import LinePattern from './LinePattern.svelte';
|
import LinePattern from './LinePattern.svelte';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { BackgroundVariant } from './types';
|
import { BackgroundVariant } from './types';
|
||||||
|
|
||||||
export let variant: BackgroundVariant = BackgroundVariant.Dots;
|
export let variant: BackgroundVariant = BackgroundVariant.Dots;
|
||||||
export let gap = 20;
|
export let gap = 20;
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
const defaultColor = {
|
const defaultColor = {
|
||||||
[BackgroundVariant.Dots]: '#91919a',
|
[BackgroundVariant.Dots]: '#91919a',
|
||||||
[BackgroundVariant.Lines]: '#eee',
|
[BackgroundVariant.Lines]: '#eee',
|
||||||
[BackgroundVariant.Cross]: '#e2e2e2',
|
[BackgroundVariant.Cross]: '#e2e2e2'
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultSize = {
|
const defaultSize = {
|
||||||
[BackgroundVariant.Dots]: 1,
|
[BackgroundVariant.Dots]: 1,
|
||||||
[BackgroundVariant.Lines]: 1,
|
[BackgroundVariant.Lines]: 1,
|
||||||
[BackgroundVariant.Cross]: 6,
|
[BackgroundVariant.Cross]: 6
|
||||||
};
|
};
|
||||||
|
|
||||||
const { transform, id } = useStore();
|
const { transform, id } = useStore();
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
: [patternDimensions[0] / 2, patternDimensions[1] / 2];
|
: [patternDimensions[0] / 2, patternDimensions[1] / 2];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class={cc(['react-flow__background', className])} >
|
<svg class={cc(['react-flow__background', className])}>
|
||||||
<pattern
|
<pattern
|
||||||
id={patternId}
|
id={patternId}
|
||||||
x={$transform[0] % scaledGap[0]}
|
x={$transform[0] % scaledGap[0]}
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
{#if isDots}
|
{#if isDots}
|
||||||
<DotPattern color={patternColor} radius={scaledSize / 2} />
|
<DotPattern color={patternColor} radius={scaledSize / 2} />
|
||||||
{:else}
|
{:else}
|
||||||
<LinePattern dimensions={patternDimensions } color={patternColor} lineWidth={lineWidth} />
|
<LinePattern dimensions={patternDimensions} color={patternColor} {lineWidth} />
|
||||||
{/if}
|
{/if}
|
||||||
</pattern>
|
</pattern>
|
||||||
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
|
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let radius = 5;
|
export let radius = 5;
|
||||||
export let color = "#000";
|
export let color = '#000';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<circle cx={radius} cy={radius} r={radius} fill={color} />
|
<circle cx={radius} cy={radius} r={radius} fill={color} />
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let lineWidth = 1;
|
export let lineWidth = 1;
|
||||||
export let color = "#777";
|
export let color = '#777';
|
||||||
export let dimensions: [number, number];
|
export let dimensions: [number, number];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
stroke={color}
|
stroke={color}
|
||||||
stroke-width={lineWidth}
|
stroke-width={lineWidth}
|
||||||
d={`M${dimensions[0] / 2} 0 V${dimensions[1]} M0 ${dimensions[1] / 2} H${
|
d={`M${dimensions[0] / 2} 0 V${dimensions[1]} M0 ${dimensions[1] / 2} H${dimensions[0]}`}
|
||||||
dimensions[0]
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export enum BackgroundVariant {
|
export enum BackgroundVariant {
|
||||||
Lines = 'lines',
|
Lines = 'lines',
|
||||||
Dots = 'dots',
|
Dots = 'dots',
|
||||||
Cross = 'cross'
|
Cross = 'cross'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,15 @@
|
|||||||
|
|
||||||
let className: string;
|
let className: string;
|
||||||
|
|
||||||
export { className as class }
|
export { className as class };
|
||||||
</script>
|
</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" />
|
<slot class="button-svg" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PanelPosition } from '@reactflow/system';
|
import type { PanelPosition } from '@reactflow/system';
|
||||||
|
|
||||||
import Panel from '$lib/container/Panel/index.svelte';
|
import Panel from '$lib/container/Panel/index.svelte';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
@@ -37,13 +37,11 @@
|
|||||||
// nodesConnectable: !isInteractive,
|
// nodesConnectable: !isInteractive,
|
||||||
// elementsSelectable: !isInteractive,
|
// elementsSelectable: !isInteractive,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// onInteractiveChange?.(!isInteractive);
|
// onInteractiveChange?.(!isInteractive);
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel class="react-flow__controls" position={position}>
|
<Panel class="react-flow__controls" {position}>
|
||||||
{#if showZoom}
|
{#if showZoom}
|
||||||
<ControlButton
|
<ControlButton
|
||||||
on:click={onZoomInHandler}
|
on:click={onZoomInHandler}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
|
<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" />
|
<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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 469 B |
@@ -1,3 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
<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" />
|
<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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 536 B |
@@ -1,3 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
<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" />
|
<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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 478 B |
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import type { PanelPosition, Rect, Node } from '@reactflow/system';
|
import type { PanelPosition, Rect, Node } from '@reactflow/system';
|
||||||
import { getBoundsOfRects, getNodePositionWithOrigin, getRectOfNodes } from '@reactflow/utils';
|
import { getBoundsOfRects, getNodePositionWithOrigin, getRectOfNodes } from '@reactflow/utils';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
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';
|
import MinimapNode from './MinimapNode.svelte';
|
||||||
|
|
||||||
let position: PanelPosition = 'bottom-right';
|
let position: PanelPosition = 'bottom-right';
|
||||||
@@ -27,20 +27,23 @@
|
|||||||
const { nodes, transform, width, height, nodeOrigin, id } = useStore();
|
const { nodes, transform, width, height, nodeOrigin, id } = useStore();
|
||||||
|
|
||||||
type GetMiniMapNodeAttribute = (node: Node) => string;
|
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 nodeColorFunc = getAttrFunction(nodeColor);
|
||||||
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
||||||
const nodeClassNameFunc = getAttrFunction(nodeClassName);
|
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}`;
|
const labelledBy = `react-flow__minimap-desc-${$id}`;
|
||||||
|
|
||||||
$: viewBB = {
|
$: viewBB = {
|
||||||
x: -$transform[0] / $transform[2],
|
x: -$transform[0] / $transform[2],
|
||||||
y: -$transform[1] / $transform[2],
|
y: -$transform[1] / $transform[2],
|
||||||
width: $width / $transform[2],
|
width: $width / $transform[2],
|
||||||
height: $height / $transform[2],
|
height: $height / $transform[2]
|
||||||
} as Rect;
|
} 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;
|
$: elementWidth = (style?.width as number) ?? defaultWidth;
|
||||||
$: elementHeight = (style?.height as number) ?? defaultHeight;
|
$: elementHeight = (style?.height as number) ?? defaultHeight;
|
||||||
$: scaledWidth = boundingRect.width / elementWidth;
|
$: scaledWidth = boundingRect.width / elementWidth;
|
||||||
@@ -55,7 +58,11 @@
|
|||||||
$: viewboxHeight = viewHeight + offset * 2;
|
$: viewboxHeight = viewHeight + offset * 2;
|
||||||
</script>
|
</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
|
<svg
|
||||||
width={elementWidth}
|
width={elementWidth}
|
||||||
height={elementHeight}
|
height={elementHeight}
|
||||||
@@ -65,7 +72,7 @@
|
|||||||
>
|
>
|
||||||
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
|
{#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}
|
{#if node.width && node.height}
|
||||||
{@const pos = getNodePositionWithOrigin(node, $nodeOrigin).positionAbsolute}
|
{@const pos = getNodePositionWithOrigin(node, $nodeOrigin).positionAbsolute}
|
||||||
<MinimapNode
|
<MinimapNode
|
||||||
@@ -77,7 +84,7 @@
|
|||||||
borderRadius={nodeBorderRadius}
|
borderRadius={nodeBorderRadius}
|
||||||
strokeColor={nodeStrokeColorFunc(node)}
|
strokeColor={nodeStrokeColorFunc(node)}
|
||||||
strokeWidth={nodeStrokeWidth}
|
strokeWidth={nodeStrokeWidth}
|
||||||
shapeRendering={shapeRendering}
|
{shapeRendering}
|
||||||
class={nodeClassNameFunc(node)}
|
class={nodeClassNameFunc(node)}
|
||||||
style={{}}
|
style={{}}
|
||||||
/>
|
/>
|
||||||
@@ -85,7 +92,9 @@
|
|||||||
{/each}
|
{/each}
|
||||||
<path
|
<path
|
||||||
class="react-flow__minimap-mask"
|
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`}
|
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`}
|
||||||
fill={maskColor}
|
fill={maskColor}
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
|
|
||||||
<rect
|
<rect
|
||||||
class={cc(['react-flow__minimap-node', className])}
|
class={cc(['react-flow__minimap-node', className])}
|
||||||
x={x}
|
{x}
|
||||||
y={y}
|
{y}
|
||||||
rx={borderRadius}
|
rx={borderRadius}
|
||||||
ry={borderRadius}
|
ry={borderRadius}
|
||||||
width={width}
|
{width}
|
||||||
height={height}
|
{height}
|
||||||
fill={fill}
|
{fill}
|
||||||
stroke={strokeColor}
|
stroke={strokeColor}
|
||||||
stroke-width={strokeWidth}
|
stroke-width={strokeWidth}
|
||||||
shape-rendering={shapeRendering}
|
shape-rendering={shapeRendering}
|
||||||
|
|||||||
@@ -5,72 +5,72 @@ import type { SvelteFlowStoreState } from './types';
|
|||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
|
|
||||||
const oppositePosition = {
|
const oppositePosition = {
|
||||||
[Position.Left]: Position.Right,
|
[Position.Left]: Position.Right,
|
||||||
[Position.Right]: Position.Left,
|
[Position.Right]: Position.Left,
|
||||||
[Position.Top]: Position.Bottom,
|
[Position.Top]: Position.Bottom,
|
||||||
[Position.Bottom]: Position.Top
|
[Position.Bottom]: Position.Top
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getConnectionPath(store: SvelteFlowStoreState) {
|
export function getConnectionPath(store: SvelteFlowStoreState) {
|
||||||
return derived(
|
return derived(
|
||||||
[
|
[
|
||||||
store.connection,
|
store.connection,
|
||||||
store.connectionLineType,
|
store.connectionLineType,
|
||||||
store.connectionMode,
|
store.connectionMode,
|
||||||
store.nodes,
|
store.nodes,
|
||||||
store.transform
|
store.transform
|
||||||
],
|
],
|
||||||
([$connection, $connectionLineType, $connectionMode, $nodes, $transform]) => {
|
([$connection, $connectionLineType, $connectionMode, $nodes, $transform]) => {
|
||||||
if (!$connection.nodeId) {
|
if (!$connection.nodeId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromNode = $nodes.find((n) => n.id === $connection.nodeId);
|
const fromNode = $nodes.find((n) => n.id === $connection.nodeId);
|
||||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
||||||
const handleBoundsStrict = fromHandleBounds?.[$connection.handleType || 'source'] || [];
|
const handleBoundsStrict = fromHandleBounds?.[$connection.handleType || 'source'] || [];
|
||||||
const handleBoundsLoose = handleBoundsStrict
|
const handleBoundsLoose = handleBoundsStrict
|
||||||
? handleBoundsStrict
|
? handleBoundsStrict
|
||||||
: fromHandleBounds?.[$connection.handleType === 'source' ? 'target' : 'source']!;
|
: fromHandleBounds?.[$connection.handleType === 'source' ? 'target' : 'source']!;
|
||||||
const handleBounds =
|
const handleBounds =
|
||||||
$connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
|
$connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
|
||||||
const fromHandle = $connection.handleId
|
const fromHandle = $connection.handleId
|
||||||
? handleBounds.find((d) => d.id === $connection.handleId)
|
? handleBounds.find((d) => d.id === $connection.handleId)
|
||||||
: handleBounds[0];
|
: handleBounds[0];
|
||||||
const fromHandleX = fromHandle
|
const fromHandleX = fromHandle
|
||||||
? fromHandle.x + fromHandle.width / 2
|
? fromHandle.x + fromHandle.width / 2
|
||||||
: (fromNode?.width ?? 0) / 2;
|
: (fromNode?.width ?? 0) / 2;
|
||||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
|
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
|
||||||
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
|
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
|
||||||
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
|
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
|
||||||
const fromPosition = fromHandle?.position;
|
const fromPosition = fromHandle?.position;
|
||||||
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
|
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
|
||||||
|
|
||||||
const pathParams = {
|
const pathParams = {
|
||||||
sourceX: fromX,
|
sourceX: fromX,
|
||||||
sourceY: fromY,
|
sourceY: fromY,
|
||||||
sourcePosition: fromPosition,
|
sourcePosition: fromPosition,
|
||||||
targetX: (($connection.position?.x ?? 0) - $transform[0]) / $transform[2],
|
targetX: (($connection.position?.x ?? 0) - $transform[0]) / $transform[2],
|
||||||
targetY: (($connection.position?.y ?? 0) - $transform[1]) / $transform[2],
|
targetY: (($connection.position?.y ?? 0) - $transform[1]) / $transform[2],
|
||||||
targetPosition: toPosition
|
targetPosition: toPosition
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = '';
|
let path = '';
|
||||||
|
|
||||||
if ($connectionLineType === ConnectionLineType.Bezier) {
|
if ($connectionLineType === ConnectionLineType.Bezier) {
|
||||||
// we assume the destination position is opposite to the source position
|
// we assume the destination position is opposite to the source position
|
||||||
[path] = getBezierPath(pathParams);
|
[path] = getBezierPath(pathParams);
|
||||||
} else if ($connectionLineType === ConnectionLineType.Step) {
|
} else if ($connectionLineType === ConnectionLineType.Step) {
|
||||||
[path] = getSmoothStepPath({
|
[path] = getSmoothStepPath({
|
||||||
...pathParams,
|
...pathParams,
|
||||||
borderRadius: 0
|
borderRadius: 0
|
||||||
});
|
});
|
||||||
} else if ($connectionLineType === ConnectionLineType.SmoothStep) {
|
} else if ($connectionLineType === ConnectionLineType.SmoothStep) {
|
||||||
[path] = getSmoothStepPath(pathParams);
|
[path] = getSmoothStepPath(pathParams);
|
||||||
} else {
|
} else {
|
||||||
[path] = getStraightPath(pathParams);
|
[path] = getStraightPath(pathParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,56 +6,56 @@ import type { WrapEdgeProps } from '$lib/types';
|
|||||||
import type { SvelteFlowStoreState } from './types';
|
import type { SvelteFlowStoreState } from './types';
|
||||||
|
|
||||||
export function getEdgesLayouted(store: SvelteFlowStoreState) {
|
export function getEdgesLayouted(store: SvelteFlowStoreState) {
|
||||||
return derived([store.edges, store.nodes], ([$edges, $nodes]) => {
|
return derived([store.edges, store.nodes], ([$edges, $nodes]) => {
|
||||||
return $edges
|
return $edges
|
||||||
.map((edge) => {
|
.map((edge) => {
|
||||||
const sourceNode = $nodes.find((node) => node.id === edge.source);
|
const sourceNode = $nodes.find((node) => node.id === edge.source);
|
||||||
const targetNode = $nodes.find((node) => node.id === edge.target);
|
const targetNode = $nodes.find((node) => node.id === edge.target);
|
||||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
|
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
|
||||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
|
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
|
||||||
|
|
||||||
if (!sourceIsValid || !targetIsValid) {
|
if (!sourceIsValid || !targetIsValid) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const edgeType = edge.type || 'default';
|
const edgeType = edge.type || 'default';
|
||||||
|
|
||||||
const targetNodeHandles = targetHandleBounds!.target;
|
const targetNodeHandles = targetHandleBounds!.target;
|
||||||
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
|
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
|
||||||
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
|
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
|
||||||
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
||||||
const targetPosition = targetHandle?.position || Position.Top;
|
const targetPosition = targetHandle?.position || Position.Top;
|
||||||
|
|
||||||
if (!sourceHandle || !targetHandle) {
|
if (!sourceHandle || !targetHandle) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
||||||
sourceNodeRect,
|
sourceNodeRect,
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetNodeRect,
|
targetNodeRect,
|
||||||
targetHandle,
|
targetHandle,
|
||||||
targetPosition
|
targetPosition
|
||||||
);
|
);
|
||||||
|
|
||||||
// we nee to do this to match the types
|
// we nee to do this to match the types
|
||||||
const sourceHandleId = edge.sourceHandle;
|
const sourceHandleId = edge.sourceHandle;
|
||||||
const targetHandleId = edge.targetHandle;
|
const targetHandleId = edge.targetHandle;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...edge,
|
...edge,
|
||||||
type: edgeType,
|
type: edgeType,
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
sourceHandleId,
|
sourceHandleId,
|
||||||
targetHandleId
|
targetHandleId
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((e) => e !== null) as WrapEdgeProps[];
|
.filter((e) => e !== null) as WrapEdgeProps[];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import {
|
import {
|
||||||
type Transform,
|
type Transform,
|
||||||
type NodeDragItem,
|
type NodeDragItem,
|
||||||
type NodeDimensionUpdate,
|
type NodeDimensionUpdate,
|
||||||
internalsSymbol,
|
internalsSymbol,
|
||||||
type NodeOrigin,
|
type NodeOrigin,
|
||||||
type ViewportHelperFunctionOptions,
|
type ViewportHelperFunctionOptions,
|
||||||
type Node as RFNode,
|
type Node as RFNode,
|
||||||
type Connection,
|
type Connection,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
type CoordinateExtent
|
type CoordinateExtent
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
import {
|
import {
|
||||||
fitView as fitViewUtil,
|
fitView as fitViewUtil,
|
||||||
getConnectedEdges,
|
getConnectedEdges,
|
||||||
getD3Transition,
|
getD3Transition,
|
||||||
getDimensions,
|
getDimensions,
|
||||||
addEdge as addEdgeUtil
|
addEdge as addEdgeUtil
|
||||||
} from '@reactflow/utils';
|
} from '@reactflow/utils';
|
||||||
import { zoomIdentity } from 'd3-zoom';
|
import { zoomIdentity } from 'd3-zoom';
|
||||||
|
|
||||||
@@ -32,307 +32,307 @@ import type { SvelteFlowStore } from './types';
|
|||||||
export const key = Symbol();
|
export const key = Symbol();
|
||||||
|
|
||||||
type CreateStoreProps = {
|
type CreateStoreProps = {
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
transform?: Transform;
|
transform?: Transform;
|
||||||
nodeTypes?: NodeTypes;
|
nodeTypes?: NodeTypes;
|
||||||
edgeTypes?: EdgeTypes;
|
edgeTypes?: EdgeTypes;
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createStore({
|
export function createStore({
|
||||||
transform = [0, 0, 1],
|
transform = [0, 0, 1],
|
||||||
nodeOrigin = [0, 0],
|
nodeOrigin = [0, 0],
|
||||||
fitView: fitViewOnInit = false,
|
fitView: fitViewOnInit = false,
|
||||||
nodeTypes = {},
|
nodeTypes = {},
|
||||||
edgeTypes = {},
|
edgeTypes = {},
|
||||||
id = '1'
|
id = '1'
|
||||||
}: CreateStoreProps): SvelteFlowStore {
|
}: CreateStoreProps): SvelteFlowStore {
|
||||||
const store = {
|
const store = {
|
||||||
...initialStoreState
|
...initialStoreState
|
||||||
};
|
};
|
||||||
|
|
||||||
let fitViewOnInitDone = false;
|
let fitViewOnInitDone = false;
|
||||||
|
|
||||||
function setEdges(edges: Edge[]) {
|
function setEdges(edges: Edge[]) {
|
||||||
store.edges.set(edges);
|
store.edges.set(edges);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEdge(edgeParams: Edge | Connection) {
|
function addEdge(edgeParams: Edge | Connection) {
|
||||||
const edges = get(store.edges);
|
const edges = get(store.edges);
|
||||||
store.edges.set(addEdgeUtil(edgeParams, edges));
|
store.edges.set(addEdgeUtil(edgeParams, edges));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNodes(nodes: Node[]) {
|
function setNodes(nodes: Node[]) {
|
||||||
store.nodes.update((currentNodes) => {
|
store.nodes.update((currentNodes) => {
|
||||||
const nextNodes = nodes.map((n) => {
|
const nextNodes = nodes.map((n) => {
|
||||||
const currentNode = currentNodes.find((cn) => cn.id === n.id) || {};
|
const currentNode = currentNodes.find((cn) => cn.id === n.id) || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...currentNode,
|
...currentNode,
|
||||||
...n,
|
...n,
|
||||||
positionAbsolute: n.position
|
positionAbsolute: n.position
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return nextNodes;
|
return nextNodes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
|
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
|
||||||
store.nodes.update((nds) => {
|
store.nodes.update((nds) => {
|
||||||
return nds.map((n) => {
|
return nds.map((n) => {
|
||||||
const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id);
|
const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id);
|
||||||
|
|
||||||
if (nodeDragItem) {
|
if (nodeDragItem) {
|
||||||
return {
|
return {
|
||||||
...n,
|
...n,
|
||||||
dragging,
|
dragging,
|
||||||
positionAbsolute: nodeDragItem.positionAbsolute,
|
positionAbsolute: nodeDragItem.positionAbsolute,
|
||||||
position: nodeDragItem.position
|
position: nodeDragItem.position
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
|
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
|
||||||
const viewportNode = document?.querySelector('.react-flow__viewport');
|
const viewportNode = document?.querySelector('.react-flow__viewport');
|
||||||
|
|
||||||
if (!viewportNode) {
|
if (!viewportNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = window.getComputedStyle(viewportNode);
|
const style = window.getComputedStyle(viewportNode);
|
||||||
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
||||||
|
|
||||||
const nextNodes = get(store.nodes).map((node) => {
|
const nextNodes = get(store.nodes).map((node) => {
|
||||||
const update = updates.find((u) => u.id === node.id);
|
const update = updates.find((u) => u.id === node.id);
|
||||||
|
|
||||||
if (update) {
|
if (update) {
|
||||||
const dimensions = getDimensions(update.nodeElement);
|
const dimensions = getDimensions(update.nodeElement);
|
||||||
|
|
||||||
const doUpdate = !!(
|
const doUpdate = !!(
|
||||||
dimensions.width &&
|
dimensions.width &&
|
||||||
dimensions.height &&
|
dimensions.height &&
|
||||||
(node.width !== dimensions.width ||
|
(node.width !== dimensions.width ||
|
||||||
node.height !== dimensions.height ||
|
node.height !== dimensions.height ||
|
||||||
update.forceUpdate)
|
update.forceUpdate)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
node[internalsSymbol] = {
|
node[internalsSymbol] = {
|
||||||
...node[internalsSymbol],
|
...node[internalsSymbol],
|
||||||
handleBounds: {
|
handleBounds: {
|
||||||
source: getHandleBounds('.source', update.nodeElement, zoom),
|
source: getHandleBounds('.source', update.nodeElement, zoom),
|
||||||
target: getHandleBounds('.target', update.nodeElement, zoom)
|
target: getHandleBounds('.target', update.nodeElement, zoom)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
node.width = dimensions.width;
|
node.width = dimensions.width;
|
||||||
node.height = dimensions.height;
|
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 =
|
||||||
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
|
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
|
||||||
|
|
||||||
store.nodes.set(nextNodes);
|
store.nodes.set(nextNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomIn(options?: ViewportHelperFunctionOptions) {
|
function zoomIn(options?: ViewportHelperFunctionOptions) {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||||
|
|
||||||
if (d3Zoom && d3Selection) {
|
if (d3Zoom && d3Selection) {
|
||||||
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2);
|
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomOut(options?: ViewportHelperFunctionOptions) {
|
function zoomOut(options?: ViewportHelperFunctionOptions) {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||||
if (d3Zoom && d3Selection) {
|
if (d3Zoom && d3Selection) {
|
||||||
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2);
|
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitView() {
|
function fitView() {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||||
|
|
||||||
if (!d3Zoom || !d3Selection) {
|
if (!d3Zoom || !d3Selection) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fitViewUtil(
|
return fitViewUtil(
|
||||||
{
|
{
|
||||||
nodes: get(store.nodes) as RFNode[],
|
nodes: get(store.nodes) as RFNode[],
|
||||||
width: get(store.width),
|
width: get(store.width),
|
||||||
height: get(store.height),
|
height: get(store.height),
|
||||||
minZoom: 0.2,
|
minZoom: 0.2,
|
||||||
maxZoom: 2,
|
maxZoom: 2,
|
||||||
d3Selection,
|
d3Selection,
|
||||||
d3Zoom,
|
d3Zoom,
|
||||||
nodeOrigin: get(store.nodeOrigin)
|
nodeOrigin: get(store.nodeOrigin)
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSelectedItem<T extends Node | Edge>(item: T) {
|
function resetSelectedItem<T extends Node | Edge>(item: T) {
|
||||||
if (item.selected) {
|
if (item.selected) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
selected: false
|
selected: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSelectedElements() {
|
function resetSelectedElements() {
|
||||||
store.nodes.update((ns) => ns.map(resetSelectedItem));
|
store.nodes.update((ns) => ns.map(resetSelectedItem));
|
||||||
store.edges.update((es) => es.map(resetSelectedItem));
|
store.edges.update((es) => es.map(resetSelectedItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
store.deleteKeyPressed.subscribe((deleteKeyPressed) => {
|
store.deleteKeyPressed.subscribe((deleteKeyPressed) => {
|
||||||
if (deleteKeyPressed) {
|
if (deleteKeyPressed) {
|
||||||
const nodes = get(store.nodes);
|
const nodes = get(store.nodes);
|
||||||
const edges = get(store.edges);
|
const edges = get(store.edges);
|
||||||
const selectedNodes = nodes.filter((node) => node.selected);
|
const selectedNodes = nodes.filter((node) => node.selected);
|
||||||
const selectedEdges = edges.filter((edge) => edge.selected);
|
const selectedEdges = edges.filter((edge) => edge.selected);
|
||||||
|
|
||||||
// @todo can we put this stuff into @reactflow/utils?
|
// @todo can we put this stuff into @reactflow/utils?
|
||||||
const nodeIds = selectedNodes.map((node) => node.id);
|
const nodeIds = selectedNodes.map((node) => node.id);
|
||||||
const edgeIds = selectedEdges.map((edge) => edge.id);
|
const edgeIds = selectedEdges.map((edge) => edge.id);
|
||||||
const nodesToRemove = nodes.reduce<Node[]>((res, node) => {
|
const nodesToRemove = nodes.reduce<Node[]>((res, node) => {
|
||||||
const parentHit =
|
const parentHit =
|
||||||
!nodeIds.includes(node.id) &&
|
!nodeIds.includes(node.id) &&
|
||||||
node.parentNode &&
|
node.parentNode &&
|
||||||
res.find((n) => n.id === node.parentNode);
|
res.find((n) => n.id === node.parentNode);
|
||||||
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
|
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
|
||||||
if (deletable && (nodeIds.includes(node.id) || parentHit)) {
|
if (deletable && (nodeIds.includes(node.id) || parentHit)) {
|
||||||
res.push(node);
|
res.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
const deletableEdges = edges.filter((e) =>
|
const deletableEdges = edges.filter((e) =>
|
||||||
typeof e.deletable === 'boolean' ? e.deletable : true
|
typeof e.deletable === 'boolean' ? e.deletable : true
|
||||||
);
|
);
|
||||||
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
|
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
|
||||||
|
|
||||||
if (nodesToRemove || initialHitEdges) {
|
if (nodesToRemove || initialHitEdges) {
|
||||||
const connectedEdges = getConnectedEdges(nodesToRemove as RFNode[], deletableEdges);
|
const connectedEdges = getConnectedEdges(nodesToRemove as RFNode[], deletableEdges);
|
||||||
const edgesToRemove = [...initialHitEdges, ...connectedEdges];
|
const edgesToRemove = [...initialHitEdges, ...connectedEdges];
|
||||||
const edgeIdsToRemove = edgesToRemove.reduce<string[]>((res, edge) => {
|
const edgeIdsToRemove = edgesToRemove.reduce<string[]>((res, edge) => {
|
||||||
if (!res.includes(edge.id)) {
|
if (!res.includes(edge.id)) {
|
||||||
res.push(edge.id);
|
res.push(edge.id);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
store.nodes.update((nds) => nds.filter((node) => !nodeIds.includes(node.id)));
|
store.nodes.update((nds) => nds.filter((node) => !nodeIds.includes(node.id)));
|
||||||
store.edges.update((eds) => eds.filter((edge) => !edgeIdsToRemove.includes(edge.id)));
|
store.edges.update((eds) => eds.filter((edge) => !edgeIdsToRemove.includes(edge.id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function addSelectedNodes(ids: string[]) {
|
function addSelectedNodes(ids: string[]) {
|
||||||
store.selectionRect.set(null);
|
store.selectionRect.set(null);
|
||||||
store.selectionRectMode.set(null);
|
store.selectionRectMode.set(null);
|
||||||
|
|
||||||
if (get(store.multiselectionKeyPressed)) {
|
if (get(store.multiselectionKeyPressed)) {
|
||||||
// @todo handle multiselection key
|
// @todo handle multiselection key
|
||||||
}
|
}
|
||||||
|
|
||||||
store.nodes.update((ns) =>
|
store.nodes.update((ns) =>
|
||||||
ns.map((node) => {
|
ns.map((node) => {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
selected: ids.includes(node.id)
|
selected: ids.includes(node.id)
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function panBy(delta: XYPosition) {
|
function panBy(delta: XYPosition) {
|
||||||
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
|
||||||
const transform = get(store.transform);
|
const transform = get(store.transform);
|
||||||
const width = get(store.width);
|
const width = get(store.width);
|
||||||
const height = get(store.height);
|
const height = get(store.height);
|
||||||
|
|
||||||
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
|
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextTransform = zoomIdentity
|
const nextTransform = zoomIdentity
|
||||||
.translate(transform[0] + delta.x, transform[1] + delta.y)
|
.translate(transform[0] + delta.x, transform[1] + delta.y)
|
||||||
.scale(transform[2]);
|
.scale(transform[2]);
|
||||||
|
|
||||||
const extent: CoordinateExtent = [
|
const extent: CoordinateExtent = [
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[width, height]
|
[width, height]
|
||||||
];
|
];
|
||||||
|
|
||||||
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, [
|
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, [
|
||||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]
|
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]
|
||||||
]);
|
]);
|
||||||
d3Zoom.transform(d3Selection, constrainedTransform);
|
d3Zoom.transform(d3Selection, constrainedTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
|
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
|
||||||
const currentConnectionData = get(store.connection);
|
const currentConnectionData = get(store.connection);
|
||||||
const nextConnectionData = currentConnectionData
|
const nextConnectionData = currentConnectionData
|
||||||
? {
|
? {
|
||||||
...initConnectionData,
|
...initConnectionData,
|
||||||
...currentConnectionData,
|
...currentConnectionData,
|
||||||
...connectionUpdate
|
...connectionUpdate
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...initConnectionData,
|
...initConnectionData,
|
||||||
...connectionUpdate
|
...connectionUpdate
|
||||||
};
|
};
|
||||||
|
|
||||||
store.connection.set(nextConnectionData);
|
store.connection.set(nextConnectionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelConnection() {
|
function cancelConnection() {
|
||||||
updateConnection(initConnectionData);
|
updateConnection(initConnectionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// state
|
// state
|
||||||
...store,
|
...store,
|
||||||
|
|
||||||
// derived state
|
// derived state
|
||||||
edgesLayouted: getEdgesLayouted(store),
|
edgesLayouted: getEdgesLayouted(store),
|
||||||
connectionPath: getConnectionPath(store),
|
connectionPath: getConnectionPath(store),
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
setNodes,
|
setNodes,
|
||||||
setEdges,
|
setEdges,
|
||||||
addEdge,
|
addEdge,
|
||||||
updateNodePositions,
|
updateNodePositions,
|
||||||
updateNodeDimensions,
|
updateNodeDimensions,
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
fitView,
|
fitView,
|
||||||
resetSelectedElements,
|
resetSelectedElements,
|
||||||
addSelectedNodes,
|
addSelectedNodes,
|
||||||
panBy,
|
panBy,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
cancelConnection
|
cancelConnection
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useStore(): SvelteFlowStore {
|
export function useStore(): SvelteFlowStore {
|
||||||
const { getStore } = getContext<{ getStore: () => SvelteFlowStore }>(key);
|
const { getStore } = getContext<{ getStore: () => SvelteFlowStore }>(key);
|
||||||
|
|
||||||
return getStore();
|
return getStore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { readable, writable } from 'svelte/store';
|
import { readable, writable } from 'svelte/store';
|
||||||
import {
|
import {
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
type D3ZoomInstance,
|
type D3ZoomInstance,
|
||||||
type D3SelectionInstance,
|
type D3SelectionInstance,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
type Transform,
|
type Transform,
|
||||||
type NodeOrigin,
|
type NodeOrigin,
|
||||||
type Rect
|
type Rect
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
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';
|
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
|
||||||
|
|
||||||
export const initConnectionData = {
|
export const initConnectionData = {
|
||||||
nodeId: null,
|
nodeId: null,
|
||||||
handleId: null,
|
handleId: null,
|
||||||
handleType: null,
|
handleType: null,
|
||||||
position: null,
|
position: null,
|
||||||
status: null
|
status: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialStoreState = {
|
export const initialStoreState = {
|
||||||
nodes: writable<Node[]>([]),
|
nodes: writable<Node[]>([]),
|
||||||
edges: writable<Edge[]>([]),
|
edges: writable<Edge[]>([]),
|
||||||
edgesLayouted: readable<Edge[]>([]),
|
edgesLayouted: readable<Edge[]>([]),
|
||||||
height: writable<number>(500),
|
height: writable<number>(500),
|
||||||
width: writable<number>(500),
|
width: writable<number>(500),
|
||||||
nodeOrigin: writable<NodeOrigin>([0.5, 0.5]),
|
nodeOrigin: writable<NodeOrigin>([0.5, 0.5]),
|
||||||
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
|
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
|
||||||
zoom: null,
|
zoom: null,
|
||||||
selection: null
|
selection: null
|
||||||
}),
|
}),
|
||||||
id: writable<string | null>(null),
|
id: writable<string | null>(null),
|
||||||
dragging: writable<boolean>(false),
|
dragging: writable<boolean>(false),
|
||||||
selectionRect: writable<(Rect & { startX: number; startY: number }) | null>(null),
|
selectionRect: writable<(Rect & { startX: number; startY: number }) | null>(null),
|
||||||
selectionKeyPressed: writable<boolean>(false),
|
selectionKeyPressed: writable<boolean>(false),
|
||||||
multiselectionKeyPressed: writable<boolean>(false),
|
multiselectionKeyPressed: writable<boolean>(false),
|
||||||
deleteKeyPressed: writable<boolean>(false),
|
deleteKeyPressed: writable<boolean>(false),
|
||||||
selectionRectMode: writable<string | null>(null),
|
selectionRectMode: writable<string | null>(null),
|
||||||
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
|
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
|
||||||
nodeTypes: writable<NodeTypes>({
|
nodeTypes: writable<NodeTypes>({
|
||||||
input: InputNode,
|
input: InputNode,
|
||||||
output: OutputNode,
|
output: OutputNode,
|
||||||
default: DefaultNode
|
default: DefaultNode
|
||||||
}),
|
}),
|
||||||
edgeTypes: writable<EdgeTypes>({
|
edgeTypes: writable<EdgeTypes>({
|
||||||
straight: StraightEdge,
|
straight: StraightEdge,
|
||||||
smoothstep: SmoothStepEdge,
|
smoothstep: SmoothStepEdge,
|
||||||
default: BezierEdge
|
default: BezierEdge
|
||||||
}),
|
}),
|
||||||
transform: writable<Transform>([0, 0, 1]),
|
transform: writable<Transform>([0, 0, 1]),
|
||||||
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
|
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
|
||||||
domNode: writable<HTMLDivElement | null>(null),
|
domNode: writable<HTMLDivElement | null>(null),
|
||||||
connectionPath: readable<string | null>(null),
|
connectionPath: readable<string | null>(null),
|
||||||
connection: writable<ConnectionData>(initConnectionData),
|
connection: writable<ConnectionData>(initConnectionData),
|
||||||
connectionRadius: writable<number>(25),
|
connectionRadius: writable<number>(25),
|
||||||
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier)
|
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier)
|
||||||
};
|
};
|
||||||
|
|||||||
+22
-22
@@ -1,32 +1,32 @@
|
|||||||
import type {
|
import type {
|
||||||
NodeDimensionUpdate,
|
NodeDimensionUpdate,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
ViewportHelperFunctionOptions,
|
ViewportHelperFunctionOptions,
|
||||||
Connection,
|
Connection,
|
||||||
NodeDragItem
|
NodeDragItem
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
import { initialStoreState } from './initial-store';
|
import { initialStoreState } from './initial-store';
|
||||||
import type { Node, Edge, ConnectionData } from '$lib/types';
|
import type { Node, Edge, ConnectionData } from '$lib/types';
|
||||||
|
|
||||||
export type SvelteFlowStoreActions = {
|
export type SvelteFlowStoreActions = {
|
||||||
setNodes: (nodes: Node[]) => void;
|
setNodes: (nodes: Node[]) => void;
|
||||||
setEdges: (edges: Edge[]) => void;
|
setEdges: (edges: Edge[]) => void;
|
||||||
addEdge: (edge: Edge | Connection) => void;
|
addEdge: (edge: Edge | Connection) => void;
|
||||||
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
|
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
|
||||||
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
|
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
|
||||||
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
|
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
|
||||||
updateNodePositions: (
|
updateNodePositions: (
|
||||||
nodeDragItems: NodeDragItem[],
|
nodeDragItems: NodeDragItem[],
|
||||||
positionChanged?: boolean,
|
positionChanged?: boolean,
|
||||||
dragging?: boolean
|
dragging?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||||
resetSelectedElements: () => void;
|
resetSelectedElements: () => void;
|
||||||
addSelectedNodes: (ids: string[]) => void;
|
addSelectedNodes: (ids: string[]) => void;
|
||||||
panBy: (delta: XYPosition) => void;
|
panBy: (delta: XYPosition) => void;
|
||||||
updateConnection: (connection: Partial<ConnectionData>) => void;
|
updateConnection: (connection: Partial<ConnectionData>) => void;
|
||||||
cancelConnection: () => void;
|
cancelConnection: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SvelteFlowStoreState = typeof initialStoreState;
|
export type SvelteFlowStoreState = typeof initialStoreState;
|
||||||
|
|||||||
@@ -7,25 +7,25 @@ export type KeyDefinitionObject = { key: string; modifier?: KeyModifier };
|
|||||||
export type KeyDefinition = string | KeyDefinitionObject;
|
export type KeyDefinition = string | KeyDefinitionObject;
|
||||||
|
|
||||||
export type ConnectionData = {
|
export type ConnectionData = {
|
||||||
position: XYPosition | null;
|
position: XYPosition | null;
|
||||||
nodeId: string | null;
|
nodeId: string | null;
|
||||||
handleId: string | null;
|
handleId: string | null;
|
||||||
handleType: HandleType | null;
|
handleType: HandleType | null;
|
||||||
status: string | null;
|
status: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConnectionLineProps = {};
|
export type ConnectionLineProps = {};
|
||||||
|
|
||||||
export type SvelteFlowProps = {
|
export type SvelteFlowProps = {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
|
|
||||||
connectionLineType?: ConnectionLineType;
|
connectionLineType?: ConnectionLineType;
|
||||||
selectionKey?: KeyDefinition;
|
selectionKey?: KeyDefinition;
|
||||||
deleteKey?: KeyDefinition;
|
deleteKey?: KeyDefinition;
|
||||||
nodeTypes?: NodeTypes;
|
nodeTypes?: NodeTypes;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
class?: string;
|
class?: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export type {
|
export type {
|
||||||
Position,
|
Position,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
Edge,
|
Edge,
|
||||||
BaseEdgeProps,
|
BaseEdgeProps,
|
||||||
WrapEdgeProps,
|
WrapEdgeProps,
|
||||||
EdgeProps
|
EdgeProps
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
export * from './nodes';
|
export * from './nodes';
|
||||||
|
|||||||
@@ -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
|
// we need a base node type or helpes that accept Node like types
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type Node<NodeData = any> = {
|
export type Node<NodeData = any> = {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
data: NodeData;
|
data: NodeData;
|
||||||
position: XYPosition;
|
position: XYPosition;
|
||||||
sourcePosition?: Position;
|
sourcePosition?: Position;
|
||||||
targetPosition?: Position;
|
targetPosition?: Position;
|
||||||
positionAbsolute?: XYPosition;
|
positionAbsolute?: XYPosition;
|
||||||
|
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
class?: string;
|
class?: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
deletable?: boolean;
|
deletable?: boolean;
|
||||||
// not supported yet
|
// not supported yet
|
||||||
parentNode?: string;
|
parentNode?: string;
|
||||||
|
|
||||||
// only used internally
|
// only used internally
|
||||||
[internalsSymbol]?: {
|
[internalsSymbol]?: {
|
||||||
z?: number;
|
z?: number;
|
||||||
handleBounds?: NodeHandleBounds;
|
handleBounds?: NodeHandleBounds;
|
||||||
isParent?: boolean;
|
isParent?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type NodeProps<NodeData = any> = Pick<
|
export type NodeProps<NodeData = any> = Pick<
|
||||||
Node<NodeData>,
|
Node<NodeData>,
|
||||||
'id' | 'data' | 'selected' | 'sourcePosition' | 'targetPosition'
|
'id' | 'data' | 'selected' | 'sourcePosition' | 'targetPosition'
|
||||||
> & {
|
> & {
|
||||||
xPos: number;
|
xPos: number;
|
||||||
yPos: number;
|
yPos: number;
|
||||||
isConnectable?: boolean;
|
isConnectable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WrapNodeProps = Node & {
|
export type WrapNodeProps = Node & {
|
||||||
resizeObserver?: ResizeObserver | null;
|
resizeObserver?: ResizeObserver | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeTypes = Record<string, typeof SvelteComponentTyped<Partial<NodeProps>>>;
|
export type NodeTypes = Record<string, typeof SvelteComponentTyped<Partial<NodeProps>>>;
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node, Edge, NodeTypes } from '../lib/types';
|
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';
|
import CustomNode from '../customnodes/Custom.svelte';
|
||||||
|
|
||||||
const nodeTypes: NodeTypes = {
|
const nodeTypes: NodeTypes = {
|
||||||
custom: CustomNode,
|
custom: CustomNode
|
||||||
};
|
};
|
||||||
|
|
||||||
// const yNodes = 10;
|
// const yNodes = 10;
|
||||||
@@ -13,8 +19,6 @@
|
|||||||
// const nodes: Node[] = [];
|
// const nodes: Node[] = [];
|
||||||
// const edges: Edge[] = [];
|
// const edges: Edge[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// let source = null;
|
// let source = null;
|
||||||
|
|
||||||
// for (let y = 0; y < yNodes; y++) {
|
// for (let y = 0; y < yNodes; y++) {
|
||||||
@@ -43,60 +47,69 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let nodes: Node<{ label: string }>[] = [{
|
let nodes: Node<{ label: string }>[] = [
|
||||||
id: '1',
|
{
|
||||||
type: 'input',
|
id: '1',
|
||||||
data: { label: 'Input Node' },
|
type: 'input',
|
||||||
position: { x: 150, y: 5 }
|
data: { label: 'Input Node' },
|
||||||
}, {
|
position: { x: 150, y: 5 }
|
||||||
id: '2',
|
},
|
||||||
type: 'default',
|
{
|
||||||
data: { label: 'Node' },
|
id: '2',
|
||||||
position: { x: 0, y: 150 },
|
type: 'default',
|
||||||
}, {
|
data: { label: 'Node' },
|
||||||
id: 'A',
|
position: { x: 0, y: 150 }
|
||||||
type: 'default',
|
},
|
||||||
data: { label: 'Styled with class' },
|
{
|
||||||
class: 'custom-style',
|
id: 'A',
|
||||||
position: { x: 150, y: 150 },
|
type: 'default',
|
||||||
}, {
|
data: { label: 'Styled with class' },
|
||||||
id: '3',
|
class: 'custom-style',
|
||||||
type: 'output',
|
position: { x: 150, y: 150 }
|
||||||
data: { label: 'Output Node' },
|
},
|
||||||
position: { x: 300, y: 150 },
|
{
|
||||||
},
|
id: '3',
|
||||||
{
|
type: 'output',
|
||||||
id: 'B',
|
data: { label: 'Output Node' },
|
||||||
type: 'default',
|
position: { x: 300, y: 150 }
|
||||||
data: { label: 'Styled with style' },
|
},
|
||||||
style: 'border: 2px solid #ff5050;',
|
{
|
||||||
position: { x: 450, y: 150 },
|
id: 'B',
|
||||||
},
|
type: 'default',
|
||||||
{
|
data: { label: 'Styled with style' },
|
||||||
id: '4',
|
style: 'border: 2px solid #ff5050;',
|
||||||
type: 'custom',
|
position: { x: 450, y: 150 }
|
||||||
data: { label: 'Custom Node' },
|
},
|
||||||
position: { x: 150, y: 300 },
|
{
|
||||||
}];
|
id: '4',
|
||||||
|
type: 'custom',
|
||||||
|
data: { label: 'Custom Node' },
|
||||||
|
position: { x: 150, y: 300 }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
let edges: Edge[] = [{
|
let edges: Edge[] = [
|
||||||
id: '1-2',
|
{
|
||||||
type: 'default',
|
id: '1-2',
|
||||||
source: '1',
|
type: 'default',
|
||||||
target: '2',
|
source: '1',
|
||||||
label: 'Edge Text'
|
target: '2',
|
||||||
}, {
|
label: 'Edge Text'
|
||||||
id: '1-3',
|
},
|
||||||
type: 'smoothstep',
|
{
|
||||||
source: '1',
|
id: '1-3',
|
||||||
target: '3'
|
type: 'smoothstep',
|
||||||
}, {
|
source: '1',
|
||||||
id: '2-4',
|
target: '3'
|
||||||
type: 'default',
|
},
|
||||||
source: '2',
|
{
|
||||||
target: '4',
|
id: '2-4',
|
||||||
animated: true
|
type: 'default',
|
||||||
}];
|
source: '2',
|
||||||
|
target: '4',
|
||||||
|
animated: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
function updateNode() {
|
function updateNode() {
|
||||||
nodes[0].position = { x: nodes[0].position.x + 20, y: nodes[0].position.y };
|
nodes[0].position = { x: nodes[0].position.x + 20, y: nodes[0].position.y };
|
||||||
|
|||||||
@@ -1,97 +1,97 @@
|
|||||||
import {
|
import {
|
||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type Dimensions,
|
type Dimensions,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
type Node,
|
type Node,
|
||||||
type XYZPosition,
|
type XYZPosition,
|
||||||
type HandleElement,
|
type HandleElement,
|
||||||
internalsSymbol,
|
internalsSymbol,
|
||||||
Position
|
Position
|
||||||
} from '@reactflow/system';
|
} from '@reactflow/system';
|
||||||
|
|
||||||
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max);
|
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) => ({
|
export const clampPosition = (position: XYPosition = { x: 0, y: 0 }, extent: CoordinateExtent) => ({
|
||||||
x: clamp(position.x, extent[0][0], extent[1][0]),
|
x: clamp(position.x, extent[0][0], extent[1][0]),
|
||||||
y: clamp(position.y, extent[0][1], extent[1][1])
|
y: clamp(position.y, extent[0][1], extent[1][1])
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isNumeric = (n: any): n is number => !isNaN(n) && isFinite(n);
|
export const isNumeric = (n: any): n is number => !isNaN(n) && isFinite(n);
|
||||||
|
|
||||||
export const getDimensions = (node: HTMLDivElement): Dimensions => ({
|
export const getDimensions = (node: HTMLDivElement): Dimensions => ({
|
||||||
width: node.offsetWidth,
|
width: node.offsetWidth,
|
||||||
height: node.offsetHeight
|
height: node.offsetHeight
|
||||||
});
|
});
|
||||||
|
|
||||||
type ParentNodes = Record<string, boolean>;
|
type ParentNodes = Record<string, boolean>;
|
||||||
|
|
||||||
function calculateXYZPosition(node: Node, nodes: Node[], result: XYZPosition): XYZPosition {
|
function calculateXYZPosition(node: Node, nodes: Node[], result: XYZPosition): XYZPosition {
|
||||||
if (!node.parentNode) {
|
if (!node.parentNode) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const parentNode = nodes.find((n) => n.id === node.parentNode)!;
|
const parentNode = nodes.find((n) => n.id === node.parentNode)!;
|
||||||
const parentNodePosition = parentNode.positionAbsolute!;
|
const parentNodePosition = parentNode.positionAbsolute!;
|
||||||
|
|
||||||
return calculateXYZPosition(parentNode, nodes, {
|
return calculateXYZPosition(parentNode, nodes, {
|
||||||
x: (result.x ?? 0) + parentNodePosition.x,
|
x: (result.x ?? 0) + parentNodePosition.x,
|
||||||
y: (result.y ?? 0) + parentNodePosition.y,
|
y: (result.y ?? 0) + parentNodePosition.y,
|
||||||
z:
|
z:
|
||||||
(parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0)
|
(parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0)
|
||||||
? parentNode[internalsSymbol]?.z ?? 0
|
? parentNode[internalsSymbol]?.z ?? 0
|
||||||
: result.z ?? 0
|
: result.z ?? 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateAbsoluteNodePositions(nodes: Node[], parentNodes?: ParentNodes) {
|
export function updateAbsoluteNodePositions(nodes: Node[], parentNodes?: ParentNodes) {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
if (node.parentNode) {
|
if (node.parentNode) {
|
||||||
throw new Error(`Parent node ${node.parentNode} not found`);
|
throw new Error(`Parent node ${node.parentNode} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.parentNode || parentNodes?.[node.id]) {
|
if (node.parentNode || parentNodes?.[node.id]) {
|
||||||
const { x, y, z } = calculateXYZPosition(node, nodes, {
|
const { x, y, z } = calculateXYZPosition(node, nodes, {
|
||||||
...node.position,
|
...node.position,
|
||||||
z: node[internalsSymbol]?.z ?? 0
|
z: node[internalsSymbol]?.z ?? 0
|
||||||
});
|
});
|
||||||
|
|
||||||
node.positionAbsolute = {
|
node.positionAbsolute = {
|
||||||
x,
|
x,
|
||||||
y
|
y
|
||||||
};
|
};
|
||||||
|
|
||||||
node[internalsSymbol]!.z = z;
|
node[internalsSymbol]!.z = z;
|
||||||
|
|
||||||
if (parentNodes?.[node.id]) {
|
if (parentNodes?.[node.id]) {
|
||||||
node[internalsSymbol]!.isParent = true;
|
node[internalsSymbol]!.isParent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHandleBounds = (
|
export const getHandleBounds = (
|
||||||
selector: string,
|
selector: string,
|
||||||
nodeElement: HTMLDivElement,
|
nodeElement: HTMLDivElement,
|
||||||
zoom: number
|
zoom: number
|
||||||
): HandleElement[] | null => {
|
): HandleElement[] | null => {
|
||||||
const handles = nodeElement.querySelectorAll(selector);
|
const handles = nodeElement.querySelectorAll(selector);
|
||||||
|
|
||||||
if (!handles || !handles.length) {
|
if (!handles || !handles.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
||||||
const nodeBounds = nodeElement.getBoundingClientRect();
|
const nodeBounds = nodeElement.getBoundingClientRect();
|
||||||
|
|
||||||
return handlesArray.map((handle): HandleElement => {
|
return handlesArray.map((handle): HandleElement => {
|
||||||
const handleBounds = handle.getBoundingClientRect();
|
const handleBounds = handle.getBoundingClientRect();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: handle.getAttribute('data-handleid'),
|
id: handle.getAttribute('data-handleid'),
|
||||||
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
||||||
x: (handleBounds.left - nodeBounds.left) / zoom,
|
x: (handleBounds.left - nodeBounds.left) / zoom,
|
||||||
y: (handleBounds.top - nodeBounds.top) / zoom,
|
y: (handleBounds.top - nodeBounds.top) / zoom,
|
||||||
...getDimensions(handle)
|
...getDimensions(handle)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
|
|||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
}
|
}
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
// 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
|
// 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
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
|||||||
import type { UserConfig } from 'vite';
|
import type { UserConfig } from 'vite';
|
||||||
|
|
||||||
const config: UserConfig = {
|
const config: UserConfig = {
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()]
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
Reference in New Issue
Block a user