Merge branch 'main' into refactor/redux

This commit is contained in:
moklick
2021-02-11 17:52:54 +01:00
39 changed files with 2983 additions and 1549 deletions
+3 -1
View File
@@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M96 124.2c0-6.9 5.2-12.2 12.2-12.2H176V64h-66.8C75.7 64 48 90.7 48 124.2V192h48v-67.8zM403.6 64H336v48h67.2c6.9 0 12.8 5.2 12.8 12.2V192h48v-67.8c0-33.5-27-60.2-60.4-60.2zM416 386.8c0 6.9-5.2 12.2-12.2 12.2H336v49h67.8c33.5 0 60.2-27.7 60.2-61.2V320h-48v66.8zM108.2 399c-6.9 0-12.2-5.2-12.2-12.2V320H48v66.8c0 33.5 27.7 61.2 61.2 61.2H176v-49h-67.8z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
<path d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z" />
</svg>

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 463 B

+3 -1
View File
@@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M376 186h-20v-40c0-55-45-100-100-100S156 91 156 146v40h-20c-22.002 0-40 17.998-40 40v200c0 22.002 17.998 40 40 40h240c22.002 0 40-17.998 40-40V226c0-22.002-17.998-40-40-40zM256 368c-22.002 0-40-17.998-40-40s17.998-40 40-40 40 17.998 40 40-17.998 40-40 40zm62.002-182H193.998v-40c0-34.004 28.003-62.002 62.002-62.002 34.004 0 62.002 27.998 62.002 62.002v40z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z" />
</svg>

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 530 B

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

Before

Width:  |  Height:  |  Size: 98 B

After

Width:  |  Height:  |  Size: 96 B

+3 -1
View File
@@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 277.333H277.333V416h-42.666V277.333H96v-42.666h138.667V96h42.666v138.667H416v42.666z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path d="M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"/>
</svg>

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 152 B

+3 -1
View File
@@ -1 +1,3 @@
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m376 187h-20v-40c0-55-45-100-100-100-54.002 23.996-17.999 27.998 4 31.998s58.002 33.998 58.002 68.002h-.002v40h-182c-22.002 0-40 17.998-40 40v200c0 22.002 17.998 40 40 40h240c22.002 0 40-17.998 40-40v-200c0-22.002-17.998-40-40-40zm-120 182c-22.002 0-40-17.998-40-40s17.998-40 40-40 40 17.998 40 40-17.998 40-40 40z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z" />
</svg>

Before

Width:  |  Height:  |  Size: 394 B

After

Width:  |  Height:  |  Size: 472 B

