feat(animate): adds basic support for CSS animations on enter and leave

Closes #3876
This commit is contained in:
Robert Messerle
2015-08-28 14:39:34 -07:00
parent effbb54f3d
commit 39ce9d3397
26 changed files with 688 additions and 8 deletions

View File

@ -86,6 +86,8 @@ import {APP_COMPONENT_REF_PROMISE, APP_COMPONENT} from './application_tokens';
import {wtfInit} from './profile/wtf_init';
import {EXCEPTION_BINDING} from './platform_bindings';
import {ApplicationRef} from './application_ref';
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
import {BrowserDetails} from 'angular2/src/animate/browser_details';
var _rootInjector: Injector;
@ -161,6 +163,8 @@ function _injectorBindings(appComponentType): Array<Type | Binding | any[]> {
Testability,
AnchorBasedAppRootUrl,
bind(AppRootUrl).toAlias(AnchorBasedAppRootUrl),
BrowserDetails,
AnimationBuilder,
FORM_BINDINGS
];
}

View File

@ -451,6 +451,8 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
return element.dataset[name];
}
getComputedStyle(elem) => elem.getComputedStyle();
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(String path, value) {
var parts = path.split('.');
@ -465,6 +467,14 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
}
obj[parts.removeAt(0)] = value;
}
requestAnimationFrame(callback) {
return window.requestAnimationFrame(callback);
}
cancelAnimationFrame(id) {
window.cancelAnimationFrame(id);
}
}
var baseElement = null;

View File

@ -317,8 +317,11 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
this.setAttribute(element, 'data-' + name, value);
}
getData(element, name: string): string { return this.getAttribute(element, 'data-' + name); }
getComputedStyle(element): any { return getComputedStyle(element); }
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
requestAnimationFrame(callback): number { return window.requestAnimationFrame(callback); }
cancelAnimationFrame(id: number) { window.cancelAnimationFrame(id); }
}

View File

@ -133,6 +133,9 @@ export class DomAdapter {
resetBaseElement(): void { throw _abstract(); }
getUserAgent(): string { throw _abstract(); }
setData(element, name: string, value: string) { throw _abstract(); }
getComputedStyle(element): any { throw _abstract(); }
getData(element, name: string): string { throw _abstract(); }
setGlobalVar(name: string, value: any) { throw _abstract(); }
requestAnimationFrame(callback): number { throw _abstract(); }
cancelAnimationFrame(id) { throw _abstract(); }
}

View File

@ -412,6 +412,10 @@ class Html5LibDomAdapter implements DomAdapter {
this.setAttribute(element, 'data-${name}', value);
}
getComputedStyle(element) {
throw 'not implemented';
}
String getData(Element element, String name) {
return this.getAttribute(element, 'data-${name}');
}
@ -420,4 +424,11 @@ class Html5LibDomAdapter implements DomAdapter {
setGlobalVar(String name, value) {
// noop on the server
}
requestAnimationFrame(callback) {
throw 'not implemented';
}
cancelAnimationFrame(id) {
throw 'not implemented';
}
}

View File

@ -539,9 +539,12 @@ export class Parse5DomAdapter extends DomAdapter {
getLocation(): Location { throw 'not implemented'; }
getUserAgent(): string { return "Fake user agent"; }
getData(el, name: string): string { return this.getAttribute(el, 'data-' + name); }
getComputedStyle(el): any { throw 'not implemented'; }
setData(el, name: string, value: string) { this.setAttribute(el, 'data-' + name, value); }
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
requestAnimationFrame(callback): number { return setTimeout(callback, 0); }
cancelAnimationFrame(id: number) { clearTimeout(id); }
}
// TODO: build a proper list, this one is all the keys of a HTMLInputElement

View File

@ -19,4 +19,6 @@ class Math {
static num ceil(num a) => a.ceil();
static num sqrt(num x) => math.sqrt(x);
static num round(num x) => x.round();
}

View File

@ -1,4 +1,5 @@
import {Inject, Injectable, OpaqueToken} from 'angular2/src/core/di';
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
import {isPresent, isBlank, RegExpWrapper, CONST_EXPR} from 'angular2/src/core/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
@ -42,7 +43,7 @@ export class DomRenderer extends Renderer {
* @private
*/
constructor(private _eventManager: EventManager,
private _domSharedStylesHost: DomSharedStylesHost,
private _domSharedStylesHost: DomSharedStylesHost, private _animate: AnimationBuilder,
private _templateCloner: TemplateCloner, @Inject(DOCUMENT) document) {
super();
this._document = document;
@ -94,7 +95,51 @@ export class DomRenderer extends Renderer {
var previousFragmentNodes = resolveInternalDomFragment(previousFragmentRef);
if (previousFragmentNodes.length > 0) {
var sibling = previousFragmentNodes[previousFragmentNodes.length - 1];
moveNodesAfterSibling(sibling, resolveInternalDomFragment(fragmentRef));
let nodes = resolveInternalDomFragment(fragmentRef);
moveNodesAfterSibling(sibling, nodes);
this.animateNodesEnter(nodes);
}
}
/**
* Iterates through all nodes being added to the DOM and animates them if necessary
* @param nodes
*/
animateNodesEnter(nodes: Node[]) {
for (let i = 0; i < nodes.length; i++) this.animateNodeEnter(nodes[i]);
}
/**
* Performs animations if necessary
* @param node
*/
animateNodeEnter(node: Node) {
if (DOM.isElementNode(node) && DOM.hasClass(node, 'ng-animate')) {
DOM.addClass(node, 'ng-enter');
this._animate.css()
.addAnimationClass('ng-enter-active')
.start(<HTMLElement>node)
.onComplete(() => { DOM.removeClass(node, 'ng-enter'); });
}
}
/**
* If animations are necessary, performs animations then removes the element; otherwise, it just
* removes the element.
* @param node
*/
animateNodeLeave(node: Node) {
if (DOM.isElementNode(node) && DOM.hasClass(node, 'ng-animate')) {
DOM.addClass(node, 'ng-leave');
this._animate.css()
.addAnimationClass('ng-leave-active')
.start(<HTMLElement>node)
.onComplete(() => {
DOM.removeClass(node, 'ng-leave');
DOM.remove(node);
});
} else {
DOM.remove(node);
}
}
@ -104,7 +149,9 @@ export class DomRenderer extends Renderer {
}
var parentView = resolveInternalDomView(elementRef.renderView);
var element = parentView.boundElements[elementRef.renderBoundElementIndex];
moveNodesAfterSibling(element, resolveInternalDomFragment(fragmentRef));
var nodes = resolveInternalDomFragment(fragmentRef);
moveNodesAfterSibling(element, nodes);
this.animateNodesEnter(nodes);
}
_detachFragmentScope = wtfCreateScope('DomRenderer#detachFragment()');
@ -112,7 +159,7 @@ export class DomRenderer extends Renderer {
var s = this._detachFragmentScope();
var fragmentNodes = resolveInternalDomFragment(fragmentRef);
for (var i = 0; i < fragmentNodes.length; i++) {
DOM.remove(fragmentNodes[i]);
this.animateNodeLeave(fragmentNodes[i]);
}
wtfLeave(s);
}