From 5d110a4514d37d7f0d6658ec240182aa2428765e Mon Sep 17 00:00:00 2001 From: shrimbly Date: Tue, 10 Mar 2026 07:04:28 +1300 Subject: [PATCH] fix: prevent nodes growing taller when toggling inline parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert trackedSettingsHeight from useState to useRef to eliminate a race condition between two useLayoutEffect hooks. On expand, both the rAF-based measure (Effect 1) and the ResizeObserver (Effect 2) were adding the panel height — a double-add. On collapse only one subtracted, leaving residual height each cycle. The fix removes the expand branch from Effect 1 (now collapse-only) and lets the ResizeObserver solely handle initial and dynamic measurement. Using a ref also prevents the ResizeObserver from being torn down and recreated on every height change. Co-Authored-By: Claude Opus 4.6 --- src/components/nodes/BaseNode.tsx | 46 ++++++++----------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx index 908e36e1..bb35711b 100644 --- a/src/components/nodes/BaseNode.tsx +++ b/src/components/nodes/BaseNode.tsx @@ -1,6 +1,6 @@ "use client"; -import { ReactNode, useCallback, useRef, useLayoutEffect, useState } from "react"; +import { ReactNode, useCallback, useRef, useLayoutEffect } from "react"; import { Node, NodeResizer, OnResize, useReactFlow } from "@xyflow/react"; import { useWorkflowStore } from "@/store/workflowStore"; import { getMediaDimensions, calculateAspectFitSize } from "@/utils/nodeDimensions"; @@ -75,35 +75,13 @@ export function BaseNode({ const { getNodes, setNodes } = useReactFlow(); const settingsPanelRef = useRef(null); - const [trackedSettingsHeight, setTrackedSettingsHeight] = useState(0); + const trackedSettingsHeightRef = useRef(0); - // Adjust node height when settings expand/collapse + // Adjust node height when settings collapse useLayoutEffect(() => { - if (settingsExpanded && settingsPanel) { - // Expanding: measure settings panel and increase node height - const measure = () => { - const panelEl = settingsPanelRef.current; - if (!panelEl) return; - const panelHeight = panelEl.scrollHeight; - if (panelHeight === 0) return; - - setTrackedSettingsHeight(panelHeight); - setNodes((nodes) => - nodes.map((node) => { - if (node.id !== id) return node; - const currentHeight = getNodeDimension(node, "height"); - const newHeight = currentHeight + panelHeight; - return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight); - }) - ); - }; - - // Use rAF to ensure DOM has rendered the panel content - requestAnimationFrame(measure); - } else if (!settingsExpanded && trackedSettingsHeight > 0) { - // Collapsing: decrease node height by the tracked amount - const heightToRemove = trackedSettingsHeight; - setTrackedSettingsHeight(0); + if (!settingsExpanded && trackedSettingsHeightRef.current > 0) { + const heightToRemove = trackedSettingsHeightRef.current; + trackedSettingsHeightRef.current = 0; setNodes((nodes) => nodes.map((node) => { if (node.id !== id) return node; @@ -126,10 +104,10 @@ export function BaseNode({ for (const entry of entries) { const newPanelHeight = entry.contentRect.height; if (newPanelHeight === 0) continue; - const delta = newPanelHeight - trackedSettingsHeight; + const delta = newPanelHeight - trackedSettingsHeightRef.current; if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes - setTrackedSettingsHeight(newPanelHeight); + trackedSettingsHeightRef.current = newPanelHeight; setNodes((nodes) => nodes.map((node) => { if (node.id !== id) return node; @@ -144,7 +122,7 @@ export function BaseNode({ observer.observe(panelEl); return () => observer.disconnect(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [settingsExpanded, settingsPanel, trackedSettingsHeight]); + }, [settingsExpanded, settingsPanel]); const handleResize: OnResize = useCallback( (_event, params) => { @@ -174,7 +152,7 @@ export function BaseNode({ if (!thisNode) return; const nodeHeight = getNodeDimension(thisNode, "height"); - const contentHeight = nodeHeight - trackedSettingsHeight; + const contentHeight = nodeHeight - trackedSettingsHeightRef.current; const newSize = calculateAspectFitSize( dims.width / dims.height, @@ -183,7 +161,7 @@ export function BaseNode({ fullBleed ); - const finalHeight = newSize.height + trackedSettingsHeight; + const finalHeight = newSize.height + trackedSettingsHeightRef.current; setNodes((nds) => nds.map((n) => { @@ -194,7 +172,7 @@ export function BaseNode({ }) ); }, - [aspectFitMedia, id, fullBleed, getNodes, setNodes, trackedSettingsHeight] + [aspectFitMedia, id, fullBleed, getNodes, setNodes] ); const hasExpandedSettings = settingsExpanded && settingsPanel;