feat(canvas): alignment guides + snap while dragging nodes
Draw.io / Figma style: while dragging a node, show dashed cyan guide lines when its edges (left / center / right / top / middle / bottom) align with another node's, and snap the position to the matched line within a configurable threshold. - utils/alignment.ts: pure snap math, returns delta + guide segments. Same-size boxes show all aligned guides simultaneously. - canvas/AlignmentGuides.tsx: SVG overlay locked to the React Flow viewport (panned/zoomed correctly). - hooks/useAlignmentGuides.ts: wires onNodeDrag/onNodeDragStop, applies snap via setNodes, listens for Alt to temporarily disable. - utils/alignmentSettings.ts: localStorage-backed prefs (enabled, threshold 2-16px) with a tiny CustomEvent pub-sub so the SettingsPanel and the drag hook stay in sync without a global store. - Sidebar settings panel: toggle + threshold slider. Multi-selection drag uses the union bounding box. Children with parentId are skipped for v1 to avoid mixing absolute and parent-relative coordinates. Hold Alt to escape snap.
This commit is contained in:
@@ -20,6 +20,8 @@ import { THEMES } from '@/utils/themes'
|
||||
import { nodeTypes } from './nodes/nodeTypes'
|
||||
import { edgeTypes } from './edges/edgeTypes'
|
||||
import { SearchBar } from './SearchBar'
|
||||
import { AlignmentGuides } from './AlignmentGuides'
|
||||
import { useAlignmentGuides } from '@/hooks/useAlignmentGuides'
|
||||
import type { NodeData, EdgeData } from '@/types'
|
||||
|
||||
interface CanvasContainerProps {
|
||||
@@ -83,6 +85,8 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
|
||||
[]
|
||||
)
|
||||
|
||||
const { guides, onNodeDrag, onNodeDragStop } = useAlignmentGuides()
|
||||
|
||||
return (
|
||||
<div className="w-full h-full" style={{ background: theme.colors.canvasBackground }}>
|
||||
<ReactFlow
|
||||
@@ -96,6 +100,8 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
|
||||
onEdgeDoubleClick={handleEdgeDoubleClick}
|
||||
onNodeDoubleClick={handleNodeDoubleClick}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
deleteKeyCode={['Backspace', 'Delete']}
|
||||
@@ -121,6 +127,7 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
|
||||
color={theme.colors.canvasDotColor}
|
||||
/>
|
||||
<SearchBar onOpenPending={onOpenPending} />
|
||||
<AlignmentGuides guides={guides} />
|
||||
<Controls>
|
||||
<ControlButton
|
||||
onClick={() => setLassoMode((m) => !m)}
|
||||
|
||||
Reference in New Issue
Block a user