33 * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
44 */
55
6- import { AnimationEvent } from '@angular/animations' ;
76import {
87 ComponentType ,
98 createGlobalPositionStrategy ,
109 createNoopScrollStrategy ,
1110 createOverlayRef
1211} from '@angular/cdk/overlay' ;
1312import { ComponentPortal } from '@angular/cdk/portal' ;
14- import { ChangeDetectorRef , DestroyRef , Directive , EventEmitter , inject , Injector , OnInit } from '@angular/core' ;
13+ import {
14+ ChangeDetectorRef ,
15+ DestroyRef ,
16+ Directive ,
17+ ElementRef ,
18+ EventEmitter ,
19+ inject ,
20+ Injector ,
21+ OnInit ,
22+ Signal
23+ } from '@angular/core' ;
1524import { Subject } from 'rxjs' ;
16- import { filter , take } from 'rxjs/operators' ;
1725
1826import { MessageConfig , NzConfigService } from 'ng-zorro-antd/core/config' ;
1927import { NzSingletonService } from 'ng-zorro-antd/core/services' ;
@@ -163,13 +171,27 @@ export abstract class NzMNComponent implements OnInit {
163171 abstract index ?: number ;
164172 abstract destroyed : EventEmitter < { id : string ; userAction : boolean } > ;
165173
166- protected cdr = inject ( ChangeDetectorRef ) ;
167174 protected destroyRef = inject ( DestroyRef ) ;
168- readonly animationStateChanged : Subject < AnimationEvent > = new Subject < AnimationEvent > ( ) ;
175+ protected elementRef = inject ( ElementRef ) ;
176+
177+ /**
178+ * To get the element which triggers the animation.
179+ */
180+ protected abstract animationElement : Signal < ElementRef < HTMLElement > > ;
181+
182+ /**
183+ * When the animation is enabled, it is used to judge whether the leave animation is done or not.
184+ * @note Keyframe of notification may have different keyframe names with individual placements, so use array here.
185+ */
186+ protected abstract _animationKeyframeMap : Record < 'enter' | 'leave' , string | string [ ] > ;
187+
188+ /**
189+ * The animation class map for enter and leave.
190+ */
191+ protected abstract _animationClassMap : Record < 'enter' | 'leave' , string > ;
169192
170193 protected options ! : Required < NzMessageDataOptions > ;
171194 protected autoClose ?: boolean ;
172- protected closeTimer ?: ReturnType < typeof setTimeout > ;
173195 protected userAction : boolean = false ;
174196 protected eraseTimer ?: ReturnType < typeof setTimeout > ;
175197 protected eraseTimingStart ?: number ;
@@ -180,24 +202,16 @@ export abstract class NzMNComponent implements OnInit {
180202 if ( this . autoClose ) {
181203 this . clearEraseTimeout ( ) ;
182204 }
183- this . animationStateChanged . complete ( ) ;
184205 } ) ;
185206 }
186207
187208 ngOnInit ( ) : void {
188209 this . options = this . instance . options as Required < NzMessageDataOptions > ;
189210
190211 if ( this . options . nzAnimate ) {
212+ // todo: remove this line in v22.0.0
191213 this . instance . state = 'enter' ;
192- this . animationStateChanged
193- . pipe (
194- filter ( event => event . phaseName === 'done' && event . toState === 'leave' ) ,
195- take ( 1 )
196- )
197- . subscribe ( ( ) => {
198- clearTimeout ( this . closeTimer ) ;
199- this . destroyed . next ( { id : this . instance . messageId , userAction : this . userAction } ) ;
200- } ) ;
214+ this . _startEnterAnimation ( ) ;
201215 }
202216
203217 this . autoClose = this . options . nzDuration > 0 ;
@@ -224,12 +238,9 @@ export abstract class NzMNComponent implements OnInit {
224238 protected destroy ( userAction : boolean = false ) : void {
225239 this . userAction = userAction ;
226240 if ( this . options . nzAnimate ) {
241+ // todo: remove this line in v22.0.0
227242 this . instance . state = 'leave' ;
228- this . cdr . detectChanges ( ) ;
229- this . closeTimer = setTimeout ( ( ) => {
230- this . closeTimer = undefined ;
231- this . destroyed . next ( { id : this . instance . messageId , userAction } ) ;
232- } , 200 ) ;
243+ this . _startLeaveAnimation ( ( ) => this . destroyed . next ( { id : this . instance . messageId , userAction } ) ) ;
233244 } else {
234245 this . destroyed . next ( { id : this . instance . messageId , userAction } ) ;
235246 }
@@ -262,4 +273,40 @@ export abstract class NzMNComponent implements OnInit {
262273 this . eraseTimer = undefined ;
263274 }
264275 }
276+
277+ private _startEnterAnimation ( ) : void {
278+ const element = this . animationElement ( ) . nativeElement ;
279+ element . classList . add ( this . _animationClassMap . enter ) ;
280+
281+ const onAnimationEnd = ( event : AnimationEvent ) : void => {
282+ if ( this . matchAnimationKeyframe ( event , this . _animationKeyframeMap . enter ) ) {
283+ element . removeEventListener ( 'animationend' , onAnimationEnd ) ;
284+ // should remove animation enter class once animation is done
285+ element . classList . remove ( this . _animationClassMap . enter ) ;
286+ }
287+ } ;
288+
289+ element . addEventListener ( 'animationend' , onAnimationEnd ) ;
290+ }
291+
292+ private _startLeaveAnimation ( callback : ( ) => void ) : void {
293+ const element = this . animationElement ( ) . nativeElement ;
294+ element . classList . remove ( this . _animationClassMap . enter ) ;
295+ element . classList . add ( this . _animationClassMap . leave ) ;
296+
297+ const onAnimationEnd = ( event : AnimationEvent ) : void => {
298+ if ( this . matchAnimationKeyframe ( event , this . _animationKeyframeMap . leave ) ) {
299+ element . removeEventListener ( 'animationend' , onAnimationEnd ) ;
300+ callback ( ) ;
301+ }
302+ } ;
303+
304+ element . addEventListener ( 'animationend' , onAnimationEnd ) ;
305+ }
306+
307+ private matchAnimationKeyframe ( event : AnimationEvent , animationName : string | string [ ] ) : boolean {
308+ return typeof animationName === 'string'
309+ ? event . animationName === animationName
310+ : animationName . includes ( event . animationName ) ;
311+ }
265312}
0 commit comments