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}
- )} +
); }