@@ -235,17 +235,20 @@ export function PropertyPanelFlat({
235235 : "layout" ,
236236 ) ;
237237
238- // Tracks which single group is actively transitioning, so its header/body
239- // gets the fast entrance animation (hf-flat-group-enter) and no one else's
240- // does. Deliberately NOT derived from remounting alone: FlatGroupHeader
241- // instances are keyed by group id and React normally preserves them across
242- // re-renders, but toggling a non-adjacent group still shifts the untouched
243- // collapsed siblings between the before/after-open slices below, and
244- // Chromium restarts a CSS animation on that kind of position shift even
245- // though nothing about the sibling actually changed. Gating on this id
246- // (cleared shortly after the 120ms CSS animation finishes) keeps the
247- // animation scoped to only the group that actually just toggled.
248- const [ justToggledId , setJustToggledId ] = useState < string | null > ( null ) ;
238+ // Tracks which group(s) are actively transitioning this toggle cycle, so
239+ // their header/body gets the fast entrance animation (hf-flat-group-enter)
240+ // and no one else's does. Deliberately NOT derived from remounting alone:
241+ // FlatGroupHeader instances are keyed by group id and React normally
242+ // preserves them across re-renders, but toggling a non-adjacent group still
243+ // shifts the untouched collapsed siblings between the before/after-open
244+ // slices below, and Chromium restarts a CSS animation on that kind of
245+ // position shift even though nothing about the sibling actually changed.
246+ // Gating on these ids (cleared shortly after the 120ms CSS animation
247+ // finishes) keeps the animation scoped to only the groups that actually
248+ // just toggled. Two ids, not one: the clicked (newly-opening/closing) group
249+ // AND whichever group was open immediately before the click and got
250+ // implicitly closed by it — both freshly-mounted headers need to animate.
251+ const [ justToggledIds , setJustToggledIds ] = useState < string [ ] > ( [ ] ) ;
249252 const justToggledTimeoutRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
250253 useEffect ( ( ) => {
251254 return ( ) => {
@@ -270,10 +273,16 @@ export function PropertyPanelFlat({
270273 const isTextEditable = isTextEditableSelection ( element ) ;
271274 const elementKind = sections . media ? "media" : element . textFields . length > 0 ? "text" : "other" ;
272275 const toggleOpen = ( groupId : string ) => {
276+ // Capture what was open BEFORE this click (this render's closure over
277+ // openGroupId), so the group that's about to be implicitly closed can be
278+ // tracked too — not just the one the user clicked.
279+ const previousOpenGroupId = openGroupId ;
273280 setOpenGroupId ( ( current ) => ( current === groupId ? "" : groupId ) ) ;
274- setJustToggledId ( groupId ) ;
281+ const implicitlyClosedId =
282+ previousOpenGroupId && previousOpenGroupId !== groupId ? previousOpenGroupId : null ;
283+ setJustToggledIds ( implicitlyClosedId ? [ groupId , implicitlyClosedId ] : [ groupId ] ) ;
275284 if ( justToggledTimeoutRef . current ) clearTimeout ( justToggledTimeoutRef . current ) ;
276- justToggledTimeoutRef . current = setTimeout ( ( ) => setJustToggledId ( null ) , 200 ) ;
285+ justToggledTimeoutRef . current = setTimeout ( ( ) => setJustToggledIds ( [ ] ) , 200 ) ;
277286 } ;
278287 // Basis for the Layout keyframe gutter (X/Y/W/H/Angle + 3D Transform) —
279288 // must agree with Motion's Timing row (FlatTimingRow), which infers the
@@ -510,7 +519,7 @@ export function PropertyPanelFlat({
510519 isOpen = { false }
511520 onToggleOpen = { ( ) => toggleOpen ( g . id ) }
512521 summary = { g . summary }
513- animateEntrance = { g . id === justToggledId }
522+ animateEntrance = { justToggledIds . includes ( g . id ) }
514523 />
515524 ) ) }
516525 { openGroup && (
@@ -520,10 +529,10 @@ export function PropertyPanelFlat({
520529 isOpen
521530 onToggleOpen = { ( ) => toggleOpen ( openGroup . id ) }
522531 accessory = { openGroup . accessory }
523- animateEntrance = { openGroup . id === justToggledId }
532+ animateEntrance = { justToggledIds . includes ( openGroup . id ) }
524533 />
525534 < div
526- className = { `${ openGroup . id === justToggledId ? "hf-flat-group-enter " : "" } min-h-0 flex-1 overflow-y-auto border-b border-panel-hairline px-4 py-3` }
535+ className = { `${ justToggledIds . includes ( openGroup . id ) ? "hf-flat-group-enter " : "" } min-h-0 flex-1 overflow-y-auto border-b border-panel-hairline px-4 py-3` }
527536 >
528537 { openGroup . content }
529538 </ div >
@@ -536,7 +545,7 @@ export function PropertyPanelFlat({
536545 isOpen = { false }
537546 onToggleOpen = { ( ) => toggleOpen ( g . id ) }
538547 summary = { g . summary }
539- animateEntrance = { g . id === justToggledId }
548+ animateEntrance = { justToggledIds . includes ( g . id ) }
540549 />
541550 ) ) }
542551 </ div >
0 commit comments