Merge branch 'main' into refactor/redux
This commit is contained in:
@@ -5,8 +5,6 @@ import { useStoreState } from '../../store/hooks';
|
||||
import { BackgroundVariant } from '../../types';
|
||||
import { createGridLinesPath, createGridDotsPath } from './utils';
|
||||
|
||||
import './style.css';
|
||||
|
||||
export interface BackgroundProps extends HTMLAttributes<SVGElement> {
|
||||
variant?: BackgroundVariant;
|
||||
gap?: number;
|
||||
@@ -34,26 +32,25 @@ const Background = ({
|
||||
const xOffset = x % scaledGap;
|
||||
const yOffset = y % scaledGap;
|
||||
|
||||
|
||||
const isLines = variant === BackgroundVariant.Lines;
|
||||
const bgColor = color ? color : defaultColors[variant];
|
||||
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
|
||||
const isLines = variant === BackgroundVariant.Lines;
|
||||
const bgColor = color ? color : defaultColors[variant];
|
||||
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={bgClasses}
|
||||
style={{
|
||||
...style,
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
...style,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<pattern id="pattern" x={xOffset} y={yOffset} width={scaledGap} height={scaledGap} patternUnits="userSpaceOnUse">
|
||||
{path}
|
||||
</pattern>
|
||||
<pattern id="pattern" x={xOffset} y={yOffset} width={scaledGap} height={scaledGap} patternUnits="userSpaceOnUse">
|
||||
{path}
|
||||
</pattern>
|
||||
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern)"></rect>
|
||||
</svg>
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern)"></rect>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.react-flow__background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -9,13 +9,14 @@ import FitviewIcon from '../../../assets/icons/fitview.svg';
|
||||
import LockIcon from '../../../assets/icons/lock.svg';
|
||||
import UnlockIcon from '../../../assets/icons/unlock.svg';
|
||||
|
||||
import './style.css';
|
||||
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
||||
import { FitViewParams } from '../../types';
|
||||
|
||||
export interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
showZoom?: boolean;
|
||||
showFitView?: boolean;
|
||||
showInteractive?: boolean;
|
||||
fitViewParams?: FitViewParams;
|
||||
onZoomIn?: () => void;
|
||||
onZoomOut?: () => void;
|
||||
onFitView?: () => void;
|
||||
@@ -27,6 +28,7 @@ const Controls = ({
|
||||
showZoom = true,
|
||||
showFitView = true,
|
||||
showInteractive = true,
|
||||
fitViewParams,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onFitView,
|
||||
@@ -50,9 +52,9 @@ const Controls = ({
|
||||
}, [zoomOut, onZoomOut]);
|
||||
|
||||
const onFitViewHandler = useCallback(() => {
|
||||
fitView?.();
|
||||
fitView?.(fitViewParams);
|
||||
onFitView?.();
|
||||
}, [fitView, onFitView]);
|
||||
}, [fitView, fitViewParams, onFitView]);
|
||||
|
||||
const onInteractiveChangeHandler = useCallback(() => {
|
||||
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;
|
||||
color: string;
|
||||
strokeColor: string;
|
||||
strokeWidth: number;
|
||||
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 fill = (color || background || backgroundColor) as string;
|
||||
|
||||
@@ -28,7 +40,7 @@ const MiniMapNode = ({ x, y, width, height, style, color, strokeColor, className
|
||||
height={height}
|
||||
fill={fill}
|
||||
stroke={strokeColor}
|
||||
strokeWidth={2}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,8 +6,6 @@ import { getRectOfNodes, getBoundsofRects, isNode } from '../../utils/graph';
|
||||
import { Node, Rect } from '../../types';
|
||||
import MiniMapNode from './MiniMapNode';
|
||||
|
||||
import './style.css';
|
||||
|
||||
type StringFunc = (node: Node) => string;
|
||||
|
||||
export interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
|
||||
@@ -15,6 +13,7 @@ export interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
|
||||
nodeStrokeColor?: string | StringFunc;
|
||||
nodeClassName?: string | StringFunc;
|
||||
nodeBorderRadius?: number;
|
||||
nodeStrokeWidth?: number;
|
||||
maskColor?: string;
|
||||
}
|
||||
|
||||
@@ -28,6 +27,7 @@ const MiniMap = ({
|
||||
nodeColor = '#fff',
|
||||
nodeClassName = '',
|
||||
nodeBorderRadius = 5,
|
||||
nodeStrokeWidth = 2,
|
||||
maskColor = 'rgb(240, 242, 243, 0.7)',
|
||||
}: MiniMapProps) => {
|
||||
const containerWidth = useStoreState((s) => s.width);
|
||||
@@ -86,6 +86,7 @@ const MiniMap = ({
|
||||
color={nodeColorFunc(node)}
|
||||
borderRadius={nodeBorderRadius}
|
||||
strokeColor={nodeStrokeColorFunc(node)}
|
||||
strokeWidth={nodeStrokeWidth}
|
||||
/>
|
||||
))}
|
||||
<path
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.react-flow__minimap {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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 Controls } from './Controls';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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 { useStoreActions } from '../../store/hooks';
|
||||
import { Provider } from '../../contexts/NodeIdContext';
|
||||
import { Node, NodeComponentProps, WrapNodeProps } from '../../types';
|
||||
import { NodeComponentProps, WrapNodeProps } from '../../types';
|
||||
|
||||
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||
const NodeWrapper = ({
|
||||
@@ -21,6 +21,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||
onMouseLeave,
|
||||
onContextMenu,
|
||||
onNodeDragStart,
|
||||
onNodeDrag,
|
||||
onNodeDragStop,
|
||||
style,
|
||||
className,
|
||||
@@ -96,7 +97,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||
unsetNodesSelection();
|
||||
|
||||
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(
|
||||
(event) => {
|
||||
(event: DraggableEvent) => {
|
||||
onNodeDragStart?.(event as MouseEvent, node);
|
||||
|
||||
if (selectNodesOnDrag && isSelectable) {
|
||||
unsetNodesSelection();
|
||||
|
||||
if (!selected) {
|
||||
addSelectedElements({ id: node.id, type: node.type } as Node);
|
||||
addSelectedElements(node);
|
||||
}
|
||||
} else if (!selectNodesOnDrag && !selected && isSelectable) {
|
||||
unsetNodesSelection();
|
||||
@@ -125,25 +126,31 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||
);
|
||||
|
||||
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({
|
||||
id,
|
||||
diff: {
|
||||
x: data.deltaX,
|
||||
y: data.deltaY,
|
||||
x: draggableData.deltaX,
|
||||
y: draggableData.deltaY,
|
||||
},
|
||||
});
|
||||
},
|
||||
[id]
|
||||
[id, node, onNodeDrag]
|
||||
);
|
||||
|
||||
const onDragStop = useCallback(
|
||||
(event) => {
|
||||
(event: DraggableEvent) => {
|
||||
// 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
|
||||
if (!isDragging) {
|
||||
if (isSelectable && !selectNodesOnDrag && !selected) {
|
||||
addSelectedElements({ id: node.id, type: node.type } as Node);
|
||||
addSelectedElements(node);
|
||||
}
|
||||
|
||||
onClick?.(event as MouseEvent, node);
|
||||
|
||||
@@ -37,8 +37,10 @@ const FlowRenderer = ({
|
||||
onMoveEnd,
|
||||
selectionKeyCode,
|
||||
multiSelectionKeyCode,
|
||||
zoomActivationKeyCode,
|
||||
elementsSelectable,
|
||||
zoomOnScroll,
|
||||
zoomOnPinch,
|
||||
panOnScroll,
|
||||
panOnScrollSpeed,
|
||||
panOnScrollMode,
|
||||
@@ -91,6 +93,7 @@ const FlowRenderer = ({
|
||||
selectionKeyPressed={selectionKeyPressed}
|
||||
elementsSelectable={elementsSelectable}
|
||||
zoomOnScroll={zoomOnScroll}
|
||||
zoomOnPinch={zoomOnPinch}
|
||||
panOnScroll={panOnScroll}
|
||||
panOnScrollSpeed={panOnScrollSpeed}
|
||||
panOnScrollMode={panOnScrollMode}
|
||||
@@ -99,6 +102,7 @@ const FlowRenderer = ({
|
||||
defaultPosition={defaultPosition}
|
||||
defaultZoom={defaultZoom}
|
||||
translateExtent={translateExtent}
|
||||
zoomActivationKeyCode={zoomActivationKeyCode}
|
||||
>
|
||||
{children}
|
||||
<UserSelection selectionKeyPressed={selectionKeyPressed} />
|
||||
|
||||
@@ -40,6 +40,7 @@ const GraphView = ({
|
||||
onNodeMouseLeave,
|
||||
onNodeContextMenu,
|
||||
onNodeDragStart,
|
||||
onNodeDrag,
|
||||
onNodeDragStop,
|
||||
onSelectionDragStart,
|
||||
onSelectionDrag,
|
||||
@@ -51,6 +52,7 @@ const GraphView = ({
|
||||
connectionLineComponent,
|
||||
selectionKeyCode,
|
||||
multiSelectionKeyCode,
|
||||
zoomActivationKeyCode,
|
||||
onElementsRemove,
|
||||
deleteKeyCode,
|
||||
onConnect,
|
||||
@@ -69,9 +71,11 @@ const GraphView = ({
|
||||
defaultZoom,
|
||||
defaultPosition,
|
||||
translateExtent,
|
||||
nodeExtent,
|
||||
arrowHeadColor,
|
||||
markerEndId,
|
||||
zoomOnScroll,
|
||||
zoomOnPinch,
|
||||
panOnScroll,
|
||||
panOnScrollSpeed,
|
||||
panOnScrollMode,
|
||||
@@ -95,6 +99,7 @@ const GraphView = ({
|
||||
const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
|
||||
const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
|
||||
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
|
||||
const setNodeExtent = useStoreActions((actions) => actions.setNodeExtent);
|
||||
const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
|
||||
const currentStore = useStore();
|
||||
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
|
||||
@@ -190,6 +195,12 @@ const GraphView = ({
|
||||
}
|
||||
}, [translateExtent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof nodeExtent !== 'undefined') {
|
||||
setNodeExtent(nodeExtent);
|
||||
}
|
||||
}, [nodeExtent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof connectionMode !== 'undefined') {
|
||||
setConnectionMode(connectionMode);
|
||||
@@ -205,11 +216,13 @@ const GraphView = ({
|
||||
deleteKeyCode={deleteKeyCode}
|
||||
selectionKeyCode={selectionKeyCode}
|
||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||
zoomActivationKeyCode={zoomActivationKeyCode}
|
||||
elementsSelectable={elementsSelectable}
|
||||
onMove={onMove}
|
||||
onMoveStart={onMoveStart}
|
||||
onMoveEnd={onMoveEnd}
|
||||
zoomOnScroll={zoomOnScroll}
|
||||
zoomOnPinch={zoomOnPinch}
|
||||
zoomOnDoubleClick={zoomOnDoubleClick}
|
||||
panOnScroll={panOnScroll}
|
||||
panOnScrollSpeed={panOnScrollSpeed}
|
||||
@@ -231,6 +244,7 @@ const GraphView = ({
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
selectNodesOnDrag={selectNodesOnDrag}
|
||||
snapToGrid={snapToGrid}
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
|
||||
import { getNodesInside, isNode } from '../../utils/graph';
|
||||
import { useStoreState, useStoreActions } from '../../store/hooks';
|
||||
import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
|
||||
|
||||
interface NodeRendererProps {
|
||||
nodeTypes: NodeTypesType;
|
||||
selectNodesOnDrag: boolean;
|
||||
@@ -13,6 +12,7 @@ interface NodeRendererProps {
|
||||
onNodeMouseLeave?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeContextMenu?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDrag?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
||||
snapToGrid: boolean;
|
||||
snapGrid: [number, number];
|
||||
@@ -94,6 +94,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
onMouseLeave={props.onNodeMouseLeave}
|
||||
onContextMenu={props.onNodeContextMenu}
|
||||
onNodeDragStart={props.onNodeDragStart}
|
||||
onNodeDrag={props.onNodeDrag}
|
||||
onNodeDragStop={props.onNodeDragStop}
|
||||
scale={transform[2]}
|
||||
selected={selectedElements?.some(({ id }) => id === node.id) || false}
|
||||
|
||||
@@ -30,9 +30,11 @@ import {
|
||||
KeyCode,
|
||||
PanOnScrollMode,
|
||||
OnEdgeUpdateFunc,
|
||||
NodeExtent,
|
||||
} from '../../types';
|
||||
|
||||
import '../../style.css';
|
||||
import '../../theme-default.css';
|
||||
|
||||
const defaultNodeTypes = {
|
||||
input: InputNode,
|
||||
@@ -56,6 +58,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
||||
onNodeMouseLeave?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeContextMenu?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDrag?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
||||
onConnect?: (connection: Edge | Connection) => void;
|
||||
onConnectStart?: OnConnectStartFunc;
|
||||
@@ -82,6 +85,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
||||
deleteKeyCode?: KeyCode;
|
||||
selectionKeyCode?: KeyCode;
|
||||
multiSelectionKeyCode?: KeyCode;
|
||||
zoomActivationKeyCode?: KeyCode;
|
||||
snapToGrid?: boolean;
|
||||
snapGrid?: [number, number];
|
||||
onlyRenderVisibleElements?: boolean;
|
||||
@@ -95,9 +99,11 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
||||
defaultZoom?: number;
|
||||
defaultPosition?: [number, number];
|
||||
translateExtent?: TranslateExtent;
|
||||
nodeExtent?: NodeExtent;
|
||||
arrowHeadColor?: string;
|
||||
markerEndId?: string;
|
||||
zoomOnScroll?: boolean;
|
||||
zoomOnPinch?: boolean;
|
||||
panOnScroll?: boolean;
|
||||
panOnScrollSpeed?: number;
|
||||
panOnScrollMode?: PanOnScrollMode;
|
||||
@@ -125,6 +131,7 @@ const ReactFlow = ({
|
||||
onNodeMouseLeave,
|
||||
onNodeContextMenu,
|
||||
onNodeDragStart,
|
||||
onNodeDrag,
|
||||
onNodeDragStop,
|
||||
onSelectionChange,
|
||||
onSelectionDragStart,
|
||||
@@ -138,6 +145,7 @@ const ReactFlow = ({
|
||||
deleteKeyCode = 'Backspace',
|
||||
selectionKeyCode = 'Shift',
|
||||
multiSelectionKeyCode = 'Meta',
|
||||
zoomActivationKeyCode = 'Meta',
|
||||
snapToGrid = false,
|
||||
snapGrid = [15, 15],
|
||||
onlyRenderVisibleElements = true,
|
||||
@@ -150,9 +158,11 @@ const ReactFlow = ({
|
||||
defaultZoom = 1,
|
||||
defaultPosition = [0, 0],
|
||||
translateExtent,
|
||||
nodeExtent,
|
||||
arrowHeadColor = '#b1b1b7',
|
||||
markerEndId,
|
||||
zoomOnScroll = true,
|
||||
zoomOnPinch = true,
|
||||
panOnScroll = false,
|
||||
panOnScrollSpeed = 0.5,
|
||||
panOnScrollMode = PanOnScrollMode.Free,
|
||||
@@ -183,6 +193,7 @@ const ReactFlow = ({
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
nodeTypes={nodeTypesParsed}
|
||||
edgeTypes={edgeTypesParsed}
|
||||
@@ -194,6 +205,7 @@ const ReactFlow = ({
|
||||
onElementsRemove={onElementsRemove}
|
||||
deleteKeyCode={deleteKeyCode}
|
||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||
zoomActivationKeyCode={zoomActivationKeyCode}
|
||||
onConnect={onConnect}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectStop={onConnectStop}
|
||||
@@ -210,9 +222,11 @@ const ReactFlow = ({
|
||||
defaultZoom={defaultZoom}
|
||||
defaultPosition={defaultPosition}
|
||||
translateExtent={translateExtent}
|
||||
nodeExtent={nodeExtent}
|
||||
arrowHeadColor={arrowHeadColor}
|
||||
markerEndId={markerEndId}
|
||||
zoomOnScroll={zoomOnScroll}
|
||||
zoomOnPinch={zoomOnPinch}
|
||||
zoomOnDoubleClick={zoomOnDoubleClick}
|
||||
panOnScroll={panOnScroll}
|
||||
panOnScrollSpeed={panOnScrollSpeed}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import React, { useEffect, useRef, ReactNode } from 'react';
|
||||
|
||||
import { zoom, zoomIdentity } from 'd3-zoom';
|
||||
import { select } from 'd3-selection';
|
||||
import { clamp } from '../../utils';
|
||||
import { select, pointer } from 'd3-selection';
|
||||
|
||||
import { clamp } from '../../utils';
|
||||
import useKeyPress from '../../hooks/useKeyPress';
|
||||
import useResizeHandler from '../../hooks/useResizeHandler';
|
||||
import { useStoreState, useStoreActions, useStore } from '../../store/hooks';
|
||||
import { FlowTransform, TranslateExtent, PanOnScrollMode } from '../../types';
|
||||
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode } from '../../types';
|
||||
|
||||
interface ZoomPaneProps {
|
||||
selectionKeyPressed: boolean;
|
||||
elementsSelectable?: boolean;
|
||||
zoomOnScroll?: boolean;
|
||||
zoomOnPinch?: boolean;
|
||||
panOnScroll?: boolean;
|
||||
panOnScrollSpeed?: number;
|
||||
panOnScrollMode?: PanOnScrollMode;
|
||||
@@ -23,6 +24,7 @@ interface ZoomPaneProps {
|
||||
onMove?: (flowTransform?: FlowTransform) => void;
|
||||
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
||||
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
||||
zoomActivationKeyCode?: KeyCode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
@@ -42,6 +44,7 @@ const ZoomPane = ({
|
||||
onMoveStart,
|
||||
onMoveEnd,
|
||||
zoomOnScroll = true,
|
||||
zoomOnPinch = true,
|
||||
panOnScroll = false,
|
||||
panOnScrollSpeed = 0.5,
|
||||
panOnScrollMode = PanOnScrollMode.Free,
|
||||
@@ -52,6 +55,7 @@ const ZoomPane = ({
|
||||
defaultPosition = [0, 0],
|
||||
defaultZoom = 1,
|
||||
translateExtent,
|
||||
zoomActivationKeyCode,
|
||||
children,
|
||||
}: ZoomPaneProps) => {
|
||||
const zoomPane = useRef<HTMLDivElement>(null);
|
||||
@@ -65,6 +69,8 @@ const ZoomPane = ({
|
||||
const initD3Zoom = useStoreActions((actions) => actions.initD3Zoom);
|
||||
const updateTransform = useStoreActions((actions) => actions.updateTransform);
|
||||
|
||||
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
|
||||
|
||||
useResizeHandler(zoomPane);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -93,13 +99,24 @@ const ZoomPane = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (d3Selection && d3Zoom) {
|
||||
if (panOnScroll) {
|
||||
if (panOnScroll && !zoomActivationKeyPressed) {
|
||||
d3Selection
|
||||
.on('wheel', (event: any) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
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
|
||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
||||
@@ -117,7 +134,7 @@ const ZoomPane = ({
|
||||
d3Selection.on('wheel', null).on('wheel.zoom', d3ZoomHandler);
|
||||
}
|
||||
}
|
||||
}, [panOnScroll, panOnScrollMode, d3Selection, d3Zoom, d3ZoomHandler]);
|
||||
}, [panOnScroll, panOnScrollMode, d3Selection, d3Zoom, d3ZoomHandler, zoomActivationKeyPressed, zoomOnPinch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (d3Zoom) {
|
||||
@@ -173,8 +190,11 @@ const ZoomPane = ({
|
||||
useEffect(() => {
|
||||
if (d3Zoom) {
|
||||
d3Zoom.filter((event: any) => {
|
||||
const zoomScroll = zoomActivationKeyPressed || zoomOnScroll;
|
||||
const pinchZoom = zoomOnPinch && event.ctrlKey;
|
||||
|
||||
// if all interactions are disabled, we prevent all zoom events
|
||||
if (!paneMoveable && !zoomOnScroll && !panOnScroll && !zoomOnDoubleClick) {
|
||||
if (!paneMoveable && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,8 +225,12 @@ const ZoomPane = ({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -219,7 +243,17 @@ const ZoomPane = ({
|
||||
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 (
|
||||
<div className="react-flow__renderer react-flow__zoompane" ref={zoomPane}>
|
||||
|
||||
+23
-21
@@ -3,35 +3,37 @@ import { useState, useEffect } from 'react';
|
||||
import { isInputDOMNode } from '../utils';
|
||||
import { KeyCode } from '../types';
|
||||
|
||||
export default (keyCode: KeyCode): boolean => {
|
||||
export default (keyCode?: KeyCode): boolean => {
|
||||
const [keyPressed, setKeyPressed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const downHandler = (event: KeyboardEvent) => {
|
||||
if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
|
||||
event.preventDefault();
|
||||
if (typeof keyCode !== 'undefined') {
|
||||
const downHandler = (event: KeyboardEvent) => {
|
||||
if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
|
||||
event.preventDefault();
|
||||
|
||||
setKeyPressed(true);
|
||||
}
|
||||
};
|
||||
setKeyPressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = (event: KeyboardEvent) => {
|
||||
if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
|
||||
setKeyPressed(false);
|
||||
}
|
||||
};
|
||||
const upHandler = (event: KeyboardEvent) => {
|
||||
if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
|
||||
setKeyPressed(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetHandler = () => setKeyPressed(false);
|
||||
const resetHandler = () => setKeyPressed(false);
|
||||
|
||||
window.addEventListener('keydown', downHandler);
|
||||
window.addEventListener('keyup', upHandler);
|
||||
window.addEventListener('blur', resetHandler);
|
||||
window.addEventListener('keydown', downHandler);
|
||||
window.addEventListener('keyup', upHandler);
|
||||
window.addEventListener('blur', resetHandler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', downHandler);
|
||||
window.removeEventListener('keyup', upHandler);
|
||||
window.removeEventListener('blur', resetHandler);
|
||||
};
|
||||
return () => {
|
||||
window.removeEventListener('keydown', downHandler);
|
||||
window.removeEventListener('keyup', upHandler);
|
||||
window.removeEventListener('blur', resetHandler);
|
||||
};
|
||||
}
|
||||
}, [keyCode, setKeyPressed]);
|
||||
|
||||
return keyPressed;
|
||||
|
||||
@@ -6,12 +6,14 @@ import { clamp } from '../utils';
|
||||
import { getRectOfNodes, isNode } from '../utils/graph';
|
||||
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, Transform } from '../types';
|
||||
|
||||
const DEFAULT_PADDING = 0.1;
|
||||
|
||||
const initialZoomPanHelper: ZoomPanHelperFunctions = {
|
||||
zoomIn: () => {},
|
||||
zoomOut: () => {},
|
||||
zoomTo: (_: number) => {},
|
||||
transform: (_: FlowTransform) => {},
|
||||
fitView: (_: FitViewParams = { padding: 0.1 }) => {},
|
||||
fitView: (_: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {},
|
||||
setCenter: (_: number, __: number) => {},
|
||||
fitBounds: (_: Rect) => {},
|
||||
initialized: false,
|
||||
@@ -23,7 +25,7 @@ const getTransformForBounds = (
|
||||
height: number,
|
||||
minZoom: number,
|
||||
maxZoom: number,
|
||||
padding = 0.1
|
||||
padding = DEFAULT_PADDING
|
||||
): Transform => {
|
||||
const xZoom = width / (bounds.width * (1 + padding));
|
||||
const yZoom = height / (bounds.height * (1 + padding));
|
||||
@@ -37,7 +39,7 @@ const getTransformForBounds = (
|
||||
return [x, y, clampedZoom];
|
||||
};
|
||||
|
||||
const usePanZoomHelper = (): ZoomPanHelperFunctions => {
|
||||
const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
||||
const store = useStore();
|
||||
const d3Zoom = useStoreState((s) => s.d3Zoom);
|
||||
const d3Selection = useStoreState((s) => s.d3Selection);
|
||||
@@ -53,7 +55,7 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
|
||||
|
||||
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 nodes = elements.filter(isNode);
|
||||
|
||||
@@ -61,8 +63,9 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = getRectOfNodes(nodes);
|
||||
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, options.padding);
|
||||
const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden));
|
||||
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);
|
||||
|
||||
d3Zoom.transform(d3Selection, transform);
|
||||
@@ -77,7 +80,7 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
|
||||
|
||||
d3Zoom.transform(d3Selection, transform);
|
||||
},
|
||||
fitBounds: (bounds: Rect, padding = 0.1) => {
|
||||
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
|
||||
const { width, height, minZoom, maxZoom } = store.getState();
|
||||
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
|
||||
const transform = zoomIdentity.translate(x, y).scale(zoom);
|
||||
@@ -94,4 +97,4 @@ const usePanZoomHelper = (): ZoomPanHelperFunctions => {
|
||||
return zoomPanHelperFunctions;
|
||||
};
|
||||
|
||||
export default usePanZoomHelper;
|
||||
export default useZoomPanHelper;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
SetConnectionId,
|
||||
SnapGrid,
|
||||
ConnectionMode,
|
||||
NodeExtent,
|
||||
} from '../types';
|
||||
|
||||
import {
|
||||
@@ -53,6 +54,7 @@ import {
|
||||
SET_ELEMENTS_SELECTABLE,
|
||||
SET_MULTI_SELECTION_ACTIVE,
|
||||
SET_CONNECTION_MODE,
|
||||
SET_NODE_EXTENT,
|
||||
} from './contants';
|
||||
|
||||
export const setOnConnect = (onConnect: OnConnectFunc) =>
|
||||
@@ -158,6 +160,8 @@ export const setMultiSelectionActive = (multiSelectionActive: boolean) =>
|
||||
export const setConnectionMode = (connectionMode: ConnectionMode) =>
|
||||
createAction(SET_CONNECTION_MODE, { connectionMode });
|
||||
|
||||
export const setNodeExtent = (nodeExtent: NodeExtent) => createAction(SET_NODE_EXTENT, { nodeExtent });
|
||||
|
||||
export type ReactFlowAction = ReturnType<
|
||||
| typeof setOnConnect
|
||||
| typeof setOnConnectStart
|
||||
@@ -192,4 +196,5 @@ export type ReactFlowAction = ReturnType<
|
||||
| typeof setElementsSelectable
|
||||
| typeof setMultiSelectionActive
|
||||
| typeof setConnectionMode
|
||||
| typeof setNodeExtent
|
||||
>;
|
||||
|
||||
@@ -31,3 +31,4 @@ export const SET_NODES_CONNECTABLE = 'SET_NODES_CONNECTABLE';
|
||||
export const SET_ELEMENTS_SELECTABLE = 'SET_ELEMENTS_SELECTABLE';
|
||||
export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE';
|
||||
export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE';
|
||||
export const SET_NODE_EXTENT = 'SET_NODE_EXTENT';
|
||||
|
||||
@@ -23,6 +23,11 @@ export const initialState: ReactFlowState = {
|
||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
],
|
||||
|
||||
nodeExtent: [
|
||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
],
|
||||
|
||||
nodesSelectionActive: false,
|
||||
selectionActive: false,
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
SET_MULTI_SELECTION_ACTIVE,
|
||||
SET_NODES_CONNECTABLE,
|
||||
SET_NODES_DRAGGABLE,
|
||||
SET_NODE_EXTENT,
|
||||
SET_ON_CONNECT,
|
||||
SET_ON_CONNECT_END,
|
||||
SET_ON_CONNECT_START,
|
||||
@@ -82,7 +83,7 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
|
||||
return storeElement;
|
||||
} else {
|
||||
// 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_MULTI_SELECTION_ACTIVE:
|
||||
case SET_CONNECTION_MODE:
|
||||
case SET_NODE_EXTENT:
|
||||
return { ...state, ...action.payload };
|
||||
default:
|
||||
return state;
|
||||
|
||||
+37
-101
@@ -31,8 +31,6 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 89, 220, 0.08);
|
||||
border: 1px dotted rgba(0, 89, 220, 0.8);
|
||||
}
|
||||
|
||||
.react-flow__edges {
|
||||
@@ -49,23 +47,6 @@
|
||||
&.inactive {
|
||||
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 {
|
||||
@@ -76,20 +57,13 @@
|
||||
|
||||
.react-flow__edge-path {
|
||||
fill: none;
|
||||
stroke: #b1b1b7;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.react-flow__edge-text {
|
||||
font-size: 10px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.react-flow__edge-textbg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.react-flow__connection {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -101,8 +75,6 @@
|
||||
|
||||
.react-flow__connection-path {
|
||||
fill: none;
|
||||
stroke: #b1b1b7;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.react-flow__nodes {
|
||||
@@ -119,70 +91,6 @@
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
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 {
|
||||
@@ -197,25 +105,16 @@
|
||||
|
||||
&-rect {
|
||||
position: absolute;
|
||||
background: rgba(0, 89, 220, 0.08);
|
||||
border: 1px dotted rgba(0, 89, 220, 0.8);
|
||||
pointer-events: all;
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
|
||||
.react-flow__handle {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #555;
|
||||
border: 1px solid white;
|
||||
pointer-events: none;
|
||||
border-radius: 100%;
|
||||
|
||||
&.connectable {
|
||||
pointer-events: all;
|
||||
cursor: crosshair;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,3 +146,40 @@
|
||||
.react-flow__edgeupdater {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -197,6 +197,7 @@ export interface NodeComponentProps<T = any> {
|
||||
onMouseLeave?: (node: Node) => void;
|
||||
onContextMenu?: (node: Node) => void;
|
||||
onNodeDragStart?: (node: Node) => void;
|
||||
onNodeDrag?: (node: Node) => void;
|
||||
onNodeDragStop?: (node: Node) => void;
|
||||
style?: CSSProperties;
|
||||
isDragging?: boolean;
|
||||
@@ -220,6 +221,7 @@ export interface WrapNodeProps<T = any> {
|
||||
onMouseLeave?: (event: ReactMouseEvent, node: Node) => void;
|
||||
onContextMenu?: (event: ReactMouseEvent, node: Node) => void;
|
||||
onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
|
||||
onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
|
||||
onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
@@ -234,7 +236,8 @@ export interface WrapNodeProps<T = any> {
|
||||
}
|
||||
|
||||
export type FitViewParams = {
|
||||
padding: number;
|
||||
padding?: number;
|
||||
includeHiddenNodes?: boolean;
|
||||
};
|
||||
|
||||
export type FlowExportObject<T = any> = {
|
||||
@@ -340,6 +343,7 @@ export type FlowTransform = {
|
||||
};
|
||||
|
||||
export type TranslateExtent = [[number, number], [number, number]];
|
||||
export type NodeExtent = TranslateExtent;
|
||||
|
||||
export type KeyCode = number | string;
|
||||
|
||||
@@ -354,7 +358,7 @@ export interface ZoomPanHelperFunctions {
|
||||
zoomOut: () => void;
|
||||
zoomTo: (zoomLevel: number) => void;
|
||||
transform: (transform: FlowTransform) => void;
|
||||
fitView: (params?: FitViewParams) => void;
|
||||
fitView: FitViewFunc;
|
||||
setCenter: (x: number, y: number, zoom?: number) => void;
|
||||
fitBounds: (bounds: Rect, padding?: number) => void;
|
||||
initialized: boolean;
|
||||
@@ -391,6 +395,7 @@ export interface ReactFlowState {
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
translateExtent: TranslateExtent;
|
||||
nodeExtent: NodeExtent;
|
||||
|
||||
nodesSelectionActive: boolean;
|
||||
selectionActive: boolean;
|
||||
|
||||
+6
-2
@@ -1,4 +1,7 @@
|
||||
import { Store } from 'redux';
|
||||
|
||||
import { clampPosition } from '../utils';
|
||||
|
||||
import {
|
||||
ElementId,
|
||||
Node,
|
||||
@@ -11,6 +14,7 @@ import {
|
||||
Connection,
|
||||
FlowExportObject,
|
||||
ReactFlowState,
|
||||
NodeExtent,
|
||||
} from '../types';
|
||||
|
||||
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) {
|
||||
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(),
|
||||
type: element.type || 'default',
|
||||
__rf: {
|
||||
position: element.position,
|
||||
position: clampPosition(element.position, nodeExtent),
|
||||
width: null,
|
||||
height: null,
|
||||
handleBounds: {},
|
||||
|
||||
+6
-1
@@ -1,7 +1,7 @@
|
||||
import { DraggableEvent } from 'react-draggable';
|
||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||
|
||||
import { Dimensions } from '../types';
|
||||
import { Dimensions, XYPosition, NodeExtent } from '../types';
|
||||
|
||||
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
|
||||
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 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]),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user