Skip to content

Commit

Permalink
Merge pull request #23 from ngxpert/fix/issue-21
Browse files Browse the repository at this point in the history
fix: add exit animation once enter animation is done
  • Loading branch information
shhdharmen authored Oct 5, 2024
2 parents d8859e7 + 48c7600 commit 7908817
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,17 @@ export class HotToastContainerComponent {
const comp = this.hotToastComponentList.find((item) => item.toast.id === id);
if (comp) {
comp.close();
this.cdr.markForCheck();
}
} else {
this.hotToastComponentList.forEach((comp) => comp.close());
this.cdr.markForCheck();
}
}

beforeClosed(toast: Toast<unknown>) {
toast.visible = false;
this.cdr.markForCheck();
}

afterClosed(closeToast: HotToastClose) {
Expand Down Expand Up @@ -243,5 +246,6 @@ export class HotToastContainerComponent {

private updateToasts(toast: Toast<unknown>, options?: UpdateToastOptions<unknown>) {
this.toasts = this.toasts.map((t) => ({ ...t, ...(t.id === toast.id && { ...toast, ...options }) }));
this.cdr.markForCheck();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<div
class="hot-toast-bar-base"
#hotToastBarBase
[ngStyle]="toastBarBaseStyles"
[ngStyle]="toastBarBaseStylesSignal()"
[ngClass]="toast.className"
[style.--hot-toast-animation-state]="isManualClose ? 'running' : 'paused'"
[style.--hot-toast-exit-animation-state]="isShowingAllToasts ? 'paused' : 'running'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import {
Output,
Renderer2,
SimpleChanges,
ViewChild, OnChanges, OnInit, AfterViewInit, OnDestroy,
ViewChild,
OnChanges,
OnInit,
AfterViewInit,
OnDestroy,
signal,
ChangeDetectorRef,
} from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
import { AnimatedIconComponent } from '../animated-icon/animated-icon.component';
Expand All @@ -28,7 +34,20 @@ import { animate } from '../../utils';
imports: [NgClass, NgStyle, AnimatedIconComponent, IndicatorComponent, DynamicViewDirective],
})
export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy {
@Input() toast: Toast<unknown>;
private _toast: Toast<unknown>;
@Input()
set toast(value: Toast<unknown>) {
this._toast = value;
const top = value.position.includes('top');
const enterAnimation = `hotToastEnterAnimation${
top ? 'Negative' : 'Positive'
} ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;

this.toastBarBaseStylesSignal.set({ ...value.style, animation: enterAnimation });
}
get toast() {
return this._toast;
}
@Input() offset = 0;
@Input() defaultConfig: ToastConfig;
@Input() toastRef: CreateHotToastRef<unknown>;
Expand All @@ -55,6 +74,7 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI
isManualClose = false;
context: Record<string, unknown>;
toastComponentInjector: Injector;
toastBarBaseStylesSignal = signal({});

private unlisteners: VoidFunction[] = [];
protected softClosed = false;
Expand All @@ -63,6 +83,7 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI
protected injector: Injector,
protected renderer: Renderer2,
protected ngZone: NgZone,
private cdr: ChangeDetectorRef
) {}

get toastBarBaseHeight() {
Expand Down Expand Up @@ -111,20 +132,6 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI
};
}

get toastBarBaseStyles() {
const enterAnimation = `hotToastEnterAnimation${
this.top ? 'Negative' : 'Positive'
} ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;

const exitAnimation = `hotToastExitAnimation${
this.top ? 'Negative' : 'Positive'
} ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1) var(--hot-toast-exit-animation-delay) var(--hot-toast-exit-animation-state)`;

const animation = this.toast.autoClose ? `${enterAnimation}, ${exitAnimation}` : enterAnimation;

return { ...this.toast.style, animation };
}

get isIconString() {
return typeof this.toast.icon === 'string';
}
Expand Down Expand Up @@ -171,16 +178,8 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI
parent: this.toast.injector || this.injector,
});
}
}

