mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
bbbe477f47
Migrate formatting to prettier for animations from clang-format PR Close #53977
143 lines
4.8 KiB
TypeScript
143 lines
4.8 KiB
TypeScript
/**
|
|
* @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.io/license
|
|
*/
|
|
import {AnimationTriggerMetadata} from '@angular/animations';
|
|
import type {NgZone, Renderer2, RendererFactory2, RendererType2} from '@angular/core';
|
|
|
|
import {AnimationEngine} from './animation_engine_next';
|
|
import {AnimationRenderer, BaseAnimationRenderer} from './renderer';
|
|
|
|
// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. Note that an
|
|
// interface declaration is used as TypeScript prior to 3.7 does not support recursive type
|
|
// references, see https://github.com/microsoft/TypeScript/pull/33050 for details.
|
|
type NestedAnimationTriggerMetadata = AnimationTriggerMetadata | RecursiveAnimationTriggerMetadata;
|
|
interface RecursiveAnimationTriggerMetadata extends Array<NestedAnimationTriggerMetadata> {}
|
|
|
|
export class AnimationRendererFactory implements RendererFactory2 {
|
|
private _currentId: number = 0;
|
|
private _microtaskId: number = 1;
|
|
private _animationCallbacksBuffer: [(e: any) => any, any][] = [];
|
|
private _rendererCache = new Map<Renderer2, BaseAnimationRenderer>();
|
|
private _cdRecurDepth = 0;
|
|
|
|
constructor(
|
|
private delegate: RendererFactory2,
|
|
private engine: AnimationEngine,
|
|
private _zone: NgZone,
|
|
) {
|
|
engine.onRemovalComplete = (element: any, delegate: Renderer2) => {
|
|
// Note: if a component element has a leave animation, and a host leave animation,
|
|
// the view engine will call `removeChild` for the parent
|
|
// component renderer as well as for the child component renderer.
|
|
// Therefore, we need to check if we already removed the element.
|
|
const parentNode = delegate?.parentNode(element);
|
|
if (parentNode) {
|
|
delegate.removeChild(parentNode, element);
|
|
}
|
|
};
|
|
}
|
|
|
|
createRenderer(hostElement: any, type: RendererType2): BaseAnimationRenderer {
|
|
const EMPTY_NAMESPACE_ID = '';
|
|
|
|
// cache the delegates to find out which cached delegate can
|
|
// be used by which cached renderer
|
|
const delegate = this.delegate.createRenderer(hostElement, type);
|
|
if (!hostElement || !type?.data?.['animation']) {
|
|
const cache = this._rendererCache;
|
|
let renderer: BaseAnimationRenderer | undefined = cache.get(delegate);
|
|
if (!renderer) {
|
|
// Ensure that the renderer is removed from the cache on destroy
|
|
// since it may contain references to detached DOM nodes.
|
|
const onRendererDestroy = () => cache.delete(delegate);
|
|
renderer = new BaseAnimationRenderer(
|
|
EMPTY_NAMESPACE_ID,
|
|
delegate,
|
|
this.engine,
|
|
onRendererDestroy,
|
|
);
|
|
// only cache this result when the base renderer is used
|
|
cache.set(delegate, renderer);
|
|
}
|
|
return renderer;
|
|
}
|
|
|
|
const componentId = type.id;
|
|
const namespaceId = type.id + '-' + this._currentId;
|
|
this._currentId++;
|
|
|
|
this.engine.register(namespaceId, hostElement);
|
|
|
|
const registerTrigger = (trigger: NestedAnimationTriggerMetadata) => {
|
|
if (Array.isArray(trigger)) {
|
|
trigger.forEach(registerTrigger);
|
|
} else {
|
|
this.engine.registerTrigger(componentId, namespaceId, hostElement, trigger.name, trigger);
|
|
}
|
|
};
|
|
const animationTriggers = type.data['animation'] as NestedAnimationTriggerMetadata[];
|
|
animationTriggers.forEach(registerTrigger);
|
|
|
|
return new AnimationRenderer(this, namespaceId, delegate, this.engine);
|
|
}
|
|
|
|
begin() {
|
|
this._cdRecurDepth++;
|
|
if (this.delegate.begin) {
|
|
this.delegate.begin();
|
|
}
|
|
}
|
|
|
|
private _scheduleCountTask() {
|
|
queueMicrotask(() => {
|
|
this._microtaskId++;
|
|
});
|
|
}
|
|
|
|
/** @internal */
|
|
scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) {
|
|
if (count >= 0 && count < this._microtaskId) {
|
|
this._zone.run(() => fn(data));
|
|
return;
|
|
}
|
|
|
|
const animationCallbacksBuffer = this._animationCallbacksBuffer;
|
|
if (animationCallbacksBuffer.length == 0) {
|
|
queueMicrotask(() => {
|
|
this._zone.run(() => {
|
|
animationCallbacksBuffer.forEach((tuple) => {
|
|
const [fn, data] = tuple;
|
|
fn(data);
|
|
});
|
|
this._animationCallbacksBuffer = [];
|
|
});
|
|
});
|
|
}
|
|
animationCallbacksBuffer.push([fn, data]);
|
|
}
|
|
|
|
end() {
|
|
this._cdRecurDepth--;
|
|
|
|
// this is to prevent animations from running twice when an inner
|
|
// component does CD when a parent component instead has inserted it
|
|
if (this._cdRecurDepth == 0) {
|
|
this._zone.runOutsideAngular(() => {
|
|
this._scheduleCountTask();
|
|
this.engine.flush(this._microtaskId);
|
|
});
|
|
}
|
|
if (this.delegate.end) {
|
|
this.delegate.end();
|
|
}
|
|
}
|
|
|
|
whenRenderingDone(): Promise<any> {
|
|
return this.engine.whenRenderingDone();
|
|
}
|
|
}
|