refactor(core): Replace usages of the Function type for animations

`Function` is usually not recommended as its not specific enough.
This commit is contained in:
Matthieu Riegler
2025-11-01 23:24:34 +01:00
committed by Andrew Scott
parent bfdb652f9e
commit 79b7ac722a
4 changed files with 59 additions and 45 deletions
+15 -21
View File
@@ -52,23 +52,8 @@ const MAX_ANIMATION_TIMEOUT_DEFAULT = 4000;
*/
export type AnimationFunction = (event: AnimationCallbackEvent) => void;
export type AnimationEventFunction = (
el: Element,
value: AnimationFunction,
) => AnimationRemoveFunction;
export type AnimationClassFunction = (
el: Element,
value: Set<string> | null,
resolvers: Function[] | undefined,
) => AnimationRemoveFunction;
export type AnimationRemoveFunction = (removeFn: VoidFunction) => void;
export interface AnimationDetails {
classes: Set<string> | null;
classFns?: Function[];
animateFn: AnimationRemoveFunction;
isEventBinding: boolean;
}
export type RunEnterAnimationFn = () => void;
export type RunLeaveAnimationFn = () => {promise: Promise<void>; resolve: VoidFunction};
export interface LongestAnimation {
animationName: string | undefined;
@@ -76,17 +61,21 @@ export interface LongestAnimation {
duration: number;
}
export interface NodeAnimations {
animateFns: Function[];
export interface EnterNodeAnimations {
animateFns: RunEnterAnimationFn[];
resolvers?: VoidFunction[];
}
export interface LeaveNodeAnimations {
animateFns: RunLeaveAnimationFn[];
resolvers?: VoidFunction[];
}
export interface AnimationLViewData {
// Enter animations that apply to nodes in this view
enter?: Map<number, NodeAnimations>;
enter?: Map<number, EnterNodeAnimations>;
// Leave animations that apply to nodes in this view
leave?: Map<number, NodeAnimations>;
leave?: Map<number, LeaveNodeAnimations>;
// Leave animations that apply to nodes in this view
// We chose to use unknown instead of PromiseSettledResult<void> to avoid requiring the type
@@ -100,3 +89,8 @@ export interface AnimationLViewData {
// the animations in that case.
skipLeaveAnimations?: boolean;
}
/**
* Function that returns the class or class list binded to the animate instruction
*/
export type AnimationClassBindingFn = () => string | string[];
+8 -5
View File
@@ -8,12 +8,12 @@
import {afterNextRender} from '../render3/after_render/hooks';
import {InjectionToken, Injector} from '../di';
import {NodeAnimations} from './interfaces';
import {EnterNodeAnimations} from './interfaces';
export interface AnimationQueue {
queue: Set<Function>;
queue: Set<VoidFunction>;
isScheduled: boolean;
scheduler: Function | null;
scheduler: typeof initializeAnimationQueueScheduler | null;
}
/**
@@ -33,7 +33,10 @@ export const ANIMATION_QUEUE = new InjectionToken<AnimationQueue>(
},
);
export function addToAnimationQueue(injector: Injector, animationFns: Function | Function[]) {
export function addToAnimationQueue(
injector: Injector,
animationFns: VoidFunction | VoidFunction[],
) {
const animationQueue = injector.get(ANIMATION_QUEUE);
if (Array.isArray(animationFns)) {
for (const animateFn of animationFns) {
@@ -71,7 +74,7 @@ export function initializeAnimationQueueScheduler(injector: Injector) {
export function queueEnterAnimations(
injector: Injector,
enterAnimations: Map<number, NodeAnimations>,
enterAnimations: Map<number, EnterNodeAnimations>,
) {
for (const [_, nodeAnimations] of enterAnimations) {
addToAnimationQueue(injector, nodeAnimations.animateFns);
+24 -12
View File
@@ -7,7 +7,15 @@
*/
import {stringify} from '../util/stringify'; // Adjust imports as per actual location
import {ANIMATIONS_DISABLED, LongestAnimation, NodeAnimations} from './interfaces';
import {
ANIMATIONS_DISABLED,
RunEnterAnimationFn,
RunLeaveAnimationFn,
LongestAnimation,
EnterNodeAnimations,
LeaveNodeAnimations,
AnimationClassBindingFn,
} from './interfaces';
import {INJECTOR, LView, DECLARATION_LCONTAINER, ANIMATIONS} from '../render3/interfaces/view';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {Renderer} from '../render3/interfaces/renderer';
@@ -61,7 +69,11 @@ export function assertElementNodes(nativeElement: Element, instruction: string)
* host binding. When removing classes, we need the entire list of animation classes
* added to properly remove them when the longest animation fires.
*/
export function trackEnterClasses(el: HTMLElement, classList: string[], cleanupFns: Function[]) {
export function trackEnterClasses(
el: HTMLElement,
classList: string[],
cleanupFns: VoidFunction[],
) {
const elementData = enterClassMap.get(el);
if (elementData) {
for (const klass of classList) {
@@ -95,7 +107,7 @@ export const noOpAnimationComplete = () => {};
// we remove all of the classes in the case of animation composition via host bindings.
export const enterClassMap = new WeakMap<
HTMLElement,
{classList: string[]; cleanupFns: Function[]}
{classList: string[]; cleanupFns: VoidFunction[]}
>();
export const longestAnimations = new WeakMap<HTMLElement, LongestAnimation>();
@@ -162,23 +174,23 @@ export function trackLeavingNodes(tNode: TNode, el: HTMLElement): void {
/**
* Retrieves the list of specified enter animations from the lView
*/
export function getLViewEnterAnimations(lView: LView): Map<number, NodeAnimations> {
export function getLViewEnterAnimations(lView: LView): Map<number, EnterNodeAnimations> {
const animationData = (lView[ANIMATIONS] ??= {});
return (animationData.enter ??= new Map<number, NodeAnimations>());
return (animationData.enter ??= new Map());
}
/**
* Retrieves the list of specified leave animations from the lView
*/
export function getLViewLeaveAnimations(lView: LView): Map<number, NodeAnimations> {
export function getLViewLeaveAnimations(lView: LView): Map<number, LeaveNodeAnimations> {
const animationData = (lView[ANIMATIONS] ??= {});
return (animationData.leave ??= new Map<number, NodeAnimations>());
return (animationData.leave ??= new Map());
}
/**
* Gets the list of classes from a passed in value
*/
export function getClassListFromValue(value: string | Function | string[]): string[] | null {
export function getClassListFromValue(value: string | AnimationClassBindingFn): string[] | null {
const classes = typeof value === 'function' ? value() : value;
let classList: string[] | null = Array.isArray(classes) ? classes : null;
if (typeof classes === 'string') {
@@ -253,9 +265,9 @@ export function isLongestAnimation(
* @param fn The animation function to be called later
*/
export function addAnimationToLView(
animations: Map<number, NodeAnimations>,
animations: Map<number, EnterNodeAnimations | LeaveNodeAnimations>,
tNode: TNode,
fn: Function,
fn: RunEnterAnimationFn | RunLeaveAnimationFn,
) {
const nodeAnimations = animations.get(tNode.index) ?? {animateFns: []};
nodeAnimations.animateFns.push(fn);
@@ -264,7 +276,7 @@ export function addAnimationToLView(
export function cleanupAfterLeaveAnimations(
resolvers: VoidFunction[] | undefined,
cleanupFns: Function[],
cleanupFns: VoidFunction[],
): void {
if (resolvers) {
for (const fn of resolvers) {
@@ -286,7 +298,7 @@ export function leaveAnimationFunctionCleanup(
tNode: TNode,
nativeElement: HTMLElement,
resolvers: VoidFunction[] | undefined,
cleanupFns: Function[],
cleanupFns: VoidFunction[],
) {
clearLeavingNodes(tNode, nativeElement as HTMLElement);
cleanupAfterLeaveAnimations(resolvers, cleanupFns);
@@ -7,6 +7,7 @@
*/
import {
AnimationClassBindingFn,
AnimationCallbackEvent,
AnimationFunction,
MAX_ANIMATION_TIMEOUT,
@@ -54,7 +55,7 @@ import {initializeAnimationQueueScheduler, queueEnterAnimations} from '../../ani
*
* @codeGenApi
*/
export function ɵɵanimateEnter(value: string | Function): typeof ɵɵanimateEnter {
export function ɵɵanimateEnter(value: string | AnimationClassBindingFn): typeof ɵɵanimateEnter {
performanceMarkFeature('NgAnimateEnter');
if ((typeof ngServerMode !== 'undefined' && ngServerMode) || !areAnimationSupported) {
@@ -86,7 +87,11 @@ export function ɵɵanimateEnter(value: string | Function): typeof ɵɵanimateEn
return ɵɵanimateEnter; // For chaining
}
export function runEnterAnimation(lView: LView, tNode: TNode, value: string | Function) {
export function runEnterAnimation(
lView: LView,
tNode: TNode,
value: string | AnimationClassBindingFn,
): void {
const nativeElement = getNativeByTNode(tNode, lView) as HTMLElement;
ngDevMode && assertElementNodes(nativeElement, 'animate.enter');
@@ -97,7 +102,7 @@ export function runEnterAnimation(lView: LView, tNode: TNode, value: string | Fu
// Retrieve the actual class list from the value. This will resolve any resolver functions from
// bindings.
const activeClasses = getClassListFromValue(value);
const cleanupFns: Function[] = [];
const cleanupFns: VoidFunction[] = [];
// In the case where multiple animations are happening on the element, we need
// to get the longest animation to ensure we don't complete animations early.
@@ -233,7 +238,7 @@ function runEnterAnimationFunction(lView: LView, tNode: TNode, value: AnimationF
*
* @codeGenApi
*/
export function ɵɵanimateLeave(value: string | Function): typeof ɵɵanimateLeave {
export function ɵɵanimateLeave(value: string | AnimationClassBindingFn): typeof ɵɵanimateLeave {
performanceMarkFeature('NgAnimateLeave');
if ((typeof ngServerMode !== 'undefined' && ngServerMode) || !areAnimationSupported) {
@@ -263,7 +268,7 @@ export function ɵɵanimateLeave(value: string | Function): typeof ɵɵanimateLe
function runLeaveAnimations(
lView: LView,
tNode: TNode,
value: string | Function,
value: string | AnimationClassBindingFn,
): {promise: Promise<void>; resolve: VoidFunction} {
const {promise, resolve} = promiseWithResolvers<void>();
const nativeElement = getNativeByTNode(tNode, lView) as Element;
@@ -305,7 +310,7 @@ function animateLeaveClassRunner(
ngZone: NgZone,
) {
cancelAnimationsIfRunning(el, renderer);
const cleanupFns: Function[] = [];
const cleanupFns: VoidFunction[] = [];
const resolvers = getLViewLeaveAnimations(lView).get(tNode.index)?.resolvers;
const handleOutAnimationEnd = (event: AnimationEvent | TransitionEvent | CustomEvent) => {
@@ -408,7 +413,7 @@ function runLeaveAnimationFunction(
ngDevMode && assertElementNodes(nativeElement, 'animate.leave');
const cleanupFns: Function[] = [];
const cleanupFns: VoidFunction[] = [];
const renderer = lView[RENDERER];
const animationsDisabled = areAnimationsDisabled(lView);
const ngZone = lView[INJECTOR]!.get(NgZone);