11import React , { useCallback , useContext , useEffect , useRef , useState } from 'react' ;
22import classNames from 'classnames' ;
3- import { CSSTransition , TransitionGroup } from 'react- transition-group ' ;
3+ import Transition from '../ transition' ;
44import { ConfigContext } from '../config-provider/config-context' ;
55import { getPrefixCls } from '../_utils/general' ;
66import { Breakpoint , WaterfallItem , WaterfallProps } from './types' ;
@@ -115,6 +115,42 @@ const Waterfall = React.forwardRef<HTMLDivElement, WaterfallProps>((props, ref)
115115 onLayoutChange ( sortInfo ) ;
116116 } , [ itemPositions , items , onLayoutChange ] ) ;
117117
118+ // ============== Exiting items (replaces TransitionGroup) ==============
119+ const prevItemsRef = useRef < WaterfallItem [ ] > ( items || [ ] ) ;
120+ const [ exitingItems , setExitingItems ] = useState < Map < React . Key , WaterfallItem > > ( new Map ( ) ) ;
121+
122+ useEffect ( ( ) => {
123+ const currentKeys = new Set ( ( items || [ ] ) . map ( ( item , i ) => item . key ?? i ) ) ;
124+ const removed = new Map < React . Key , WaterfallItem > ( ) ;
125+
126+ prevItemsRef . current . forEach ( ( item , index ) => {
127+ const key = item . key ?? index ;
128+ if ( ! currentKeys . has ( key ) ) {
129+ removed . set ( key , item ) ;
130+ }
131+ } ) ;
132+
133+ if ( removed . size > 0 ) {
134+ setExitingItems ( ( prev ) => {
135+ const next = new Map ( prev ) ;
136+ removed . forEach ( ( item , key ) => next . set ( key , item ) ) ;
137+ return next ;
138+ } ) ;
139+ }
140+
141+ prevItemsRef . current = items || [ ] ;
142+ } , [ items ] ) ;
143+
144+ const handleExited = useCallback ( ( key : React . Key ) => {
145+ itemRefsMap . current . delete ( key ) ;
146+ setExitingItems ( ( prev ) => {
147+ const next = new Map ( prev ) ;
148+ next . delete ( key ) ;
149+ return next ;
150+ } ) ;
151+ collectItemSizes ( ) ;
152+ } , [ collectItemSizes ] ) ;
153+
118154 // ==================== Render ====================
119155 const cls = classNames ( prefixCls , className ) ;
120156
@@ -126,6 +162,46 @@ const Waterfall = React.forwardRef<HTMLDivElement, WaterfallProps>((props, ref)
126162
127163 const mergedItems = items || [ ] ;
128164
165+ const renderItem = ( item : WaterfallItem , index : number , isExiting : boolean ) => {
166+ const key = item . key ?? index ;
167+ const position = itemPositions . get ( key ) ;
168+ const hasPosition = ! ! position ;
169+ const columnIndex = position ?. column ?? 0 ;
170+
171+ const itemStyle : React . CSSProperties = {
172+ position : 'absolute' ,
173+ width : `calc((100% - ${ horizontalGutter * ( columnCount - 1 ) } px) / ${ columnCount } )` ,
174+ left : `calc((100% - ${ horizontalGutter * ( columnCount - 1 ) } px) / ${ columnCount } * ${ columnIndex } + ${ horizontalGutter * columnIndex } px)` ,
175+ top : position ?. top ?? 0 ,
176+ // Only transition position changes after initial placement
177+ transition : hasPosition ? 'top 0.3s ease, left 0.3s ease, opacity 0.3s ease' : 'none' ,
178+ // Hide until position is computed so items don't flash at (0,0)
179+ opacity : hasPosition ? 1 : 0 ,
180+ } ;
181+
182+ const content = item . children ?? itemRender ?.( { ...item , index, column : columnIndex } ) ;
183+
184+ return (
185+ < Transition
186+ key = { key }
187+ in = { ! isExiting }
188+ timeout = { 300 }
189+ appear = { false }
190+ unmountOnExit = { true }
191+ classNames = { `${ prefixCls } __item-fade` }
192+ onExited = { ( ) => handleExited ( key ) }
193+ >
194+ < div
195+ ref = { ( el ) => setItemRef ( key , el ) }
196+ className = { `${ prefixCls } __item` }
197+ style = { itemStyle }
198+ >
199+ { content }
200+ </ div >
201+ </ Transition >
202+ ) ;
203+ } ;
204+
129205 return (
130206 < div
131207 ref = { ref }
@@ -135,47 +211,10 @@ const Waterfall = React.forwardRef<HTMLDivElement, WaterfallProps>((props, ref)
135211 onLoad = { collectItemSizes }
136212 onError = { collectItemSizes }
137213 >
138- < TransitionGroup component = { null } >
139- { mergedItems . map ( ( item , index ) => {
140- const key = item . key ?? index ;
141- const position = itemPositions . get ( key ) ;
142- const hasPosition = ! ! position ;
143- const columnIndex = position ?. column ?? 0 ;
144-
145- const itemStyle : React . CSSProperties = {
146- position : 'absolute' ,
147- width : `calc((100% - ${ horizontalGutter * ( columnCount - 1 ) } px) / ${ columnCount } )` ,
148- left : `calc((100% - ${ horizontalGutter * ( columnCount - 1 ) } px) / ${ columnCount } * ${ columnIndex } + ${ horizontalGutter * columnIndex } px)` ,
149- top : position ?. top ?? 0 ,
150- // Only transition position changes after initial placement
151- transition : hasPosition ? 'top 0.3s ease, left 0.3s ease, opacity 0.3s ease' : 'none' ,
152- // Hide until position is computed so items don't flash at (0,0)
153- opacity : hasPosition ? 1 : 0 ,
154- } ;
155-
156- const content = item . children ?? itemRender ?.( { ...item , index, column : columnIndex } ) ;
157-
158- return (
159- < CSSTransition
160- key = { key }
161- timeout = { 300 }
162- classNames = { `${ prefixCls } __item-fade` }
163- onExited = { ( ) => {
164- itemRefsMap . current . delete ( key ) ;
165- collectItemSizes ( ) ;
166- } }
167- >
168- < div
169- ref = { ( el ) => setItemRef ( key , el ) }
170- className = { `${ prefixCls } __item` }
171- style = { itemStyle }
172- >
173- { content }
174- </ div >
175- </ CSSTransition >
176- ) ;
177- } ) }
178- </ TransitionGroup >
214+ { mergedItems . map ( ( item , index ) => renderItem ( item , index , false ) ) }
215+ { Array . from ( exitingItems . entries ( ) ) . map ( ( [ key , item ] ) =>
216+ renderItem ( item , Number ( key ) , true )
217+ ) }
179218 </ div >
180219 ) ;
181220} ) ;
0 commit comments