ngAfterViewInit() {
const nativeElement = this.toastBarBase.nativeElement;
// Caretaker note: accessing `offsetHeight` triggers the whole layout update.
// Macro tasks (like `setTimeout`) might be executed within the current rendering frame and cause a frame drop.
requestAnimationFrame(() => {
this.height.emit(nativeElement.offsetHeight);
});

// Caretaker note: `animationstart` and `animationend` events are event tasks that trigger change detection.
// We'd want to trigger the change detection only if it's an exit animation.
this.ngZone.runOutsideAngular(() => {
Expand All @@ -190,16 +189,39 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI
// with callback that capture `this`.
this.renderer.listen(nativeElement, 'animationstart', (event: AnimationEvent) => {
if (this.isExitAnimation(event)) {
this.ngZone.run(() => this.beforeClosed.emit());
this.ngZone.run(() => {
this.renderer.setStyle(nativeElement, 'pointer-events', 'none');
this.renderer.setStyle(nativeElement.parentElement, 'pointer-events', 'none');
this.beforeClosed.emit();
});
}
}),
this.renderer.listen(nativeElement, 'animationend', (event: AnimationEvent) => {
if (this.isEnterAnimation(event)) {
this.ngZone.run(() => {
if (this.toast.autoClose) {
const exitAnimation = `hotToastExitAnimation${
this.top ? 'Negative' : 'Positive'
} ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1) var(--hot-toast-exit-animation-delay) var(--hot-toast-exit-animation-state)`;
this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });
}
});
}
if (this.isExitAnimation(event)) {
this.ngZone.run(() => this.afterClosed.emit({ dismissedByAction: this.isManualClose, id: this.toast.id }));
}
})
);
});
}

ngAfterViewInit() {
const nativeElement = this.toastBarBase.nativeElement;
// Caretaker note: accessing `offsetHeight` triggers the whole layout update.
// Macro tasks (like `setTimeout`) might be executed within the current rendering frame and cause a frame drop.
requestAnimationFrame(() => {
this.height.emit(nativeElement.offsetHeight);
});

this.setToastAttributes();
}
Expand Down Expand Up @@ -227,14 +249,12 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI

close() {
this.isManualClose = true;
this.cdr.markForCheck();

const exitAnimation = `hotToastExitAnimation${
this.top ? 'Negative' : 'Positive'
} ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1)`;

const nativeElement = this.toastBarBase.nativeElement;

animate(this.renderer, nativeElement, exitAnimation);
this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });
}

handleMouseEnter() {
Expand All @@ -255,6 +275,10 @@ export class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewI
return ev.animationName.includes('hotToastExitAnimation');
}

private isEnterAnimation(ev: AnimationEvent) {
return ev.animationName.includes('hotToastEnterAnimation');
}

private setToastAttributes() {
const toastAttributes: Record<string, string> = this.toast.attributes;
for (const [key, value] of Object.entries(toastAttributes)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div
class="hot-toast-bar-base"
#hotToastBarBase
[ngStyle]="toastBarBaseStyles"
[ngStyle]="toastBarBaseStylesSignal()"
[ngClass]="toast.className"
[style.--hot-toast-animation-state]="isManualClose ? 'running' : 'paused'"
[style.--hot-toast-exit-animation-state]="isShowingAllToasts ? 'paused' : 'running'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
OnInit,
Output,
Renderer2,
signal,
SimpleChanges,
ViewChild,
} from '@angular/core';
Expand All @@ -36,7 +37,20 @@ import { HotToastGroupItemComponent } from '../hot-toast-group-item/hot-toast-gr
imports: [CommonModule, DynamicViewDirective, IndicatorComponent, AnimatedIconComponent, HotToastGroupItemComponent],
})
export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges, DoCheck {
@Input() toast: Toast<unknown>;
private _toast: Toast<unknown>;
@Input()
set toast(value: Toast<unknown>) {
this._toast = value;
const top = value.position.includes('top');
const enterAnimation = `hotToastEnterAnimation${
top ? 'Negative' : 'Positive'
} ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;

this.toastBarBaseStylesSignal.set({ ...value.style, animation: enterAnimation });
}
get toast() {
return this._toast;
}
@Input() offset = 0;
@Input() defaultConfig: ToastConfig;
@Input() toastRef: CreateHotToastRef<unknown>;
Expand Down Expand Up @@ -77,6 +91,7 @@ export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnCh
context: Record<string, unknown>;
toastComponentInjector: Injector;
isExpanded = false;
toastBarBaseStylesSignal = signal({});

private unlisteners: VoidFunction[] = [];
private softClosed = false;
Expand Down Expand Up @@ -135,20 +150,6 @@ export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnCh
};
}

get toastBarBaseStyles() {
const enterAnimation = `hotToastEnterAnimation${
this.top ? 'Negative' : 'Positive'
} ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;

const exitAnimation = `hotToastExitAnimation${
this.top ? 'Negative' : 'Positive'
} ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1) var(--hot-toast-exit-animation-delay) var(--hot-toast-exit-animation-state)`;

const animation = this.toast.autoClose ? `${enterAnimation}, ${exitAnimation}` : enterAnimation;

return { ...this.toast.style, animation };
}

get isIconString() {
return typeof this.toast.icon === 'string';
}
Expand Down Expand Up @@ -214,16 +215,8 @@ export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnCh
parent: this.toast.injector || this.injector,
});
}
}

