Skip to content

Commit 10c4c96

Browse files
committed
重构 Canvas 组件,提取 findNodeInSchema 函数至顶层,并添加选中节点边界测量逻辑以支持调整大小
1 parent bfa9af1 commit 10c4c96

1 file changed

Lines changed: 62 additions & 34 deletions

File tree

packages/designer/src/components/Canvas.tsx

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ const INSERT_AT_END = undefined; // undefined means append to end in addNode/mov
1919
// Set to true to allow context menu on the root component, false to disable it
2020
const ALLOW_ROOT_CONTEXT_MENU = false;
2121

22+
// Helper to find node in schema - extracted to top level
23+
const findNodeInSchema = (node: SchemaNode, targetId: string): SchemaNode | null => {
24+
if (node.id === targetId) return node;
25+
26+
if (Array.isArray(node.body)) {
27+
for (const child of node.body) {
28+
if (typeof child === 'object' && child !== null) {
29+
const found = findNodeInSchema(child as SchemaNode, targetId);
30+
if (found) return found;
31+
}
32+
}
33+
} else if (node.body && typeof node.body === 'object') {
34+
return findNodeInSchema(node.body as SchemaNode, targetId);
35+
}
36+
37+
return null;
38+
};
39+
2240
export const Canvas: React.FC<CanvasProps> = React.memo(({ className }) => {
2341
const {
2442
schema,
@@ -39,6 +57,7 @@ export const Canvas: React.FC<CanvasProps> = React.memo(({ className }) => {
3957

4058
const [scale, setScale] = useState(1);
4159
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; nodeId: string } | null>(null);
60+
const [selectionBounds, setSelectionBounds] = useState<{ top: number; left: number; width: number; height: number } | null>(null);
4261
const canvasRef = React.useRef<HTMLDivElement>(null);
4362

4463
// Memoize canvas width calculation
@@ -258,24 +277,6 @@ export const Canvas: React.FC<CanvasProps> = React.memo(({ className }) => {
258277
};
259278
}, [resizingNode, setResizingNode, updateNode, schema]);
260279

261-
// Helper to find node in schema
262-
const findNodeInSchema = (node: SchemaNode, targetId: string): SchemaNode | null => {
263-
if (node.id === targetId) return node;
264-
265-
if (Array.isArray(node.body)) {
266-
for (const child of node.body) {
267-
if (typeof child === 'object' && child !== null) {
268-
const found = findNodeInSchema(child as SchemaNode, targetId);
269-
if (found) return found;
270-
}
271-
}
272-
} else if (node.body && typeof node.body === 'object') {
273-
return findNodeInSchema(node.body as SchemaNode, targetId);
274-
}
275-
276-
return null;
277-
};
278-
279280
// Make components in canvas draggable
280281
React.useEffect(() => {
281282
if (!canvasRef.current) return;
@@ -321,6 +322,44 @@ export const Canvas: React.FC<CanvasProps> = React.memo(({ className }) => {
321322
};
322323
}, [schema, setDraggingNodeId]);
323324

325+
// Measure selected node bounds for resize handles
326+
React.useLayoutEffect(() => {
327+
if (!selectedNodeId || !canvasRef.current) {
328+
setSelectionBounds(null);
329+
return;
330+
}
331+
332+
const measure = () => {
333+
const element = canvasRef.current?.querySelector(`[data-obj-id="${selectedNodeId}"]`);
334+
if (!element) {
335+
setSelectionBounds(null);
336+
return;
337+
}
338+
339+
const rect = element.getBoundingClientRect();
340+
const canvasRect = canvasRef.current?.getBoundingClientRect();
341+
if (!canvasRect) return;
342+
343+
setSelectionBounds({
344+
top: rect.top - canvasRect.top,
345+
left: rect.left - canvasRect.left,
346+
width: rect.width,
347+
height: rect.height
348+
});
349+
};
350+
351+
measure();
352+
353+
// Update on resize or scroll
354+
window.addEventListener('resize', measure);
355+
window.addEventListener('scroll', measure, true);
356+
357+
return () => {
358+
window.removeEventListener('resize', measure);
359+
window.removeEventListener('scroll', measure, true);
360+
};
361+
}, [selectedNodeId, schema]);
362+
324363
// Inject styles for selection/hover using dynamic CSS
325364
// Enhanced with smooth transitions and gradient effects for premium UX
326365
const highlightStyles = `
@@ -555,24 +594,13 @@ export const Canvas: React.FC<CanvasProps> = React.memo(({ className }) => {
555594
<SchemaRenderer schema={schema} />
556595

557596
{/* Resize Handles - show only when a resizable component is selected */}
558-
{selectedNodeId && (() => {
597+
{selectedNodeId && selectionBounds && (() => {
559598
const selectedNode = findNodeInSchema(schema, selectedNodeId);
560599
if (!selectedNode) return null;
561600

562601
const config = ComponentRegistry.getConfig(selectedNode.type);
563602
if (!config?.resizable) return null;
564603

565-
const element = canvasRef.current?.querySelector(`[data-obj-id="${selectedNodeId}"]`);
566-
if (!element) return null;
567-
568-
const rect = element.getBoundingClientRect();
569-
const canvasRect = canvasRef.current?.getBoundingClientRect();
570-
if (!canvasRect) return null;
571-
572-
// Calculate position relative to canvas
573-
const top = rect.top - canvasRect.top;
574-
const left = rect.left - canvasRect.left;
575-
576604
// Determine which directions to show based on constraints
577605
const constraints = config.resizeConstraints || {};
578606
const directions: ResizeDirection[] = [];
@@ -591,10 +619,10 @@ export const Canvas: React.FC<CanvasProps> = React.memo(({ className }) => {
591619
<div
592620
className="absolute pointer-events-none"
593621
style={{
594-
top: `${top}px`,
595-
left: `${left}px`,
596-
width: `${rect.width}px`,
597-
height: `${rect.height}px`,
622+
top: `${selectionBounds.top}px`,
623+
left: `${selectionBounds.left}px`,
624+
width: `${selectionBounds.width}px`,
625+
height: `${selectionBounds.height}px`,
598626
}}
599627
>
600628
<ResizeHandles

0 commit comments

Comments
 (0)