Merge pull request #3482 from wbkd/feat/ssr
feat(react/svelte): add ssr support
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Astro examples
|
||||||
|
|
||||||
|
Tiny app for testing React Flow and Svelte Flow with Astro.
|
||||||
|
|
||||||
|
## Start local dev server
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import react from '@astrojs/react';
|
||||||
|
import svelte from '@astrojs/svelte';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [react(), svelte()],
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "astro",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"start": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/react": "^3.0.2",
|
||||||
|
"@astrojs/svelte": "^4.0.2",
|
||||||
|
"@types/react": "^18.2.24",
|
||||||
|
"@types/react-dom": "^18.2.8",
|
||||||
|
"@xyflow/react": "workspace:^",
|
||||||
|
"@xyflow/svelte": "workspace:^",
|
||||||
|
"astro": "^3.2.2",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"svelte": "^4.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { memo, type FC, type CSSProperties } from 'react';
|
||||||
|
import { Handle, Position, type NodeProps } from '@xyflow/react';
|
||||||
|
|
||||||
|
const sourceHandleStyleA: CSSProperties = { left: 50 };
|
||||||
|
const sourceHandleStyleB: CSSProperties = {
|
||||||
|
right: 50,
|
||||||
|
left: 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomNode: FC<NodeProps> = ({ data, xPos, yPos }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Handle type="target" position={Position.Top} />
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Label: <strong>{data.label}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Position:{' '}
|
||||||
|
<strong>
|
||||||
|
{xPos.toFixed(2)},{yPos.toFixed(2)}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle type="source" position={Position.Bottom} id="a" style={sourceHandleStyleA} />
|
||||||
|
<Handle type="source" position={Position.Bottom} id="b" style={sourceHandleStyleB} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CustomNode);
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
ReactFlow,
|
||||||
|
addEdge,
|
||||||
|
useEdgesState,
|
||||||
|
useNodesState,
|
||||||
|
Background,
|
||||||
|
Controls,
|
||||||
|
type Connection,
|
||||||
|
type Edge,
|
||||||
|
type Node,
|
||||||
|
Position,
|
||||||
|
} from '@xyflow/react';
|
||||||
|
|
||||||
|
import CustomNode from './CustomNode';
|
||||||
|
|
||||||
|
import '@xyflow/react/dist/style.css';
|
||||||
|
|
||||||
|
const nodeSize = {
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialNodes: Node[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'input',
|
||||||
|
data: { label: 'Node 1' },
|
||||||
|
position: { x: 250, y: 5 },
|
||||||
|
size: nodeSize,
|
||||||
|
handles: [
|
||||||
|
{
|
||||||
|
type: 'source',
|
||||||
|
position: Position.Bottom,
|
||||||
|
x: nodeSize.width * 0.5,
|
||||||
|
y: nodeSize.height,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
data: { label: 'Node 2' },
|
||||||
|
position: { x: 100, y: 100 },
|
||||||
|
size: nodeSize,
|
||||||
|
handles: [
|
||||||
|
{
|
||||||
|
type: 'source',
|
||||||
|
position: Position.Bottom,
|
||||||
|
x: nodeSize.width * 0.5,
|
||||||
|
y: nodeSize.height,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'target',
|
||||||
|
position: Position.Top,
|
||||||
|
x: nodeSize.width * 0.5,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
data: { label: 'Node 3' },
|
||||||
|
position: { x: 400, y: 100 },
|
||||||
|
size: nodeSize,
|
||||||
|
handles: [
|
||||||
|
{
|
||||||
|
type: 'source',
|
||||||
|
position: Position.Bottom,
|
||||||
|
x: nodeSize.width * 0.5,
|
||||||
|
y: nodeSize.height,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'target',
|
||||||
|
position: Position.Top,
|
||||||
|
x: nodeSize.width * 0.5,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
data: { label: 'Node 4' },
|
||||||
|
position: { x: 400, y: 200 },
|
||||||
|
type: 'custom',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialEdges: Edge[] = [
|
||||||
|
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||||
|
{ id: 'e1-3', source: '1', target: '3', animated: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = {
|
||||||
|
custom: CustomNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
function Flow() {
|
||||||
|
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: 700, height: 400 }}>
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
edges={edges}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onConnect={onConnect}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
fitView
|
||||||
|
width={700}
|
||||||
|
height={400}
|
||||||
|
>
|
||||||
|
<Background />
|
||||||
|
<Controls />
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Flow;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import { SvelteFlow, Controls, Background, BackgroundVariant, Position, type Node, type Edge } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
|
const nodes = writable<Node[]>([
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
position: { x: 0, y: 150 },
|
||||||
|
data: { label: 'Node 0' },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
size: {
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
handles: [
|
||||||
|
{ type: 'source', x: 100, y: 20, position: Position.Right },
|
||||||
|
{ type: 'target', x: 0, y: 20, position: Position.Left },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'A',
|
||||||
|
position: { x: 250, y: 0 },
|
||||||
|
data: { label: 'A' },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
size: {
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
handles: [
|
||||||
|
{ type: 'source', x: 100, y: 20, position: Position.Right },
|
||||||
|
{ type: 'target', x: 0, y: 20, position: Position.Left },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'B',
|
||||||
|
position: { x: 250, y: 150 },
|
||||||
|
data: { label: 'B' },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
size: {
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
handles: [
|
||||||
|
{ type: 'source', x: 100, y: 20, position: Position.Right },
|
||||||
|
{ type: 'target', x: 0, y: 20, position: Position.Left },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'C',
|
||||||
|
position: { x: 250, y: 300 },
|
||||||
|
data: { label: 'C' },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
size: {
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
handles: [
|
||||||
|
{ type: 'source', x: 100, y: 20, position: Position.Right },
|
||||||
|
{ type: 'target', x: 0, y: 20, position: Position.Left },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const edges = writable<Edge[]>([
|
||||||
|
{ id: '0A', source: '0', target: 'A', animated: true },
|
||||||
|
{ id: '0B', source: '0', target: 'B', animated: true },
|
||||||
|
{ id: '0C', source: '0', target: 'C', animated: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const defaultEdgeOptions = {
|
||||||
|
animated: true,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="height: 400px; width: 700px;">
|
||||||
|
<SvelteFlow {nodes} {edges} fitView {defaultEdgeOptions} width={700} height={400}>
|
||||||
|
<Controls />
|
||||||
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
|
</SvelteFlow>
|
||||||
|
</div>
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="astro/client" />
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
import ReactFlowApp from '../components/ReactFlowExample'
|
||||||
|
import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte'
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>Astro example for React Flow and Svelte Flow</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin:0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Svelte Flow</h2>
|
||||||
|
<SvelteFlowApp />
|
||||||
|
|
||||||
|
<h2>React Flow</h2>
|
||||||
|
<ReactFlowApp />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ const DnDFlow = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.dndflow}>
|
<div className={styles.dndflow}>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider nodes={initialNodes} edges={[]}>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -77,7 +77,6 @@ const DnDFlow = () => {
|
|||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
nodeOrigin={nodeOrigin}
|
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ export function getNodesAndEdges(xElements = 10, yElements = 10): ElementsCollec
|
|||||||
const data = { label: `Node ${nodeId}` };
|
const data = { label: `Node ${nodeId}` };
|
||||||
const node = {
|
const node = {
|
||||||
id: nodeId.toString(),
|
id: nodeId.toString(),
|
||||||
style: { width: 50, fontSize: 11 },
|
style: { width: 50, height: 30, fontSize: 11 },
|
||||||
data,
|
data,
|
||||||
position,
|
position,
|
||||||
|
width: 50,
|
||||||
|
height: 30,
|
||||||
};
|
};
|
||||||
initialNodes.push(node);
|
initialNodes.push(node);
|
||||||
|
|
||||||
|
|||||||
@@ -204,9 +204,6 @@ const Subflow = () => {
|
|||||||
onNodeDrag={onNodeDrag}
|
onNodeDrag={onNodeDrag}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
className="react-flow-basic-example"
|
className="react-flow-basic-example"
|
||||||
defaultViewport={defaultViewport}
|
|
||||||
minZoom={0.2}
|
|
||||||
maxZoom={4}
|
|
||||||
onlyRenderVisibleElements={false}
|
onlyRenderVisibleElements={false}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
fitView
|
fitView
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"dev": "turbo run dev --parallel",
|
"dev": "turbo run dev --parallel --concurrency 12",
|
||||||
"dev:svelte": "turbo run dev --filter=svelte --filter=system",
|
"dev:svelte": "turbo run dev --filter=svelte --filter=system",
|
||||||
"dev:react": "turbo run dev --filter=react",
|
"dev:react": "turbo run dev --filter=react",
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { memo, useEffect, useState, type FC, type PropsWithChildren } from 'react';
|
import { memo, type FC, type PropsWithChildren } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
@@ -37,18 +37,9 @@ const Controls: FC<PropsWithChildren<ControlProps>> = ({
|
|||||||
position = 'bottom-left',
|
position = 'bottom-left',
|
||||||
}) => {
|
}) => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
|
||||||
const { isInteractive, minZoomReached, maxZoomReached } = useStore(selector, shallow);
|
const { isInteractive, minZoomReached, maxZoomReached } = useStore(selector, shallow);
|
||||||
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsVisible(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onZoomInHandler = () => {
|
const onZoomInHandler = () => {
|
||||||
zoomIn();
|
zoomIn();
|
||||||
onZoomIn?.();
|
onZoomIn?.();
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
disableKeyboardA11y,
|
disableKeyboardA11y,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
rfId,
|
rfId,
|
||||||
|
sizeWidth,
|
||||||
|
sizeHeight,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const nodeRef = useRef<HTMLDivElement>(null);
|
const nodeRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -183,6 +185,8 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
transform: `translate(${xPosOrigin}px,${yPosOrigin}px)`,
|
transform: `translate(${xPosOrigin}px,${yPosOrigin}px)`,
|
||||||
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
||||||
visibility: initialized ? 'visible' : 'hidden',
|
visibility: initialized ? 'visible' : 'hidden',
|
||||||
|
width: sizeWidth,
|
||||||
|
height: sizeHeight,
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
|
|||||||
@@ -1,20 +1,40 @@
|
|||||||
import { useRef, type FC, type PropsWithChildren } from 'react';
|
import { useRef, type ReactNode } from 'react';
|
||||||
import { type StoreApi } from 'zustand';
|
import { type StoreApi } from 'zustand';
|
||||||
import { UseBoundStoreWithEqualityFn } from 'zustand/traditional';
|
import { UseBoundStoreWithEqualityFn } from 'zustand/traditional';
|
||||||
|
|
||||||
import { Provider } from '../../contexts/RFStoreContext';
|
import { Provider } from '../../contexts/RFStoreContext';
|
||||||
import { createRFStore } from '../../store';
|
import { createRFStore } from '../../store';
|
||||||
import type { ReactFlowState } from '../../types';
|
import type { ReactFlowState, Node, Edge } from '../../types';
|
||||||
|
|
||||||
const ReactFlowProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
function ReactFlowProvider({
|
||||||
|
children,
|
||||||
|
initialNodes,
|
||||||
|
initialEdges,
|
||||||
|
initialWidth,
|
||||||
|
initialHeight,
|
||||||
|
fitView,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
initialNodes?: Node[];
|
||||||
|
initialEdges?: Edge[];
|
||||||
|
initialWidth?: number;
|
||||||
|
initialHeight?: number;
|
||||||
|
fitView?: boolean;
|
||||||
|
}) {
|
||||||
const storeRef = useRef<UseBoundStoreWithEqualityFn<StoreApi<ReactFlowState>> | null>(null);
|
const storeRef = useRef<UseBoundStoreWithEqualityFn<StoreApi<ReactFlowState>> | null>(null);
|
||||||
|
|
||||||
if (!storeRef.current) {
|
if (!storeRef.current) {
|
||||||
storeRef.current = createRFStore();
|
storeRef.current = createRFStore({
|
||||||
|
nodes: initialNodes,
|
||||||
|
edges: initialEdges,
|
||||||
|
width: initialWidth,
|
||||||
|
height: initialHeight,
|
||||||
|
fitView,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Provider value={storeRef.current}>{children}</Provider>;
|
return <Provider value={storeRef.current}>{children}</Provider>;
|
||||||
};
|
}
|
||||||
|
|
||||||
ReactFlowProvider.displayName = 'ReactFlowProvider';
|
ReactFlowProvider.displayName = 'ReactFlowProvider';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { StoreApi } from 'zustand';
|
import { StoreApi } from 'zustand';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import type { CoordinateExtent } from '@xyflow/system';
|
import { devWarn, type CoordinateExtent } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||||
import type { Node, Edge, ReactFlowState, ReactFlowProps, ReactFlowStore } from '../../types';
|
import type { Node, Edge, ReactFlowState, ReactFlowProps, ReactFlowStore } from '../../types';
|
||||||
@@ -201,7 +201,7 @@ const StoreUpdater = ({
|
|||||||
useDirectStoreUpdater('rfId', rfId, store.setState);
|
useDirectStoreUpdater('rfId', rfId, store.setState);
|
||||||
useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState);
|
useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState);
|
||||||
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
||||||
useDirectStoreUpdater('onError', onError, store.setState);
|
useDirectStoreUpdater('onError', onError || devWarn, store.setState);
|
||||||
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
|
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
|
||||||
useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState);
|
useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState);
|
||||||
useDirectStoreUpdater('selectNodesOnDrag', selectNodesOnDrag, store.setState);
|
useDirectStoreUpdater('selectNodesOnDrag', selectNodesOnDrag, store.setState);
|
||||||
|
|||||||
@@ -62,23 +62,13 @@ const EdgeRenderer = ({
|
|||||||
onEdgeUpdateEnd,
|
onEdgeUpdateEnd,
|
||||||
children,
|
children,
|
||||||
}: EdgeRendererProps) => {
|
}: EdgeRendererProps) => {
|
||||||
const { width, height, edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
|
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
|
||||||
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect);
|
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, elevateEdgesOnSelect);
|
||||||
|
|
||||||
if (!width) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{edgeTree.map(({ level, edges, isMaxLevel }) => (
|
{edgeTree.map(({ level, edges, isMaxLevel }) => (
|
||||||
<svg
|
<svg key={level} style={{ zIndex: level }} className="react-flow__edges react-flow__container">
|
||||||
key={level}
|
|
||||||
style={{ zIndex: level }}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
className="react-flow__edges react-flow__container"
|
|
||||||
>
|
|
||||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
|
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
|
||||||
<g>
|
<g>
|
||||||
{edges.map((edge) => {
|
{edges.map((edge) => {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any
|
|||||||
const typesParsed = useMemo(() => {
|
const typesParsed = useMemo(() => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
const typeKeys = Object.keys(nodeOrEdgeTypes);
|
const typeKeys = Object.keys(nodeOrEdgeTypes);
|
||||||
|
|
||||||
if (shallow(typesKeysRef.current, typeKeys)) {
|
if (shallow(typesKeysRef.current, typeKeys)) {
|
||||||
store.getState().onError?.('002', errorMessages['error002']());
|
store.getState().onError?.('002', errorMessages['error002']());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
height: node.height ?? 0,
|
height: node.height ?? 0,
|
||||||
origin: node.origin || props.nodeOrigin,
|
origin: node.origin || props.nodeOrigin,
|
||||||
});
|
});
|
||||||
|
const initialized = (!!node.width && !!node.height) || (!!node.size?.width && !!node.size?.height);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeComponent
|
<NodeComponent
|
||||||
@@ -105,6 +106,8 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
id={node.id}
|
id={node.id}
|
||||||
className={node.className}
|
className={node.className}
|
||||||
style={node.style}
|
style={node.style}
|
||||||
|
sizeWidth={node.size?.width}
|
||||||
|
sizeHeight={node.size?.height}
|
||||||
type={nodeType}
|
type={nodeType}
|
||||||
data={node.data}
|
data={node.data}
|
||||||
sourcePosition={node.sourcePosition || Position.Bottom}
|
sourcePosition={node.sourcePosition || Position.Bottom}
|
||||||
@@ -131,7 +134,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
isParent={!!node[internalsSymbol]?.isParent}
|
isParent={!!node[internalsSymbol]?.isParent}
|
||||||
noDragClassName={props.noDragClassName}
|
noDragClassName={props.noDragClassName}
|
||||||
noPanClassName={props.noPanClassName}
|
noPanClassName={props.noPanClassName}
|
||||||
initialized={!!node.width && !!node.height}
|
initialized={initialized}
|
||||||
rfId={props.rfId}
|
rfId={props.rfId}
|
||||||
disableKeyboardA11y={props.disableKeyboardA11y}
|
disableKeyboardA11y={props.disableKeyboardA11y}
|
||||||
ariaLabel={node.ariaLabel}
|
ariaLabel={node.ariaLabel}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { useContext, type FC, type PropsWithChildren } from 'react';
|
import { useContext, type ReactNode } from 'react';
|
||||||
|
|
||||||
import StoreContext from '../../contexts/RFStoreContext';
|
import StoreContext from '../../contexts/RFStoreContext';
|
||||||
import ReactFlowProvider from '../../components/ReactFlowProvider';
|
import ReactFlowProvider from '../../components/ReactFlowProvider';
|
||||||
|
import type { Node, Edge } from '../../types';
|
||||||
|
|
||||||
const Wrapper: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
function Wrapper({
|
||||||
|
children,
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fitView,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
nodes?: Node[];
|
||||||
|
edges?: Edge[];
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fitView?: boolean;
|
||||||
|
}) {
|
||||||
const isWrapped = useContext(StoreContext);
|
const isWrapped = useContext(StoreContext);
|
||||||
|
|
||||||
if (isWrapped) {
|
if (isWrapped) {
|
||||||
@@ -12,8 +27,18 @@ const Wrapper: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
|||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ReactFlowProvider>{children}</ReactFlowProvider>;
|
return (
|
||||||
};
|
<ReactFlowProvider
|
||||||
|
initialNodes={nodes}
|
||||||
|
initialEdges={edges}
|
||||||
|
initialWidth={width}
|
||||||
|
initialHeight={height}
|
||||||
|
fitView={fitView}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ReactFlowProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Wrapper.displayName = 'ReactFlowWrapper';
|
Wrapper.displayName = 'ReactFlowWrapper';
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
nodeDragThreshold,
|
nodeDragThreshold,
|
||||||
viewport,
|
viewport,
|
||||||
onViewportChange,
|
onViewportChange,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
@@ -181,7 +183,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
data-testid="rf__wrapper"
|
data-testid="rf__wrapper"
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
<Wrapper>
|
<Wrapper nodes={nodes} edges={edges} width={width} height={height} fitView={fitView}>
|
||||||
<GraphView
|
<GraphView
|
||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
|
|
||||||
const getNodeRect = useCallback(
|
const getNodeRect = useCallback(
|
||||||
(
|
(
|
||||||
nodeOrRect: (Partial<Node<NodeData>> & { id: Node['id'] }) | Rect
|
nodeOrRect: Node<NodeData> | { id: Node['id'] } | Rect
|
||||||
): [Rect | null, Node<NodeData> | null | undefined, boolean] => {
|
): [Rect | null, Node<NodeData> | null | undefined, boolean] => {
|
||||||
const isRect = isRectObject(nodeOrRect);
|
const isRect = isRectObject(nodeOrRect);
|
||||||
const node = isRect ? null : store.getState().nodes.find((n) => n.id === nodeOrRect.id);
|
const node = isRect ? null : store.getState().nodes.find((n) => n.id === nodeOrRect.id);
|
||||||
@@ -211,7 +211,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
||||||
const partiallyVisible = partially && overlappingArea > 0;
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
|
|
||||||
return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
|
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@@ -228,7 +228,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
const overlappingArea = getOverlappingArea(nodeRect, area);
|
const overlappingArea = getOverlappingArea(nodeRect, area);
|
||||||
const partiallyVisible = partially && overlappingArea > 0;
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
|
|
||||||
return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
|
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
import { updateNodesAndEdgesSelections } from './utils';
|
import { updateNodesAndEdgesSelections } from './utils';
|
||||||
import initialState from './initialState';
|
import getInitialState from './initialState';
|
||||||
import type {
|
import type {
|
||||||
ReactFlowState,
|
ReactFlowState,
|
||||||
Node,
|
Node,
|
||||||
@@ -24,10 +24,22 @@ import type {
|
|||||||
FitViewOptions,
|
FitViewOptions,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
const createRFStore = () =>
|
const createRFStore = ({
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fitView,
|
||||||
|
}: {
|
||||||
|
nodes?: Node[];
|
||||||
|
edges?: Edge[];
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fitView?: boolean;
|
||||||
|
}) =>
|
||||||
createWithEqualityFn<ReactFlowState>(
|
createWithEqualityFn<ReactFlowState>(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
...initialState,
|
...getInitialState({ nodes, edges, width, height, fitView }),
|
||||||
setNodes: (nodes: Node[]) => {
|
setNodes: (nodes: Node[]) => {
|
||||||
const { nodes: storeNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
const { nodes: storeNodes, nodeOrigin, elevateNodesOnSelect } = get();
|
||||||
const nextNodes = updateNodes(nodes, storeNodes, { nodeOrigin, elevateNodesOnSelect });
|
const nextNodes = updateNodes(nodes, storeNodes, { nodeOrigin, elevateNodesOnSelect });
|
||||||
@@ -45,15 +57,27 @@ const createRFStore = () =>
|
|||||||
const hasDefaultNodes = typeof nodes !== 'undefined';
|
const hasDefaultNodes = typeof nodes !== 'undefined';
|
||||||
const hasDefaultEdges = typeof edges !== 'undefined';
|
const hasDefaultEdges = typeof edges !== 'undefined';
|
||||||
|
|
||||||
const nextNodes = hasDefaultNodes
|
const nextState: {
|
||||||
? updateNodes(nodes, [], {
|
nodes?: Node[];
|
||||||
nodeOrigin: get().nodeOrigin,
|
edges?: Edge[];
|
||||||
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
hasDefaultNodes: boolean;
|
||||||
})
|
hasDefaultEdges: boolean;
|
||||||
: [];
|
} = {
|
||||||
const nextEdges = hasDefaultEdges ? edges : [];
|
hasDefaultNodes,
|
||||||
|
hasDefaultEdges,
|
||||||
|
};
|
||||||
|
|
||||||
set({ nodes: nextNodes, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
if (hasDefaultNodes) {
|
||||||
|
nextState.nodes = updateNodes(nodes, [], {
|
||||||
|
nodeOrigin: get().nodeOrigin,
|
||||||
|
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasDefaultEdges) {
|
||||||
|
nextState.edges = edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(nextState);
|
||||||
},
|
},
|
||||||
updateNodeDimensions: (updates) => {
|
updateNodeDimensions: (updates) => {
|
||||||
const { onNodesChange, fitView, nodes, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin } =
|
const { onNodesChange, fitView, nodes, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin } =
|
||||||
@@ -263,9 +287,9 @@ const createRFStore = () =>
|
|||||||
},
|
},
|
||||||
cancelConnection: () =>
|
cancelConnection: () =>
|
||||||
set({
|
set({
|
||||||
connectionStatus: initialState.connectionStatus,
|
connectionStatus: null,
|
||||||
connectionStartHandle: initialState.connectionStartHandle,
|
connectionStartHandle: null,
|
||||||
connectionEndHandle: initialState.connectionEndHandle,
|
connectionEndHandle: null,
|
||||||
}),
|
}),
|
||||||
updateConnection: (params) => {
|
updateConnection: (params) => {
|
||||||
const { connectionStatus, connectionStartHandle, connectionEndHandle, connectionPosition } = get();
|
const { connectionStatus, connectionStartHandle, connectionEndHandle, connectionPosition } = get();
|
||||||
@@ -279,7 +303,13 @@ const createRFStore = () =>
|
|||||||
|
|
||||||
set(currentConnection);
|
set(currentConnection);
|
||||||
},
|
},
|
||||||
reset: () => set({ ...initialState }),
|
reset: () => {
|
||||||
|
// @todo: what should we do about this? Do we still need it?
|
||||||
|
// if you are on a SPA with multiple flows, we want to make sure that the store gets resetted
|
||||||
|
// when you switch pages. Does this reset solves this? Currently it always gets called. This
|
||||||
|
// leads to an emtpy nodes array at the beginning.
|
||||||
|
// set({ ...getInitialState() });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
Object.is
|
Object.is
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,65 +1,100 @@
|
|||||||
import { devWarn, infiniteExtent, ConnectionMode } from '@xyflow/system';
|
import {
|
||||||
|
infiniteExtent,
|
||||||
|
ConnectionMode,
|
||||||
|
updateNodes,
|
||||||
|
getRectOfNodes,
|
||||||
|
getTransformForBounds,
|
||||||
|
Transform,
|
||||||
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { ReactFlowStore } from '../types';
|
import type { Edge, Node, ReactFlowStore } from '../types';
|
||||||
|
|
||||||
const initialState: ReactFlowStore = {
|
const getInitialState = ({
|
||||||
rfId: '1',
|
nodes = [],
|
||||||
width: 0,
|
edges = [],
|
||||||
height: 0,
|
width,
|
||||||
transform: [0, 0, 1],
|
height,
|
||||||
nodes: [],
|
fitView,
|
||||||
edges: [],
|
}: {
|
||||||
onNodesChange: null,
|
nodes?: Node[];
|
||||||
onEdgesChange: null,
|
edges?: Edge[];
|
||||||
hasDefaultNodes: false,
|
width?: number;
|
||||||
hasDefaultEdges: false,
|
height?: number;
|
||||||
panZoom: null,
|
fitView?: boolean;
|
||||||
minZoom: 0.5,
|
} = {}): ReactFlowStore => {
|
||||||
maxZoom: 2,
|
const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||||
translateExtent: infiniteExtent,
|
|
||||||
nodeExtent: infiniteExtent,
|
|
||||||
nodesSelectionActive: false,
|
|
||||||
userSelectionActive: false,
|
|
||||||
userSelectionRect: null,
|
|
||||||
connectionPosition: { x: 0, y: 0 },
|
|
||||||
connectionStatus: null,
|
|
||||||
connectionMode: ConnectionMode.Strict,
|
|
||||||
domNode: null,
|
|
||||||
paneDragging: false,
|
|
||||||
noPanClassName: 'nopan',
|
|
||||||
nodeOrigin: [0, 0],
|
|
||||||
nodeDragThreshold: 0,
|
|
||||||
|
|
||||||
snapGrid: [15, 15],
|
let transform: Transform = [0, 0, 1];
|
||||||
snapToGrid: false,
|
|
||||||
|
|
||||||
nodesDraggable: true,
|
if (fitView && width && height) {
|
||||||
nodesConnectable: true,
|
const nodesWithDimensions = nextNodes.map((node) => ({
|
||||||
nodesFocusable: true,
|
...node,
|
||||||
edgesFocusable: true,
|
width: node.size?.width,
|
||||||
edgesUpdatable: true,
|
height: node.size?.height,
|
||||||
elementsSelectable: true,
|
}));
|
||||||
elevateNodesOnSelect: true,
|
const bounds = getRectOfNodes(nodesWithDimensions, [0, 0]);
|
||||||
fitViewOnInit: false,
|
transform = getTransformForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||||
fitViewDone: false,
|
}
|
||||||
fitViewOnInitOptions: undefined,
|
|
||||||
selectNodesOnDrag: true,
|
|
||||||
|
|
||||||
multiSelectionActive: false,
|
return {
|
||||||
|
rfId: '1',
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
transform,
|
||||||
|
nodes: nextNodes,
|
||||||
|
edges: edges,
|
||||||
|
onNodesChange: null,
|
||||||
|
onEdgesChange: null,
|
||||||
|
hasDefaultNodes: false,
|
||||||
|
hasDefaultEdges: false,
|
||||||
|
panZoom: null,
|
||||||
|
minZoom: 0.5,
|
||||||
|
maxZoom: 2,
|
||||||
|
translateExtent: infiniteExtent,
|
||||||
|
nodeExtent: infiniteExtent,
|
||||||
|
nodesSelectionActive: false,
|
||||||
|
userSelectionActive: false,
|
||||||
|
userSelectionRect: null,
|
||||||
|
connectionPosition: { x: 0, y: 0 },
|
||||||
|
connectionStatus: null,
|
||||||
|
connectionMode: ConnectionMode.Strict,
|
||||||
|
domNode: null,
|
||||||
|
paneDragging: false,
|
||||||
|
noPanClassName: 'nopan',
|
||||||
|
nodeOrigin: [0, 0],
|
||||||
|
nodeDragThreshold: 0,
|
||||||
|
|
||||||
connectionStartHandle: null,
|
snapGrid: [15, 15],
|
||||||
connectionEndHandle: null,
|
snapToGrid: false,
|
||||||
connectionClickStartHandle: null,
|
|
||||||
connectOnClick: true,
|
|
||||||
|
|
||||||
ariaLiveMessage: '',
|
nodesDraggable: true,
|
||||||
autoPanOnConnect: true,
|
nodesConnectable: true,
|
||||||
autoPanOnNodeDrag: true,
|
nodesFocusable: true,
|
||||||
connectionRadius: 20,
|
edgesFocusable: true,
|
||||||
onError: devWarn,
|
edgesUpdatable: true,
|
||||||
isValidConnection: undefined,
|
elementsSelectable: true,
|
||||||
|
elevateNodesOnSelect: true,
|
||||||
|
fitViewOnInit: false,
|
||||||
|
fitViewDone: false,
|
||||||
|
fitViewOnInitOptions: undefined,
|
||||||
|
selectNodesOnDrag: true,
|
||||||
|
|
||||||
lib: 'react',
|
multiSelectionActive: false,
|
||||||
|
|
||||||
|
connectionStartHandle: null,
|
||||||
|
connectionEndHandle: null,
|
||||||
|
connectionClickStartHandle: null,
|
||||||
|
connectOnClick: true,
|
||||||
|
|
||||||
|
ariaLiveMessage: '',
|
||||||
|
autoPanOnConnect: true,
|
||||||
|
autoPanOnNodeDrag: true,
|
||||||
|
connectionRadius: 20,
|
||||||
|
onError: () => null,
|
||||||
|
isValidConnection: undefined,
|
||||||
|
|
||||||
|
lib: 'react',
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default initialState;
|
export default getInitialState;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import type {
|
|||||||
EdgeMouseHandler,
|
EdgeMouseHandler,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
|
||||||
nodes?: Node[];
|
nodes?: Node[];
|
||||||
edges?: Edge[];
|
edges?: Edge[];
|
||||||
defaultNodes?: Node[];
|
defaultNodes?: Node[];
|
||||||
@@ -151,6 +151,8 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
isValidConnection?: IsValidConnection;
|
isValidConnection?: IsValidConnection;
|
||||||
nodeDragThreshold?: number;
|
nodeDragThreshold?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowRefType = HTMLDivElement;
|
export type ReactFlowRefType = HTMLDivElement;
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export type ReactFlowJsonObject<NodeData = any, EdgeData = any> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DeleteElementsOptions = {
|
export type DeleteElementsOptions = {
|
||||||
nodes?: (Partial<Node> & { id: Node['id'] })[];
|
nodes?: (Node | { id: Node['id'] })[];
|
||||||
edges?: (Partial<Edge> & { id: Edge['id'] })[];
|
edges?: (Edge | { id: Edge['id'] })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export namespace Instance {
|
export namespace Instance {
|
||||||
@@ -33,18 +33,18 @@ export namespace Instance {
|
|||||||
deletedEdges: Edge[];
|
deletedEdges: Edge[];
|
||||||
};
|
};
|
||||||
export type GetIntersectingNodes<NodeData> = (
|
export type GetIntersectingNodes<NodeData> = (
|
||||||
node: (Partial<Node<NodeData>> & { id: Node['id'] }) | Rect,
|
node: Node<NodeData> | { id: Node['id'] } | Rect,
|
||||||
partially?: boolean,
|
partially?: boolean,
|
||||||
nodes?: Node<NodeData>[]
|
nodes?: Node<NodeData>[]
|
||||||
) => Node<NodeData>[];
|
) => Node<NodeData>[];
|
||||||
export type IsNodeIntersecting<NodeData> = (
|
export type IsNodeIntersecting<NodeData> = (
|
||||||
node: (Partial<Node<NodeData>> & { id: Node['id'] }) | Rect,
|
node: Node<NodeData> | { id: Node['id'] } | Rect,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
partially?: boolean
|
partially?: boolean
|
||||||
) => boolean;
|
) => boolean;
|
||||||
export type getConnectedEdges = (id: string | (Partial<Node> & { id: Node['id'] })[]) => Edge[];
|
export type getConnectedEdges = (id: string | (Node | { id: Node['id'] })[]) => Edge[];
|
||||||
export type getIncomers = (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
export type getIncomers = (node: string | Node | { id: Node['id'] }) => Node[];
|
||||||
export type getOutgoers = (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
|
export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
|
||||||
|
|||||||
@@ -40,4 +40,6 @@ export type WrapNodeProps<NodeData = any> = Pick<
|
|||||||
noPanClassName: string;
|
noPanClassName: string;
|
||||||
rfId: string;
|
rfId: string;
|
||||||
disableKeyboardA11y: boolean;
|
disableKeyboardA11y: boolean;
|
||||||
|
sizeWidth?: number;
|
||||||
|
sizeHeight?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import cc from 'classcat';
|
||||||
import { EdgeLabelRenderer } from '$lib/components/EdgeLabelRenderer';
|
import { EdgeLabelRenderer } from '$lib/components/EdgeLabelRenderer';
|
||||||
import type { BaseEdgeProps } from './types';
|
import type { BaseEdgeProps } from './types';
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
<path
|
<path
|
||||||
d={path}
|
d={path}
|
||||||
{id}
|
{id}
|
||||||
class="svelte-flow__edge-path"
|
class={cc(['svelte-flow__edge-path', className])}
|
||||||
marker-start={markerStart}
|
marker-start={markerStart}
|
||||||
marker-end={markerEnd}
|
marker-end={markerEnd}
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
|
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
|
||||||
export let zIndex: NodeWrapperProps['zIndex'];
|
export let zIndex: NodeWrapperProps['zIndex'];
|
||||||
export let dragHandle: NodeWrapperProps['dragHandle'] = undefined;
|
export let dragHandle: NodeWrapperProps['dragHandle'] = undefined;
|
||||||
|
export let initialized: NodeWrapperProps['initialized'] = false;
|
||||||
let className: string = '';
|
let className: string = '';
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
@@ -157,6 +158,9 @@
|
|||||||
class:parent={isParent}
|
class:parent={isParent}
|
||||||
style:z-index={zIndex}
|
style:z-index={zIndex}
|
||||||
style:transform="translate({positionOrigin?.x ?? 0}px, {positionOrigin?.y ?? 0}px)"
|
style:transform="translate({positionOrigin?.x ?? 0}px, {positionOrigin?.y ?? 0}px)"
|
||||||
|
style:width={node.size?.width && `${node.size?.width}px`}
|
||||||
|
style:height={node.size?.height && `${node.size?.height}px`}
|
||||||
|
style:visibility={initialized ? 'visible' : 'hidden'}
|
||||||
{style}
|
{style}
|
||||||
on:click={onSelectNodeHandler}
|
on:click={onSelectNodeHandler}
|
||||||
on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })}
|
on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })}
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ export type NodeWrapperProps = Pick<
|
|||||||
isParent?: boolean;
|
isParent?: boolean;
|
||||||
zIndex: number;
|
zIndex: number;
|
||||||
node: Node;
|
node: Node;
|
||||||
|
initialized: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,23 @@
|
|||||||
import { onDestroy, setContext } from 'svelte';
|
import { onDestroy, setContext } from 'svelte';
|
||||||
|
|
||||||
import { createStore, key } from '$lib/store';
|
import { createStore, key } from '$lib/store';
|
||||||
|
import type { SvelteFlowProviderProps } from './types';
|
||||||
|
|
||||||
const store = createStore();
|
type $$Props = SvelteFlowProviderProps;
|
||||||
|
|
||||||
|
export let initialNodes: $$Props['initialNodes'] = undefined;
|
||||||
|
export let initialEdges: $$Props['initialEdges'] = undefined;
|
||||||
|
export let initialWidth: $$Props['initialWidth'] = undefined;
|
||||||
|
export let initialHeight: $$Props['initialHeight'] = undefined;
|
||||||
|
export let fitView: $$Props['fitView'] = undefined;
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
nodes: initialNodes,
|
||||||
|
edges: initialEdges,
|
||||||
|
width: initialWidth,
|
||||||
|
height: initialHeight,
|
||||||
|
fitView
|
||||||
|
});
|
||||||
|
|
||||||
setContext(key, {
|
setContext(key, {
|
||||||
getStore: () => store
|
getStore: () => store
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Edge, Node } from '$lib/types';
|
||||||
|
|
||||||
|
export type SvelteFlowProviderProps = {
|
||||||
|
initialNodes?: Node[];
|
||||||
|
initialEdges?: Edge[];
|
||||||
|
initialWidth?: number;
|
||||||
|
initialHeight?: number;
|
||||||
|
fitView?: boolean;
|
||||||
|
};
|
||||||
@@ -8,8 +8,6 @@
|
|||||||
export let defaultEdgeOptions: DefaultEdgeOptions | undefined;
|
export let defaultEdgeOptions: DefaultEdgeOptions | undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
width,
|
|
||||||
height,
|
|
||||||
elementsSelectable,
|
elementsSelectable,
|
||||||
edgeTree,
|
edgeTree,
|
||||||
edges: { setDefaultOptions }
|
edges: { setDefaultOptions }
|
||||||
@@ -21,7 +19,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $edgeTree as group (group.level)}
|
{#each $edgeTree as group (group.level)}
|
||||||
<svg width={$width} height={$height} style="z-index: {group.level}" class="svelte-flow__edges">
|
<svg style="z-index: {group.level}" class="svelte-flow__edges">
|
||||||
{#if group.isMaxLevel} <MarkerDefinition />{/if}
|
{#if group.isMaxLevel} <MarkerDefinition />{/if}
|
||||||
<g>
|
<g>
|
||||||
{#each group.edges as edge (edge.id)}
|
{#each group.edges as edge (edge.id)}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
{@const posOrigin = getPositionWithOrigin({
|
{@const posOrigin = getPositionWithOrigin({
|
||||||
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.size?.width || node.width) ?? 0,
|
||||||
height: node.height ?? 0,
|
height: (node.size?.height || node.height) ?? 0,
|
||||||
origin: node.origin
|
origin: node.origin
|
||||||
})}
|
})}
|
||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
dragging={node.dragging}
|
dragging={node.dragging}
|
||||||
zIndex={node[internalsSymbol]?.z ?? 0}
|
zIndex={node[internalsSymbol]?.z ?? 0}
|
||||||
dragHandle={node.dragHandle}
|
dragHandle={node.dragHandle}
|
||||||
|
initialized={(!!node.width && !!node.height) || (!!node.size?.width && !!node.size?.height)}
|
||||||
{resizeObserver}
|
{resizeObserver}
|
||||||
on:nodeclick
|
on:nodeclick
|
||||||
on:nodemouseenter
|
on:nodemouseenter
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
import { key, useStore, createStoreContext } from '$lib/store';
|
import { key, useStore, createStoreContext } from '$lib/store';
|
||||||
import type { SvelteFlowProps } from './types';
|
import type { SvelteFlowProps } from './types';
|
||||||
import { updateStore, updateStoreByKeys, type UpdatableStoreProps } from './utils';
|
import { updateStore, updateStoreByKeys, type UpdatableStoreProps } from './utils';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
type $$Props = SvelteFlowProps;
|
type $$Props = SvelteFlowProps;
|
||||||
|
|
||||||
@@ -63,6 +64,8 @@
|
|||||||
export let attributionPosition: $$Props['attributionPosition'] = undefined;
|
export let attributionPosition: $$Props['attributionPosition'] = undefined;
|
||||||
export let proOptions: $$Props['proOptions'] = undefined;
|
export let proOptions: $$Props['proOptions'] = undefined;
|
||||||
export let defaultEdgeOptions: $$Props['defaultEdgeOptions'] = undefined;
|
export let defaultEdgeOptions: $$Props['defaultEdgeOptions'] = undefined;
|
||||||
|
export let width: $$Props['width'] = undefined;
|
||||||
|
export let height: $$Props['height'] = undefined;
|
||||||
|
|
||||||
export let defaultMarkerColor = '#b1b1b7';
|
export let defaultMarkerColor = '#b1b1b7';
|
||||||
|
|
||||||
@@ -74,7 +77,9 @@
|
|||||||
let clientWidth: number;
|
let clientWidth: number;
|
||||||
let clientHeight: number;
|
let clientHeight: number;
|
||||||
|
|
||||||
const store = hasContext(key) ? useStore() : createStoreContext();
|
const store = hasContext(key)
|
||||||
|
? useStore()
|
||||||
|
: createStoreContext({ nodes: get(nodes), edges: get(edges), width, height, fitView });
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
store.width.set(clientWidth);
|
store.width.set(clientWidth);
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
|||||||
attributionPosition?: PanelPosition;
|
attributionPosition?: PanelPosition;
|
||||||
proOptions?: ProOptions;
|
proOptions?: ProOptions;
|
||||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
|
||||||
class?: string;
|
class?: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
|
|||||||
@@ -31,26 +31,26 @@ export function useSvelteFlow(): {
|
|||||||
getViewport: () => Viewport;
|
getViewport: () => Viewport;
|
||||||
fitView: (options?: FitViewOptions) => void;
|
fitView: (options?: FitViewOptions) => void;
|
||||||
getIntersectingNodes: (
|
getIntersectingNodes: (
|
||||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
partially?: boolean,
|
partially?: boolean,
|
||||||
nodesToIntersect?: Node[]
|
nodesToIntersect?: Node[]
|
||||||
) => Node[];
|
) => Node[];
|
||||||
isNodeIntersecting: (
|
isNodeIntersecting: (
|
||||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
partially?: boolean
|
partially?: boolean
|
||||||
) => boolean;
|
) => boolean;
|
||||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
||||||
deleteElements: (
|
deleteElements: (
|
||||||
nodesToRemove?: Partial<Node> & { id: string }[],
|
nodesToRemove?: (Node | { id: Node['id'] })[],
|
||||||
edgesToRemove?: Partial<Edge> & { id: string }[]
|
edgesToRemove?: (Edge | { id: Edge['id'] })[]
|
||||||
) => { deletedNodes: Node[]; deletedEdges: Edge[] };
|
) => { deletedNodes: Node[]; deletedEdges: Edge[] };
|
||||||
screenToFlowCoordinate: (position: XYPosition) => XYPosition;
|
screenToFlowCoordinate: (position: XYPosition) => XYPosition;
|
||||||
flowToScreenCoordinate: (position: XYPosition) => XYPosition;
|
flowToScreenCoordinate: (position: XYPosition) => XYPosition;
|
||||||
viewport: Writable<Viewport>;
|
viewport: Writable<Viewport>;
|
||||||
getConnectedEdges: (id: string | (Partial<Node> & { id: Node['id'] })[]) => Edge[];
|
getConnectedEdges: (id: string | (Node | { id: Node['id'] })[]) => Edge[];
|
||||||
getIncomers: (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
getIncomers: (node: string | Node | { id: Node['id'] }) => Node[];
|
||||||
getOutgoers: (node: string | (Partial<Node> & { id: Node['id'] })) => Node[];
|
getOutgoers: (node: string | Node | { id: Node['id'] }) => Node[];
|
||||||
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
||||||
} {
|
} {
|
||||||
const {
|
const {
|
||||||
@@ -70,7 +70,7 @@ export function useSvelteFlow(): {
|
|||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
const getNodeRect = (
|
const getNodeRect = (
|
||||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect
|
nodeOrRect: Node | { id: Node['id'] } | Rect
|
||||||
): [Rect | null, Node | null | undefined, boolean] => {
|
): [Rect | null, Node | null | undefined, boolean] => {
|
||||||
const isRect = isRectObject(nodeOrRect);
|
const isRect = isRectObject(nodeOrRect);
|
||||||
const node = isRect ? null : get(nodes).find((n) => n.id === nodeOrRect.id);
|
const node = isRect ? null : get(nodes).find((n) => n.id === nodeOrRect.id);
|
||||||
@@ -137,7 +137,7 @@ export function useSvelteFlow(): {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
getIntersectingNodes: (
|
getIntersectingNodes: (
|
||||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
partially = true,
|
partially = true,
|
||||||
nodesToIntersect?: Node[]
|
nodesToIntersect?: Node[]
|
||||||
) => {
|
) => {
|
||||||
@@ -156,11 +156,11 @@ export function useSvelteFlow(): {
|
|||||||
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
||||||
const partiallyVisible = partially && overlappingArea > 0;
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
|
|
||||||
return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
|
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isNodeIntersecting: (
|
isNodeIntersecting: (
|
||||||
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
partially = true
|
partially = true
|
||||||
) => {
|
) => {
|
||||||
@@ -173,11 +173,11 @@ export function useSvelteFlow(): {
|
|||||||
const overlappingArea = getOverlappingArea(nodeRect, area);
|
const overlappingArea = getOverlappingArea(nodeRect, area);
|
||||||
const partiallyVisible = partially && overlappingArea > 0;
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
|
|
||||||
return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
|
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
||||||
},
|
},
|
||||||
deleteElements: (
|
deleteElements: (
|
||||||
nodesToRemove: Partial<Node> & { id: string }[] = [],
|
nodesToRemove: (Node | { id: Node['id'] })[] = [],
|
||||||
edgesToRemove: Partial<Edge> & { id: string }[] = []
|
edgesToRemove: (Edge | { id: Edge['id'] })[] = []
|
||||||
) => {
|
) => {
|
||||||
const _nodes = get(nodes);
|
const _nodes = get(nodes);
|
||||||
const _edges = get(edges);
|
const _edges = get(edges);
|
||||||
|
|||||||
@@ -29,8 +29,20 @@ import { getDerivedConnectionProps } from './derived-connection-props';
|
|||||||
|
|
||||||
export const key = Symbol();
|
export const key = Symbol();
|
||||||
|
|
||||||
export function createStore(): SvelteFlowStore {
|
export function createStore({
|
||||||
const store = getInitialStore();
|
nodes,
|
||||||
|
edges,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fitView: fitViewOnCreate
|
||||||
|
}: {
|
||||||
|
nodes?: Node[];
|
||||||
|
edges?: Edge[];
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fitView?: boolean;
|
||||||
|
}): SvelteFlowStore {
|
||||||
|
const store = getInitialStore({ nodes, edges, width, height, fitView: fitViewOnCreate });
|
||||||
|
|
||||||
function setNodeTypes(nodeTypes: NodeTypes) {
|
function setNodeTypes(nodeTypes: NodeTypes) {
|
||||||
store.nodeTypes.set({
|
store.nodeTypes.set({
|
||||||
@@ -333,8 +345,20 @@ export function useStore(): SvelteFlowStore {
|
|||||||
return store.getStore();
|
return store.getStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createStoreContext() {
|
export function createStoreContext({
|
||||||
const store = createStore();
|
nodes,
|
||||||
|
edges,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fitView
|
||||||
|
}: {
|
||||||
|
nodes?: Node[];
|
||||||
|
edges?: Edge[];
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fitView?: boolean;
|
||||||
|
}) {
|
||||||
|
const store = createStore({ nodes, edges, width, height, fitView });
|
||||||
|
|
||||||
setContext(key, {
|
setContext(key, {
|
||||||
getStore: () => store
|
getStore: () => store
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import {
|
|||||||
type NodeOrigin,
|
type NodeOrigin,
|
||||||
type OnError,
|
type OnError,
|
||||||
devWarn,
|
devWarn,
|
||||||
type Viewport
|
type Viewport,
|
||||||
|
updateNodes,
|
||||||
|
getRectOfNodes,
|
||||||
|
getTransformForBounds
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||||
@@ -25,7 +28,7 @@ import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
|
|||||||
import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
|
import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
|
||||||
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
|
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
|
||||||
import StepEdge from '$lib/components/edges/StepEdge.svelte';
|
import StepEdge from '$lib/components/edges/StepEdge.svelte';
|
||||||
import type { NodeTypes, EdgeTypes, EdgeLayouted, Node, FitViewOptions } from '$lib/types';
|
import type { NodeTypes, EdgeTypes, EdgeLayouted, Node, Edge, FitViewOptions } from '$lib/types';
|
||||||
import { createNodesStore, createEdgesStore } from './utils';
|
import { createNodesStore, createEdgesStore } from './utils';
|
||||||
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
|
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
|
||||||
|
|
||||||
@@ -43,51 +46,80 @@ export const initialEdgeTypes = {
|
|||||||
step: StepEdge
|
step: StepEdge
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInitialStore = () => ({
|
export const getInitialStore = ({
|
||||||
flowId: writable<string | null>(null),
|
nodes = [],
|
||||||
nodes: createNodesStore([]),
|
edges = [],
|
||||||
visibleNodes: readable<Node[]>([]),
|
width,
|
||||||
edges: createEdgesStore([]),
|
height,
|
||||||
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
fitView
|
||||||
height: writable<number>(500),
|
}: {
|
||||||
width: writable<number>(500),
|
nodes?: Node[];
|
||||||
minZoom: writable<number>(0.5),
|
edges?: Edge[];
|
||||||
maxZoom: writable<number>(2),
|
width?: number;
|
||||||
nodeOrigin: writable<NodeOrigin>([0, 0]),
|
height?: number;
|
||||||
nodeDragThreshold: writable<number>(0),
|
fitView?: boolean;
|
||||||
nodeExtent: writable<CoordinateExtent>(infiniteExtent),
|
}) => {
|
||||||
translateExtent: writable<CoordinateExtent>(infiniteExtent),
|
const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||||
autoPanOnNodeDrag: writable<boolean>(true),
|
|
||||||
autoPanOnConnect: writable<boolean>(true),
|
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||||
fitViewOnInit: writable<boolean>(false),
|
|
||||||
fitViewOnInitDone: writable<boolean>(false),
|
if (fitView && width && height) {
|
||||||
fitViewOptions: writable<FitViewOptions>(undefined),
|
const nodesWithDimensions = nextNodes.map((node) => ({
|
||||||
panZoom: writable<PanZoomInstance | null>(null),
|
...node,
|
||||||
snapGrid: writable<SnapGrid | null>(null),
|
width: node.size?.width,
|
||||||
dragging: writable<boolean>(false),
|
height: node.size?.height
|
||||||
selectionRect: writable<SelectionRect | null>(null),
|
}));
|
||||||
selectionKeyPressed: writable<boolean>(false),
|
const bounds = getRectOfNodes(nodesWithDimensions, [0, 0]);
|
||||||
multiselectionKeyPressed: writable<boolean>(false),
|
const transform = getTransformForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||||
deleteKeyPressed: writable<boolean>(false),
|
viewport = { x: transform[0], y: transform[1], zoom: transform[2] };
|
||||||
panActivationKeyPressed: writable<boolean>(false),
|
}
|
||||||
selectionRectMode: writable<string | null>(null),
|
|
||||||
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
|
return {
|
||||||
nodeTypes: writable<NodeTypes>(initialNodeTypes),
|
flowId: writable<string | null>(null),
|
||||||
edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
|
nodes: createNodesStore(nextNodes),
|
||||||
viewport: writable<Viewport>({ x: 0, y: 0, zoom: 1 }),
|
visibleNodes: readable<Node[]>([]),
|
||||||
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
|
edges: createEdgesStore(edges),
|
||||||
domNode: writable<HTMLDivElement | null>(null),
|
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
|
||||||
connection: readable<ConnectionProps>(initConnectionProps),
|
height: writable<number>(500),
|
||||||
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
|
width: writable<number>(500),
|
||||||
connectionRadius: writable<number>(20),
|
minZoom: writable<number>(0.5),
|
||||||
isValidConnection: writable<IsValidConnection>(() => true),
|
maxZoom: writable<number>(2),
|
||||||
nodesDraggable: writable<boolean>(true),
|
nodeOrigin: writable<NodeOrigin>([0, 0]),
|
||||||
nodesConnectable: writable<boolean>(true),
|
nodeDragThreshold: writable<number>(0),
|
||||||
elementsSelectable: writable<boolean>(true),
|
nodeExtent: writable<CoordinateExtent>(infiniteExtent),
|
||||||
selectNodesOnDrag: writable<boolean>(true),
|
translateExtent: writable<CoordinateExtent>(infiniteExtent),
|
||||||
markers: readable<MarkerProps[]>([]),
|
autoPanOnNodeDrag: writable<boolean>(true),
|
||||||
defaultMarkerColor: writable<string>('#b1b1b7'),
|
autoPanOnConnect: writable<boolean>(true),
|
||||||
lib: readable<string>('svelte'),
|
fitViewOnInit: writable<boolean>(false),
|
||||||
onlyRenderVisibleElements: writable<boolean>(false),
|
fitViewOnInitDone: writable<boolean>(false),
|
||||||
onError: writable<OnError>(devWarn)
|
fitViewOptions: writable<FitViewOptions>(undefined),
|
||||||
});
|
panZoom: writable<PanZoomInstance | null>(null),
|
||||||
|
snapGrid: writable<SnapGrid | null>(null),
|
||||||
|
dragging: writable<boolean>(false),
|
||||||
|
selectionRect: writable<SelectionRect | null>(null),
|
||||||
|
selectionKeyPressed: writable<boolean>(false),
|
||||||
|
multiselectionKeyPressed: writable<boolean>(false),
|
||||||
|
deleteKeyPressed: writable<boolean>(false),
|
||||||
|
panActivationKeyPressed: writable<boolean>(false),
|
||||||
|
selectionRectMode: writable<string | null>(null),
|
||||||
|
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
|
||||||
|
nodeTypes: writable<NodeTypes>(initialNodeTypes),
|
||||||
|
edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
|
||||||
|
viewport: writable<Viewport>(viewport),
|
||||||
|
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
|
||||||
|
domNode: writable<HTMLDivElement | null>(null),
|
||||||
|
connection: readable<ConnectionProps>(initConnectionProps),
|
||||||
|
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
|
||||||
|
connectionRadius: writable<number>(20),
|
||||||
|
isValidConnection: writable<IsValidConnection>(() => true),
|
||||||
|
nodesDraggable: writable<boolean>(true),
|
||||||
|
nodesConnectable: writable<boolean>(true),
|
||||||
|
elementsSelectable: writable<boolean>(true),
|
||||||
|
selectNodesOnDrag: writable<boolean>(true),
|
||||||
|
markers: readable<MarkerProps[]>([]),
|
||||||
|
defaultMarkerColor: writable<string>('#b1b1b7'),
|
||||||
|
lib: readable<string>('svelte'),
|
||||||
|
onlyRenderVisibleElements: writable<boolean>(false),
|
||||||
|
onError: writable<OnError>(devWarn)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
type Writable,
|
type Writable,
|
||||||
get
|
get
|
||||||
} from 'svelte/store';
|
} from 'svelte/store';
|
||||||
import { updateNodes, type Transform, type Viewport, type PanZoomInstance } from '@xyflow/system';
|
import { updateNodes, type Viewport, type PanZoomInstance } from '@xyflow/system';
|
||||||
|
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export function getVisibleNodes(store: SvelteFlowStoreState) {
|
|||||||
[store.nodes, store.onlyRenderVisibleElements, store.width, store.height, store.viewport],
|
[store.nodes, store.onlyRenderVisibleElements, store.width, store.height, store.viewport],
|
||||||
([nodes, onlyRenderVisibleElements, width, height, viewport]) => {
|
([nodes, onlyRenderVisibleElements, width, height, viewport]) => {
|
||||||
const transform: Transform = [viewport.x, viewport.y, viewport.zoom];
|
const transform: Transform = [viewport.x, viewport.y, viewport.zoom];
|
||||||
|
|
||||||
return onlyRenderVisibleElements
|
return onlyRenderVisibleElements
|
||||||
? getNodesInside<Node>(nodes, { x: 0, y: 0, width, height }, transform, true)
|
? getNodesInside<Node>(nodes, { x: 0, y: 0, width, height }, transform, true)
|
||||||
: nodes;
|
: nodes;
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export type Edge<T = any> =
|
|||||||
|
|
||||||
export type EdgeProps = Omit<Edge, 'sourceHandle' | 'targetHandle'> &
|
export type EdgeProps = Omit<Edge, 'sourceHandle' | 'targetHandle'> &
|
||||||
EdgePosition & {
|
EdgePosition & {
|
||||||
|
markerStart?: string;
|
||||||
|
markerEnd?: string;
|
||||||
sourceHandleId?: string | null;
|
sourceHandleId?: string | null;
|
||||||
targetHandleId?: string | null;
|
targetHandleId?: string | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export type FitViewOptionsBase<NodeType extends NodeBase> = {
|
|||||||
minZoom?: number;
|
minZoom?: number;
|
||||||
maxZoom?: number;
|
maxZoom?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
nodes?: (Partial<NodeType> & { id: NodeType['id'] })[];
|
nodes?: (NodeType | { id: NodeType['id'] })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Viewport = {
|
export type Viewport = {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import type { XYPosition, Position, Dimensions, OnConnect, IsValidConnection } from '.';
|
import type { Position, OnConnect, IsValidConnection } from '.';
|
||||||
|
|
||||||
export type HandleType = 'source' | 'target';
|
export type HandleType = 'source' | 'target';
|
||||||
|
|
||||||
export type HandleElement = XYPosition &
|
export type HandleElement = {
|
||||||
Dimensions & {
|
id?: string | null;
|
||||||
id?: string | null;
|
x: number;
|
||||||
position: Position;
|
y: number;
|
||||||
};
|
width: number;
|
||||||
|
height: number;
|
||||||
|
position: Position;
|
||||||
|
type?: HandleType;
|
||||||
|
};
|
||||||
|
|
||||||
export type ConnectingHandle = {
|
export type ConnectingHandle = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { internalsSymbol } from '../constants';
|
import { internalsSymbol } from '../constants';
|
||||||
import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.';
|
import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.';
|
||||||
|
import { Optional } from '../utils/types';
|
||||||
|
|
||||||
// this is stuff that all nodes share independent of the framework
|
// this is stuff that all nodes share independent of the framework
|
||||||
export type NodeBase<T = any, U extends string | undefined = string | undefined> = {
|
export type NodeBase<T = any, U extends string | undefined = string | undefined> = {
|
||||||
@@ -28,6 +29,11 @@ export type NodeBase<T = any, U extends string | undefined = string | undefined>
|
|||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
focusable?: boolean;
|
focusable?: boolean;
|
||||||
origin?: NodeOrigin;
|
origin?: NodeOrigin;
|
||||||
|
handles?: NodeHandle[];
|
||||||
|
size?: {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
};
|
||||||
|
|
||||||
// only used internally
|
// only used internally
|
||||||
[internalsSymbol]?: {
|
[internalsSymbol]?: {
|
||||||
@@ -89,3 +95,5 @@ export type NodeOrigin = [number, number];
|
|||||||
export type OnNodeDrag = (event: MouseEvent, node: NodeBase, nodes: NodeBase[]) => void;
|
export type OnNodeDrag = (event: MouseEvent, node: NodeBase, nodes: NodeBase[]) => void;
|
||||||
|
|
||||||
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
||||||
|
|
||||||
|
export type NodeHandle = Optional<HandleElement, 'width' | 'height'>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EdgePosition } from '../../types/edges';
|
import { EdgePosition } from '../../types/edges';
|
||||||
import { ConnectionMode, OnError } from '../../types/general';
|
import { ConnectionMode, OnError } from '../../types/general';
|
||||||
import { NodeBase, NodeHandleBounds } from '../../types/nodes';
|
import { NodeBase, NodeHandle, NodeHandleBounds } from '../../types/nodes';
|
||||||
import { Position, Rect, XYPosition } from '../../types/utils';
|
import { Position, Rect, XYPosition } from '../../types/utils';
|
||||||
import { errorMessages, internalsSymbol } from '../../constants';
|
import { errorMessages, internalsSymbol } from '../../constants';
|
||||||
import { HandleElement } from '../../types';
|
import { HandleElement } from '../../types';
|
||||||
@@ -16,10 +16,10 @@ export type GetEdgePositionParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null {
|
export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null {
|
||||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getHandleDataByNode(params.sourceNode);
|
const [sourceNodeRect, sourceHandleBounds, isSourceValid] = getHandleDataByNode(params.sourceNode);
|
||||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getHandleDataByNode(params.targetNode);
|
const [targetNodeRect, targetHandleBounds, isTargetValid] = getHandleDataByNode(params.targetNode);
|
||||||
|
|
||||||
if (!sourceIsValid || !targetIsValid) {
|
if (!isSourceValid || !isTargetValid) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,13 +59,42 @@ export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | n
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toHandleBounds(handles?: NodeHandle[]) {
|
||||||
|
if (!handles) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handles.reduce<NodeHandleBounds>(
|
||||||
|
(res, item) => {
|
||||||
|
item.width = item.width || 1;
|
||||||
|
item.height = item.height || 1;
|
||||||
|
|
||||||
|
if (item.type === 'source') {
|
||||||
|
res.source?.push(item as HandleElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'target') {
|
||||||
|
res.target?.push(item as HandleElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: [],
|
||||||
|
target: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getHandleDataByNode(node?: NodeBase): [Rect, NodeHandleBounds | null, boolean] {
|
function getHandleDataByNode(node?: NodeBase): [Rect, NodeHandleBounds | null, boolean] {
|
||||||
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
|
const handleBounds = node?.[internalsSymbol]?.handleBounds || toHandleBounds(node?.handles) || null;
|
||||||
|
const nodeWidth = node?.width || node?.size?.width;
|
||||||
|
const nodeHeight = node?.height || node?.size?.height;
|
||||||
|
|
||||||
const isValid =
|
const isValid =
|
||||||
handleBounds &&
|
handleBounds &&
|
||||||
node?.width &&
|
nodeWidth &&
|
||||||
node?.height &&
|
nodeHeight &&
|
||||||
typeof node?.positionAbsolute?.x !== 'undefined' &&
|
typeof node?.positionAbsolute?.x !== 'undefined' &&
|
||||||
typeof node?.positionAbsolute?.y !== 'undefined';
|
typeof node?.positionAbsolute?.y !== 'undefined';
|
||||||
|
|
||||||
@@ -73,8 +102,8 @@ function getHandleDataByNode(node?: NodeBase): [Rect, NodeHandleBounds | null, b
|
|||||||
{
|
{
|
||||||
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: nodeWidth || 0,
|
||||||
height: node?.height || 0,
|
height: nodeHeight || 0,
|
||||||
},
|
},
|
||||||
handleBounds,
|
handleBounds,
|
||||||
!!isValid,
|
!!isValid,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const isNodeBase = <NodeType extends NodeBase = NodeBase, EdgeType extend
|
|||||||
): element is NodeType => 'id' in element && !('source' in element) && !('target' in element);
|
): element is NodeType => 'id' in element && !('source' in element) && !('target' in element);
|
||||||
|
|
||||||
export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||||
node: Partial<NodeType> & { id: string },
|
node: NodeType | { id: string },
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
edges: EdgeType[]
|
edges: EdgeType[]
|
||||||
): NodeType[] => {
|
): NodeType[] => {
|
||||||
@@ -54,7 +54,7 @@ export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType e
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getIncomersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
export const getIncomersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||||
node: Partial<NodeType> & { id: string },
|
node: NodeType | { id: string },
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
edges: EdgeType[]
|
edges: EdgeType[]
|
||||||
): NodeType[] => {
|
): NodeType[] => {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||||
Generated
+4890
-1755
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@ export const esmConfig = defineConfig({
|
|||||||
output: {
|
output: {
|
||||||
file: pkg.module,
|
file: pkg.module,
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
|
banner: '"use client"',
|
||||||
},
|
},
|
||||||
onwarn,
|
onwarn,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
Reference in New Issue
Block a user