ngAfterViewInit() {
const nativeElement = this.toastBarBase.nativeElement;
// Caretaker note: accessing `offsetHeight` triggers the whole layout update.
// Macro tasks (like `setTimeout`) might be executed within the current rendering frame and cause a frame drop.
requestAnimationFrame(() => {
this.height.emit(nativeElement.offsetHeight);
});

// Caretaker note: `animationstart` and `animationend` events are event tasks that trigger change detection.
// We'd want to trigger the change detection only if it's an exit animation.
this.ngZone.runOutsideAngular(() => {
Expand All @@ -233,16 +226,39 @@ export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnCh
// with callback that capture `this`.
this.renderer.listen(nativeElement, 'animationstart', (event: AnimationEvent) => {
if (this.isExitAnimation(event)) {
this.ngZone.run(() => this.beforeClosed.emit());
this.ngZone.run(() => {
this.renderer.setStyle(nativeElement, 'pointer-events', 'none');
this.renderer.setStyle(nativeElement.parentElement, 'pointer-events', 'none');
this.beforeClosed.emit();
});
}
}),
this.renderer.listen(nativeElement, 'animationend', (event: AnimationEvent) => {
if (this.isEnterAnimation(event)) {
this.ngZone.run(() => {
if (this.toast.autoClose) {
const exitAnimation = `hotToastExitAnimation${
this.top ? 'Negative' : 'Positive'
} ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1) var(--hot-toast-exit-animation-delay) var(--hot-toast-exit-animation-state)`;
this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });
}
});
}
if (this.isExitAnimation(event)) {
this.ngZone.run(() => this.afterClosed.emit({ dismissedByAction: this.isManualClose, id: this.toast.id }));
}
})
);
});
}

ngAfterViewInit() {
const nativeElement = this.toastBarBase.nativeElement;
// Caretaker note: accessing `offsetHeight` triggers the whole layout update.
// Macro tasks (like `setTimeout`) might be executed within the current rendering frame and cause a frame drop.
requestAnimationFrame(() => {
this.height.emit(nativeElement.offsetHeight);
});

this.setToastAttributes();
}
Expand Down Expand Up @@ -275,14 +291,13 @@ export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnCh

close() {
this.isManualClose = true;
this.cdr.markForCheck();

const exitAnimation = `hotToastExitAnimation${
this.top ? 'Negative' : 'Positive'
} ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1)`;

const nativeElement = this.toastBarBase.nativeElement;

animate(this.renderer, nativeElement, exitAnimation);
this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });
}

handleMouseEnter() {
Expand All @@ -303,6 +318,10 @@ export class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnCh
return ev.animationName.includes('hotToastExitAnimation');
}

private isEnterAnimation(ev: AnimationEvent) {
return ev.animationName.includes('hotToastEnterAnimation');
}

private setToastAttributes() {
const toastAttributes: Record<string, string> = this.toast.attributes;
for (const [key, value] of Object.entries(toastAttributes)) {
Expand Down

0 comments on commit 7908817

Please sign in to comment.