chore: fix conflicts
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>React Flow Examples</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@reactflow/examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 3000 --open",
|
||||
@@ -13,6 +13,7 @@
|
||||
"test-e2e": "start-server-and-test 'pnpm serve' http-get://localhost:3000 'pnpm test-e2e-cypress'"
|
||||
},
|
||||
"dependencies": {
|
||||
"classcat": "^5.0.3",
|
||||
"dagre": "^0.8.5",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import Basic from '../examples/Basic';
|
||||
@@ -35,160 +35,218 @@ import UseReactFlow from '../examples/UseReactFlow';
|
||||
import Validation from '../examples/Validation';
|
||||
import UseKeyPress from '../examples/UseKeyPress';
|
||||
import EdgeRouting from '../examples/EdgeRouting';
|
||||
import CancelConnection from '../examples/CancelConnection';
|
||||
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
|
||||
|
||||
const routes = [
|
||||
interface IRoute {
|
||||
name: string;
|
||||
path: string;
|
||||
component: React.ComponentType;
|
||||
}
|
||||
|
||||
const routes: IRoute[] = [
|
||||
{
|
||||
name: 'Basic',
|
||||
path: '/',
|
||||
component: Basic,
|
||||
},
|
||||
{
|
||||
name: 'Backgrounds',
|
||||
path: '/backgrounds',
|
||||
component: Backgrounds,
|
||||
},
|
||||
{
|
||||
name: 'Cancel Connection',
|
||||
path: '/cancel-connection',
|
||||
component: CancelConnection,
|
||||
},
|
||||
{
|
||||
name: 'Controlled/Uncontrolled',
|
||||
path: '/controlled-uncontrolled',
|
||||
component: ControlledUncontrolled,
|
||||
},
|
||||
{
|
||||
name: 'Custom Connection Line',
|
||||
path: '/custom-connectionline',
|
||||
component: CustomConnectionLine,
|
||||
},
|
||||
{
|
||||
name: 'Custom Node',
|
||||
path: '/custom-node',
|
||||
component: CustomNode,
|
||||
},
|
||||
{
|
||||
name: 'Default Nodes',
|
||||
path: '/default-nodes',
|
||||
component: DefaultNodes,
|
||||
},
|
||||
{
|
||||
name: 'Drag Handle',
|
||||
path: '/draghandle',
|
||||
component: DragHandle,
|
||||
},
|
||||
{
|
||||
name: 'Drag and Drop',
|
||||
path: '/dragndrop',
|
||||
component: DragNDrop,
|
||||
},
|
||||
{
|
||||
name: 'Edges',
|
||||
path: '/edges',
|
||||
component: Edges,
|
||||
},
|
||||
{
|
||||
name: 'Edge Types',
|
||||
path: '/edge-types',
|
||||
component: EdgeTypes,
|
||||
},
|
||||
{
|
||||
name: 'Edge Routing',
|
||||
path: '/edge-routing',
|
||||
component: EdgeRouting,
|
||||
},
|
||||
{
|
||||
name: 'Empty',
|
||||
path: '/empty',
|
||||
component: Empty,
|
||||
},
|
||||
{
|
||||
name: 'Floating Edges',
|
||||
path: '/floating-edges',
|
||||
component: FloatingEdges,
|
||||
},
|
||||
{
|
||||
name: 'Hidden',
|
||||
path: '/hidden',
|
||||
component: Hidden,
|
||||
},
|
||||
{
|
||||
name: 'Interaction',
|
||||
path: '/interaction',
|
||||
component: Interaction,
|
||||
},
|
||||
{
|
||||
name: 'Layouting',
|
||||
path: '/layouting',
|
||||
component: Layouting,
|
||||
},
|
||||
{
|
||||
name: 'Multi Flows',
|
||||
path: '/multiflows',
|
||||
component: MultiFlows,
|
||||
},
|
||||
{
|
||||
name: 'Nested Nodes',
|
||||
path: '/nested-nodes',
|
||||
component: NestedNodes,
|
||||
},
|
||||
{
|
||||
name: 'Node Type Change',
|
||||
path: '/nodetype-change',
|
||||
component: NodeTypeChange,
|
||||
},
|
||||
{
|
||||
name: 'nodeTypes Object Change',
|
||||
path: '/nodetypesobject-change',
|
||||
component: NodeTypesObjectChange,
|
||||
},
|
||||
{
|
||||
name: 'Overview',
|
||||
path: '/overview',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
name: 'Provider',
|
||||
path: '/provider',
|
||||
component: Provider,
|
||||
},
|
||||
{
|
||||
name: 'Save/Restore',
|
||||
path: '/save-restore',
|
||||
component: SaveRestore,
|
||||
},
|
||||
{
|
||||
name: 'Stress',
|
||||
path: '/stress',
|
||||
component: Stress,
|
||||
},
|
||||
{
|
||||
name: 'Subflow',
|
||||
path: '/subflow',
|
||||
component: Subflow,
|
||||
},
|
||||
{
|
||||
name: 'Switch Flow',
|
||||
path: '/switch',
|
||||
component: SwitchFlow,
|
||||
},
|
||||
{
|
||||
name: 'Touch Device',
|
||||
path: '/touch-device',
|
||||
component: TouchDevice,
|
||||
},
|
||||
{
|
||||
name: 'Undirectional',
|
||||
path: '/undirectional',
|
||||
component: Undirectional,
|
||||
},
|
||||
{
|
||||
name: 'Updatable Edge',
|
||||
path: '/updatable-edge',
|
||||
component: UpdatableEdge,
|
||||
},
|
||||
{
|
||||
name: 'Update Node',
|
||||
path: '/update-node',
|
||||
component: UpdateNode,
|
||||
},
|
||||
{
|
||||
name: 'useOnSelectionChange',
|
||||
path: '/use-on-selection-change',
|
||||
component: UseOnSelectionChange,
|
||||
},
|
||||
{
|
||||
name: 'useReactFlow',
|
||||
path: '/usereactflow',
|
||||
component: UseReactFlow,
|
||||
},
|
||||
{
|
||||
name: 'useUpdateNodeInternals',
|
||||
path: '/useupdatenodeinternals',
|
||||
component: UseUpdateNodeInternals,
|
||||
},
|
||||
{
|
||||
name: 'Validation',
|
||||
path: '/validation',
|
||||
component: Validation,
|
||||
},
|
||||
{
|
||||
name: 'useKeyPress',
|
||||
path: '/use-key-press',
|
||||
component: UseKeyPress,
|
||||
},
|
||||
];
|
||||
|
||||
const Header = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const onChange = (event: ChangeEvent<HTMLSelectElement>) => navigate(event.target.value);
|
||||
const navigate = useNavigate();
|
||||
const [currentPath, setCurrentPath] = useState(location.pathname);
|
||||
|
||||
useEffect(() => {
|
||||
const name = routes.find((route) => route.path === currentPath)?.name;
|
||||
document.title = `React Flow Examples${name ? ' - ' + name : ''}`;
|
||||
navigate(currentPath);
|
||||
}, [currentPath]);
|
||||
|
||||
return (
|
||||
<header>
|
||||
<a className="logo" href="https://github.com/wbkd/react-flow">
|
||||
React Flow Dev
|
||||
</a>
|
||||
<select defaultValue={location.pathname} onChange={onChange}>
|
||||
<select value={currentPath} onChange={(event) => setCurrentPath(event.target.value)}>
|
||||
{routes.map((route) => (
|
||||
<option value={route.path} key={route.path}>
|
||||
{route.path === '/' ? 'basic' : route.path.substring(1, route.path.length)}
|
||||
{route.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -92,7 +92,7 @@ const BasicFlow = () => {
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
selectNodesOnDrag={false}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
.Timer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transition-duration: 0.3s;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 100%);
|
||||
font-size: 1.5rem;
|
||||
z-index: 999;
|
||||
background: linear-gradient(#fff, #fff, #f5f5f5);
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.Timer.show {
|
||||
transform: translate(-50%, 0%) translateY(-15px);
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 5px;
|
||||
background: linear-gradient(to right, #42df96, #16dfed);
|
||||
}
|
||||
|
||||
.Timer.show .progress {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import cc from 'classcat';
|
||||
import styles from './Timer.module.css';
|
||||
|
||||
interface Props {
|
||||
duration: number;
|
||||
remaining: number;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export default function Timer({
|
||||
duration,
|
||||
remaining,
|
||||
show,
|
||||
}: Props) {
|
||||
const percentage = 100 - (remaining / duration) * 100;
|
||||
|
||||
return (
|
||||
<div className={cc({
|
||||
[styles.Timer]: true,
|
||||
[styles.show]: show,
|
||||
})}>
|
||||
<div className={styles.progress} style={{
|
||||
width: `${percentage}%`,
|
||||
}} />
|
||||
Connection will be canceled in {remaining} seconds
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Edge, Node } from 'reactflow';
|
||||
|
||||
export const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
export const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
const useCountdown = (callback: () => void) => {
|
||||
const interval = useRef<NodeJS.Timer>();
|
||||
const [remaining, setRemaining] = useState(0);
|
||||
|
||||
const start = (duration: number) => {
|
||||
setRemaining(duration);
|
||||
|
||||
interval.current = setInterval(() => {
|
||||
setRemaining((prev) => {
|
||||
if (prev === 1) {
|
||||
clearInterval(interval.current);
|
||||
callback();
|
||||
}
|
||||
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
clearInterval(interval.current);
|
||||
setRemaining(0);
|
||||
};
|
||||
|
||||
return {
|
||||
start,
|
||||
remaining,
|
||||
stop,
|
||||
counting: remaining > 0,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCountdown;
|
||||
@@ -0,0 +1,62 @@
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
MiniMap,
|
||||
addEdge,
|
||||
ReactFlowProvider,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
OnConnectStart,
|
||||
OnConnectEnd,
|
||||
useStore,
|
||||
} from 'reactflow';
|
||||
|
||||
import useCountdown from './hooks/useCountdown';
|
||||
import { initialEdges, initialNodes } from './data';
|
||||
import Timer from './Timer';
|
||||
|
||||
const CANCEL_AFTER = 5; // seconds
|
||||
|
||||
const CancelConnection = () => {
|
||||
const [nodes, _, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const cancelConnection = useStore(state => state.cancelConnection)
|
||||
|
||||
// Cancels connection after 5 seconds
|
||||
const countdown = useCountdown(() => cancelConnection());
|
||||
const onConnectStart: OnConnectStart = () => countdown.start(CANCEL_AFTER);
|
||||
const onConnectEnd: OnConnectEnd = () => countdown.stop();
|
||||
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Timer
|
||||
duration={CANCEL_AFTER}
|
||||
show={countdown.counting}
|
||||
remaining={countdown.remaining}
|
||||
/>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectEnd={onConnectEnd}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
maxZoom={2}
|
||||
>
|
||||
<Background />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default () => (
|
||||
<ReactFlowProvider>
|
||||
<CancelConnection />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
Node,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import ReactFlow, { useReactFlow, Node, Edge, ReactFlowProvider, Background, BackgroundVariant } from 'reactflow';
|
||||
|
||||
const defaultNodes: Node[] = [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import ReactFlow, { Node, Edge, Position, MarkerType } from 'reactflow';
|
||||
|
||||
const nodes: Node[] = [
|
||||
@@ -107,7 +106,7 @@ const edges: Edge[] = [
|
||||
|
||||
const defaultEdgeOptions = {
|
||||
label: 'Edge Label',
|
||||
type: 'default',
|
||||
type: 'smoothstep',
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useStore, useStoreApi } from 'reactflow';
|
||||
|
||||
import styles from './provider.module.css';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useKeyPress } from 'reactflow';
|
||||
|
||||
const UseKeyPressComponent = () => {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import React, { useState, memo, FC, useMemo, CSSProperties } from 'react';
|
||||
import { Handle, Position, NodeProps, useUpdateNodeInternals } from 'reactflow';
|
||||
|
||||
const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' };
|
||||
|
||||
const CustomNode: FC<NodeProps> = ({ id }) => {
|
||||
const [handleCount, setHandleCount] = useState(1);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
const handles = useMemo(
|
||||
() =>
|
||||
Array.from({ length: handleCount }, (x, i) => {
|
||||
const handleId = `handle-${i}`;
|
||||
return <Handle key={handleId} type="source" position={Position.Right} id={handleId} style={{ top: 10 * i }} />;
|
||||
}),
|
||||
[handleCount]
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={nodeStyles}>
|
||||
<Handle type="target" position={Position.Left} />
|
||||
<div>output handle count: {handleCount}</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setHandleCount((c) => c + 1);
|
||||
updateNodeInternals(id);
|
||||
}}
|
||||
>
|
||||
add handle
|
||||
</button>
|
||||
{handles}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CustomNode);
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
ReactFlowProvider,
|
||||
Node,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
useOnSelectionChange,
|
||||
OnSelectionChangeParams,
|
||||
} from 'reactflow';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'default',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
];
|
||||
|
||||
const SelectionLogger = () => {
|
||||
const onChange = useCallback(({ nodes, edges }: OnSelectionChangeParams) => {
|
||||
console.log(nodes, edges);
|
||||
}, []);
|
||||
|
||||
useOnSelectionChange({
|
||||
onChange,
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Flow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const WrappedFlow = () => (
|
||||
<ReactFlowProvider>
|
||||
<Flow />
|
||||
<SelectionLogger />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
|
||||
export default WrappedFlow;
|
||||
Reference in New Issue
Block a user