diff --git a/packages/core/src/animation/queue.ts b/packages/core/src/animation/queue.ts index 952d517fa1b..e5432a2d1fd 100644 --- a/packages/core/src/animation/queue.ts +++ b/packages/core/src/animation/queue.ts @@ -24,11 +24,14 @@ export const ANIMATION_QUEUE = new InjectionToken( typeof ngDevMode !== 'undefined' && ngDevMode ? 'AnimationQueue' : '', { factory: () => { + const injector = inject(EnvironmentInjector); + const queue = new Set(); + injector.onDestroy(() => queue.clear()); return { - queue: new Set(), + queue, isScheduled: false, scheduler: null, - injector: inject(EnvironmentInjector), // should be the root injector + injector, }; }, }, diff --git a/packages/core/src/render3/node_animations.ts b/packages/core/src/render3/node_animations.ts new file mode 100644 index 00000000000..3b53d0e3a93 --- /dev/null +++ b/packages/core/src/render3/node_animations.ts @@ -0,0 +1,263 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + RunLeaveAnimationFn, + LeaveNodeAnimations, + AnimationLViewData, +} from '../animation/interfaces'; +import {allLeavingAnimations} from '../animation/longest_animation'; +import { + queueEnterAnimations, + addToAnimationQueue, + removeAnimationsFromQueue, +} from '../animation/queue'; +import {Injector, INJECTOR} from '../di'; +import {CONTAINER_HEADER_OFFSET} from './interfaces/container'; +import {TNode, TNodeType} from './interfaces/node'; +import {RElement} from './interfaces/renderer_dom'; +import {isComponentHost, isLContainer} from './interfaces/type_checks'; +import {ANIMATIONS, ID, LView, TVIEW} from './interfaces/view'; +import {getComponentLViewByIndex} from './util/view_utils'; + +export 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)) { + queueEnterAnimations(injector, enterAnimations); + } +} + +export function runLeaveAnimationsWithCallback( + lView: LView | undefined, + tNode: TNode, + injector: Injector, + callback: Function, +) { + // It's possible that the AppRef has been destroyed, which would also destroy + // the injector tree. If this happens, we will get an error when we try to + // get the injector, so we catch it here and avoid the error and return + // safely. + try { + injector.get(INJECTOR); + } catch { + return callback(false); + } + + const animations = lView?.[ANIMATIONS]; + + // regarding the TNode index to see if it is the same element. + if (animations?.enter?.has(tNode.index)) { + removeAnimationsFromQueue(injector, animations.enter.get(tNode.index)!.animateFns); + } + + // get all nodes in the current view that are descendants of tNode and have leave animations + const nodesWithExitAnimations = aggregateDescendantAnimations(lView, tNode, animations); + + if (nodesWithExitAnimations.size === 0) { + let hasNestedAnimations = false; + if (lView) { + const nestedPromises: Promise[] = []; + collectNestedViewAnimations(lView, tNode, nestedPromises); + hasNestedAnimations = nestedPromises.length > 0; + } + + if (!hasNestedAnimations) { + return callback(false); + } + } + + if (lView) allLeavingAnimations.add(lView[ID]); + + addToAnimationQueue( + injector, + () => + executeLeaveAnimations( + lView, + tNode, + animations || undefined, + nodesWithExitAnimations, + callback, + ), + animations || undefined, + ); +} + +// Identifies all elements that are descendants of `tNode` *within the same component view* +// (LView) and have active leave animations. Since `tNode` is being removed, its descendants +// will also be removed. We must execute their leave animations and wait for them to finish +// before physically removing `tNode` from the DOM. +// +// Instead of performing a potentially expensive downward traversal of the +// entire `tNode` subtree to find animated descendants, we iterate over the `leaveAnimations` +// map. This map contains all pending leave animations in the current LView and is typically +// very small. +// +// Note: Animations across LView boundaries (e.g., in child components or embedded views) +// are collected separately via `collectNestedViewAnimations`. +function aggregateDescendantAnimations( + lView: LView | undefined, + tNode: TNode, + animations: AnimationLViewData | null | undefined, +): Map { + const nodesWithExitAnimations = new Map(); + const leaveAnimations = animations?.leave; + + if (leaveAnimations && leaveAnimations.has(tNode.index)) { + nodesWithExitAnimations.set(tNode.index, leaveAnimations.get(tNode.index)!); + } + + if (lView && leaveAnimations) { + for (const [index, animationData] of leaveAnimations) { + if (nodesWithExitAnimations.has(index)) continue; + + // Get the tNode for the animation. This node might be a descendant of the tNode we are removing. + // If so, we need to run its leave animation as well. + const nestedTNode = lView[TVIEW].data[index] as TNode; + let parent = nestedTNode.parent; + + // Traverse upward to check if `tNode` is an ancestor of `nestedTNode` + // For each animation in the map, we retrieve its corresponding TNode (`nestedTNode`) and + // traverse UP the tree using parent pointers. If we encounter `tNode` during this upward + // traversal, we know the animated element is a descendant, and we add its animation data + // to `nodesWithExitAnimations`. + while (parent) { + if (parent === tNode) { + nodesWithExitAnimations.set(index, animationData); + break; + } + parent = parent.parent; + } + } + } + return nodesWithExitAnimations; +} + +function executeLeaveAnimations( + lView: LView | undefined, + tNode: TNode, + animations: AnimationLViewData | undefined, + nodesWithExitAnimations: Map, + callback: Function, +) { + // it's possible that in the time between when the leave animation was + // and the time it was executed, the data structure changed. So we need + // to be safe here. + const runningAnimations: Promise[] = []; + + if (animations && animations.leave) { + for (const [index] of nodesWithExitAnimations) { + if (!animations.leave.has(index)) continue; + + const currentAnimationData = animations.leave.get(index)!; + for (const animationFn of currentAnimationData.animateFns) { + const {promise} = animationFn() as ReturnType; + runningAnimations.push(promise); + } + animations.detachedLeaveAnimationFns = undefined; + } + } + + // Also add nested view animations + if (lView) { + collectNestedViewAnimations(lView, tNode, runningAnimations); + } + + if (runningAnimations.length > 0) { + const currentAnimations = animations || lView?.[ANIMATIONS]; + if (currentAnimations) { + const prevRunning = currentAnimations.running; + if (prevRunning) { + runningAnimations.push(prevRunning); + } + currentAnimations.running = Promise.allSettled(runningAnimations); + runAfterLeaveAnimations(lView!, currentAnimations.running, callback); + } else { + Promise.allSettled(runningAnimations).then(() => { + if (lView) allLeavingAnimations.delete(lView[ID]); + callback(true); + }); + } + } else { + if (lView) allLeavingAnimations.delete(lView[ID]); + callback(false); + } +} + +/** + * Collects leave animations from nested views (components and containers) + * starting from the given TNode's children. + */ +function collectNestedViewAnimations( + lView: LView, + tNode: TNode, + collectedPromises: Promise[], +) { + if (isComponentHost(tNode)) { + const componentView = getComponentLViewByIndex(tNode.index, lView); + collectAllViewLeaveAnimations(componentView, collectedPromises); + } else if (tNode.type & TNodeType.AnyContainer) { + const lContainer = lView[tNode.index]; + if (isLContainer(lContainer)) { + for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { + const subView = lContainer[i] as LView; + collectAllViewLeaveAnimations(subView, collectedPromises); + } + } + } + + let child = tNode.child; + while (child) { + collectNestedViewAnimations(lView, child, collectedPromises); + child = child.next; + } +} + +/** + * Recursively collects all leave animations from a view and its children. + */ +function collectAllViewLeaveAnimations(view: LView, collectedPromises: Promise[]) { + const animations = view[ANIMATIONS]; + if (animations && animations.leave) { + for (const animationData of animations.leave.values()) { + for (const animationFn of animationData.animateFns) { + // We interpret the animation function to get the promise + const {promise} = animationFn() as ReturnType; + collectedPromises.push(promise); + } + } + } + + let child = view[TVIEW].firstChild; + while (child) { + collectNestedViewAnimations(view, child, collectedPromises); + child = child.next; + } +} + +function runAfterLeaveAnimations( + lView: LView, + runningAnimations: Promise, + callback: Function, +) { + runningAnimations.then(() => { + // We only want to clear the running flag and the allLeavingAnimations set if + // the current running animation is the same as the one we just waited for. + // If it's different, it means another animation started while we were waiting, + // and that other animation is now responsible for clearing the flag. + if (lView[ANIMATIONS]?.running === runningAnimations) { + lView[ANIMATIONS]!.running = undefined; + allLeavingAnimations.delete(lView[ID]); + } + callback(true); + }); +} diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 2f7160ce3c5..a29c9032d10 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -82,14 +82,8 @@ import {assertTNodeType} from './node_assert'; import {profiler} from './profiler'; import {ProfilerEvent} from '../../primitives/devtools'; import {getLViewParent, getNativeByTNode, unwrapRNode} from './util/view_utils'; -import {allLeavingAnimations} from '../animation/longest_animation'; import {Injector} from '../di'; -import { - addToAnimationQueue, - queueEnterAnimations, - removeAnimationsFromQueue, -} from '../animation/queue'; -import {RunLeaveAnimationFn} from '../animation/interfaces'; +import {maybeQueueEnterAnimation, runLeaveAnimationsWithCallback} from './node_animations'; const enum WalkTNodeTreeAction { /** node create in the native environment. Run on initial creation. */ @@ -108,18 +102,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)) { - queueEnterAnimations(injector, enterAnimations); - } -} - /** * NOTE: for performance reasons, the possible actions are inlined within the function instead of * being passed as an argument. @@ -382,64 +364,6 @@ function cleanUpView(tView: TView, lView: LView): void { } } -function runLeaveAnimationsWithCallback( - lView: LView | undefined, - tNode: TNode, - injector: Injector, - callback: Function, -) { - const animations = lView?.[ANIMATIONS]; - if (animations?.enter?.has(tNode.index)) { - removeAnimationsFromQueue(injector, animations.enter.get(tNode.index)!.animateFns); - } - - if (animations == null || animations.leave == undefined || !animations.leave.has(tNode.index)) - return callback(false); - - if (lView) allLeavingAnimations.add(lView[ID]); - - addToAnimationQueue( - injector, - () => { - // it's possible that in the time between when the leave animation was - // and the time it was executed, the data structure changed. So we need - // to be safe here. - if (animations.leave && animations.leave.has(tNode.index)) { - const leaveAnimationMap = animations.leave; - const leaveAnimations = leaveAnimationMap.get(tNode.index); - const runningAnimations = []; - if (leaveAnimations) { - for (let index = 0; index < leaveAnimations.animateFns.length; index++) { - const animationFn = leaveAnimations.animateFns[index]; - const {promise} = animationFn() as ReturnType; - runningAnimations.push(promise); - } - animations.detachedLeaveAnimationFns = undefined; - } - animations.running = Promise.allSettled(runningAnimations); - runAfterLeaveAnimations(lView!, callback); - } else { - if (lView) allLeavingAnimations.delete(lView[ID]); - callback(false); - } - }, - animations, - ); -} - -function runAfterLeaveAnimations(lView: LView, callback: Function) { - const runningAnimations = lView[ANIMATIONS]?.running; - if (runningAnimations) { - runningAnimations.then(() => { - lView[ANIMATIONS]!.running = undefined; - allLeavingAnimations.delete(lView[ID]); - callback(true); - }); - return; - } - callback(false); -} - /** Removes listeners and unsubscribes from output subscriptions */ function processCleanups(tView: TView, lView: LView): void { ngDevMode && assertNotReactive(processCleanups.name); diff --git a/packages/core/test/acceptance/authoring/signal_inputs_spec.ts b/packages/core/test/acceptance/authoring/signal_inputs_spec.ts index bfac1c27fbf..3dfd33f82db 100644 --- a/packages/core/test/acceptance/authoring/signal_inputs_spec.ts +++ b/packages/core/test/acceptance/authoring/signal_inputs_spec.ts @@ -652,5 +652,71 @@ describe('signal inputs', () => { fixture.detectChanges(); // Detect changes after animation completes and element is removed expect(fixture.nativeElement.querySelector('notification')).toBeNull(); // Verify element is removed })); + + it('should support nested animate.leave with signal inputs via host binding', fakeAsync(() => { + const styles = ` + .fade-out { + animation: fade-out 500ms; + } + @keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } + } + `; + + @Component({ + selector: 'child-cmp', + template: '
Content
', + host: { + '[animate.leave]': 'animClass()', + }, + }) + class ChildCmp { + animClass = input.required(); + } + + @Component({ + selector: 'test-cmp', + styles: [styles], + imports: [ChildCmp], + template: ` + @if (show()) { +
+ +
+ } + `, + encapsulation: ViewEncapsulation.None, + }) + class TestCmp { + show = signal(true); + } + + TestBed.configureTestingModule({animationsEnabled: true}); + const fixture = TestBed.createComponent(TestCmp); + const cmp = fixture.componentInstance; + fixture.detectChanges(); + + const target = fixture.nativeElement.querySelector('.child-comp'); + expect(target).toBeTruthy(); + + cmp.show.set(false); + fixture.detectChanges(); + tickAnimationFrames(1); + + const targetAfter = fixture.nativeElement.querySelector('.child-comp'); + expect(targetAfter).withContext('Nested component should persist').not.toBeNull(); + + if (targetAfter) { + expect(targetAfter.classList.contains('fade-out')) + .withContext('Should have animation class from signal input host binding') + .toBeTruthy(); + + targetAfter.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade-out'})); + tick(); + + expect(fixture.nativeElement.querySelector('.child-comp')).toBeNull(); + } + })); }); }); diff --git a/packages/core/test/acceptance/nested_animation_spec.ts b/packages/core/test/acceptance/nested_animation_spec.ts new file mode 100644 index 00000000000..8ae3e9064df --- /dev/null +++ b/packages/core/test/acceptance/nested_animation_spec.ts @@ -0,0 +1,366 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Component, signal, ViewEncapsulation, OnDestroy, destroyPlatform} from '@angular/core'; +import {TestBed, fakeAsync, tick} from '@angular/core/testing'; +import {BrowserTestingModule, platformBrowserTesting} from '@angular/platform-browser/testing'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {tickAnimationFrames} from '../animation_utils/tick_animation_frames'; + +describe('Nested animate.leave', () => { + beforeEach(() => { + destroyPlatform(); + TestBed.resetTestEnvironment(); + TestBed.initTestEnvironment( + [BrowserTestingModule, NoopAnimationsModule], + platformBrowserTesting(), + ); + }); + + it('should wait for nested animations inside @if', fakeAsync(() => { + const styles = ` + .fade { + animation: fade-out 500ms; + } + @keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } + } + `; + + @Component({ + selector: 'test-cmp', + template: ` +
+ @if (show()) { +
+
Child
+
+ } +
+ `, + styles: [styles], + encapsulation: ViewEncapsulation.None, + }) + class TestCmp { + show = signal(true); + } + + TestBed.configureTestingModule({animationsEnabled: true}); + const fixture = TestBed.createComponent(TestCmp); + const cmp = fixture.componentInstance; + fixture.detectChanges(); + + const child = fixture.nativeElement.querySelector('.child'); + expect(child).toBeTruthy(); + + cmp.show.set(false); + fixture.detectChanges(); + tickAnimationFrames(1); + + // In current Angular, this likely fails immediately because parent is removed, taking child with it. + // We expect child to remain if we support nested animations. + const childAfter = fixture.nativeElement.querySelector('.child'); + + // This expectation confirms if it works or fails. + // If it works, childAfter is NOT null. + // If it fails (current behavior), childAfter IS null. + + // We want to prove it fails currently, so we expect rejection or we just assert strict truth. + // I will use strict truth to demonstrate failure. + expect(childAfter).withContext('Child element should persist during animation').not.toBeNull(); + if (childAfter) { + expect(childAfter.classList.contains('fade')) + .withContext('Child should get animation class') + .toBeTruthy(); + + // Simulate end + childAfter.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade-out'})); + tick(); + } + })); + + it('should support nested host bindings', fakeAsync(() => { + const styles = ` + .fade { + animation: fade-out 500ms; + } + @keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } + } + `; + + @Component({ + selector: 'fade-cmp', + host: {'animate.leave': 'fade'}, + template: '

I should fade

', + encapsulation: ViewEncapsulation.None, + }) + class FadeComponent {} + + @Component({ + selector: 'test-cmp', + styles: [styles], + imports: [FadeComponent], + template: ` +
+ @if (show()) { +
+ +
+ } +
+ `, + encapsulation: ViewEncapsulation.None, + }) + class TestCmp { + show = signal(true); + } + + TestBed.configureTestingModule({animationsEnabled: true}); + const fixture = TestBed.createComponent(TestCmp); + const cmp = fixture.componentInstance; + fixture.detectChanges(); + + const child = fixture.nativeElement.querySelector('.child'); + expect(child).toBeTruthy(); + + cmp.show.set(false); + fixture.detectChanges(); + tickAnimationFrames(1); + + const childAfter = fixture.nativeElement.querySelector('.child'); + expect(childAfter).withContext('Child element should persist during animation').not.toBeNull(); + + if (childAfter) { + expect(childAfter.classList.contains('fade')) + .withContext('Child should get animation class from host binding') + .toBeTruthy(); + + childAfter.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade-out'})); + tick(); + + const childFinal = fixture.nativeElement.querySelector('.child'); + expect(childFinal).toBeNull(); + } + })); + + it('should support nested function syntax', fakeAsync(() => { + let completeFn: Function | undefined; + @Component({ + selector: 'test-cmp', + template: ` +
+ @if (show()) { +
+
Child
+
+ } +
+ `, + encapsulation: ViewEncapsulation.None, + }) + class TestCmp { + show = signal(true); + animateFn = (event: any) => { + event.target.classList.add('custom-anim'); + completeFn = event.animationComplete; + }; + } + + TestBed.configureTestingModule({animationsEnabled: true}); + const fixture = TestBed.createComponent(TestCmp); + const cmp = fixture.componentInstance; + fixture.detectChanges(); + + cmp.show.set(false); + fixture.detectChanges(); + tickAnimationFrames(1); + + const childAfter = fixture.nativeElement.querySelector('.child'); + expect(childAfter).withContext('Child element should persist during animation').not.toBeNull(); + + if (childAfter) { + expect(childAfter.classList.contains('custom-anim')) + .withContext('Child should get class from animation function') + .toBeTruthy(); + + expect(completeFn).withContext('animationComplete should be captured').toBeDefined(); + completeFn!(); + + tick(); + const childFinal = fixture.nativeElement.querySelector('.child'); + expect(childFinal).toBeNull(); + } + })); + + it('should support nested animate.leave with component host binding (GitHub #66476)', fakeAsync(() => { + // Reproduction from https://github.com/angular/angular/issues/66476 + // Structure: Host -> @if -> Child -> Popup (with host animation) + + const styles = ` + .popup-overlay { + animation: fade-in 0.3s; + } + .popup-overlay-leave { + animation: fade-out 0.3s; + } + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } + } + @keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } + } + `; + + @Component({ + selector: 'app-popup', + template: ` + + `, + styles: [styles], + encapsulation: ViewEncapsulation.None, + host: { + '(animate.leave)': 'animateLeave($event)', + }, + }) + class PopupComponent { + animateLeave(event: any) { + // In the issue, they use setTimeout to call animationComplete. + // We simulate this async completion. + setTimeout(() => event.animationComplete(), 300); + } + } + + @Component({ + selector: 'app-child', + template: ` Projected content. `, + imports: [PopupComponent], + }) + class ChildComponent {} + + @Component({ + selector: 'app-host', + template: ` + @if (showChild()) { + + } + `, + imports: [ChildComponent], + }) + class HostComponent { + showChild = signal(true); + } + + TestBed.configureTestingModule({animationsEnabled: true}); + const fixture = TestBed.createComponent(HostComponent); + const host = fixture.componentInstance; + fixture.detectChanges(); + + const popup = fixture.nativeElement.querySelector('app-popup'); + expect(popup).toBeTruthy(); + + host.showChild.set(false); + fixture.detectChanges(); + tickAnimationFrames(1); + + const popupAfter = fixture.nativeElement.querySelector('app-popup'); + expect(popupAfter).withContext('Popup should persist due to host animation').not.toBeNull(); + + // Advance time for the setTimeout in animateLeave + tick(300); + + // The nested animation needs to complete as well. + // Query the nested element (popup overlay) that has the animation. + const popupOverlay = fixture.nativeElement.querySelector('.popup-overlay'); + if (popupOverlay) { + popupOverlay.dispatchEvent( + new AnimationEvent('animationend', {animationName: 'fade-out', bubbles: true}), + ); + tick(); + } + + // Attempt to verify removal + const popupFinal = fixture.nativeElement.querySelector('app-popup'); + expect(popupFinal).toBeFalsy(); + })); + + it('should not throw "Injector destroyed" when a parent with leave animations is destroyed with nested components', async () => { + @Component({ + selector: 'child-comp', + standalone: true, + template: '
Child
', + }) + class ChildComp implements OnDestroy { + ngOnDestroy() {} + } + + @Component({ + selector: 'parent-comp', + standalone: true, + imports: [ChildComp], + template: ` +
+ +
+ `, + }) + class ParentComp { + leaving = () => ({promise: Promise.resolve()}); + } + + TestBed.configureTestingModule({}); + + const fixture = TestBed.createComponent(ParentComp); + fixture.detectChanges(); + + // Before the fix, the framework would recursively dive into ChildComp's + // internal view to collect animations. During a full destruction of the + // component tree, this could lead to starting animations or async tasks + // that resolve after the component's injector is already gone. + fixture.destroy(); + + // If we reach here without an exception, the regression is prevented. + }); + + it('should still support nested animations within the same template', () => { + // This test ensures that while we stopped recursing into component views, + // we still support nested elements within the same view having animations. + + @Component({ + selector: 'nested-view-comp', + standalone: true, + template: ` +
+ @if (showNested) { +
Nested
+ } +
+ `, + }) + class NestedViewComp { + showNested = true; + leaving = () => ({promise: Promise.resolve()}); + } + + TestBed.configureTestingModule({}); + + const fixture = TestBed.createComponent(NestedViewComp); + fixture.detectChanges(); + + // This should not throw and correctly handle the nested animations. + fixture.destroy(); + }); +}); diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index 65a9994a8fb..366f1ea9bb8 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -323,6 +323,7 @@ "addServerStyles", "addToAnimationQueue", "addToEndOfViewTree", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -369,8 +370,10 @@ "cleanUpView", "cloakAndComputeStyles", "cloakElement", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "computeStaticStyling", "computeStyle", "concatStringsWithSpace", @@ -461,6 +464,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeOnDestroys", "executeTemplate", "executeViewQueryFn", diff --git a/packages/core/test/bundling/create_component/bundle.golden_symbols.json b/packages/core/test/bundling/create_component/bundle.golden_symbols.json index 1b5f2ae5b89..de11c10a6f4 100644 --- a/packages/core/test/bundling/create_component/bundle.golden_symbols.json +++ b/packages/core/test/bundling/create_component/bundle.golden_symbols.json @@ -253,6 +253,7 @@ "addToArray", "addToEndOfViewTree", "addViewToDOM", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -286,8 +287,10 @@ "captureError", "checkStable", "cleanUpView", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "computeStaticStyling", "concatStringsWithSpace", "config", @@ -369,6 +372,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeListenerWithErrorHandling", "executeOnDestroys", "executeTemplate", diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index fb4a1524aa4..f8abf859540 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -296,6 +296,7 @@ "addToArray", "addToEndOfViewTree", "addViewToDOM", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -330,8 +331,10 @@ "checkStable", "classIndexOf", "cleanUpView", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "computeStaticStyling", "concatStringsWithSpace", "config", @@ -410,6 +413,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeOnDestroys", "executeTemplate", "executeViewQueryFn", diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index ca1fb7d3b36..48562db8277 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -352,6 +352,7 @@ "addToEndOfViewTree", "addValidators", "addViewToDOM", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -400,8 +401,10 @@ "cleanUpView", "coerceToAsyncValidator", "coerceToValidator", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "collectResidual", "collectStylingFromDirectives", "collectStylingFromTAttrs", @@ -507,6 +510,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeListenerWithErrorHandling", "executeOnDestroys", "executeSchedule", diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index ad8899b59df..8b53ac27f56 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -356,6 +356,7 @@ "addToEndOfViewTree", "addValidators", "addViewToDOM", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -402,8 +403,10 @@ "cleanUpView", "coerceToAsyncValidator", "coerceToValidator", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "collectResidual", "collectStylingFromDirectives", "collectStylingFromTAttrs", @@ -509,6 +512,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeListenerWithErrorHandling", "executeOnDestroys", "executeSchedule", diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index ce45600f521..c619b441b0b 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -292,6 +292,7 @@ "addServerStyles", "addToAnimationQueue", "addToEndOfViewTree", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -334,8 +335,10 @@ "cleanupLView", "cleanupMatchingDehydratedViews", "clearElementContents", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "computeStaticStyling", "concatStringsWithSpace", "config", @@ -425,6 +428,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeOnDestroys", "executeSchedule", "executeTemplate", diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 43849f27c66..acd27999779 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -408,6 +408,7 @@ "addViewToDOM", "advanceActivatedRoute", "afterNextNavigation", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -450,8 +451,10 @@ "checkStable", "classIndexOf", "cleanUpView", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "collectQueryResults", "combineLatest", "combineLatestInit", @@ -601,6 +604,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeListenerWithErrorHandling", "executeOnDestroys", "executeSchedule", diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 0b0025a5879..6e94b2663a7 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -233,6 +233,7 @@ "addServerStyles", "addToAnimationQueue", "addToEndOfViewTree", + "aggregateDescendantAnimations", "allLeavingAnimations", "allocExpando", "allocLFrame", @@ -265,8 +266,10 @@ "captureError", "checkStable", "cleanUpView", + "collectAllViewLeaveAnimations", "collectNativeNodes", "collectNativeNodesInLContainer", + "collectNestedViewAnimations", "computeStaticStyling", "concatStringsWithSpace", "config", @@ -340,6 +343,7 @@ "executeCheckHooks", "executeContentQueries", "executeInitAndCheckHooks", + "executeLeaveAnimations", "executeOnDestroys", "executeTemplate", "executeViewQueryFn",