refactor(resizer): export resize control, add line control, styling
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import { memo, FC, CSSProperties } from 'react';
|
||||||
|
import { Handle, Position, NodeProps } from 'reactflow';
|
||||||
|
|
||||||
|
import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer';
|
||||||
|
import '@reactflow/node-resizer/dist/style.css';
|
||||||
|
import ResizeIcon from './ResizeIcon';
|
||||||
|
|
||||||
|
const controlStyle = {
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomNode: FC<NodeProps> = ({ id, data }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NodeResizeControl nodeId={id} style={controlStyle}>
|
||||||
|
<ResizeIcon />
|
||||||
|
</NodeResizeControl>
|
||||||
|
|
||||||
|
<Handle type="target" position={Position.Left} />
|
||||||
|
<div>{data.label}</div>
|
||||||
|
<Handle type="source" position={Position.Right} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CustomNode);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { memo, FC } from 'react';
|
||||||
|
import { Handle, Position, NodeProps } from 'reactflow';
|
||||||
|
|
||||||
|
import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer';
|
||||||
|
import '@reactflow/node-resizer/dist/style.css';
|
||||||
|
|
||||||
|
const CustomNode: FC<NodeProps> = ({ id, data }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NodeResizeControl nodeId={id} position="top" />
|
||||||
|
<NodeResizeControl nodeId={id} position="bottom" />
|
||||||
|
<Handle type="target" position={Position.Left} />
|
||||||
|
<div>{data.label}</div>
|
||||||
|
<Handle type="source" position={Position.Right} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CustomNode);
|
||||||
+4
-4
@@ -2,17 +2,17 @@ import { memo, FC } from 'react';
|
|||||||
import { Handle, Position, NodeProps } from 'reactflow';
|
import { Handle, Position, NodeProps } from 'reactflow';
|
||||||
|
|
||||||
import { NodeResizer } from '@reactflow/node-resizer';
|
import { NodeResizer } from '@reactflow/node-resizer';
|
||||||
|
import '@reactflow/node-resizer/dist/style.css';
|
||||||
|
|
||||||
const ColorSelectorNode: FC<NodeProps> = ({ id, data }) => {
|
const CustomNode: FC<NodeProps> = ({ id, data }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<NodeResizer nodeId={id} />
|
||||||
<Handle type="target" position={Position.Left} />
|
<Handle type="target" position={Position.Left} />
|
||||||
<div>{data.label}</div>
|
<div>{data.label}</div>
|
||||||
<Handle type="source" position={Position.Right} />
|
<Handle type="source" position={Position.Right} />
|
||||||
|
|
||||||
<NodeResizer nodeId={id} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ColorSelectorNode);
|
export default memo(CustomNode);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
function ResizeIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
style={{ position: 'absolute', right: 2, bottom: 2 }}
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<polyline points="16 20 20 20 20 16" />
|
||||||
|
<line x1="14" y1="14" x2="20" y2="20" />
|
||||||
|
<polyline points="8 4 4 4 4 8" />
|
||||||
|
<line x1="4" y1="4" x2="10" y2="10" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResizeIcon;
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useEdgesState } from 'reactflow';
|
import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useEdgesState } from 'reactflow';
|
||||||
|
|
||||||
import CustomNode from './CustomNode';
|
import NodeResizerNode from './NodeResizerNode';
|
||||||
|
import CustomResizer from './CustomResizer';
|
||||||
|
import CustomResizer2 from './CustomResizer2';
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
custom: CustomNode,
|
resizer: NodeResizerNode,
|
||||||
|
customResizer: CustomResizer,
|
||||||
|
customResizer2: CustomResizer2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialEdges = [
|
const initialEdges = [
|
||||||
@@ -25,10 +29,24 @@ const initialNodes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
type: 'custom',
|
type: 'resizer',
|
||||||
data: { label: 'resize me!' },
|
data: { label: 'default resizer' },
|
||||||
position: { x: 250, y: 0 },
|
position: { x: 250, y: 0 },
|
||||||
style: { padding: 10, border: '1px solid #222' },
|
style: { padding: 10, border: '1px solid #222', fontSize: 10 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'customResizer',
|
||||||
|
data: { label: 'resize control with child component' },
|
||||||
|
position: { x: 250, y: 150 },
|
||||||
|
style: { padding: 10, border: '1px solid #222', fontSize: 10, width: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'customResizer2',
|
||||||
|
data: { label: 'resize controls' },
|
||||||
|
position: { x: 100, y: 150 },
|
||||||
|
style: { padding: 10, border: '1px solid #222', fontSize: 10 },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"rollup --config node:@reactflow/rollup-config --watch\" pnpm:css-watch",
|
"dev": "concurrently \"rollup --config node:@reactflow/rollup-config --watch\" pnpm:css-watch",
|
||||||
"build": "rollup --config node:@reactflow/rollup-config --environment NODE_ENV:production",
|
"build": "rollup --config node:@reactflow/rollup-config --environment NODE_ENV:production && npm run css",
|
||||||
|
"css": "postcss src/*.css --config ../../tooling/postcss-config/postcss.config.js --dir dist",
|
||||||
|
"css-watch": "pnpm css --watch",
|
||||||
"lint": "eslint --ext .js,.jsx,.ts,.tsx src",
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx src",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,179 +1,25 @@
|
|||||||
import { useRef, useEffect } from 'react';
|
import ResizeControl from './ResizeControl';
|
||||||
import cc from 'classcat';
|
import type { NodeResizerProps } from './types';
|
||||||
import { drag } from 'd3-drag';
|
|
||||||
import { select } from 'd3-selection';
|
|
||||||
import {
|
|
||||||
useStoreApi,
|
|
||||||
useGetPointerPosition,
|
|
||||||
NodeChange,
|
|
||||||
NodePositionChange,
|
|
||||||
NodeDimensionChange,
|
|
||||||
} from '@reactflow/core';
|
|
||||||
import type { Dimensions, Node, XYPosition } from '@reactflow/core';
|
|
||||||
import type { D3DragEvent, SubjectPosition } from 'd3';
|
|
||||||
|
|
||||||
type NodeResizerProps = {
|
export default function NodeResizer({
|
||||||
nodeId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ResizeHandleProps = {
|
|
||||||
nodeId: string;
|
|
||||||
invertX?: boolean;
|
|
||||||
invertY?: boolean;
|
|
||||||
enableX?: boolean;
|
|
||||||
enableY?: boolean;
|
|
||||||
top?: number | string;
|
|
||||||
left?: number | string;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ResizeDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
|
||||||
|
|
||||||
function ResizeHandle({
|
|
||||||
top = 0,
|
|
||||||
left = 0,
|
|
||||||
invertX = false,
|
|
||||||
invertY = false,
|
|
||||||
enableX = false,
|
|
||||||
enableY = false,
|
|
||||||
nodeId,
|
nodeId,
|
||||||
className,
|
handleClassName,
|
||||||
}: ResizeHandleProps) {
|
handleStyle,
|
||||||
const store = useStoreApi();
|
lineClassName,
|
||||||
const resizeHandleRef = useRef<HTMLDivElement>(null);
|
lineStyle,
|
||||||
const initialDimensionsRef = useRef<Dimensions & XYPosition & { nodeX: number; nodeY: number }>({
|
}: NodeResizerProps) {
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
nodeX: 0,
|
|
||||||
nodeY: 0,
|
|
||||||
});
|
|
||||||
const nodeElementRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const getPointerPosition = useGetPointerPosition();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!resizeHandleRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selection = select(resizeHandleRef.current);
|
|
||||||
const dragHandler = drag<HTMLDivElement, unknown>()
|
|
||||||
.on('start', (event: ResizeDragEvent) => {
|
|
||||||
const node = store.getState().nodeInternals.get(nodeId);
|
|
||||||
const pointerPos = getPointerPosition(event);
|
|
||||||
|
|
||||||
initialDimensionsRef.current = {
|
|
||||||
width: node?.width ?? 0,
|
|
||||||
height: node?.height ?? 0,
|
|
||||||
nodeX: node?.position.x ?? 0,
|
|
||||||
nodeY: node?.position.y ?? 0,
|
|
||||||
x: pointerPos.xSnapped,
|
|
||||||
y: pointerPos.ySnapped,
|
|
||||||
};
|
|
||||||
nodeElementRef.current = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`) as HTMLDivElement;
|
|
||||||
})
|
|
||||||
.on('drag', (event: ResizeDragEvent) => {
|
|
||||||
const { updateNodePositions, nodeInternals, onNodesChange } = store.getState();
|
|
||||||
const pointerPos = getPointerPosition(event);
|
|
||||||
const nodeEl = nodeElementRef.current;
|
|
||||||
const node = nodeInternals.get(nodeId);
|
|
||||||
|
|
||||||
if (nodeEl && node) {
|
|
||||||
const changes: NodeChange[] = [];
|
|
||||||
const distX = enableX ? pointerPos.xSnapped - initialDimensionsRef.current.x : 0;
|
|
||||||
const distY = enableY ? pointerPos.ySnapped - initialDimensionsRef.current.y : 0;
|
|
||||||
const width = initialDimensionsRef.current.width + (invertX ? -distX : distX);
|
|
||||||
const height = initialDimensionsRef.current.height + (invertY ? -distY : distY);
|
|
||||||
|
|
||||||
if (invertX || invertY) {
|
|
||||||
const x = invertX ? initialDimensionsRef.current.nodeX + distX : initialDimensionsRef.current.nodeX;
|
|
||||||
const y = invertY ? initialDimensionsRef.current.nodeY + distY : initialDimensionsRef.current.nodeY;
|
|
||||||
|
|
||||||
if (x !== node.position.x || y !== node.position.y) {
|
|
||||||
const positionChanges: NodePositionChange[] | null = updateNodePositions(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: nodeId,
|
|
||||||
position: { x, y },
|
|
||||||
} as Node,
|
|
||||||
],
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (positionChanges?.length) {
|
|
||||||
changes.push(positionChanges[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width !== node.width || height !== node.height) {
|
|
||||||
const dimensionChange: NodeDimensionChange = {
|
|
||||||
id: nodeId,
|
|
||||||
type: 'dimensions',
|
|
||||||
updateStyle: true,
|
|
||||||
dimensions: {
|
|
||||||
width: width !== node.width ? width : node.width,
|
|
||||||
height: height !== node.height ? height : node.height,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
changes.push(dimensionChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.length) {
|
|
||||||
onNodesChange?.(changes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
selection.call(dragHandler);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
selection.on('.drag', null);
|
|
||||||
};
|
|
||||||
}, [nodeId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cc([className, 'nodrag'])}
|
|
||||||
ref={resizeHandleRef}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
background: 'red',
|
|
||||||
opacity: 0.7,
|
|
||||||
borderRadius: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NodeResizer({ nodeId }: NodeResizerProps) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResizeHandle
|
<ResizeControl className={handleClassName} style={handleStyle} nodeId={nodeId} position="top-left" />
|
||||||
className="react-flow__node-resizer"
|
<ResizeControl className={lineClassName} variant="line" style={lineStyle} nodeId={nodeId} position="top" />
|
||||||
nodeId={nodeId}
|
<ResizeControl className={handleClassName} style={handleStyle} nodeId={nodeId} position="top-right" />
|
||||||
top={0}
|
|
||||||
left={0}
|
<ResizeControl className={lineClassName} variant="line" style={lineStyle} nodeId={nodeId} position="left" />
|
||||||
enableX
|
|
||||||
enableY
|
<ResizeControl className={handleClassName} style={handleStyle} nodeId={nodeId} position="bottom-left" />
|
||||||
invertX
|
<ResizeControl className={lineClassName} variant="line" style={lineStyle} nodeId={nodeId} position="right" />
|
||||||
invertY
|
<ResizeControl className={handleClassName} style={handleStyle} nodeId={nodeId} position="bottom-right" />
|
||||||
/>
|
<ResizeControl className={lineClassName} variant="line" position="bottom" style={lineStyle} nodeId={nodeId} />
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top="50%" left={0} enableX invertX />
|
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top="100%" left={0} enableX enableY />
|
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top={0} left="50%" enableY invertY />
|
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top={0} left="100%" enableX enableY invertY />
|
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top="50%" left="100%" enableX />
|
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top="100%" left="100%" enableX enableY />
|
|
||||||
<ResizeHandle className="react-flow__node-resizer" nodeId={nodeId} top="100%" left="50%" enableY />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
import cc from 'classcat';
|
||||||
|
import { drag } from 'd3-drag';
|
||||||
|
import { select } from 'd3-selection';
|
||||||
|
import {
|
||||||
|
useStoreApi,
|
||||||
|
useGetPointerPosition,
|
||||||
|
NodeChange,
|
||||||
|
NodePositionChange,
|
||||||
|
NodeDimensionChange,
|
||||||
|
} from '@reactflow/core';
|
||||||
|
import type { Dimensions, Node, XYPosition } from '@reactflow/core';
|
||||||
|
|
||||||
|
import type { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps } from './types';
|
||||||
|
|
||||||
|
function ResizeControl({
|
||||||
|
nodeId,
|
||||||
|
position = 'bottom-right',
|
||||||
|
variant = 'handle',
|
||||||
|
className,
|
||||||
|
style = {},
|
||||||
|
children,
|
||||||
|
}: ResizeControlProps) {
|
||||||
|
const store = useStoreApi();
|
||||||
|
const resizeHandleRef = useRef<HTMLDivElement>(null);
|
||||||
|
const initialDimensionsRef = useRef<Dimensions & XYPosition & { nodeX: number; nodeY: number }>({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
nodeX: 0,
|
||||||
|
nodeY: 0,
|
||||||
|
});
|
||||||
|
const nodeElementRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const getPointerPosition = useGetPointerPosition();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!resizeHandleRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = select(resizeHandleRef.current);
|
||||||
|
const dragHandler = drag<HTMLDivElement, unknown>()
|
||||||
|
.on('start', (event: ResizeDragEvent) => {
|
||||||
|
const node = store.getState().nodeInternals.get(nodeId);
|
||||||
|
const pointerPos = getPointerPosition(event);
|
||||||
|
|
||||||
|
initialDimensionsRef.current = {
|
||||||
|
width: node?.width ?? 0,
|
||||||
|
height: node?.height ?? 0,
|
||||||
|
nodeX: node?.position.x ?? 0,
|
||||||
|
nodeY: node?.position.y ?? 0,
|
||||||
|
x: pointerPos.xSnapped,
|
||||||
|
y: pointerPos.ySnapped,
|
||||||
|
};
|
||||||
|
nodeElementRef.current = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`) as HTMLDivElement;
|
||||||
|
})
|
||||||
|
.on('drag', (event: ResizeDragEvent) => {
|
||||||
|
const { updateNodePositions, nodeInternals, onNodesChange } = store.getState();
|
||||||
|
const pointerPos = getPointerPosition(event);
|
||||||
|
const nodeEl = nodeElementRef.current;
|
||||||
|
const node = nodeInternals.get(nodeId);
|
||||||
|
const enableX = position.includes('right') || position.includes('left');
|
||||||
|
const enableY = position.includes('bottom') || position.includes('top');
|
||||||
|
const invertX = position.includes('left');
|
||||||
|
const invertY = position.includes('top');
|
||||||
|
|
||||||
|
if (nodeEl && node) {
|
||||||
|
const changes: NodeChange[] = [];
|
||||||
|
const distX = enableX ? pointerPos.xSnapped - initialDimensionsRef.current.x : 0;
|
||||||
|
const distY = enableY ? pointerPos.ySnapped - initialDimensionsRef.current.y : 0;
|
||||||
|
const width = initialDimensionsRef.current.width + (invertX ? -distX : distX);
|
||||||
|
const height = initialDimensionsRef.current.height + (invertY ? -distY : distY);
|
||||||
|
|
||||||
|
if (invertX || invertY) {
|
||||||
|
const x = invertX ? initialDimensionsRef.current.nodeX + distX : initialDimensionsRef.current.nodeX;
|
||||||
|
const y = invertY ? initialDimensionsRef.current.nodeY + distY : initialDimensionsRef.current.nodeY;
|
||||||
|
|
||||||
|
if (x !== node.position.x || y !== node.position.y) {
|
||||||
|
const positionChanges: NodePositionChange[] | null = updateNodePositions(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: nodeId,
|
||||||
|
position: { x, y },
|
||||||
|
} as Node,
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (positionChanges?.length) {
|
||||||
|
changes.push(positionChanges[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width !== node.width || height !== node.height) {
|
||||||
|
const dimensionChange: NodeDimensionChange = {
|
||||||
|
id: nodeId,
|
||||||
|
type: 'dimensions',
|
||||||
|
updateStyle: true,
|
||||||
|
dimensions: {
|
||||||
|
width: width !== node.width ? width : node.width,
|
||||||
|
height: height !== node.height ? height : node.height,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
changes.push(dimensionChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.length) {
|
||||||
|
onNodesChange?.(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
selection.call(dragHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
selection.on('.drag', null);
|
||||||
|
};
|
||||||
|
}, [nodeId, position, getPointerPosition]);
|
||||||
|
|
||||||
|
const positionClassNames = position.split('-');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cc([className, ...positionClassNames, variant, 'react-flow__resize-control', 'nodrag'])}
|
||||||
|
ref={resizeHandleRef}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResizeControlLine(props: ResizeControlLineProps) {
|
||||||
|
return <ResizeControl {...props} variant="line" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResizeControl;
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
export { default as NodeResizer } from './NodeResizer';
|
export { default as NodeResizer } from './NodeResizer';
|
||||||
|
export { default as NodeResizeControl } from './ResizeControl';
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
.react-flow__resize-control {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(195, 195, 195, 1);
|
||||||
|
border: 1px solid white;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.left,
|
||||||
|
.react-flow__resize-control.right {
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.top,
|
||||||
|
.react-flow__resize-control.bottom {
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.top.left,
|
||||||
|
.react-flow__resize-control.bottom.right {
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.bottom.left,
|
||||||
|
.react-flow__resize-control.top.right {
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle styles */
|
||||||
|
.react-flow__resize-control.handle {
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.handle.left {
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.right {
|
||||||
|
left: 100%;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.top {
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.bottom {
|
||||||
|
left: 50%;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.top.left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.bottom.left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.top.right {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.handle.bottom.right {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line styles */
|
||||||
|
.react-flow__resize-control.line {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.line.left,
|
||||||
|
.react-flow__resize-control.line.right {
|
||||||
|
width: 1px;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.line.left {
|
||||||
|
left: 0;
|
||||||
|
border-left: 1px solid rgba(195, 195, 195, 1);
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.line.right {
|
||||||
|
left: 100%;
|
||||||
|
border-right: 1px solid rgba(195, 195, 195, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.line.top,
|
||||||
|
.react-flow__resize-control.line.bottom {
|
||||||
|
height: 1px;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__resize-control.line.top {
|
||||||
|
top: 0;
|
||||||
|
border-top: 1px solid rgba(195, 195, 195, 1);
|
||||||
|
}
|
||||||
|
.react-flow__resize-control.line.bottom {
|
||||||
|
border-bottom: 1px solid rgba(195, 195, 195, 1);
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
@@ -1,9 +1,29 @@
|
|||||||
import { Position } from '@reactflow/core';
|
import type { CSSProperties, ReactNode } from 'react';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { D3DragEvent, SubjectPosition } from 'd3-drag';
|
||||||
|
|
||||||
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
export type NodeResizerProps = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
isVisible?: boolean;
|
handleClassName?: string;
|
||||||
position?: Position;
|
handleStyle?: CSSProperties;
|
||||||
offset?: number;
|
lineClassName?: string;
|
||||||
|
lineStyle?: CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right';
|
||||||
|
|
||||||
|
export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||||
|
|
||||||
|
export type ResizeControlProps = {
|
||||||
|
nodeId: string;
|
||||||
|
position: ControlPosition;
|
||||||
|
variant?: 'line' | 'handle';
|
||||||
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResizeControlLineProps = ResizeControlProps & {
|
||||||
|
position: ControlLinePosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResizeDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
||||||
|
|||||||
Reference in New Issue
Block a user