From aacae7e3cb0a7d1fc76203be69849d3084d59dad Mon Sep 17 00:00:00 2001 From: shrimbly Date: Wed, 11 Mar 2026 20:43:31 +1300 Subject: [PATCH] fix: smooth settings panel transition and prevent image flicker on toggle Animate InlineParameterPanel expand/collapse with max-height transition instead of conditional rendering. Lock BaseNode content height during resize operations to prevent intermediate layout states causing image flicker. Co-Authored-By: Claude Opus 4.6 --- src/components/nodes/BaseNode.tsx | 31 ++++++++++++++++++- src/components/nodes/InlineParameterPanel.tsx | 28 +++++++++++------ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx index fb5cd96a..74b1601e 100644 --- a/src/components/nodes/BaseNode.tsx +++ b/src/components/nodes/BaseNode.tsx @@ -75,6 +75,7 @@ export function BaseNode({ const { getNodes, setNodes } = useReactFlow(); const settingsPanelRef = useRef(null); + const contentRef = useRef(null); const trackedSettingsHeightRef = useRef(0); // Adjust node height when settings collapse @@ -82,6 +83,13 @@ export function BaseNode({ if (!settingsExpanded && trackedSettingsHeightRef.current > 0) { const heightToRemove = trackedSettingsHeightRef.current; trackedSettingsHeightRef.current = 0; + + // Lock content height to prevent image flicker during resize + const contentEl = contentRef.current; + if (contentEl) { + contentEl.style.height = contentEl.offsetHeight + "px"; + } + setNodes((nodes) => nodes.map((node) => { if (node.id !== id) return node; @@ -90,6 +98,13 @@ export function BaseNode({ return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight); }) ); + + // Release locked height after layout settles + requestAnimationFrame(() => { + if (contentEl) { + contentEl.style.height = ""; + } + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [settingsExpanded]); @@ -108,6 +123,13 @@ export function BaseNode({ if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes trackedSettingsHeightRef.current = newPanelHeight; + + // Lock content height to prevent image flicker during resize + const contentEl = contentRef.current; + if (contentEl) { + contentEl.style.height = contentEl.offsetHeight + "px"; + } + setNodes((nodes) => nodes.map((node) => { if (node.id !== id) return node; @@ -116,6 +138,13 @@ export function BaseNode({ return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight); }) ); + + // Release locked height after layout settles + requestAnimationFrame(() => { + if (contentEl) { + contentEl.style.height = ""; + } + }); } }); @@ -214,7 +243,7 @@ export function BaseNode({ setHoveredNodeId(null); }} > -
{children}
+
{children}
{settingsPanel && (
diff --git a/src/components/nodes/InlineParameterPanel.tsx b/src/components/nodes/InlineParameterPanel.tsx index b26b139d..92539f30 100644 --- a/src/components/nodes/InlineParameterPanel.tsx +++ b/src/components/nodes/InlineParameterPanel.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { ReactNode } from "react"; +import React, { ReactNode, useRef, useState, useEffect } from "react"; interface InlineParameterPanelProps { expanded: boolean; @@ -11,7 +11,7 @@ interface InlineParameterPanelProps { /** * Collapsible parameter container for inline display within generation nodes. - * Provides a chevron toggle button and instant expand/collapse. + * Provides a chevron toggle button and smooth animated expand/collapse. */ function InlineParameterPanelInner({ expanded, @@ -19,6 +19,15 @@ function InlineParameterPanelInner({ children, nodeId, }: InlineParameterPanelProps) { + const contentRef = useRef(null); + const [contentHeight, setContentHeight] = useState(0); + + useEffect(() => { + if (expanded && contentRef.current) { + setContentHeight(contentRef.current.scrollHeight); + } + }, [expanded, children]); + return ( <> {/* Settings toggle button — no background when collapsed, floats below node edge */} @@ -45,15 +54,16 @@ function InlineParameterPanelInner({ - {/* Content area — instant show/hide */} - {expanded && ( -
+ {/* Content area — smooth height animation */} +
+
{children}
- )} +
); }