From eca569061b09be64c037ceefc2f2804ee4447cd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:55:44 +0000 Subject: [PATCH 1/5] Initial plan From 27e49df43d6aa4c884f04bbf812a0f46343f9cb0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:11:00 +0000 Subject: [PATCH 2/5] Fix panel height not updating on conditional rendering Co-authored-by: gsimone <1862172+gsimone@users.noreply.github.com> --- packages/leva/src/hooks/useToggle.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/leva/src/hooks/useToggle.ts b/packages/leva/src/hooks/useToggle.ts index b5676b03..8229649e 100644 --- a/packages/leva/src/hooks/useToggle.ts +++ b/packages/leva/src/hooks/useToggle.ts @@ -126,5 +126,28 @@ export function useToggle(toggled: boolean) { } }, [toggled]) + // Watch for content size changes when panel is expanded + useEffect(() => { + if (!toggled || !contentRef.current || !wrapperRef.current) return + + const resizeObserver = new ResizeObserver(() => { + // Only update if the panel is expanded and height is not already managed by CSS + if (toggled && wrapperRef.current && contentRef.current) { + const currentHeight = wrapperRef.current.style.height + // Only update if we have a fixed height (not empty/auto) + if (currentHeight && currentHeight !== '0px') { + const { height } = contentRef.current.getBoundingClientRect() + wrapperRef.current.style.height = height + 'px' + } + } + }) + + resizeObserver.observe(contentRef.current) + + return () => { + resizeObserver.disconnect() + } + }, [toggled]) + return { wrapperRef, contentRef } } From 9e0eccdeb6d4963b3d86d8958f7412a64808f982 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:22:19 +0000 Subject: [PATCH 3/5] Improve ResizeObserver implementation based on code review feedback Co-authored-by: gsimone <1862172+gsimone@users.noreply.github.com> --- packages/leva/src/hooks/useToggle.ts | 32 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/leva/src/hooks/useToggle.ts b/packages/leva/src/hooks/useToggle.ts index 8229649e..c6f2b776 100644 --- a/packages/leva/src/hooks/useToggle.ts +++ b/packages/leva/src/hooks/useToggle.ts @@ -131,14 +131,30 @@ export function useToggle(toggled: boolean) { if (!toggled || !contentRef.current || !wrapperRef.current) return const resizeObserver = new ResizeObserver(() => { - // Only update if the panel is expanded and height is not already managed by CSS - if (toggled && wrapperRef.current && contentRef.current) { - const currentHeight = wrapperRef.current.style.height - // Only update if we have a fixed height (not empty/auto) - if (currentHeight && currentHeight !== '0px') { - const { height } = contentRef.current.getBoundingClientRect() - wrapperRef.current.style.height = height + 'px' - } + const wrapper = wrapperRef.current + const content = contentRef.current + if (!wrapper || !content) return + + // Get the current and target heights + const currentHeight = wrapper.getBoundingClientRect().height + const targetHeight = content.getBoundingClientRect().height + + // Only update if there's a meaningful difference + if (Math.abs(currentHeight - targetHeight) > 1) { + // Set explicit height to enable transition + wrapper.style.height = currentHeight + 'px' + + // Use requestAnimationFrame to ensure the height is set before changing it + requestAnimationFrame(() => { + wrapper.style.height = targetHeight + 'px' + + // Remove fixed height after transition completes + const handleTransitionEnd = () => { + wrapper.style.removeProperty('height') + wrapper.removeEventListener('transitionend', handleTransitionEnd) + } + wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true }) + }) } }) From e340bd0fe17ab4e0ed18f62bd9ac426349702ffd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:30:41 +0000 Subject: [PATCH 4/5] Fix race conditions and cleanup issues in ResizeObserver Co-authored-by: gsimone <1862172+gsimone@users.noreply.github.com> --- packages/leva/src/hooks/useToggle.ts | 31 ++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/leva/src/hooks/useToggle.ts b/packages/leva/src/hooks/useToggle.ts index c6f2b776..b9578626 100644 --- a/packages/leva/src/hooks/useToggle.ts +++ b/packages/leva/src/hooks/useToggle.ts @@ -130,10 +130,25 @@ export function useToggle(toggled: boolean) { useEffect(() => { if (!toggled || !contentRef.current || !wrapperRef.current) return + const wrapper = wrapperRef.current + let rafId: number | null = null + let currentTransitionHandler: (() => void) | null = null + const resizeObserver = new ResizeObserver(() => { - const wrapper = wrapperRef.current const content = contentRef.current - if (!wrapper || !content) return + if (!content) return + + // Cancel any pending animation + if (rafId !== null) { + cancelAnimationFrame(rafId) + rafId = null + } + + // Remove any existing transition handler + if (currentTransitionHandler) { + wrapper.removeEventListener('transitionend', currentTransitionHandler) + currentTransitionHandler = null + } // Get the current and target heights const currentHeight = wrapper.getBoundingClientRect().height @@ -145,14 +160,16 @@ export function useToggle(toggled: boolean) { wrapper.style.height = currentHeight + 'px' // Use requestAnimationFrame to ensure the height is set before changing it - requestAnimationFrame(() => { + rafId = requestAnimationFrame(() => { + rafId = null wrapper.style.height = targetHeight + 'px' // Remove fixed height after transition completes const handleTransitionEnd = () => { wrapper.style.removeProperty('height') - wrapper.removeEventListener('transitionend', handleTransitionEnd) + currentTransitionHandler = null } + currentTransitionHandler = handleTransitionEnd wrapper.addEventListener('transitionend', handleTransitionEnd, { once: true }) }) } @@ -162,6 +179,12 @@ export function useToggle(toggled: boolean) { return () => { resizeObserver.disconnect() + if (rafId !== null) { + cancelAnimationFrame(rafId) + } + if (currentTransitionHandler) { + wrapper.removeEventListener('transitionend', currentTransitionHandler) + } } }, [toggled]) From bda7da4dfdd3c7523e0997857f652a61a9510aa5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:29:10 +0000 Subject: [PATCH 5/5] Add Storybook story for conditional rendering panel height fix Co-authored-by: gsimone <1862172+gsimone@users.noreply.github.com> --- .../leva/stories/input-options.stories.tsx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/leva/stories/input-options.stories.tsx b/packages/leva/stories/input-options.stories.tsx index d3f69982..fc4d2f40 100644 --- a/packages/leva/stories/input-options.stories.tsx +++ b/packages/leva/stories/input-options.stories.tsx @@ -62,6 +62,51 @@ export const Render = () => { ) } +export const ConditionalRenderingPanelHeight = () => { + const values = useControls({ + showBasicFields: { value: true, label: 'Show Basic Fields' }, + name: { value: 'John Doe', render: (get) => get('showBasicFields') }, + age: { value: 25, render: (get) => get('showBasicFields') }, + + showAdvanced: { value: false, label: 'Show Advanced Settings' }, + advancedSettings: folder( + { + apiEndpoint: { value: 'https://api.example.com', render: (get) => get('advancedSettings.enableAPI') }, + enableAPI: true, + timeout: { value: 5000, min: 1000, max: 30000 }, + retries: { value: 3, min: 0, max: 10 }, + }, + { render: (get) => get('showAdvanced') } + ), + + showDebug: { value: false, label: 'Show Debug Options' }, + debugOptions: folder( + { + verbose: false, + logLevel: { value: 'info', options: ['debug', 'info', 'warn', 'error'] }, + showTimestamps: true, + colorOutput: { value: true, render: (get) => get('debugOptions.verbose') }, + }, + { render: (get) => get('showDebug') } + ), + }) + + return ( +
+ Toggle the checkboxes to show/hide different sections. + The panel height will smoothly animate to accommodate the content. +
+
+ {JSON.stringify(values, null, 2)}
+
+