feat(ivy): provide groundwork for animations in core (#25234)

PR Close #25234
This commit is contained in:
Matias Niemelä
2018-08-28 16:49:52 -07:00
committed by Kara Erickson
parent a880686081
commit 82a14dc107
24 changed files with 2379 additions and 267 deletions

View File

@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "jasmine_node_test", "ng_module", "ng_rollup_bundle", "ts_library")
load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test")
load("//tools/http-server:http_server.bzl", "http_server")
ng_module(
name = "animation_world",
srcs = ["index.ts"],
tags = ["ivy-only"],
deps = [
"//packages/common",
"//packages/core",
"//packages/core/test/bundling/util:reflect_metadata",
],
)
ng_rollup_bundle(
name = "bundle",
# TODO(alexeagle): This is inconsistent.
# We try to teach users to always have their workspace at the start of a
# path, to disambiguate from other workspaces.
# Here, the rule implementation is looking in an execroot where the layout
# has an "external" directory for external dependencies.
# This should probably start with "angular/" and let the rule deal with it.
entry_point = "packages/core/test/bundling/animation_world/index.js",
tags = ["ivy-only"],
deps = [
":animation_world",
"//packages/core",
],
)
js_expected_symbol_test(
name = "symbol_test",
src = ":bundle.min_debug.js",
golden = ":bundle.golden_symbols.json",
tags = [
"ivy-local",
"ivy-only",
],
)
http_server(
name = "devserver",
data = [
"animation_world.css",
"base.css",
"index.html",
":bundle.min.js",
":bundle.min_debug.js",
],
)

View File

@ -0,0 +1,49 @@
html, body {
padding:0;
margin:0;
font-family: Verdana;
}
animation-world {
display:block;
margin: 0 auto;
max-width:1000px;
border:2px solid black;
}
animation-world nav {
padding:1em;
border-bottom:2px solid black;
background:#eee;
}
animation-world .list {
display:grid;
grid-template-columns: 33% 33% 34%;
grid-template-rows: 33% 33% 34%;
height:1000px;
}
animation-world .record {
line-height:333px;
text-align:center;
font-weight: bold;
font-size:50px;
vertical-align: middle;
}
.record-1 { color:white; background: green;}
.record-2 { color:white; background: red;}
.record-3 { color:white; background: blue;}
.record-4 { color:white; background: orange;}
.record-5 { color:white; background: purple;}
.record-6 { color:white; background: black;}
.record-7 { color:white; background: silver;}
.record-8 { color:white; background: teal;}
.record-9 { color:white; background: pink;}
@keyframes fadeInOut {
from { opacity: 0; background: white; }
33% { opacity: 1; background: black; }
66% { opacity: 0.5; background: black; }
100% { opacity: 1 }
}

View File

@ -0,0 +1,141 @@
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="animation_world.css">
<title>Angular Hello World Example</title>
</head>
<body>
<!-- The Angular application will be bootstrapped into this element. -->
<animation-world></animation-world>
<!--
Script tag which bootstraps the application. Use `?debug` in URL to select
the debug version of the script.
There are two scripts sources: `bundle.min.js` and `bundle.min_debug.js` You can
switch between which bundle the browser loads to experiment with the application.
- `bundle.min.js`: Is what the site would serve to their users. It has gone
through rollup, build-optimizer, and uglify with tree shaking.
- `bundle.min_debug.js`: Is what the developer would like to see when debugging
the application. It has also done through full pipeline of rollup, build-optimizer,
and uglify, however special flags were passed to uglify to prevent inlining and
property renaming.
-->
<script>
document.write('<script src="' +
(document.location.search.endsWith('debug') ? '/bundle.min_debug.js' : '/bundle.min.js') +
'"></' + 'script>');
</script>
</body>
</html>

View File

@ -0,0 +1,136 @@
/**
* @license
* Copyright Google Inc. 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 '@angular/core/test/bundling/util/src/reflect_metadata';
import {CommonModule} from '@angular/common';
import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵrenderComponent as renderComponent} from '@angular/core';
@Component({
selector: 'animation-world',
template: `
<nav>
<button (click)="doAnimate()">Populate List</button>
</nav>
<div class="list">
<div *ngFor="let item of items" class="record" [class]="makeClass(item)">
{{ item }}
</div>
</div>
`,
})
class AnimationWorldComponent {
items: any[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
private _hostElement: HTMLElement;
constructor(element: ElementRef) { this._hostElement = element.nativeElement; }
makeClass(index: number) { return `record-${index}`; }
doAnimate() {
const elements = this._hostElement.querySelectorAll('div.record') as any as HTMLElement[];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const delay = i * 100;
const player = buildAnimationPlayer(element, 'fadeInOut', `500ms ease-out ${delay}ms both`);
addPlayer(element, player);
}
}
}
@NgModule({declarations: [AnimationWorldComponent], imports: [CommonModule]})
class AnimationWorldModule {
}
function buildAnimationPlayer(element: HTMLElement, animationName: string, time: string): Player {
return new SimpleKeyframePlayer(element, animationName, time);
}
class SimpleKeyframePlayer implements Player {
state = PlayState.Pending;
parent: Player|null = null;
private _animationStyle: string;
private _listeners: {[stateName: string]: (() => any)[]} = {};
constructor(private _element: HTMLElement, private _animationName: string, time: string) {
this._animationStyle = `${time} ${_animationName}`;
}
private _start() {
this._element.style.animation = this._animationStyle;
const animationFn = (event: AnimationEvent) => {
if (event.animationName == this._animationName) {
this._element.removeEventListener('animationend', animationFn);
this.finish();
}
};
this._element.addEventListener('animationend', animationFn);
}
addEventListener(state: PlayState|string, cb: () => any): void {
const key = state.toString();
const arr = this._listeners[key] = (this._listeners[key] || []);
arr.push(cb);
}
play(): void {
if (this.state <= PlayState.Pending) {
this._start();
}
if (this.state != PlayState.Running) {
setAnimationPlayState(this._element, 'running');
this.state = PlayState.Running;
this._emit(this.state);
}
}
pause(): void {
if (this.state != PlayState.Paused) {
setAnimationPlayState(this._element, 'paused');
this.state = PlayState.Paused;
this._emit(this.state);
}
}
finish(): void {
if (this.state < PlayState.Finished) {
this._element.style.animation = '';
this.state = PlayState.Finished;
this._emit(this.state);
}
}
destroy(): void {
if (this.state < PlayState.Destroyed) {
this.finish();
this.state = PlayState.Destroyed;
this._emit(this.state);
}
}
capture(): any {}
private _emit(state: PlayState) {
const arr = this._listeners[state.toString()] || [];
arr.forEach(cb => cb());
}
}
function setAnimationPlayState(element: HTMLElement, state: string) {
element.style.animationPlayState = state;
}
class AnimationDebugger implements PlayerHandler {
private _players: Player[] = [];
flushPlayers() {
this._players.forEach(player => {
if (!player.parent) {
player.play();
}
});
this._players.length = 0;
}
queuePlayer(player: Player): void { this._players.push(player); }
}
const playerHandler = new AnimationDebugger();
renderComponent(AnimationWorldComponent, {playerHandler});

View File

@ -416,6 +416,9 @@
{
"name": "createEmbeddedViewAndNode"
},
{
"name": "createEmptyStylingContext"
},
{
"name": "createLContainer"
},

View File

@ -1286,6 +1286,9 @@
{
"name": "createEmbeddedViewAndNode"
},
{
"name": "createEmptyStylingContext"
},
{
"name": "createInjector"
},