You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
flow-playform-react/src/hooks/useAlignmentGuidelines.tsx

125 lines
3.4 KiB
TypeScript

import React, { useState, useCallback } from 'react';
import { Node, useReactFlow } from '@xyflow/react';
interface Guideline {
type: 'left' | 'right' | 'center' | 'top' | 'middle' | 'bottom';
x?: number;
y?: number;
from: string;
to: string;
}
interface AlignmentGuidelines {
verticalLines: Array<Guideline & { x: number }>;
horizontalLines: Array<Guideline & { y: number }>;
}
const TOLERANCE = 2;
export const useAlignmentGuidelines = () => {
const [guidelines, setGuidelines] = useState<AlignmentGuidelines>({
verticalLines: [],
horizontalLines: []
});
const reactFlowInstance = useReactFlow();
const clearGuidelines = useCallback(() => {
setGuidelines({ verticalLines: [], horizontalLines: [] });
}, []);
const getGuidelines = useCallback((draggingNode: Node, allNodes: Node[]) => {
const { position, width = 100, height = 50 } = draggingNode;
const { x: dx, y: dy } = position;
const dWidth = width;
const dHeight = height;
const verticalLines: Array<Guideline & { x: number }> = [];
const horizontalLines: Array<Guideline & { y: number }> = [];
allNodes.forEach(node => {
if (node.id === draggingNode.id) return;
const { position: pos, measured } = node;
const { x, y } = pos;
const width2 = measured.width | 0;
const height2 = measured.height | 0;
// 垂直对齐
if (Math.abs(dx - x) < TOLERANCE) {
verticalLines.push({ type: 'left', x: x - width2 - 1, from: draggingNode.id, to: node.id });
}
// 水平对齐
if (Math.abs(dy - y) < TOLERANCE) {
horizontalLines.push({ type: 'top', y: y - height2 / 2 - 3, from: draggingNode.id, to: node.id });
}
});
setGuidelines({ verticalLines, horizontalLines });
return { verticalLines, horizontalLines };
}, []);
const AlignmentGuides = () => {
// 将节点坐标转换为屏幕坐标
const screenVerticalLines = guidelines.verticalLines.map(line => ({
...line,
x: reactFlowInstance.flowToScreenPosition({ x: line.x, y: 0 }).x
}));
const screenHorizontalLines = guidelines.horizontalLines.map(line => ({
...line,
y: reactFlowInstance.flowToScreenPosition({ x: 0, y: line.y }).y
}));
return (
<>
{/* 垂直辅助线 */}
{screenVerticalLines.map((line, i) => (
<div
key={`v-${i}`}
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: `${Math.round(line.x)}px`,
width: '1px',
backgroundColor: '#2196F3',
pointerEvents: 'none',
zIndex: 1000,
transform: 'translateX(-0.5px)'
}}
/>
))}
{/* 水平辅助线 */}
{screenHorizontalLines.map((line, i) => (
<div
key={`h-${i}`}
style={{
position: 'absolute',
left: 0,
right: 0,
top: `${Math.round(line.y)}px`,
height: '1px',
backgroundColor: '#2196F3',
pointerEvents: 'none',
zIndex: 1000,
transform: 'translateY(-0.5px)'
}}
/>
))}
</>
);
};
return {
guidelines,
getGuidelines,
clearGuidelines,
AlignmentGuides
};
};
export type { AlignmentGuidelines, Guideline };