( reactFlowInstance: ReactFlowInstance | null, viewportOptions?: CanvasViewportOptions )
| 98 | * Hook providing canvas viewport utilities that account for sidebar, panel, and terminal overlays. |
| 99 | */ |
| 100 | export function useCanvasViewport( |
| 101 | reactFlowInstance: ReactFlowInstance | null, |
| 102 | viewportOptions?: CanvasViewportOptions |
| 103 | ) { |
| 104 | const embedded = viewportOptions?.embedded |
| 105 | const stableOptions = useMemo<CanvasViewportOptions | undefined>( |
| 106 | () => (embedded ? { embedded } : undefined), |
| 107 | [embedded] |
| 108 | ) |
| 109 | |
| 110 | /** |
| 111 | * Gets the center of the visible canvas in flow coordinates. |
| 112 | */ |
| 113 | const getViewportCenter = useCallback(() => { |
| 114 | if (!reactFlowInstance) { |
| 115 | return { x: 0, y: 0 } |
| 116 | } |
| 117 | |
| 118 | const center = getVisibleCanvasCenter(stableOptions) |
| 119 | return reactFlowInstance.screenToFlowPosition(center) |
| 120 | }, [reactFlowInstance, stableOptions]) |
| 121 | |
| 122 | /** |
| 123 | * Fits the view to show all nodes within the visible canvas bounds, |
| 124 | * accounting for sidebar, panel, and terminal overlays. |
| 125 | * @param padding - Fraction of viewport to leave as margin (0.1 = 10% on each side) |
| 126 | */ |
| 127 | const fitViewToBounds = useCallback( |
| 128 | (options: FitViewToBoundsOptions = {}) => { |
| 129 | if (!reactFlowInstance) return |
| 130 | |
| 131 | const { |
| 132 | padding = 0.1, |
| 133 | maxZoom = 1, |
| 134 | minZoom = 0.1, |
| 135 | duration = 300, |
| 136 | nodes: targetNodes, |
| 137 | } = options |
| 138 | |
| 139 | const nodes = targetNodes ?? reactFlowInstance.getNodes() |
| 140 | if (nodes.length === 0) { |
| 141 | return |
| 142 | } |
| 143 | |
| 144 | const bounds = getVisibleCanvasBounds(stableOptions) |
| 145 | |
| 146 | // Calculate node bounds |
| 147 | let minX = Number.POSITIVE_INFINITY |
| 148 | let minY = Number.POSITIVE_INFINITY |
| 149 | let maxX = Number.NEGATIVE_INFINITY |
| 150 | let maxY = Number.NEGATIVE_INFINITY |
| 151 | |
| 152 | nodes.forEach((node) => { |
| 153 | const nodeWidth = node.width ?? BLOCK_DIMENSIONS.FIXED_WIDTH |
| 154 | const nodeHeight = node.height ?? BLOCK_DIMENSIONS.MIN_HEIGHT |
| 155 | |
| 156 | minX = Math.min(minX, node.position.x) |
| 157 | minY = Math.min(minY, node.position.y) |
no test coverage detected