+14
View File
@@ -27,6 +27,7 @@ const InteractionFlow = () => {
const [isDraggable, setIsDraggable] = useState(false); const [isDraggable, setIsDraggable] = useState(false);
const [isConnectable, setIsConnectable] = useState(false); const [isConnectable, setIsConnectable] = useState(false);
const [zoomOnScroll, setZoomOnScroll] = useState(false); const [zoomOnScroll, setZoomOnScroll] = useState(false);
const [zoomOnPinch, setZoomOnPinch] = useState(false);
const [panOnScroll, setPanOnScroll] = useState(false); const [panOnScroll, setPanOnScroll] = useState(false);
const [panOnScrollMode, setPanOnScrollMode] = useState('free'); const [panOnScrollMode, setPanOnScrollMode] = useState('free');
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(false); const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(false);
@@ -42,6 +43,7 @@ const InteractionFlow = () => {
nodesConnectable={isConnectable} nodesConnectable={isConnectable}
nodesDraggable={isDraggable} nodesDraggable={isDraggable}
zoomOnScroll={zoomOnScroll} zoomOnScroll={zoomOnScroll}
zoomOnPinch={zoomOnPinch}
panOnScroll={panOnScroll} panOnScroll={panOnScroll}
panOnScrollMode={panOnScrollMode} panOnScrollMode={panOnScrollMode}
zoomOnDoubleClick={zoomOnDoubleClick} zoomOnDoubleClick={zoomOnDoubleClick}
@@ -107,6 +109,18 @@ const InteractionFlow = () => {
/> />
</label> </label>
</div> </div>
<div>
<label htmlFor="zoomonpinch">
zoomOnPinch
<input
id="zoomonpinch"
type="checkbox"
checked={zoomOnPinch}
onChange={(event) => setZoomOnPinch(event.target.checked)}
className="react-flow__zoomonpinch"
/>
</label>
</div>
<div> <div>
<label htmlFor="panonscroll"> <label htmlFor="panonscroll">
panOnScroll panOnScroll
+13 -6
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import ReactFlow, { ReactFlowProvider, addEdge, removeElements, Controls, isNode } from 'react-flow-renderer'; import ReactFlow, { ReactFlowProvider, addEdge, removeElements, Controls, isNode } from 'react-flow-renderer';
import dagre from 'dagre'; import dagre from 'dagre';
@@ -9,6 +9,11 @@ import './layouting.css';
const dagreGraph = new dagre.graphlib.Graph(); const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({})); dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeExtent = [
[0, 0],
[1000, 1000],
];
const LayoutFlow = () => { const LayoutFlow = () => {
const [elements, setElements] = useState(initialElements); const [elements, setElements] = useState(initialElements);
const onConnect = (params) => setElements((els) => addEdge(params, els)); const onConnect = (params) => setElements((els) => addEdge(params, els));
@@ -44,14 +49,16 @@ const LayoutFlow = () => {
setElements(layoutedElements); setElements(layoutedElements);
}; };
useEffect(() => {
onLayout('TB');
}, []);
return ( return (
<div className="layoutflow"> <div className="layoutflow">
<ReactFlowProvider> <ReactFlowProvider>
<ReactFlow elements={elements} onConnect={onConnect} onElementsRemove={onElementsRemove}> <ReactFlow
elements={elements}
onConnect={onConnect}
onElementsRemove={onElementsRemove}
nodeExtent={nodeExtent}
onLoad={() => onLayout('TB')}
>
<Controls /> <Controls />
</ReactFlow> </ReactFlow>
<div className="controls"> <div className="controls">
+2
View File
@@ -3,6 +3,7 @@ import React, { useState } from 'react';
import ReactFlow, { removeElements, addEdge, MiniMap, Controls, Background, isNode } from 'react-flow-renderer'; import ReactFlow, { removeElements, addEdge, MiniMap, Controls, Background, isNode } from 'react-flow-renderer';
const onNodeDragStart = (event, node) => console.log('drag start', node); const onNodeDragStart = (event, node) => console.log('drag start', node);
const onNodeDrag = (event, node) => console.log('drag', node);
const onNodeDragStop = (event, node) => console.log('drag stop', node); const onNodeDragStop = (event, node) => console.log('drag stop', node);
const onPaneClick = (event) => console.log('pane click', event); const onPaneClick = (event) => console.log('pane click', event);
const onPaneScroll = (event) => console.log('pane scroll', event); const onPaneScroll = (event) => console.log('pane scroll', event);
@@ -132,6 +133,7 @@ const OverviewFlow = () => {
onPaneScroll={onPaneScroll} onPaneScroll={onPaneScroll}
onPaneContextMenu={onPaneContextMenu} onPaneContextMenu={onPaneContextMenu}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart} onSelectionDragStart={onSelectionDragStart}
onSelectionDrag={onSelectionDrag} onSelectionDrag={onSelectionDrag}
+6
View File
@@ -0,0 +1,6 @@
{
"internal": true,
"main": "../dist/nocss/ReactFlow-nocss.js",
"module": "../dist/nocss/ReactFlow-nocss.esm.js",
"types": "../dist/nocss/index.d.ts"
}
+2485 -1274
View File
File diff suppressed because it is too large Load Diff
+23 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "react-flow-renderer", "name": "react-flow-renderer",
"version": "8.3.7", "version": "8.8.0",
"main": "dist/ReactFlow.js", "main": "dist/ReactFlow.js",
"module": "dist/ReactFlow.esm.js", "module": "dist/ReactFlow.esm.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
@@ -10,12 +10,12 @@
"url": "https://github.com/wbkd/react-flow.git" "url": "https://github.com/wbkd/react-flow.git"
}, },
"scripts": { "scripts": {
"build": "rollup -c --environment NODE_ENV:production", "build": "rollup -c --environment NODE_ENV:production && postcss src/*.css --dir dist",
"start": "rollup -w -c", "start": "rollup -w -c",
"start:examples": "rollup -c rollup.examples.js -w", "start:examples": "rollup -c rollup.examples.js -w",
"build:examples": "rollup -c rollup.examples.js --environment NODE_ENV:production", "build:examples": "rollup -c rollup.examples.js --environment NODE_ENV:production",
"dev": "npm run start:examples", "dev": "npm run start:examples",
"start:testserver": "npm run build && rollup -c rollup.examples.js -w --environment NODE_ENV:testing", "start:testserver": "rollup -c --environment NODE_ENV:testing && rollup -c rollup.examples.js -w --environment NODE_ENV:testing",
"build:dev": "npm install && npm run build:examples", "build:dev": "npm install && npm run build:examples",
"dev:wait": "start-server-and-test start:testserver http-get://localhost:3000", "dev:wait": "start-server-and-test start:testserver http-get://localhost:3000",
"test": "npm run dev:wait test:chrome", "test": "npm run dev:wait test:chrome",
@@ -29,7 +29,7 @@
"release:next": "release-it --preRelease=next" "release:next": "release-it --preRelease=next"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.13",
"classcat": "^4.1.0", "classcat": "^4.1.0",
"d3-selection": "^2.0.0", "d3-selection": "^2.0.0",
"d3-zoom": "^2.0.0", "d3-zoom": "^2.0.0",
@@ -41,50 +41,51 @@
"redux-thunk": "^2.3.0" "redux-thunk": "^2.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.10", "@babel/core": "^7.12.13",
"@babel/plugin-transform-runtime": "^7.12.10", "@babel/plugin-transform-runtime": "^7.12.15",
"@babel/preset-env": "^7.12.11", "@babel/preset-env": "^7.12.13",
"@babel/preset-react": "^7.12.10", "@babel/preset-react": "^7.12.13",
"@rollup/plugin-alias": "^3.1.1", "@rollup/plugin-alias": "^3.1.2",
"@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-babel": "^5.2.3",
"@rollup/plugin-commonjs": "^17.0.0", "@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.0", "@rollup/plugin-node-resolve": "^11.1.1",
"@rollup/plugin-replace": "^2.3.4", "@rollup/plugin-replace": "^2.3.4",
"@svgr/rollup": "^5.5.0", "@svgr/rollup": "^5.5.0",
"@types/classnames": "^2.2.11", "@types/classnames": "^2.2.11",
"@types/d3": "^6.2.0", "@types/d3": "^6.3.0",
"@types/react": "^17.0.0", "@types/react": "^17.0.1",
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"@types/react-redux": "^7.1.15", "@types/react-redux": "^7.1.15",
"@types/redux": "^3.6.0", "@types/redux": "^3.6.0",
"@types/resize-observer-browser": "^0.1.5", "@types/resize-observer-browser": "^0.1.5",
"autoprefixer": "^10.2.1", "autoprefixer": "^10.2.4",
"babel-preset-react-app": "^10.0.0", "babel-preset-react-app": "^10.0.0",
"cypress": "^6.2.1", "cypress": "^6.4.0",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"localforage": "^1.9.0", "localforage": "^1.9.0",
"postcss": "^8.2.4", "postcss": "^8.2.5",
"postcss-cli": "^8.3.1",
"postcss-nested": "^5.0.3", "postcss-nested": "^5.0.3",
"prettier": "2.2.1", "prettier": "2.2.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-flow-renderer": "^8.3.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"release-it": "^14.2.2", "release-it": "^14.3.0",
"rollup": "^2.36.2", "rollup": "^2.38.5",
"rollup-plugin-bundle-size": "^1.0.3", "rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-livereload": "^2.0.0", "rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-postcss": "^4.0.0", "rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-serve": "^1.1.0", "rollup-plugin-serve": "^1.1.0",
"rollup-plugin-typescript2": "^0.29.0", "rollup-plugin-typescript2": "^0.29.0",
"start-server-and-test": "^1.11.7", "start-server-and-test": "^1.12.0",
"typescript": "^4.1.3" "typescript": "^4.1.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "16 || 17" "react": "16 || 17"
}, },
"files": [ "files": [
"dist" "dist",
"nocss"
] ]
} }
+20 -5
View File
@@ -10,9 +10,10 @@ import typescript from 'rollup-plugin-typescript2';
import pkg from './package.json'; import pkg from './package.json';
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
const processEnv = isProd ? 'production' : 'development'; const isTesting = process.env.NODE_ENV === 'testing';
const processEnv = isProd || isTesting ? 'production' : 'development';
export default { export const baseConfig = ({ mainFile = pkg.main, moduleFile = pkg.module, injectCSS = true } = {}) => ({
input: 'src/index.ts', input: 'src/index.ts',
external: ['react', 'react-dom', /@babel\/runtime/], external: ['react', 'react-dom', /@babel\/runtime/],
onwarn(warning, rollupWarn) { onwarn(warning, rollupWarn) {
@@ -22,13 +23,13 @@ export default {
}, },
output: [ output: [
{ {
file: pkg.main, file: mainFile,
format: 'cjs', format: 'cjs',
sourcemap: true, sourcemap: true,
exports: 'named', exports: 'named',
}, },
{ {
file: pkg.module, file: moduleFile,
format: 'esm', format: 'esm',
sourcemap: true, sourcemap: true,
exports: 'named', exports: 'named',
@@ -38,10 +39,13 @@ export default {
replace({ replace({
__ENV__: JSON.stringify(processEnv), __ENV__: JSON.stringify(processEnv),
__REACT_FLOW_VERSION__: JSON.stringify(pkg.version), __REACT_FLOW_VERSION__: JSON.stringify(pkg.version),
// this comes from the easy-peasy dependency
'process.env.FORCE_SIMILAR_INSTEAD_OF_MAP': false,
}), }),
bundleSize(), bundleSize(),
postcss({ postcss({
minimize: isProd, minimize: isProd,
inject: injectCSS,
}), }),
babel({ babel({
exclude: 'node_modules/**', exclude: 'node_modules/**',
@@ -56,4 +60,15 @@ export default {
include: 'node_modules/**', include: 'node_modules/**',
}), }),
], ],
}; });
export default isProd && !isTesting
? [
baseConfig(),
baseConfig({
mainFile: 'dist/nocss/ReactFlow-nocss.js',
moduleFile: 'dist/nocss/ReactFlow-nocss.esm.js',
injectCSS: false,
}),
]
: baseConfig();
+5 -5
View File
@@ -8,7 +8,7 @@ import postcss from 'rollup-plugin-postcss';
import livereload from 'rollup-plugin-livereload'; import livereload from 'rollup-plugin-livereload';
import serve from 'rollup-plugin-serve'; import serve from 'rollup-plugin-serve';
import libraryRollupConfig from './rollup.config.js'; import { baseConfig } from './rollup.config.js';
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
const isTesting = process.env.NODE_ENV === 'testing'; const isTesting = process.env.NODE_ENV === 'testing';
@@ -16,8 +16,8 @@ const isTesting = process.env.NODE_ENV === 'testing';
const serveFiles = !isProd || isTesting; const serveFiles = !isProd || isTesting;
const doLiveReload = !isProd && !isTesting; const doLiveReload = !isProd && !isTesting;
const rollupConfig = { const rollupExamplesConfig = {
input: ['example/src/index.js'], input: 'example/src/index.js',
output: [ output: [
{ {
dir: 'example/public/dist', dir: 'example/public/dist',
@@ -28,7 +28,7 @@ const rollupConfig = {
plugins: [ plugins: [
replace({ replace({
'process.env.NODE_ENV': isProd ? JSON.stringify('production') : JSON.stringify('development'), 'process.env.NODE_ENV': isProd ? JSON.stringify('production') : JSON.stringify('development'),
'process.env.FORCE_SIMILAR_INSTEAD_OF_MAP': 'false', 'process.env.FORCE_SIMILAR_INSTEAD_OF_MAP': false,
}), }),
postcss({ postcss({
modules: false, modules: false,
@@ -55,4 +55,4 @@ const rollupConfig = {
], ],
}; };
export default isTesting ? rollupConfig : [libraryRollupConfig, rollupConfig]; export default isTesting ? rollupExamplesConfig : [baseConfig(), rollupExamplesConfig];
+11 -14
View File
@@ -5,8 +5,6 @@ import { useStoreState } from '../../store/hooks';
import { BackgroundVariant } from '../../types'; import { BackgroundVariant } from '../../types';
import { createGridLinesPath, createGridDotsPath } from './utils'; import { createGridLinesPath, createGridDotsPath } from './utils';
import './style.css';
export interface BackgroundProps extends HTMLAttributes<SVGElement> { export interface BackgroundProps extends HTMLAttributes<SVGElement> {
variant?: BackgroundVariant; variant?: BackgroundVariant;
gap?: number; gap?: number;
@@ -34,26 +32,25 @@ const Background = ({
const xOffset = x % scaledGap; const xOffset = x % scaledGap;
const yOffset = y % scaledGap; const yOffset = y % scaledGap;
const isLines = variant === BackgroundVariant.Lines;
const isLines = variant === BackgroundVariant.Lines; const bgColor = color ? color : defaultColors[variant];
const bgColor = color ? color : defaultColors[variant]; const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
return ( return (
<svg <svg
className={bgClasses} className={bgClasses}
style={{ style={{
...style, ...style,
width: "100%", width: '100%',
height: "100%" height: '100%',
}} }}
> >
<pattern id="pattern" x={xOffset} y={yOffset} width={scaledGap} height={scaledGap} patternUnits="userSpaceOnUse"> <pattern id="pattern" x={xOffset} y={yOffset} width={scaledGap} height={scaledGap} patternUnits="userSpaceOnUse">
{path} {path}
</pattern> </pattern>
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern)"></rect> <rect x="0" y="0" width="100%" height="100%" fill="url(#pattern)"></rect>
</svg> </svg>
); );
}; };
@@ -1,7 +0,0 @@
.react-flow__background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
+5 -3
View File
@@ -9,13 +9,14 @@ import FitviewIcon from '../../../assets/icons/fitview.svg';
import LockIcon from '../../../assets/icons/lock.svg'; import LockIcon from '../../../assets/icons/lock.svg';
import UnlockIcon from '../../../assets/icons/unlock.svg'; import UnlockIcon from '../../../assets/icons/unlock.svg';
import './style.css';
import useZoomPanHelper from '../../hooks/useZoomPanHelper'; import useZoomPanHelper from '../../hooks/useZoomPanHelper';
import { FitViewParams } from '../../types';
export interface ControlProps extends React.HTMLAttributes<HTMLDivElement> { export interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {
showZoom?: boolean; showZoom?: boolean;
showFitView?: boolean; showFitView?: boolean;
showInteractive?: boolean; showInteractive?: boolean;
fitViewParams?: FitViewParams;
onZoomIn?: () => void; onZoomIn?: () => void;
onZoomOut?: () => void; onZoomOut?: () => void;
onFitView?: () => void; onFitView?: () => void;
@@ -27,6 +28,7 @@ const Controls = ({
showZoom = true, showZoom = true,
showFitView = true, showFitView = true,
showInteractive = true, showInteractive = true,
fitViewParams,
onZoomIn, onZoomIn,
onZoomOut, onZoomOut,
onFitView, onFitView,
@@ -50,9 +52,9 @@ const Controls = ({
}, [zoomOut, onZoomOut]); }, [zoomOut, onZoomOut]);
const onFitViewHandler = useCallback(() => { const onFitViewHandler = useCallback(() => {
fitView?.(); fitView?.(fitViewParams);
onFitView?.(); onFitView?.();
}, [fitView, onFitView]); }, [fitView, fitViewParams, onFitView]);
const onInteractiveChangeHandler = useCallback(() => { const onInteractiveChangeHandler = useCallback(() => {
setInteractive?.(!isInteractive); setInteractive?.(!isInteractive);
@@ -1,29 +0,0 @@
.react-flow__controls {
position: absolute;
z-index: 5;
bottom: 10px;
left: 10px;
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);
&-button {
background: #fefefe;
border-bottom: 1px solid #eee;
box-sizing: content-box;
display: flex;
justify-content: center;
align-items: center;
width: 16px;
height: 16px;
cursor: pointer;
user-select: none;
padding: 5px;
svg {
width: 100%;
}
&:hover {
background: #f4f4f4;
}
}
}
@@ -10,10 +10,22 @@ interface MiniMapNodeProps {
className: string; className: string;
color: string; color: string;
strokeColor: string; strokeColor: string;
strokeWidth: number;
style?: CSSProperties; style?: CSSProperties;
} }
const MiniMapNode = ({ x, y, width, height, style, color, strokeColor, className, borderRadius }: MiniMapNodeProps) => { const MiniMapNode = ({
x,
y,
width,
height,
style,
color,
strokeColor,
strokeWidth,
className,
borderRadius,
}: MiniMapNodeProps) => {
const { background, backgroundColor } = style || {}; const { background, backgroundColor } = style || {};
const fill = (color || background || backgroundColor) as string; const fill = (color || background || backgroundColor) as string;
@@ -28,7 +40,7 @@ const MiniMapNode = ({ x, y, width, height, style, color, strokeColor, className
height={height} height={height}
fill={fill} fill={fill}
stroke={strokeColor} stroke={strokeColor}
strokeWidth={2} strokeWidth={strokeWidth}
/> />
); );
}; };
+3 -2
View File
@@ -6,8 +6,6 @@ import { getRectOfNodes, getBoundsofRects, isNode } from '../../utils/graph';
import { Node, Rect } from '../../types'; import { Node, Rect } from '../../types';
import MiniMapNode from './MiniMapNode'; import MiniMapNode from './MiniMapNode';
import './style.css';
type StringFunc = (node: Node) => string; type StringFunc = (node: Node) => string;
export interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> { export interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
@@ -15,6 +13,7 @@ export interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
nodeStrokeColor?: string | StringFunc; nodeStrokeColor?: string | StringFunc;
nodeClassName?: string | StringFunc; nodeClassName?: string | StringFunc;
nodeBorderRadius?: number; nodeBorderRadius?: number;
nodeStrokeWidth?: number;
maskColor?: string; maskColor?: string;
} }
@@ -28,6 +27,7 @@ const MiniMap = ({
nodeColor = '#fff', nodeColor = '#fff',
nodeClassName = '', nodeClassName = '',
nodeBorderRadius = 5, nodeBorderRadius = 5,
nodeStrokeWidth = 2,
maskColor = 'rgb(240, 242, 243, 0.7)', maskColor = 'rgb(240, 242, 243, 0.7)',
}: MiniMapProps) => { }: MiniMapProps) => {
const containerWidth = useStoreState((s) => s.width); const containerWidth = useStoreState((s) => s.width);
@@ -86,6 +86,7 @@ const MiniMap = ({
color={nodeColorFunc(node)} color={nodeColorFunc(node)}
borderRadius={nodeBorderRadius} borderRadius={nodeBorderRadius}
strokeColor={nodeStrokeColorFunc(node)} strokeColor={nodeStrokeColorFunc(node)}
strokeWidth={nodeStrokeWidth}
/> />
))} ))}
<path <path
@@ -1,7 +0,0 @@
.react-flow__minimap {
position: absolute;
z-index: 5;
bottom: 10px;
right: 10px;
background-color: #fff;
}
+1 -1
View File
@@ -1,5 +1,5 @@
// These components are not used by React Flow directly // These components are not used by React Flow directly
// but the user can add them as a child of a React Flow component // but the user can add them as children of a React Flow component
export { default as MiniMap } from './MiniMap'; export { default as MiniMap } from './MiniMap';
export { default as Controls } from './Controls'; export { default as Controls } from './Controls';
+18 -11
View File
@@ -1,10 +1,10 @@
import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react'; import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react';
import { DraggableCore } from 'react-draggable'; import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
import cc from 'classcat'; import cc from 'classcat';
import { useStoreActions } from '../../store/hooks'; import { useStoreActions } from '../../store/hooks';
import { Provider } from '../../contexts/NodeIdContext'; import { Provider } from '../../contexts/NodeIdContext';
import { Node, NodeComponentProps, WrapNodeProps } from '../../types'; import { NodeComponentProps, WrapNodeProps } from '../../types';
export default (NodeComponent: ComponentType<NodeComponentProps>) => { export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = ({ const NodeWrapper = ({
@@ -21,6 +21,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onMouseLeave, onMouseLeave,
onContextMenu, onContextMenu,
onNodeDragStart, onNodeDragStart,
onNodeDrag,
onNodeDragStop, onNodeDragStop,
style, style,
className, className,
@@ -96,7 +97,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
unsetNodesSelection(); unsetNodesSelection();
if (!selected) { if (!selected) {
addSelectedElements({ id: node.id, type: node.type } as Node); addSelectedElements(node);
} }
} }
@@ -107,14 +108,14 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
); );
const onDragStart = useCallback( const onDragStart = useCallback(
(event) => { (event: DraggableEvent) => {
onNodeDragStart?.(event as MouseEvent, node); onNodeDragStart?.(event as MouseEvent, node);
if (selectNodesOnDrag && isSelectable) { if (selectNodesOnDrag && isSelectable) {
unsetNodesSelection(); unsetNodesSelection();
if (!selected) { if (!selected) {
addSelectedElements({ id: node.id, type: node.type } as Node); addSelectedElements(node);
} }
} else if (!selectNodesOnDrag && !selected && isSelectable) { } else if (!selectNodesOnDrag && !selected && isSelectable) {
unsetNodesSelection(); unsetNodesSelection();
@@ -125,25 +126,31 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
); );
const onDrag = useCallback( const onDrag = useCallback(
(_, data) => { (event: DraggableEvent, draggableData: DraggableData) => {
if (onNodeDrag) {
node.position.x += draggableData.deltaX;
node.position.y += draggableData.deltaY;
onNodeDrag(event as MouseEvent, node);
}
updateNodePosDiff({ updateNodePosDiff({
id, id,
diff: { diff: {
x: data.deltaX, x: draggableData.deltaX,
y: data.deltaY, y: draggableData.deltaY,
}, },
}); });
}, },
[id] [id, node, onNodeDrag]
); );
const onDragStop = useCallback( const onDragStop = useCallback(
(event) => { (event: DraggableEvent) => {
// onDragStop also gets called when user just clicks on a node. // onDragStop also gets called when user just clicks on a node.
// Because of that we set dragging to true inside the onDrag handler and handle the click here // Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!isDragging) { if (!isDragging) {
if (isSelectable && !selectNodesOnDrag && !selected) { if (isSelectable && !selectNodesOnDrag && !selected) {
addSelectedElements({ id: node.id, type: node.type } as Node); addSelectedElements(node);
} }
onClick?.(event as MouseEvent, node); onClick?.(event as MouseEvent, node);
+4
View File
@@ -37,8 +37,10 @@ const FlowRenderer = ({
onMoveEnd, onMoveEnd,
selectionKeyCode, selectionKeyCode,
multiSelectionKeyCode, multiSelectionKeyCode,
zoomActivationKeyCode,
elementsSelectable, elementsSelectable,
zoomOnScroll, zoomOnScroll,
zoomOnPinch,
panOnScroll, panOnScroll,
panOnScrollSpeed, panOnScrollSpeed,
panOnScrollMode, panOnScrollMode,
@@ -91,6 +93,7 @@ const FlowRenderer = ({
selectionKeyPressed={selectionKeyPressed} selectionKeyPressed={selectionKeyPressed}
elementsSelectable={elementsSelectable} elementsSelectable={elementsSelectable}
zoomOnScroll={zoomOnScroll} zoomOnScroll={zoomOnScroll}
zoomOnPinch={zoomOnPinch}
panOnScroll={panOnScroll} panOnScroll={panOnScroll}
panOnScrollSpeed={panOnScrollSpeed} panOnScrollSpeed={panOnScrollSpeed}
panOnScrollMode={panOnScrollMode} panOnScrollMode={panOnScrollMode}
@@ -99,6 +102,7 @@ const FlowRenderer = ({
defaultPosition={defaultPosition} defaultPosition={defaultPosition}
defaultZoom={defaultZoom} defaultZoom={defaultZoom}
translateExtent={translateExtent} translateExtent={translateExtent}
zoomActivationKeyCode={zoomActivationKeyCode}
> >
{children} {children}
<UserSelection selectionKeyPressed={selectionKeyPressed} /> <UserSelection selectionKeyPressed={selectionKeyPressed} />
+14
View File
@@ -40,6 +40,7 @@ const GraphView = ({
onNodeMouseLeave, onNodeMouseLeave,
onNodeContextMenu, onNodeContextMenu,
onNodeDragStart, onNodeDragStart,
onNodeDrag,
onNodeDragStop, onNodeDragStop,
onSelectionDragStart, onSelectionDragStart,
onSelectionDrag, onSelectionDrag,
@@ -51,6 +52,7 @@ const GraphView = ({
connectionLineComponent, connectionLineComponent,
selectionKeyCode, selectionKeyCode,
multiSelectionKeyCode, multiSelectionKeyCode,
zoomActivationKeyCode,
onElementsRemove, onElementsRemove,
deleteKeyCode, deleteKeyCode,
onConnect, onConnect,
@@ -69,9 +71,11 @@ const GraphView = ({
defaultZoom, defaultZoom,
defaultPosition, defaultPosition,
translateExtent, translateExtent,
nodeExtent,
arrowHeadColor, arrowHeadColor,
markerEndId, markerEndId,
zoomOnScroll, zoomOnScroll,
zoomOnPinch,
panOnScroll, panOnScroll,
panOnScrollSpeed, panOnScrollSpeed,
panOnScrollMode, panOnScrollMode,
@@ -95,6 +99,7 @@ const GraphView = ({
const setMinZoom = useStoreActions((actions) => actions.setMinZoom); const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom); const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent); const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
const setNodeExtent = useStoreActions((actions) => actions.setNodeExtent);
const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode); const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
const currentStore = useStore(); const currentStore = useStore();
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper(); const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
@@ -190,6 +195,12 @@ const GraphView = ({
} }
}, [translateExtent]); }, [translateExtent]);
useEffect(() => {
if (typeof nodeExtent !== 'undefined') {
setNodeExtent(nodeExtent);
}
}, [nodeExtent]);
useEffect(() => { useEffect(() => {
if (typeof connectionMode !== 'undefined') { if (typeof connectionMode !== 'undefined') {
setConnectionMode(connectionMode); setConnectionMode(connectionMode);
@@ -205,11 +216,13 @@ const GraphView = ({
deleteKeyCode={deleteKeyCode} deleteKeyCode={deleteKeyCode}
selectionKeyCode={selectionKeyCode} selectionKeyCode={selectionKeyCode}
multiSelectionKeyCode={multiSelectionKeyCode} multiSelectionKeyCode={multiSelectionKeyCode}
zoomActivationKeyCode={zoomActivationKeyCode}
elementsSelectable={elementsSelectable} elementsSelectable={elementsSelectable}
onMove={onMove} onMove={onMove}
onMoveStart={onMoveStart} onMoveStart={onMoveStart}
onMoveEnd={onMoveEnd} onMoveEnd={onMoveEnd}
zoomOnScroll={zoomOnScroll} zoomOnScroll={zoomOnScroll}
zoomOnPinch={zoomOnPinch}
zoomOnDoubleClick={zoomOnDoubleClick} zoomOnDoubleClick={zoomOnDoubleClick}
panOnScroll={panOnScroll} panOnScroll={panOnScroll}
panOnScrollSpeed={panOnScrollSpeed} panOnScrollSpeed={panOnScrollSpeed}
@@ -231,6 +244,7 @@ const GraphView = ({
onNodeMouseLeave={onNodeMouseLeave} onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu} onNodeContextMenu={onNodeContextMenu}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onNodeDrag={onNodeDrag}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
selectNodesOnDrag={selectNodesOnDrag} selectNodesOnDrag={selectNodesOnDrag}
snapToGrid={snapToGrid} snapToGrid={snapToGrid}
+2 -1
View File
@@ -3,7 +3,6 @@ import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
import { getNodesInside, isNode } from '../../utils/graph'; import { getNodesInside, isNode } from '../../utils/graph';
import { useStoreState, useStoreActions } from '../../store/hooks'; import { useStoreState, useStoreActions } from '../../store/hooks';
import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types'; import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
selectNodesOnDrag: boolean; selectNodesOnDrag: boolean;
@@ -13,6 +12,7 @@ interface NodeRendererProps {
onNodeMouseLeave?: (event: MouseEvent, node: Node) => void; onNodeMouseLeave?: (event: MouseEvent, node: Node) => void;
onNodeContextMenu?: (event: MouseEvent, node: Node) => void; onNodeContextMenu?: (event: MouseEvent, node: Node) => void;
onNodeDragStart?: (event: MouseEvent, node: Node) => void; onNodeDragStart?: (event: MouseEvent, node: Node) => void;
onNodeDrag?: (event: MouseEvent, node: Node) => void;
onNodeDragStop?: (event: MouseEvent, node: Node) => void; onNodeDragStop?: (event: MouseEvent, node: Node) => void;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [number, number]; snapGrid: [number, number];
@@ -94,6 +94,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
onMouseLeave={props.onNodeMouseLeave} onMouseLeave={props.onNodeMouseLeave}
onContextMenu={props.onNodeContextMenu} onContextMenu={props.onNodeContextMenu}
onNodeDragStart={props.onNodeDragStart} onNodeDragStart={props.onNodeDragStart}
onNodeDrag={props.onNodeDrag}
onNodeDragStop={props.onNodeDragStop} onNodeDragStop={props.onNodeDragStop}
scale={transform[2]} scale={transform[2]}
selected={selectedElements?.some(({ id }) => id === node.id) || false} selected={selectedElements?.some(({ id }) => id === node.id) || false}
+14
View File
@@ -30,9 +30,11 @@ import {
KeyCode, KeyCode,
PanOnScrollMode, PanOnScrollMode,
OnEdgeUpdateFunc, OnEdgeUpdateFunc,
NodeExtent,
} from '../../types'; } from '../../types';
import '../../style.css'; import '../../style.css';
import '../../theme-default.css';
const defaultNodeTypes = { const defaultNodeTypes = {
input: InputNode, input: InputNode,
@@ -56,6 +58,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
onNodeMouseLeave?: (event: MouseEvent, node: Node) => void; onNodeMouseLeave?: (event: MouseEvent, node: Node) => void;
onNodeContextMenu?: (event: MouseEvent, node: Node) => void; onNodeContextMenu?: (event: MouseEvent, node: Node) => void;
onNodeDragStart?: (event: MouseEvent, node: Node) => void; onNodeDragStart?: (event: MouseEvent, node: Node) => void;
onNodeDrag?: (event: MouseEvent, node: Node) => void;
onNodeDragStop?: (event: MouseEvent, node: Node) => void; onNodeDragStop?: (event: MouseEvent, node: Node) => void;
onConnect?: (connection: Edge | Connection) => void; onConnect?: (connection: Edge | Connection) => void;
onConnectStart?: OnConnectStartFunc; onConnectStart?: OnConnectStartFunc;
@@ -82,6 +85,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
deleteKeyCode?: KeyCode; deleteKeyCode?: KeyCode;
selectionKeyCode?: KeyCode; selectionKeyCode?: KeyCode;
multiSelectionKeyCode?: KeyCode; multiSelectionKeyCode?: KeyCode;
zoomActivationKeyCode?: KeyCode;
snapToGrid?: boolean; snapToGrid?: boolean;
snapGrid?: [number, number]; snapGrid?: [number, number];
onlyRenderVisibleElements?: boolean; onlyRenderVisibleElements?: boolean;
@@ -95,9 +99,11 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
defaultZoom?: number; defaultZoom?: number;
defaultPosition?: [number, number]; defaultPosition?: [number, number];
translateExtent?: TranslateExtent; translateExtent?: TranslateExtent;
nodeExtent?: NodeExtent;
arrowHeadColor?: string; arrowHeadColor?: string;
markerEndId?: string; markerEndId?: string;
zoomOnScroll?: boolean; zoomOnScroll?: boolean;
zoomOnPinch?: boolean;
panOnScroll?: boolean; panOnScroll?: boolean;
panOnScrollSpeed?: number; panOnScrollSpeed?: number;
panOnScrollMode?: PanOnScrollMode; panOnScrollMode?: PanOnScrollMode;
@@ -125,6 +131,7 @@ const ReactFlow = ({
onNodeMouseLeave, onNodeMouseLeave,
onNodeContextMenu, onNodeContextMenu,
onNodeDragStart, onNodeDragStart,
onNodeDrag,
onNodeDragStop, onNodeDragStop,
onSelectionChange, onSelectionChange,
onSelectionDragStart, onSelectionDragStart,
@@ -138,6 +145,7 @@ const ReactFlow = ({
deleteKeyCode = 'Backspace', deleteKeyCode = 'Backspace',
selectionKeyCode = 'Shift', selectionKeyCode = 'Shift',
multiSelectionKeyCode = 'Meta', multiSelectionKeyCode = 'Meta',
zoomActivationKeyCode = 'Meta',
snapToGrid = false, snapToGrid = false,
snapGrid = [15, 15], snapGrid = [15, 15],
onlyRenderVisibleElements = true, onlyRenderVisibleElements = true,
@@ -150,9 +158,11 @@ const ReactFlow = ({
defaultZoom = 1, defaultZoom = 1,
defaultPosition = [0, 0], defaultPosition = [0, 0],
translateExtent, translateExtent,
nodeExtent,
arrowHeadColor = '#b1b1b7', arrowHeadColor = '#b1b1b7',
markerEndId, markerEndId,
zoomOnScroll = true, zoomOnScroll = true,
zoomOnPinch = true,
panOnScroll = false, panOnScroll = false,
panOnScrollSpeed = 0.5, panOnScrollSpeed = 0.5,
panOnScrollMode = PanOnScrollMode.Free, panOnScrollMode = PanOnScrollMode.Free,
@@ -183,6 +193,7 @@ const ReactFlow = ({
onNodeMouseLeave={onNodeMouseLeave} onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu} onNodeContextMenu={onNodeContextMenu}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
nodeTypes={nodeTypesParsed} nodeTypes={nodeTypesParsed}
edgeTypes={edgeTypesParsed} edgeTypes={edgeTypesParsed}
@@ -194,6 +205,7 @@ const ReactFlow = ({
onElementsRemove={onElementsRemove} onElementsRemove={onElementsRemove}
deleteKeyCode={deleteKeyCode} deleteKeyCode={deleteKeyCode}
multiSelectionKeyCode={multiSelectionKeyCode} multiSelectionKeyCode={multiSelectionKeyCode}
zoomActivationKeyCode={zoomActivationKeyCode}
onConnect={onConnect} onConnect={onConnect}
onConnectStart={onConnectStart} onConnectStart={onConnectStart}
onConnectStop={onConnectStop} onConnectStop={onConnectStop}
@@ -210,9 +222,11 @@ const ReactFlow = ({
defaultZoom={defaultZoom} defaultZoom={defaultZoom}
defaultPosition={defaultPosition} defaultPosition={defaultPosition}
translateExtent={translateExtent} translateExtent={translateExtent}
nodeExtent={nodeExtent}
arrowHeadColor={arrowHeadColor} arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId} markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll} zoomOnScroll={zoomOnScroll}
zoomOnPinch={zoomOnPinch}
zoomOnDoubleClick={zoomOnDoubleClick} zoomOnDoubleClick={zoomOnDoubleClick}
panOnScroll={panOnScroll} panOnScroll={panOnScroll}
panOnScrollSpeed={panOnScrollSpeed} panOnScrollSpeed={panOnScrollSpeed}
+43 -9
View File
@@ -1,17 +1,18 @@
import React, { useEffect, useRef, ReactNode } from 'react'; import React, { useEffect, useRef, ReactNode } from 'react';
import { zoom, zoomIdentity } from 'd3-zoom'; import { zoom, zoomIdentity } from 'd3-zoom';
import { select } from 'd3-selection'; import { select, pointer } from 'd3-selection';
import { clamp } from '../../utils';
import { clamp } from '../../utils';
import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler'; import useResizeHandler from '../../hooks/useResizeHandler';
import { useStoreState, useStoreActions, useStore } from '../../store/hooks'; import { useStoreState, useStoreActions, useStore } from '../../store/hooks';
import { FlowTransform, TranslateExtent, PanOnScrollMode } from '../../types'; import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode } from '../../types';
interface ZoomPaneProps { interface ZoomPaneProps {
selectionKeyPressed: boolean; selectionKeyPressed: boolean;
elementsSelectable?: boolean; elementsSelectable?: boolean;
zoomOnScroll?: boolean; zoomOnScroll?: boolean;
zoomOnPinch?: boolean;
panOnScroll?: boolean; panOnScroll?: boolean;
panOnScrollSpeed?: number; panOnScrollSpeed?: number;
panOnScrollMode?: PanOnScrollMode; panOnScrollMode?: PanOnScrollMode;
@@ -23,6 +24,7 @@ interface ZoomPaneProps {
onMove?: (flowTransform?: FlowTransform) => void; onMove?: (flowTransform?: FlowTransform) => void;
onMoveStart?: (flowTransform?: FlowTransform) => void; onMoveStart?: (flowTransform?: FlowTransform) => void;
onMoveEnd?: (flowTransform?: FlowTransform) => void; onMoveEnd?: (flowTransform?: FlowTransform) => void;
zoomActivationKeyCode?: KeyCode;
children: ReactNode; children: ReactNode;
} }
@@ -42,6 +44,7 @@ const ZoomPane = ({
onMoveStart, onMoveStart,
onMoveEnd, onMoveEnd,
zoomOnScroll = true, zoomOnScroll = true,
zoomOnPinch = true,
panOnScroll = false, panOnScroll = false,
panOnScrollSpeed = 0.5, panOnScrollSpeed = 0.5,
panOnScrollMode = PanOnScrollMode.Free, panOnScrollMode = PanOnScrollMode.Free,
@@ -52,6 +55,7 @@ const ZoomPane = ({
defaultPosition = [0, 0], defaultPosition = [0, 0],
defaultZoom = 1, defaultZoom = 1,
translateExtent, translateExtent,
zoomActivationKeyCode,
children, children,
}: ZoomPaneProps) => { }: ZoomPaneProps) => {
const zoomPane = useRef<HTMLDivElement>(null); const zoomPane = useRef<HTMLDivElement>(null);
@@ -65,6 +69,8 @@ const ZoomPane = ({
const initD3Zoom = useStoreActions((actions) => actions.initD3Zoom); const initD3Zoom = useStoreActions((actions) => actions.initD3Zoom);
const updateTransform = useStoreActions((actions) => actions.updateTransform); const updateTransform = useStoreActions((actions) => actions.updateTransform);
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
useResizeHandler(zoomPane); useResizeHandler(zoomPane);
useEffect(() => { useEffect(() => {
@@ -93,13 +99,24 @@ const ZoomPane = ({
useEffect(() => { useEffect(() => {
if (d3Selection && d3Zoom) { if (d3Selection && d3Zoom) {
if (panOnScroll) { if (panOnScroll && !zoomActivationKeyPressed) {
d3Selection d3Selection
.on('wheel', (event: any) => { .on('wheel', (event: any) => {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
const currentZoom = d3Selection.property('__zoom').k || 1; const currentZoom = d3Selection.property('__zoom').k || 1;
if (event.ctrlKey && zoomOnPinch) {
const point = pointer(event);
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10;
const zoom = currentZoom * Math.pow(2, pinchDelta);
d3Zoom.scaleTo(d3Selection, zoom, point);
return;
}
// increase scroll speed in firefox // increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0 // firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1; const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
@@ -117,7 +134,7 @@ const ZoomPane = ({
d3Selection.on('wheel', null).on('wheel.zoom', d3ZoomHandler); d3Selection.on('wheel', null).on('wheel.zoom', d3ZoomHandler);
} }
} }
}, [panOnScroll, panOnScrollMode, d3Selection, d3Zoom, d3ZoomHandler]); }, [panOnScroll, panOnScrollMode, d3Selection, d3Zoom, d3ZoomHandler, zoomActivationKeyPressed, zoomOnPinch]);
useEffect(() => { useEffect(() => {
if (d3Zoom) { if (d3Zoom) {
@@ -173,8 +190,11 @@ const ZoomPane = ({
useEffect(() => { useEffect(() => {
if (d3Zoom) { if (d3Zoom) {
d3Zoom.filter((event: any) => { d3Zoom.filter((event: any) => {
const zoomScroll = zoomActivationKeyPressed || zoomOnScroll;
const pinchZoom = zoomOnPinch && event.ctrlKey;
// if all interactions are disabled, we prevent all zoom events // if all interactions are disabled, we prevent all zoom events
if (!paneMoveable && !zoomOnScroll && !panOnScroll && !zoomOnDoubleClick) { if (!paneMoveable && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
return false; return false;
} }
@@ -205,8 +225,12 @@ const ZoomPane = ({
return false; return false;
} }
if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
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 (!zoomOnScroll && !panOnScroll && event.type === 'wheel') { if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
return false; return false;
} }
@@ -219,7 +243,17 @@ const ZoomPane = ({
return (!event.ctrlKey || event.type === 'wheel') && !event.button; return (!event.ctrlKey || event.type === 'wheel') && !event.button;
}); });
} }
}, [d3Zoom, zoomOnScroll, panOnScroll, zoomOnDoubleClick, paneMoveable, selectionKeyPressed, elementsSelectable]); }, [
d3Zoom,
zoomOnScroll,
zoomOnPinch,
panOnScroll,
zoomOnDoubleClick,
paneMoveable,
selectionKeyPressed,
elementsSelectable,
zoomActivationKeyPressed,
]);
return ( return (
<div className="react-flow__renderer react-flow__zoompane" ref={zoomPane}> <div className="react-flow__renderer react-flow__zoompane" ref={zoomPane}>
+23 -21
View File
@@ -3,35 +3,37 @@ import { useState, useEffect } from 'react';
import { isInputDOMNode } from '../utils'; import { isInputDOMNode } from '../utils';
import { KeyCode } from '../types'; import { KeyCode } from '../types';
export default (keyCode: KeyCode): boolean => { export default (keyCode?: KeyCode): boolean => {
const [keyPressed, setKeyPressed] = useState(false); const [keyPressed, setKeyPressed] = useState(false);
useEffect(() => { useEffect(() => {
const downHandler = (event: KeyboardEvent) => { if (typeof keyCode !== 'undefined') {
if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) { const downHandler = (event: KeyboardEvent) => {
event.preventDefault(); if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
event.preventDefault();
setKeyPressed(true); setKeyPressed(true);
} }
}; };
const upHandler = (event: KeyboardEvent) => { const upHandler = (event: KeyboardEvent) => {
if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) { if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
setKeyPressed(false); setKeyPressed(false);
} }
}; };
const resetHandler = () => setKeyPressed(false); const resetHandler = () => setKeyPressed(false);
window.addEventListener('keydown', downHandler); window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler); window.addEventListener('keyup', upHandler);
window.addEventListener('blur', resetHandler); window.addEventListener('blur', resetHandler);
return () => { return () => {
window.removeEventListener('keydown', downHandler); window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler); window.removeEventListener('keyup', upHandler);
window.removeEventListener('blur', resetHandler); window.removeEventListener('blur', resetHandler);
}; };
}
}, [keyCode, setKeyPressed]); }, [keyCode, setKeyPressed]);
return keyPressed; return keyPressed;
+11 -8
View File
@@ -6,12 +6,14 @@ import { clamp } from '../utils';
import { getRectOfNodes, isNode } from '../utils/graph'; import { getRectOfNodes, isNode } from '../utils/graph';
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, Transform } from '../types'; import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, Transform } from '../types';
const DEFAULT_PADDING = 0.1;
const initialZoomPanHelper: ZoomPanHelperFunctions = { const initialZoomPanHelper: ZoomPanHelperFunctions = {
zoomIn: () => {}, zoomIn: () => {},
zoomOut: () => {}, zoomOut: () => {},
zoomTo: (_: number) => {}, zoomTo: (_: number) => {},
transform: (_: FlowTransform) => {}, transform: (_: FlowTransform) => {},
fitView: (_: FitViewParams = { padding: 0.1 }) => {}, fitView: (_: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {},
setCenter: (_: number, __: number) => {}, setCenter: (_: number, __: number) => {},
fitBounds: (_: Rect) => {}, fitBounds: (_: Rect) => {},
initialized: false, initialized: false,
@@ -23,7 +25,7 @@ const getTransformForBounds = (
height: number, height: number,
minZoom: number, minZoom: number,
maxZoom: number, maxZoom: number,
padding = 0.1 padding = DEFAULT_PADDING
): Transform => { ): Transform => {
const xZoom = width / (bounds.width * (1 + padding)); const xZoom = width / (bounds.width * (1 + padding));
const yZoom = height / (bounds.height * (1 + padding)); const yZoom = height / (bounds.height * (1 + padding));
@@ -37,7 +39,7 @@ const getTransformForBounds = (
return [x, y, clampedZoom]; return [x, y, clampedZoom];
}; };
const usePanZoomHelper = (): ZoomPanHelperFunctions => { const useZoomPanHelper = (): ZoomPanHelperFunctions => {
const store = useStore(); const store = useStore();
const d3Zoom = useStoreState((s) => s.d3Zoom); const d3Zoom = useStoreState((s) => s.d3Zoom);
const d3Selection = useStoreState((s) => s.d3Selection); const d3Selection = useStoreState((s) => s.d3Selection);
@@ -53,7 +55,7 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
d3Zoom.transform(d3Selection, nextTransform); d3Zoom.transform(d3Selection, nextTransform);
}, },
fitView: (options: FitViewParams = { padding: 0.1 }) => { fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {
const { elements, width, height, minZoom, maxZoom } = store.getState(); const { elements, width, height, minZoom, maxZoom } = store.getState();
const nodes = elements.filter(isNode); const nodes = elements.filter(isNode);
@@ -61,8 +63,9 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
return; return;
} }
const bounds = getRectOfNodes(nodes); const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden));
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, options.padding); const padding = options.padding ?? DEFAULT_PADDING;
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(d3Selection, transform);
@@ -77,7 +80,7 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(d3Selection, transform);
}, },
fitBounds: (bounds: Rect, padding = 0.1) => { fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
const { width, height, minZoom, maxZoom } = store.getState(); const { width, height, minZoom, maxZoom } = store.getState();
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding); const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom);
@@ -94,4 +97,4 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
return zoomPanHelperFunctions; return zoomPanHelperFunctions;
}; };
export default usePanZoomHelper; export default useZoomPanHelper;
+5
View File
@@ -17,6 +17,7 @@ import {
SetConnectionId, SetConnectionId,
SnapGrid, SnapGrid,
ConnectionMode, ConnectionMode,
NodeExtent,
} from '../types'; } from '../types';
import { import {
@@ -53,6 +54,7 @@ import {
SET_ELEMENTS_SELECTABLE, SET_ELEMENTS_SELECTABLE,
SET_MULTI_SELECTION_ACTIVE, SET_MULTI_SELECTION_ACTIVE,
SET_CONNECTION_MODE, SET_CONNECTION_MODE,
SET_NODE_EXTENT,
} from './contants'; } from './contants';
export const setOnConnect = (onConnect: OnConnectFunc) => export const setOnConnect = (onConnect: OnConnectFunc) =>
@@ -158,6 +160,8 @@ export const setMultiSelectionActive = (multiSelectionActive: boolean) =>
export const setConnectionMode = (connectionMode: ConnectionMode) => export const setConnectionMode = (connectionMode: ConnectionMode) =>
createAction(SET_CONNECTION_MODE, { connectionMode }); createAction(SET_CONNECTION_MODE, { connectionMode });
export const setNodeExtent = (nodeExtent: NodeExtent) => createAction(SET_NODE_EXTENT, { nodeExtent });
export type ReactFlowAction = ReturnType< export type ReactFlowAction = ReturnType<
| typeof setOnConnect | typeof setOnConnect
| typeof setOnConnectStart | typeof setOnConnectStart
@@ -192,4 +196,5 @@ export type ReactFlowAction = ReturnType<
| typeof setElementsSelectable | typeof setElementsSelectable
| typeof setMultiSelectionActive | typeof setMultiSelectionActive
| typeof setConnectionMode | typeof setConnectionMode
| typeof setNodeExtent
>; >;
+1
View File
@@ -31,3 +31,4 @@ export const SET_NODES_CONNECTABLE = 'SET_NODES_CONNECTABLE';
export const SET_ELEMENTS_SELECTABLE = 'SET_ELEMENTS_SELECTABLE'; export const SET_ELEMENTS_SELECTABLE = 'SET_ELEMENTS_SELECTABLE';
export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE'; export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE';
export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE'; export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE';
export const SET_NODE_EXTENT = 'SET_NODE_EXTENT';
+5
View File
@@ -23,6 +23,11 @@ export const initialState: ReactFlowState = {
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
], ],
nodeExtent: [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
nodesSelectionActive: false, nodesSelectionActive: false,
selectionActive: false, selectionActive: false,
+3 -1
View File
@@ -21,6 +21,7 @@ import {
SET_MULTI_SELECTION_ACTIVE, SET_MULTI_SELECTION_ACTIVE,
SET_NODES_CONNECTABLE, SET_NODES_CONNECTABLE,
SET_NODES_DRAGGABLE, SET_NODES_DRAGGABLE,
SET_NODE_EXTENT,
SET_ON_CONNECT, SET_ON_CONNECT,
SET_ON_CONNECT_END, SET_ON_CONNECT_END,
SET_ON_CONNECT_START, SET_ON_CONNECT_START,
@@ -82,7 +83,7 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
return storeElement; return storeElement;
} else { } else {
// add new element // add new element
return parseElement(el); return parseElement(el, state.nodeExtent);
} }
}); });
@@ -389,6 +390,7 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
case SET_ELEMENTS_SELECTABLE: case SET_ELEMENTS_SELECTABLE:
case SET_MULTI_SELECTION_ACTIVE: case SET_MULTI_SELECTION_ACTIVE:
case SET_CONNECTION_MODE: case SET_CONNECTION_MODE:
case SET_NODE_EXTENT:
return { ...state, ...action.payload }; return { ...state, ...action.payload };
default: default:
return state; return state;
+37 -101
View File
@@ -31,8 +31,6 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: rgba(0, 89, 220, 0.08);
border: 1px dotted rgba(0, 89, 220, 0.8);
} }
.react-flow__edges { .react-flow__edges {
@@ -49,23 +47,6 @@
&.inactive { &.inactive {
pointer-events: none; pointer-events: none;
} }
&.selected {
.react-flow__edge-path {
stroke: #555;
}
}
&.animated path {
stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite;
}
&.updating {
.react-flow__edge-path {
stroke: #777;
}
}
} }
@keyframes dashdraw { @keyframes dashdraw {
@@ -76,20 +57,13 @@
.react-flow__edge-path { .react-flow__edge-path {
fill: none; fill: none;
stroke: #b1b1b7;
stroke-width: 1;
} }
.react-flow__edge-text { .react-flow__edge-text {
font-size: 10px;
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
} }
.react-flow__edge-textbg {
fill: white;
}
.react-flow__connection { .react-flow__connection {
pointer-events: none; pointer-events: none;
@@ -101,8 +75,6 @@
.react-flow__connection-path { .react-flow__connection-path {
fill: none; fill: none;
stroke: #b1b1b7;
stroke-width: 1;
} }
.react-flow__nodes { .react-flow__nodes {
@@ -119,70 +91,6 @@
user-select: none; user-select: none;
pointer-events: all; pointer-events: all;
transform-origin: 0 0; transform-origin: 0 0;
cursor: grab;
}
.react-flow__node-default,
.react-flow__node-input,
.react-flow__node-output {
padding: 10px;
border-radius: 3px;
width: 150px;
font-size: 12px;
color: #222;
text-align: center;
border-width: 1px;
border-style: solid;
}
.react-flow__node-default.selectable,
.react-flow__node-input.selectable,
.react-flow__node-output.selectable {
&:hover {
box-shadow: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
}
}
.react-flow__node-input {
background: #fff;
border-color: #0041d0;
&.selected,
&.selected:hover {
box-shadow: 0 0 0 0.5px #0041d0;
}
.react-flow__handle {
background: #0041d0;
}
}
.react-flow__node-default {
background: #fff;
border-color: #1a192b;
&.selected,
&.selected:hover {
box-shadow: 0 0 0 0.5px #1a192b;
}
.react-flow__handle {
background: #1a192b;
}
}
.react-flow__node-output {
background: #fff;
border-color: #ff0072;
&.selected,
&.selected:hover {
box-shadow: 0 0 0 0.5px #ff0072;
}
.react-flow__handle {
background: #ff0072;
}
} }
.react-flow__nodesselection { .react-flow__nodesselection {
@@ -197,25 +105,16 @@
&-rect { &-rect {
position: absolute; position: absolute;
background: rgba(0, 89, 220, 0.08);
border: 1px dotted rgba(0, 89, 220, 0.8);
pointer-events: all; pointer-events: all;
cursor: grab; cursor: grab;
} }
} }
.react-flow__handle { .react-flow__handle {
position: absolute;
width: 6px;
height: 6px;
background: #555;
border: 1px solid white;
pointer-events: none; pointer-events: none;
border-radius: 100%;
&.connectable { &.connectable {
pointer-events: all; pointer-events: all;
cursor: crosshair;
} }
} }
@@ -247,3 +146,40 @@
.react-flow__edgeupdater { .react-flow__edgeupdater {
cursor: move; cursor: move;
} }
/* additional components */
.react-flow__background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.react-flow__controls {
position: absolute;
z-index: 5;
bottom: 10px;
left: 10px;
&-button {
width: 24px;
height: 24px;
svg {
width: 100%;
}
}
}
.react-flow__minimap {
position: absolute;
z-index: 5;
bottom: 10px;
right: 10px;
&-node {
shape-rendering: crispedges;
}
}
+157
View File
@@ -0,0 +1,157 @@
.react-flow__selection {
background: rgba(0, 89, 220, 0.08);
border: 1px dotted rgba(0, 89, 220, 0.8);
}
.react-flow__edge {
&.selected {
.react-flow__edge-path {
stroke: #555;
}
}
&.animated path {
stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite;
}
&.updating {
.react-flow__edge-path {
stroke: #777;
}
}
}
.react-flow__edge-path {
stroke: #b1b1b7;
stroke-width: 1;
}
.react-flow__edge-text {
font-size: 10px;
}
.react-flow__edge-textbg {
fill: white;
}
.react-flow__connection-path {
stroke: #b1b1b7;
stroke-width: 1;
}
.react-flow__node {
cursor: grab;
}
.react-flow__node-default,
.react-flow__node-input,
.react-flow__node-output {
padding: 10px;
border-radius: 3px;
width: 150px;
font-size: 12px;
color: #222;
text-align: center;
border-width: 1px;
border-style: solid;
}
.react-flow__node-default.selectable,
.react-flow__node-input.selectable,
.react-flow__node-output.selectable {
&:hover {
box-shadow: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
}
}
.react-flow__node-input {
background: #fff;
border-color: #0041d0;
&.selected,
&.selected:hover {
box-shadow: 0 0 0 0.5px #0041d0;
}
.react-flow__handle {
background: #0041d0;
}
}
.react-flow__node-default {
background: #fff;
border-color: #1a192b;
&.selected,
&.selected:hover {
box-shadow: 0 0 0 0.5px #1a192b;
}
.react-flow__handle {
background: #1a192b;
}
}
.react-flow__node-output {
background: #fff;
border-color: #ff0072;
&.selected,
&.selected:hover {
box-shadow: 0 0 0 0.5px #ff0072;
}
.react-flow__handle {
background: #ff0072;
}
}
.react-flow__nodesselection-rect {
background: rgba(0, 89, 220, 0.08);
border: 1px dotted rgba(0, 89, 220, 0.8);
}
.react-flow__handle {
position: absolute;
width: 6px;
height: 6px;
background: #555;
border: 1px solid white;
border-radius: 100%;
&.connectable {
cursor: crosshair;
}
}
.react-flow__minimap {
background-color: #fff;
}
.react-flow__controls {
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);
&-button {
background: #fefefe;
border-bottom: 1px solid #eee;
box-sizing: content-box;
display: flex;
justify-content: center;
align-items: center;
width: 16px;
height: 16px;
cursor: pointer;
user-select: none;
padding: 5px;
svg {
max-width: 12px;
max-height: 12px;
}
&:hover {
background: #f4f4f4;
}
}
}
+7 -2
View File
@@ -197,6 +197,7 @@ export interface NodeComponentProps<T = any> {
onMouseLeave?: (node: Node) => void; onMouseLeave?: (node: Node) => void;
onContextMenu?: (node: Node) => void; onContextMenu?: (node: Node) => void;
onNodeDragStart?: (node: Node) => void; onNodeDragStart?: (node: Node) => void;
onNodeDrag?: (node: Node) => void;
onNodeDragStop?: (node: Node) => void; onNodeDragStop?: (node: Node) => void;
style?: CSSProperties; style?: CSSProperties;
isDragging?: boolean; isDragging?: boolean;
@@ -220,6 +221,7 @@ export interface WrapNodeProps<T = any> {
onMouseLeave?: (event: ReactMouseEvent, node: Node) => void; onMouseLeave?: (event: ReactMouseEvent, node: Node) => void;
onContextMenu?: (event: ReactMouseEvent, node: Node) => void; onContextMenu?: (event: ReactMouseEvent, node: Node) => void;
onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void; onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void; onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
style?: CSSProperties; style?: CSSProperties;
className?: string; className?: string;
@@ -234,7 +236,8 @@ export interface WrapNodeProps<T = any> {
} }
export type FitViewParams = { export type FitViewParams = {
padding: number; padding?: number;
includeHiddenNodes?: boolean;
}; };
export type FlowExportObject<T = any> = { export type FlowExportObject<T = any> = {
@@ -340,6 +343,7 @@ export type FlowTransform = {
}; };
export type TranslateExtent = [[number, number], [number, number]]; export type TranslateExtent = [[number, number], [number, number]];
export type NodeExtent = TranslateExtent;
export type KeyCode = number | string; export type KeyCode = number | string;
@@ -354,7 +358,7 @@ export interface ZoomPanHelperFunctions {
zoomOut: () => void; zoomOut: () => void;
zoomTo: (zoomLevel: number) => void; zoomTo: (zoomLevel: number) => void;
transform: (transform: FlowTransform) => void; transform: (transform: FlowTransform) => void;
fitView: (params?: FitViewParams) => void; fitView: FitViewFunc;
setCenter: (x: number, y: number, zoom?: number) => void; setCenter: (x: number, y: number, zoom?: number) => void;
fitBounds: (bounds: Rect, padding?: number) => void; fitBounds: (bounds: Rect, padding?: number) => void;
initialized: boolean; initialized: boolean;
@@ -391,6 +395,7 @@ export interface ReactFlowState {
minZoom: number; minZoom: number;
maxZoom: number; maxZoom: number;
translateExtent: TranslateExtent; translateExtent: TranslateExtent;
nodeExtent: NodeExtent;
nodesSelectionActive: boolean; nodesSelectionActive: boolean;
selectionActive: boolean; selectionActive: boolean;
+6 -2
View File
@@ -1,4 +1,7 @@
import { Store } from 'redux'; import { Store } from 'redux';
import { clampPosition } from '../utils';
import { import {
ElementId, ElementId,
Node, Node,
@@ -11,6 +14,7 @@ import {
Connection, Connection,
FlowExportObject, FlowExportObject,
ReactFlowState, ReactFlowState,
NodeExtent,
} from '../types'; } from '../types';
export const isEdge = (element: Node | Connection | Edge): element is Edge => export const isEdge = (element: Node | Connection | Edge): element is Edge =>
@@ -142,7 +146,7 @@ export const onLoadProject = (currentStore: Store<ReactFlowState>) => {
}; };
}; };
export const parseElement = (element: Node | Edge): Node | Edge => { export const parseElement = (element: Node | Edge, nodeExtent: NodeExtent): Node | Edge => {
if (!element.id) { if (!element.id) {
throw new Error('All nodes and edges need to have an id.'); throw new Error('All nodes and edges need to have an id.');
} }
@@ -164,7 +168,7 @@ export const parseElement = (element: Node | Edge): Node | Edge => {
id: element.id.toString(), id: element.id.toString(),
type: element.type || 'default', type: element.type || 'default',
__rf: { __rf: {
position: element.position, position: clampPosition(element.position, nodeExtent),
width: null, width: null,
height: null, height: null,
handleBounds: {}, handleBounds: {},
+6 -1
View File
@@ -1,7 +1,7 @@
import { DraggableEvent } from 'react-draggable'; import { DraggableEvent } from 'react-draggable';
import { MouseEvent as ReactMouseEvent } from 'react'; import { MouseEvent as ReactMouseEvent } from 'react';
import { Dimensions } from '../types'; import { Dimensions, XYPosition, NodeExtent } from '../types';
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => { export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
const target = e?.target as HTMLElement; const target = e?.target as HTMLElement;
@@ -17,3 +17,8 @@ export const getDimensions = (node: HTMLDivElement): Dimensions => ({
}); });
export const clamp = (val: number, min: number = 0, max: number = 1): number => Math.min(Math.max(val, min), max); export const clamp = (val: number, min: number = 0, max: number = 1): number => Math.min(Math.max(val, min), max);
export const clampPosition = (position: XYPosition, extent: NodeExtent) => ({
x: clamp(position.x, extent[0][0], extent[1][0]),
y: clamp(position.y, extent[0][1], extent[1][1]),
});
+10 -10
View File
@@ -7,17 +7,17 @@
"jsx": "react", "jsx": "react",
"moduleResolution": "node", "moduleResolution": "node",
"declaration": true, "declaration": true,
"strict": false, "strict": true,
"noImplicitAny": false, "noImplicitAny": true,
"strictNullChecks": false, "strictNullChecks": true,
"strictFunctionTypes": false, "strictFunctionTypes": true,
"strictPropertyInitialization": false, "strictPropertyInitialization": true,
"noImplicitThis": false, "noImplicitThis": true,
"alwaysStrict": false, "alwaysStrict": true,
"noUnusedLocals": false, "noUnusedLocals": true,
"noUnusedParameters": false, "noUnusedParameters": true,
"noImplicitReturns": false, "noImplicitReturns": false,
"noFallthroughCasesInSwitch": false, "noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"esModuleInterop": true "esModuleInterop": true
}, },