mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
This reverts commit 1cb16fddb5.
PR Close #64189
This commit is contained in:
@@ -18,27 +18,6 @@ export const ANIMATIONS_DISABLED = new InjectionToken<boolean>(
|
||||
},
|
||||
);
|
||||
|
||||
export interface AnimationQueue {
|
||||
queue: Set<Function>;
|
||||
isScheduled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A [DI token](api/core/InjectionToken) for the queue of all animations.
|
||||
*/
|
||||
export const ANIMATION_QUEUE = new InjectionToken<AnimationQueue>(
|
||||
typeof ngDevMode !== 'undefined' && ngDevMode ? 'AnimationQueue' : '',
|
||||
{
|
||||
providedIn: 'root',
|
||||
factory: () => {
|
||||
return {
|
||||
queue: new Set(),
|
||||
isScheduled: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* The event type for when `animate.enter` and `animate.leave` are used with function
|
||||
* callbacks.
|
||||
@@ -99,10 +78,10 @@ export interface LongestAnimation {
|
||||
|
||||
export interface AnimationLViewData {
|
||||
// Enter animations that apply to nodes in this view
|
||||
enter?: Map<number, Function[]>;
|
||||
enter?: Function[];
|
||||
|
||||
// Leave animations that apply to nodes in this view
|
||||
leave?: Map<number, Function[]>;
|
||||
leave?: (() => Promise<void>)[];
|
||||
|
||||
// Leave animations that apply to nodes in this view
|
||||
// We chose to use unknown instead of PromiseSettledResult<void> to avoid requiring the type
|
||||
|
||||
@@ -162,17 +162,17 @@ 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, Function[]> {
|
||||
export function getLViewEnterAnimations(lView: LView): Function[] {
|
||||
const animationData = (lView[ANIMATIONS] ??= {});
|
||||
return (animationData.enter ??= new Map<number, Function[]>());
|
||||
return (animationData.enter ??= []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of specified leave animations from the lView
|
||||
*/
|
||||
export function getLViewLeaveAnimations(lView: LView): Map<number, Function[]> {
|
||||
export function getLViewLeaveAnimations(lView: LView): Function[] {
|
||||
const animationData = (lView[ANIMATIONS] ??= {});
|
||||
return (animationData.leave ??= new Map<number, Function[]>());
|
||||
return (animationData.leave ??= []);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,18 +244,8 @@ export function isLongestAnimation(
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a given animation function in the LView's animation map for later execution
|
||||
*
|
||||
* @param animations Either the enter or leave animation map from the LView
|
||||
* @param tNode The TNode the animation is associated with
|
||||
* @param fn The animation function to be called later
|
||||
* Determines if a given tNode is a content projection root node.
|
||||
*/
|
||||
export function addAnimationToLView(
|
||||
animations: Map<number, Function[]>,
|
||||
tNode: TNode,
|
||||
fn: Function,
|
||||
) {
|
||||
const animationFns = animations.get(tNode.index) ?? [];
|
||||
animationFns.push(fn);
|
||||
animations.set(tNode.index, animationFns);
|
||||
export function isTNodeContentProjectionRoot(tNode: TNode): boolean {
|
||||
return Array.isArray(tNode.projection);
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ANIMATION_QUEUE,
|
||||
AnimationCallbackEvent,
|
||||
AnimationFunction,
|
||||
MAX_ANIMATION_TIMEOUT,
|
||||
} from '../../animation/interfaces';
|
||||
import {getLView, getCurrentTNode} from '../state';
|
||||
import {RENDERER, INJECTOR, CONTEXT, LView, ANIMATIONS} from '../interfaces/view';
|
||||
import {RENDERER, INJECTOR, CONTEXT, LView} from '../interfaces/view';
|
||||
import {getNativeByTNode} from '../util/view_utils';
|
||||
import {performanceMarkFeature} from '../../util/performance';
|
||||
import {Renderer} from '../interfaces/renderer';
|
||||
@@ -21,11 +20,8 @@ import {NgZone} from '../../zone';
|
||||
import {determineLongestAnimation, allLeavingAnimations} from '../../animation/longest_animation';
|
||||
import {TNode} from '../interfaces/node';
|
||||
import {promiseWithResolvers} from '../../util/promise_with_resolvers';
|
||||
import {Injector} from '../../di';
|
||||
import {afterEveryRender} from '../after_render/hooks';
|
||||
|
||||
import {
|
||||
addAnimationToLView,
|
||||
areAnimationsDisabled,
|
||||
areAnimationSupported,
|
||||
assertAnimationTypes,
|
||||
@@ -70,16 +66,12 @@ export function ɵɵanimateEnter(value: string | Function): typeof ɵɵanimateEn
|
||||
|
||||
cancelLeavingNodes(tNode, lView);
|
||||
|
||||
addAnimationToLView(getLViewEnterAnimations(lView), tNode, () =>
|
||||
runEnterAnimation(lView, tNode, value),
|
||||
);
|
||||
|
||||
queueEnterAnimations(lView);
|
||||
getLViewEnterAnimations(lView).push(() => runEnterAnimation(lView, tNode, value));
|
||||
|
||||
return ɵɵanimateEnter; // For chaining
|
||||
}
|
||||
|
||||
export function runEnterAnimation(lView: LView, tNode: TNode, value: string | Function) {
|
||||
export function runEnterAnimation(lView: LView, tNode: TNode, value: string | Function): void {
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as HTMLElement;
|
||||
|
||||
ngDevMode && assertElementNodes(nativeElement, 'animate.enter');
|
||||
@@ -102,7 +94,7 @@ export function runEnterAnimation(lView: LView, tNode: TNode, value: string | Fu
|
||||
|
||||
const eventName = event instanceof AnimationEvent ? 'animationend' : 'transitionend';
|
||||
ngZone.runOutsideAngular(() => {
|
||||
renderer.listen(nativeElement, eventName, handleEnterAnimationEnd);
|
||||
cleanupFns.push(renderer.listen(nativeElement, eventName, handleEnterAnimationEnd));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -126,7 +118,6 @@ export function runEnterAnimation(lView: LView, tNode: TNode, value: string | Fu
|
||||
for (const klass of activeClasses) {
|
||||
renderer.addClass(nativeElement, klass);
|
||||
}
|
||||
|
||||
// In the case that the classes added have no animations, we need to remove
|
||||
// the classes right away. This could happen because someone is intentionally
|
||||
// preventing an animation via selector specificity.
|
||||
@@ -191,11 +182,7 @@ export function ɵɵanimateEnterListener(value: AnimationFunction): typeof ɵɵa
|
||||
|
||||
cancelLeavingNodes(tNode, lView);
|
||||
|
||||
addAnimationToLView(getLViewEnterAnimations(lView), tNode, () =>
|
||||
runEnterAnimationFunction(lView, tNode, value),
|
||||
);
|
||||
|
||||
queueEnterAnimations(lView);
|
||||
getLViewEnterAnimations(lView).push(() => runEnterAnimationFunction(lView, tNode, value));
|
||||
|
||||
return ɵɵanimateEnterListener;
|
||||
}
|
||||
@@ -203,7 +190,7 @@ export function ɵɵanimateEnterListener(value: AnimationFunction): typeof ɵɵa
|
||||
/**
|
||||
* runs enter animations when a custom function is provided
|
||||
*/
|
||||
function runEnterAnimationFunction(lView: LView, tNode: TNode, value: AnimationFunction): void {
|
||||
function runEnterAnimationFunction(lView: LView, tNode: TNode, value: AnimationFunction) {
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as HTMLElement;
|
||||
ngDevMode && assertElementNodes(nativeElement, 'animate.enter');
|
||||
|
||||
@@ -237,16 +224,19 @@ export function ɵɵanimateLeave(value: string | Function): typeof ɵɵanimateLe
|
||||
|
||||
const tNode = getCurrentTNode()!;
|
||||
|
||||
addAnimationToLView(getLViewLeaveAnimations(lView), tNode, () =>
|
||||
runLeaveAnimations(lView, tNode, value),
|
||||
getLViewLeaveAnimations(lView).push(() =>
|
||||
runLeaveAnimations(lView, tNode, value, animationsDisabled),
|
||||
);
|
||||
|
||||
enableAnimationQueueScheduler(lView[INJECTOR]);
|
||||
|
||||
return ɵɵanimateLeave; // For chaining
|
||||
}
|
||||
|
||||
function runLeaveAnimations(lView: LView, tNode: TNode, value: string | Function): Promise<void> {
|
||||
function runLeaveAnimations(
|
||||
lView: LView,
|
||||
tNode: TNode,
|
||||
value: string | Function,
|
||||
animationsDisabled: boolean,
|
||||
): Promise<void> {
|
||||
const {promise, resolve} = promiseWithResolvers<void>();
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as Element;
|
||||
|
||||
@@ -263,6 +253,7 @@ function runLeaveAnimations(lView: LView, tNode: TNode, value: string | Function
|
||||
tNode,
|
||||
activeClasses,
|
||||
renderer,
|
||||
animationsDisabled,
|
||||
ngZone,
|
||||
resolve,
|
||||
);
|
||||
@@ -282,11 +273,17 @@ function animateLeaveClassRunner(
|
||||
tNode: TNode,
|
||||
classList: string[],
|
||||
renderer: Renderer,
|
||||
animationsDisabled: boolean,
|
||||
ngZone: NgZone,
|
||||
resolver: VoidFunction,
|
||||
) {
|
||||
if (animationsDisabled) {
|
||||
longestAnimations.delete(el);
|
||||
resolver();
|
||||
return;
|
||||
}
|
||||
|
||||
cancelAnimationsIfRunning(el, renderer);
|
||||
const cleanupFns: Function[] = [];
|
||||
|
||||
const handleOutAnimationEnd = (event: AnimationEvent | TransitionEvent | CustomEvent) => {
|
||||
// this early exit case is to prevent issues with bubbling events that are from child element animations
|
||||
@@ -310,14 +307,11 @@ function animateLeaveClassRunner(
|
||||
}
|
||||
}
|
||||
resolver();
|
||||
for (const fn of cleanupFns) {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
ngZone.runOutsideAngular(() => {
|
||||
cleanupFns.push(renderer.listen(el, 'animationend', handleOutAnimationEnd));
|
||||
cleanupFns.push(renderer.listen(el, 'transitionend', handleOutAnimationEnd));
|
||||
renderer.listen(el, 'animationend', handleOutAnimationEnd);
|
||||
renderer.listen(el, 'transitionend', handleOutAnimationEnd);
|
||||
});
|
||||
trackLeavingNodes(tNode, el);
|
||||
for (const item of classList) {
|
||||
@@ -332,9 +326,6 @@ function animateLeaveClassRunner(
|
||||
if (!longestAnimations.has(el)) {
|
||||
clearLeavingNodes(tNode, el);
|
||||
resolver();
|
||||
for (const fn of cleanupFns) {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -368,11 +359,7 @@ export function ɵɵanimateLeaveListener(value: AnimationFunction): typeof ɵɵa
|
||||
const tNode = getCurrentTNode()!;
|
||||
allLeavingAnimations.add(lView);
|
||||
|
||||
addAnimationToLView(getLViewLeaveAnimations(lView), tNode, () =>
|
||||
runLeaveAnimationFunction(lView, tNode, value),
|
||||
);
|
||||
|
||||
enableAnimationQueueScheduler(lView[INJECTOR]);
|
||||
getLViewLeaveAnimations(lView).push(() => runLeaveAnimationFunction(lView, tNode, value));
|
||||
|
||||
return ɵɵanimateLeaveListener; // For chaining
|
||||
}
|
||||
@@ -429,38 +416,3 @@ function runLeaveAnimationFunction(
|
||||
// Ensure cleanup if the LView is destroyed before the animation runs.
|
||||
return promise;
|
||||
}
|
||||
|
||||
function queueEnterAnimations(lView: LView) {
|
||||
enableAnimationQueueScheduler(lView[INJECTOR]);
|
||||
const enterAnimations = lView[ANIMATIONS]?.enter;
|
||||
if (enterAnimations) {
|
||||
const animationQueue = lView[INJECTOR].get(ANIMATION_QUEUE);
|
||||
for (const [_, animateFns] of enterAnimations) {
|
||||
for (const animateFn of animateFns) {
|
||||
animationQueue.queue.add(animateFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enableAnimationQueueScheduler(injector: Injector) {
|
||||
const animationQueue = injector.get(ANIMATION_QUEUE);
|
||||
// We only need to schedule the animation queue runner once per application.
|
||||
if (!animationQueue.isScheduled) {
|
||||
afterEveryRender(
|
||||
() => {
|
||||
runQueuedAnimations(injector);
|
||||
},
|
||||
{injector},
|
||||
);
|
||||
animationQueue.isScheduled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function runQueuedAnimations(injector: Injector) {
|
||||
const animationQueue = injector.get(ANIMATION_QUEUE);
|
||||
for (let animateFn of animationQueue.queue) {
|
||||
animateFn();
|
||||
}
|
||||
animationQueue.queue.clear();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {ComponentTemplate, HostBindingsFunction, RenderFlags} from '../interface
|
||||
import {
|
||||
CONTEXT,
|
||||
EFFECTS_TO_SCHEDULE,
|
||||
ANIMATIONS,
|
||||
ENVIRONMENT,
|
||||
FLAGS,
|
||||
InitPhaseState,
|
||||
@@ -231,6 +232,7 @@ export function refreshView<T>(
|
||||
if (templateFn !== null) {
|
||||
executeTemplate(tView, lView, templateFn, RenderFlags.Update, context);
|
||||
}
|
||||
runEnterAnimations(lView);
|
||||
|
||||
const hooksInitPhaseCompleted =
|
||||
(flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted;
|
||||
@@ -372,6 +374,16 @@ export function refreshView<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function runEnterAnimations(lView: LView) {
|
||||
const animationData = lView[ANIMATIONS];
|
||||
if (animationData?.enter) {
|
||||
for (const animateFn of animationData.enter) {
|
||||
animateFn();
|
||||
}
|
||||
animationData.enter = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes over embedded views (ones created through ViewContainerRef APIs) and refreshes
|
||||
* them by executing an associated template function.
|
||||
|
||||
@@ -75,15 +75,12 @@ import {
|
||||
TVIEW,
|
||||
TView,
|
||||
TViewType,
|
||||
INJECTOR,
|
||||
} from './interfaces/view';
|
||||
import {assertTNodeType} from './node_assert';
|
||||
import {profiler} from './profiler';
|
||||
import {ProfilerEvent} from './profiler_types';
|
||||
import {getLViewParent, getNativeByTNode, unwrapRNode} from './util/view_utils';
|
||||
import {allLeavingAnimations} from '../animation/longest_animation';
|
||||
import {ANIMATION_QUEUE} from '../animation/interfaces';
|
||||
import {Injector} from '../di';
|
||||
|
||||
const enum WalkTNodeTreeAction {
|
||||
/** node create in the native environment. Run on initial creation. */
|
||||
@@ -102,21 +99,6 @@ const enum WalkTNodeTreeAction {
|
||||
Destroy = 3,
|
||||
}
|
||||
|
||||
function maybeQueueEnterAnimation(
|
||||
parentLView: LView | undefined,
|
||||
parent: RElement | null,
|
||||
tNode: TNode,
|
||||
injector: Injector,
|
||||
): void {
|
||||
const enterAnimations = parentLView?.[ANIMATIONS]?.enter;
|
||||
if (parent !== null && enterAnimations && enterAnimations.has(tNode.index)) {
|
||||
const animationQueue = injector.get(ANIMATION_QUEUE);
|
||||
for (const animateFn of enterAnimations.get(tNode.index)!) {
|
||||
animationQueue.queue.add(animateFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
|
||||
* being passed as an argument.
|
||||
@@ -124,10 +106,8 @@ function maybeQueueEnterAnimation(
|
||||
function applyToElementOrContainer(
|
||||
action: WalkTNodeTreeAction,
|
||||
renderer: Renderer,
|
||||
injector: Injector,
|
||||
parent: RElement | null,
|
||||
lNodeToHandle: RNode | LContainer | LView,
|
||||
tNode: TNode,
|
||||
beforeNode?: RNode | null,
|
||||
parentLView?: LView,
|
||||
) {
|
||||
@@ -149,50 +129,33 @@ function applyToElementOrContainer(
|
||||
lNodeToHandle = lNodeToHandle[HOST]!;
|
||||
}
|
||||
const rNode: RNode = unwrapRNode(lNodeToHandle);
|
||||
|
||||
if (action === WalkTNodeTreeAction.Create && parent !== null) {
|
||||
maybeQueueEnterAnimation(parentLView, parent, tNode, injector);
|
||||
if (beforeNode == null) {
|
||||
nativeAppendChild(renderer, parent, rNode);
|
||||
} else {
|
||||
nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true);
|
||||
}
|
||||
} else if (action === WalkTNodeTreeAction.Insert && parent !== null) {
|
||||
maybeQueueEnterAnimation(parentLView, parent, tNode, injector);
|
||||
nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true);
|
||||
} else if (action === WalkTNodeTreeAction.Detach) {
|
||||
runLeaveAnimationsWithCallback(
|
||||
parentLView,
|
||||
tNode,
|
||||
injector,
|
||||
(nodeHasLeaveAnimations: boolean) => {
|
||||
// the nodeHasLeaveAnimations indicates to the renderer that the element needs to
|
||||
// be removed synchronously and sets the requireSynchronousElementRemoval flag in
|
||||
// the renderer.
|
||||
nativeRemoveNode(renderer, rNode, isComponent, nodeHasLeaveAnimations);
|
||||
},
|
||||
);
|
||||
runLeaveAnimationsWithCallback(parentLView, (nodeHasLeaveAnimations: boolean) => {
|
||||
// the nodeHasLeaveAnimations indicates to the renderer that the element needs to
|
||||
// be removed synchronously and sets the requireSynchronousElementRemoval flag in
|
||||
// the renderer.
|
||||
nativeRemoveNode(renderer, rNode, isComponent, nodeHasLeaveAnimations);
|
||||
});
|
||||
} else if (action === WalkTNodeTreeAction.Destroy) {
|
||||
runLeaveAnimationsWithCallback(parentLView, tNode, injector, () => {
|
||||
runLeaveAnimationsWithCallback(parentLView, () => {
|
||||
renderer.destroyNode!(rNode);
|
||||
});
|
||||
}
|
||||
if (lContainer != null) {
|
||||
applyContainer(renderer, action, injector, lContainer, tNode, parent, beforeNode);
|
||||
applyContainer(renderer, action, lContainer, parent, beforeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addToAnimationQueue(injector: Injector, animationFns: Function | Function[]) {
|
||||
const animationQueue = injector.get(ANIMATION_QUEUE);
|
||||
if (Array.isArray(animationFns)) {
|
||||
for (const animateFn of animationFns) {
|
||||
animationQueue.queue.add(animateFn);
|
||||
}
|
||||
} else {
|
||||
animationQueue.queue.add(animationFns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all DOM elements associated with a view.
|
||||
*
|
||||
@@ -390,42 +353,34 @@ function cleanUpView(tView: TView, lView: LView): void {
|
||||
}
|
||||
}
|
||||
|
||||
function runLeaveAnimationsWithCallback(
|
||||
lView: LView | undefined,
|
||||
tNode: TNode,
|
||||
injector: Injector,
|
||||
callback: Function,
|
||||
) {
|
||||
const animations = lView?.[ANIMATIONS];
|
||||
if (animations == null) return callback(false);
|
||||
|
||||
// this is solely for move operations to prevent leave animations from running
|
||||
// on the moved nodes, which would have deleted the node.
|
||||
if (animations.skipLeaveAnimations) {
|
||||
animations.skipLeaveAnimations = false;
|
||||
return callback(false);
|
||||
}
|
||||
|
||||
addToAnimationQueue(injector, () => {
|
||||
const leaveAnimationMap = animations.leave!;
|
||||
const leaveAnimations = leaveAnimationMap.get(tNode.index);
|
||||
const runningAnimations = [];
|
||||
if (leaveAnimations) {
|
||||
for (let index = 0; index < leaveAnimations.length; index++) {
|
||||
const animationFn = leaveAnimations[index];
|
||||
runningAnimations.push(animationFn());
|
||||
}
|
||||
}
|
||||
animations.running = Promise.allSettled(runningAnimations);
|
||||
runAfterLeaveAnimations(lView!, callback);
|
||||
});
|
||||
function hasLeaveAnimations(lView: LView | undefined): boolean {
|
||||
return lView !== undefined && lView[ANIMATIONS] !== null && lView[ANIMATIONS].leave !== undefined;
|
||||
}
|
||||
|
||||
function runAfterLeaveAnimations(lView: LView, callback: Function) {
|
||||
const runningAnimations = lView[ANIMATIONS]?.running;
|
||||
if (runningAnimations) {
|
||||
runningAnimations.then(() => {
|
||||
lView[ANIMATIONS]!.running = undefined;
|
||||
function runLeaveAnimationsWithCallback(lView: LView | undefined, callback: Function) {
|
||||
if (lView && lView[ANIMATIONS] && lView[ANIMATIONS].leave) {
|
||||
if (lView[ANIMATIONS].skipLeaveAnimations) {
|
||||
lView[ANIMATIONS].skipLeaveAnimations = false;
|
||||
} else {
|
||||
const leaveAnimations = lView[ANIMATIONS].leave;
|
||||
const runningAnimations = [];
|
||||
for (let index = 0; index < leaveAnimations.length; index++) {
|
||||
const animateFn = leaveAnimations[index];
|
||||
runningAnimations.push(animateFn());
|
||||
}
|
||||
lView[ANIMATIONS].running = Promise.allSettled(runningAnimations);
|
||||
lView[ANIMATIONS].leave = undefined;
|
||||
}
|
||||
}
|
||||
runAfterLeaveAnimations(lView, callback);
|
||||
}
|
||||
|
||||
function runAfterLeaveAnimations(lView: LView | undefined, callback: Function) {
|
||||
if (lView && lView[ANIMATIONS] && lView[ANIMATIONS].running) {
|
||||
lView[ANIMATIONS].running.then(() => {
|
||||
if (lView[ANIMATIONS] && lView[ANIMATIONS].running) {
|
||||
lView[ANIMATIONS].running = undefined;
|
||||
}
|
||||
allLeavingAnimations.delete(lView);
|
||||
callback(true);
|
||||
});
|
||||
@@ -821,7 +776,6 @@ function applyNodes(
|
||||
) {
|
||||
while (tNode != null) {
|
||||
ngDevMode && assertTNodeForLView(tNode, lView);
|
||||
const injector = lView[INJECTOR];
|
||||
|
||||
// Let declarations don't have corresponding DOM nodes so we skip over them.
|
||||
if (tNode.type === TNodeType.LetDeclaration) {
|
||||
@@ -848,10 +802,8 @@ function applyNodes(
|
||||
applyToElementOrContainer(
|
||||
action,
|
||||
renderer,
|
||||
injector,
|
||||
parentRElement,
|
||||
rawSlotValue,
|
||||
tNode,
|
||||
beforeNode,
|
||||
lView,
|
||||
);
|
||||
@@ -859,24 +811,13 @@ function applyNodes(
|
||||
const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView);
|
||||
let rNode: RNode | null;
|
||||
while ((rNode = nextRNode())) {
|
||||
applyToElementOrContainer(
|
||||
action,
|
||||
renderer,
|
||||
injector,
|
||||
parentRElement,
|
||||
rNode,
|
||||
tNode,
|
||||
beforeNode,
|
||||
lView,
|
||||
);
|
||||
applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode, lView);
|
||||
}
|
||||
applyToElementOrContainer(
|
||||
action,
|
||||
renderer,
|
||||
injector,
|
||||
parentRElement,
|
||||
rawSlotValue,
|
||||
tNode,
|
||||
beforeNode,
|
||||
lView,
|
||||
);
|
||||
@@ -894,10 +835,8 @@ function applyNodes(
|
||||
applyToElementOrContainer(
|
||||
action,
|
||||
renderer,
|
||||
injector,
|
||||
parentRElement,
|
||||
rawSlotValue,
|
||||
tNode,
|
||||
beforeNode,
|
||||
lView,
|
||||
);
|
||||
@@ -1017,16 +956,7 @@ function applyProjectionRecursive(
|
||||
// This should be refactored and cleaned up.
|
||||
for (let i = 0; i < nodeToProjectOrRNodes.length; i++) {
|
||||
const rNode = nodeToProjectOrRNodes[i];
|
||||
applyToElementOrContainer(
|
||||
action,
|
||||
renderer,
|
||||
lView[INJECTOR],
|
||||
parentRElement,
|
||||
rNode,
|
||||
tProjectionNode,
|
||||
beforeNode,
|
||||
lView,
|
||||
);
|
||||
applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode, lView);
|
||||
}
|
||||
} else {
|
||||
let nodeToProject: TNode | null = nodeToProjectOrRNodes;
|
||||
@@ -1064,9 +994,7 @@ function applyProjectionRecursive(
|
||||
function applyContainer(
|
||||
renderer: Renderer,
|
||||
action: WalkTNodeTreeAction,
|
||||
injector: Injector,
|
||||
lContainer: LContainer,
|
||||
tNode: TNode,
|
||||
parentRElement: RElement | null,
|
||||
beforeNode: RNode | null | undefined,
|
||||
) {
|
||||
@@ -1084,15 +1012,7 @@ function applyContainer(
|
||||
// don't see a reason why they should be different, but they are.
|
||||
//
|
||||
// If they are we need to process the second anchor as well.
|
||||
applyToElementOrContainer(
|
||||
action,
|
||||
renderer,
|
||||
injector,
|
||||
parentRElement,
|
||||
anchor,
|
||||
tNode,
|
||||
beforeNode,
|
||||
);
|
||||
applyToElementOrContainer(action, renderer, parentRElement, anchor, beforeNode);
|
||||
}
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
||||
const lView = lContainer[i] as LView;
|
||||
|
||||
@@ -400,7 +400,9 @@ describe('Animation', () => {
|
||||
expect(fixture.nativeElement.outerHTML).not.toContain('class="fade"');
|
||||
cmp.show.set(false);
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
expect(cmp.show()).toBeFalsy();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.outerHTML).toContain('class="fade"');
|
||||
fixture.detectChanges();
|
||||
fadeCmp.nativeElement.dispatchEvent(
|
||||
@@ -441,7 +443,9 @@ describe('Animation', () => {
|
||||
expect(fixture.nativeElement.outerHTML).not.toContain('class="fade"');
|
||||
cmp.show.set(false);
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
expect(cmp.show()).toBeFalsy();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.outerHTML).toContain('class="fade"');
|
||||
fixture.detectChanges();
|
||||
fadeCmp.nativeElement.dispatchEvent(
|
||||
@@ -805,25 +809,6 @@ describe('Animation', () => {
|
||||
}
|
||||
`;
|
||||
|
||||
it('should apply classes on entry when animation is specified with no control flow', fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
styles: styles,
|
||||
template: '<div><p animate.enter="slide-in" #el>I should slide in</p></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
class TestComponent {
|
||||
@ViewChild('el', {read: ElementRef}) el!: ElementRef<HTMLParagraphElement>;
|
||||
}
|
||||
TestBed.configureTestingModule({animationsEnabled: true});
|
||||
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
const cmp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
expect(cmp.el.nativeElement.outerHTML).toContain('class="slide-in"');
|
||||
}));
|
||||
|
||||
it('should apply classes on entry when animation is specified', fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
|
||||
@@ -12,14 +12,9 @@ import {
|
||||
Directive,
|
||||
effect,
|
||||
EventEmitter,
|
||||
inject,
|
||||
input,
|
||||
Output,
|
||||
signal,
|
||||
TemplateRef,
|
||||
viewChild,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import {SIGNAL} from '../../../primitives/signals';
|
||||
import {fakeAsync, TestBed, tick} from '../../../testing';
|
||||
@@ -366,158 +361,5 @@ describe('signal inputs', () => {
|
||||
tick();
|
||||
expect(childCmp.nativeElement.className).not.toContain('fade-in');
|
||||
}));
|
||||
|
||||
it('should support content projection', fakeAsync(() => {
|
||||
const animateStyles = `
|
||||
.fade-in {
|
||||
animation: fade 1ms forwards;
|
||||
}
|
||||
.fade-out {
|
||||
animation: fade 2ms reverse;
|
||||
}
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
@Component({
|
||||
selector: 'app-container',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
template: `
|
||||
<button (click)="toggle()">{{ state() ? 'Leave' : 'Enter' }}</button>
|
||||
|
||||
<ng-template #template>
|
||||
<ng-content></ng-content>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class Container {
|
||||
private readonly viewContainerRef = inject(ViewContainerRef);
|
||||
|
||||
protected readonly template = viewChild.required<TemplateRef<any>>('template');
|
||||
|
||||
protected state = signal<boolean>(false);
|
||||
|
||||
protected toggle() {
|
||||
this.state() ? this.destroy() : this.create();
|
||||
}
|
||||
|
||||
protected create() {
|
||||
this.state.set(true);
|
||||
this.viewContainerRef.createEmbeddedView(this.template());
|
||||
}
|
||||
|
||||
protected destroy() {
|
||||
this.state.set(false);
|
||||
this.viewContainerRef.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-content',
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
styles: animateStyles,
|
||||
host: {
|
||||
'animate.leave': 'fade-out',
|
||||
'animate.enter': 'fade-in',
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
class Content {}
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
template: `
|
||||
<app-container>
|
||||
<app-content>This content will be animated on enter/leave</app-content>
|
||||
</app-container>
|
||||
`,
|
||||
imports: [Container, Content],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
class TestComponent {}
|
||||
TestBed.configureTestingModule({animationsEnabled: true});
|
||||
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('app-content')).toBeNull();
|
||||
expect(button).not.toBeNull();
|
||||
|
||||
button.click();
|
||||
|
||||
// show first time
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
const enterAppContent = fixture.nativeElement.querySelector('app-content');
|
||||
|
||||
expect(enterAppContent).not.toBeNull();
|
||||
expect(enterAppContent.className).toEqual('fade-in');
|
||||
enterAppContent.dispatchEvent(new AnimationEvent('animationstart'));
|
||||
enterAppContent.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
|
||||
tick();
|
||||
expect(enterAppContent.className).not.toEqual('fade-in');
|
||||
expect(fixture.debugElement.query(By.css('app-content'))).not.toBeNull();
|
||||
|
||||
button.click();
|
||||
|
||||
// hide first time
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
const leaveAppContent = fixture.nativeElement.querySelector('app-content');
|
||||
|
||||
expect(fixture.nativeElement.outerHTML).toContain('app-content class="fade-out"');
|
||||
expect(leaveAppContent.className).toEqual('fade-out');
|
||||
leaveAppContent.dispatchEvent(new AnimationEvent('animationstart'));
|
||||
leaveAppContent.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
|
||||
expect(fixture.debugElement.query(By.css('app-content'))).toBeNull();
|
||||
|
||||
button.click();
|
||||
|
||||
// show second time
|
||||
fixture.detectChanges();
|
||||
fixture.changeDetectorRef.markForCheck();
|
||||
tickAnimationFrames(1);
|
||||
const enterAppContent2 = fixture.nativeElement.querySelector('app-content');
|
||||
|
||||
expect(enterAppContent2).not.toBeNull();
|
||||
expect(enterAppContent2.className).toEqual('fade-in');
|
||||
const fadeInEl = fixture.nativeElement.querySelector('.fade-in');
|
||||
fadeInEl.dispatchEvent(new AnimationEvent('animationstart'));
|
||||
fadeInEl.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
|
||||
expect(fixture.nativeElement.querySelector('app-content').className).not.toEqual('fade-in');
|
||||
expect(fixture.debugElement.query(By.css('app-content'))).not.toBeNull();
|
||||
|
||||
button.click();
|
||||
|
||||
// hide second time
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
const leaveAppContent2 = fixture.nativeElement.querySelector('app-content');
|
||||
|
||||
expect(fixture.nativeElement.outerHTML).toContain('app-content class="fade-out"');
|
||||
expect(leaveAppContent2.className).toEqual('fade-out');
|
||||
leaveAppContent2.dispatchEvent(new AnimationEvent('animationstart'));
|
||||
leaveAppContent2.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
|
||||
fixture.detectChanges();
|
||||
tickAnimationFrames(1);
|
||||
|
||||
expect(fixture.debugElement.query(By.css('app-content'))).toBeNull();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,7 +48,6 @@ import {ComponentFixture, TestBed, TestComponentRenderer} from '../../testing';
|
||||
import {clearTranslations, loadTranslations} from '@angular/localize';
|
||||
import {By, DomSanitizer} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/private/testing/matchers';
|
||||
import {ANIMATION_QUEUE} from '../../src/animation/interfaces';
|
||||
|
||||
describe('ViewContainerRef', () => {
|
||||
/**
|
||||
@@ -1414,7 +1413,6 @@ describe('ViewContainerRef', () => {
|
||||
{provide: Sanitizer, useValue: TestBed.inject(DomSanitizer)},
|
||||
{provide: ErrorHandler, useValue: TestBed.inject(ErrorHandler)},
|
||||
{provide: RendererFactory2, useValue: TestBed.inject(RendererFactory2)},
|
||||
{provide: ANIMATION_QUEUE, useValue: TestBed.inject(ANIMATION_QUEUE)},
|
||||
],
|
||||
})
|
||||
class MyAppModule {}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_MODULE_TYPE",
|
||||
"ANIMATION_PREFIX",
|
||||
"ANIMATION_QUEUE",
|
||||
"ANY_STATE",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
@@ -322,7 +321,6 @@
|
||||
"addAfterRenderSequencesForView",
|
||||
"addClass",
|
||||
"addServerStyles",
|
||||
"addToAnimationQueue",
|
||||
"addToEndOfViewTree",
|
||||
"allLeavingAnimations",
|
||||
"allocExpando",
|
||||
@@ -682,7 +680,6 @@
|
||||
"markViewForRefresh",
|
||||
"markedFeatures",
|
||||
"matchTemplateAttribute",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeWrapInNotSelector",
|
||||
"mergeHostAttribute",
|
||||
@@ -782,6 +779,7 @@
|
||||
"roundOffset",
|
||||
"runAfterLeaveAnimations",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
"lazy": [
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_INITIALIZER",
|
||||
@@ -292,7 +291,6 @@
|
||||
"addAfterRenderSequencesForView",
|
||||
"addDepsToRegistry",
|
||||
"addLViewToLContainer",
|
||||
"addToAnimationQueue",
|
||||
"addToArray",
|
||||
"addToEndOfViewTree",
|
||||
"addViewToDOM",
|
||||
@@ -607,7 +605,6 @@
|
||||
"markViewForRefresh",
|
||||
"markedFeatures",
|
||||
"matchTemplateAttribute",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeWrapInNotSelector",
|
||||
"mergeHostAttribute",
|
||||
@@ -677,6 +674,7 @@
|
||||
"retrieveHydrationInfo",
|
||||
"runAfterLeaveAnimations",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"main": [
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -347,7 +346,6 @@
|
||||
"addAfterRenderSequencesForView",
|
||||
"addLViewToLContainer",
|
||||
"addServerStyles",
|
||||
"addToAnimationQueue",
|
||||
"addToArray",
|
||||
"addToEndOfViewTree",
|
||||
"addValidators",
|
||||
@@ -789,7 +787,6 @@
|
||||
"matchTemplateAttribute",
|
||||
"maxLengthValidator",
|
||||
"maxValidator",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeUnwrapEmpty",
|
||||
"maybeUnwrapFn",
|
||||
@@ -902,6 +899,7 @@
|
||||
"retrieveHydrationInfo",
|
||||
"runAfterLeaveAnimations",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"main": [
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -351,7 +350,6 @@
|
||||
"addAfterRenderSequencesForView",
|
||||
"addLViewToLContainer",
|
||||
"addServerStyles",
|
||||
"addToAnimationQueue",
|
||||
"addToArray",
|
||||
"addToEndOfViewTree",
|
||||
"addValidators",
|
||||
@@ -788,7 +786,6 @@
|
||||
"markViewForRefresh",
|
||||
"markedFeatures",
|
||||
"matchTemplateAttribute",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeUnwrapEmpty",
|
||||
"maybeUnwrapFn",
|
||||
@@ -900,6 +897,7 @@
|
||||
"retrieveHydrationInfo",
|
||||
"runAfterLeaveAnimations",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ALLOWED_METHODS",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -288,7 +287,6 @@
|
||||
"activeConsumer",
|
||||
"addAfterRenderSequencesForView",
|
||||
"addServerStyles",
|
||||
"addToAnimationQueue",
|
||||
"addToEndOfViewTree",
|
||||
"allLeavingAnimations",
|
||||
"allocExpando",
|
||||
@@ -638,7 +636,6 @@
|
||||
"markViewDirty",
|
||||
"markViewForRefresh",
|
||||
"markedFeatures",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeWrapInNotSelector",
|
||||
"mergeHostAttribute",
|
||||
@@ -718,6 +715,7 @@
|
||||
"retrieveTransferredState",
|
||||
"runAfterLeaveAnimations",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"main": [
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APP_BASE_HREF",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
@@ -397,7 +396,6 @@
|
||||
"addEmptyPathsToChildrenIfNeeded",
|
||||
"addLViewToLContainer",
|
||||
"addServerStyles",
|
||||
"addToAnimationQueue",
|
||||
"addToArray",
|
||||
"addToEndOfViewTree",
|
||||
"addViewToDOM",
|
||||
@@ -898,7 +896,6 @@
|
||||
"matchWithChecks",
|
||||
"materializeViewResults",
|
||||
"matrixParamsMatch",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeResolveResources",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeSchedule",
|
||||
@@ -1017,6 +1014,7 @@
|
||||
"runCanLoadGuards",
|
||||
"runCanMatchGuards",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"main": [
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -232,7 +231,6 @@
|
||||
"activeConsumer",
|
||||
"addAfterRenderSequencesForView",
|
||||
"addServerStyles",
|
||||
"addToAnimationQueue",
|
||||
"addToEndOfViewTree",
|
||||
"allLeavingAnimations",
|
||||
"allocExpando",
|
||||
@@ -504,7 +502,6 @@
|
||||
"markViewDirty",
|
||||
"markViewForRefresh",
|
||||
"markedFeatures",
|
||||
"maybeQueueEnterAnimation",
|
||||
"maybeReturnReactiveLViewConsumer",
|
||||
"maybeWrapInNotSelector",
|
||||
"mergeHostAttribute",
|
||||
@@ -571,6 +568,7 @@
|
||||
"retrieveHydrationInfo",
|
||||
"runAfterLeaveAnimations",
|
||||
"runEffectsInView",
|
||||
"runEnterAnimations",
|
||||
"runInInjectionContext",
|
||||
"runLeaveAnimationsWithCallback",
|
||||
"runPlatformInitializers",
|
||||
|
||||
Reference in New Issue
Block a user