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

@ -166,6 +166,17 @@ export {
getContext as ɵgetContext
} from './render3/context_discovery';
export {
Player as ɵPlayer,
PlayState as ɵPlayState,
PlayerHandler as ɵPlayerHandler,
} from './render3/animations/interfaces';
export {
addPlayer as ɵaddPlayer,
getPlayers as ɵgetPlayers,
} from './render3/animations/players';
// we reexport these symbols just so that they are retained during the dead code elimination
// performed by rollup while it's creating fesm files.
//

View File

@ -0,0 +1,24 @@
/**
* @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 {Player, PlayerHandler} from './interfaces';
export class CorePlayerHandler implements PlayerHandler {
private _players: Player[] = [];
flushPlayers() {
for (let i = 0; i < this._players.length; i++) {
const player = this._players[i];
if (!player.parent) {
player.play();
}
}
this._players.length = 0;
}
queuePlayer(player: Player) { this._players.push(player); }
}

View File

@ -0,0 +1,56 @@
/**
* @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
*/
/**
* A shared interface which contains an animation player
*/
export interface Player {
parent?: Player|null;
state: PlayState;
play(): void;
pause(): void;
finish(): void;
destroy(): void;
addEventListener(state: PlayState|string, cb: (data?: any) => any): void;
}
/**
* The state of a given player
*
* Do not change the increasing nature of the numbers since the player
* code may compare state by checking if a number is higher or lower than
* a certain numeric value.
*/
export const enum PlayState {Pending = 0, Running = 1, Paused = 2, Finished = 100, Destroyed = 200}
/**
* The context that stores all active animation players present on an element.
*/
export declare type AnimationContext = Player[];
export declare type ComponentInstance = {};
export declare type DirectiveInstance = {};
/**
* Designed to be used as an injection service to capture all animation players.
*
* When present all animation players will be passed into the flush method below.
* This feature is designed to service application-wide animation testing, live
* debugging as well as custom animation choreographing tools.
*/
export interface PlayerHandler {
/**
* Designed to kick off the player at the end of change detection
*/
flushPlayers(): void;
/**
* @param player The player that has been scheduled to run within the application.
* @param context The context as to where the player was bound to
*/
queuePlayer(player: Player, context: ComponentInstance|DirectiveInstance|HTMLElement): void;
}

View File

@ -0,0 +1,72 @@
/**
* @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 '../ng_dev_mode';
import {LContext, getContext} from '../context_discovery';
import {scheduleTick} from '../instructions';
import {LElementNode} from '../interfaces/node';
import {RootContextFlags} from '../interfaces/view';
import {StylingContext, StylingIndex, createEmptyStylingContext} from '../styling';
import {getRootContext} from '../util';
import {CorePlayerHandler} from './core_player_handler';
import {AnimationContext, ComponentInstance, DirectiveInstance, PlayState, Player} from './interfaces';
export function addPlayer(
ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void {
const elementContext = getContext(ref) !;
const animationContext = getOrCreateAnimationContext(elementContext.native, elementContext) !;
animationContext.push(player);
player.addEventListener(PlayState.Destroyed, () => {
const index = animationContext.indexOf(player);
if (index >= 0) {
animationContext.splice(index, 1);
}
player.destroy();
});
const rootContext = getRootContext(elementContext.lViewData);
const playerHandler =
rootContext.playerHandler || (rootContext.playerHandler = new CorePlayerHandler());
playerHandler.queuePlayer(player, ref);
const nothingScheduled = rootContext.flags === RootContextFlags.Empty;
// change detection may or may not happen therefore
// the core code needs to be kicked off to flush the animations
rootContext.flags |= RootContextFlags.FlushPlayers;
if (nothingScheduled) {
scheduleTick(rootContext);
}
}
export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] {
return getOrCreateAnimationContext(ref);
}
export function getOrCreateAnimationContext(
target: {}, context?: LContext | null): AnimationContext {
context = context || getContext(target) !;
if (ngDevMode && !context) {
throw new Error(
'Only elements that exist in an Angular application can be used for animations');
}
const {lViewData, lNodeIndex} = context;
const value = lViewData[lNodeIndex];
let stylingContext = value as StylingContext;
if (!Array.isArray(value)) {
stylingContext = lViewData[lNodeIndex] = createEmptyStylingContext(value as LElementNode);
}
return stylingContext[StylingIndex.AnimationContext] || allocAnimationContext(stylingContext);
}
function allocAnimationContext(data: StylingContext): AnimationContext {
return data[StylingIndex.AnimationContext] = [];
}

View File

@ -12,16 +12,18 @@ import {Type} from '../core';
import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/security';
import {PlayerHandler} from './animations/interfaces';
import {assertComponentType, assertDefined} from './assert';
import {getLElementFromComponent, readPatchedLViewData} from './context_discovery';
import {getComponentDef} from './definition';
import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, leaveView, locateHostElement, setHostBindings, queueHostBindingForCheck,} from './instructions';
import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, hostElement, leaveView, locateHostElement, setHostBindings, queueHostBindingForCheck,} from './instructions';
import {ComponentDef, ComponentDefInternal, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {LViewData, LViewFlags, RootContext, INJECTOR, CONTEXT, TVIEW} from './interfaces/view';
import {stringify} from './util';
import {getComponentDef} from './definition';
import {getLElementFromComponent, readPatchedLViewData} from './context_discovery';
import {CONTEXT, INJECTOR, LViewData, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {getRootView, stringify} from './util';
/** Options that control how the component should be bootstrapped. */
@ -32,6 +34,9 @@ export interface CreateComponentOptions {
/** A custom sanitizer instance */
sanitizer?: Sanitizer;
/** A custom animation player handler */
playerHandler?: PlayerHandler;
/**
* Host element on which the component will be bootstrapped. If not specified,
* the component definition's `tag` is used to query the existing DOM for the
@ -109,7 +114,8 @@ export function renderComponent<T>(
const hostNode = locateHostElement(rendererFactory, opts.host || componentTag);
const rootFlags = componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
LViewFlags.CheckAlways | LViewFlags.IsRoot;
const rootContext = createRootContext(opts.scheduler || requestAnimationFrame.bind(window));
const rootContext = createRootContext(
opts.scheduler || requestAnimationFrame.bind(window), opts.playerHandler || null);
const rootView: LViewData = createLViewData(
rendererFactory.createRenderer(hostNode, componentDef),
@ -157,11 +163,14 @@ export function createRootComponent<T>(
}
export function createRootContext(scheduler: (workFn: () => void) => void): RootContext {
export function createRootContext(
scheduler: (workFn: () => void) => void, playerHandler?: PlayerHandler|null): RootContext {
return {
components: [],
scheduler: scheduler,
clean: CLEAN_PROMISE,
playerHandler: playerHandler || null,
flags: RootContextFlags.Empty
};
}

View File

@ -11,7 +11,6 @@ import {assertEqual} from './assert';
import {LElementNode, TNode, TNodeFlags} from './interfaces/node';
import {RElement} from './interfaces/renderer';
import {CONTEXT, DIRECTIVES, HEADER_OFFSET, LViewData, TVIEW} from './interfaces/view';
import {readElementValue} from './util';
/**
* This property will be monkey-patched on elements, components and directives
@ -401,3 +400,7 @@ function getDirectiveEndIndex(tNode: TNode, startIndex: number): number {
const count = tNode.flags & TNodeFlags.DirectiveCountMask;
return count ? (startIndex + count) : -1;
}
export function readElementValue(value: LElementNode | any[]): LElementNode {
return (Array.isArray(value) ? (value as any as any[])[0] : value) as LElementNode;
}

View File

@ -13,7 +13,7 @@ import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
import {attachPatchData, getLElementFromComponent, readPatchedLViewData} from './context_discovery';
import {attachPatchData, getLElementFromComponent, readElementValue, readPatchedLViewData} from './context_discovery';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
@ -23,12 +23,12 @@ import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LEleme
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getContainerNode, getHostElementNode, getLViewChild, getParentOrContainerNode, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling';
import {assertDataInRangeInternal, getLNode, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, readElementValue, stringify} from './util';
import {assertDataInRangeInternal, getLNode, getRootContext, getRootView, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
import {ViewRef} from './view_ref';
@ -2354,9 +2354,14 @@ export function markViewDirty(view: LViewData): void {
}
currentView[FLAGS] |= LViewFlags.Dirty;
ngDevMode && assertDefined(currentView[CONTEXT], 'rootContext should be defined');
scheduleTick(currentView[CONTEXT] as RootContext);
}
const rootContext = currentView[CONTEXT] as RootContext;
const nothingScheduled = rootContext.flags === RootContextFlags.Empty;
rootContext.flags |= RootContextFlags.DetectChanges;
if (nothingScheduled) {
scheduleTick(rootContext);
}
}
/**
* Used to schedule change detection on the whole application.
@ -2374,9 +2379,21 @@ export function scheduleTick<T>(rootContext: RootContext) {
let res: null|((val: null) => void);
rootContext.clean = new Promise<null>((r) => res = r);
rootContext.scheduler(() => {
tickRootContext(rootContext);
res !(null);
if (rootContext.flags & RootContextFlags.DetectChanges) {
rootContext.flags &= ~RootContextFlags.DetectChanges;
tickRootContext(rootContext);
}
if (rootContext.flags & RootContextFlags.FlushPlayers) {
rootContext.flags &= ~RootContextFlags.FlushPlayers;
const playerHandler = rootContext.playerHandler;
if (playerHandler) {
playerHandler.flushPlayers();
}
}
rootContext.clean = _CLEAN_PROMISE;
res !(null);
});
}
}
@ -2406,22 +2423,6 @@ function tickRootContext(rootContext: RootContext) {
}
}
/**
* Retrieve the root view from any component by walking the parent `LViewData` until
* reaching the root `LViewData`.
*
* @param component any component
*/
export function getRootView(component: any): LViewData {
ngDevMode && assertDefined(component, 'component');
let lViewData = readPatchedLViewData(component) !;
while (lViewData && !(lViewData[FLAGS] & LViewFlags.IsRoot)) {
lViewData = lViewData[PARENT] !;
}
return lViewData;
}
/**
* Synchronously perform change detection on a component (and possibly its sub-components).
*

View File

@ -9,6 +9,7 @@
import {Injector} from '../../di/injector';
import {QueryList} from '../../linker';
import {Sanitizer} from '../../sanitization/security';
import {PlayerHandler} from '../animations/interfaces';
import {LContainer} from './container';
import {ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDefInternal, PipeDefList} from './definition';
@ -499,6 +500,9 @@ export interface TView {
contentQueries: number[]|null;
}
export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10}
/**
* RootContext contains information which is shared for all components which
* were bootstrapped with {@link renderComponent}.
@ -522,6 +526,16 @@ export interface RootContext {
* {@link renderComponent}.
*/
components: {}[];
/**
* The player flushing handler to kick off all animations
*/
playerHandler: PlayerHandler|null;
/**
* What render-related operations to run once a scheduler has been set
*/
flags: RootContextFlags;
}
/**

View File

@ -7,7 +7,7 @@
*/
import {assertDefined} from './assert';
import {attachPatchData} from './context_discovery';
import {attachPatchData, readElementValue} from './context_discovery';
import {callHooks} from './hooks';
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementContainerNode, LElementNode, LTextNode, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
@ -15,7 +15,7 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {CLEANUP, CONTAINER_INDEX, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeType} from './node_assert';
import {getLNode, readElementValue, stringify} from './util';
import {getLNode, stringify} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;

View File

@ -7,11 +7,11 @@
*/
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {AnimationContext} from './animations/interfaces';
import {InitialStylingFlags} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
/**
* The styling context acts as a styling manifest (shaped as an array) for determining which
* styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp`
@ -115,41 +115,47 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
* `updateStylingMap` can include new CSS properties that will be added to the context).
*/
export interface StylingContext extends
Array<InitialStyles|number|string|boolean|LElementNode|StyleSanitizeFn|null> {
Array<InitialStyles|number|string|boolean|LElementNode|StyleSanitizeFn|AnimationContext|null> {
/**
* Location of element that is used as a target for this context.
*/
[0]: LElementNode|null;
[StylingIndex.ElementPosition]: LElementNode|null;
/**
* Location of animation context (which contains the active players) for this element styling
* context.
*/
[StylingIndex.AnimationContext]: AnimationContext|null;
/**
* The style sanitizer that is used within this context
*/
[1]: StyleSanitizeFn|null;
[StylingIndex.StyleSanitizerPosition]: StyleSanitizeFn|null;
/**
* Location of initial data shared by all instances of this style.
*/
[2]: InitialStyles;
[StylingIndex.InitialStylesPosition]: InitialStyles;
/**
* A numeric value representing the configuration status (whether the context is dirty or not)
* mixed together (using bit shifting) with a index value which tells the starting index value
* of where the multi style entries begin.
*/
[3]: number;
[StylingIndex.MasterFlagPosition]: number;
/**
* A numeric value representing the class index offset value. Whenever a single class is
* applied (using `elementClassProp`) it should have an styling index value that doesn't
* need to take into account any style values that exist in the context.
*/
[4]: number;
[StylingIndex.ClassOffsetPosition]: number;
/**
* The last CLASS STRING VALUE that was interpreted by elementStylingMap. This is cached
* So that the algorithm can exit early incase the string has not changed.
*/
[5]: string|null;
[StylingIndex.CachedCssClassString]: string|null;
}
/**
@ -185,18 +191,20 @@ export const enum StylingFlags {
export const enum StylingIndex {
// Position of where the initial styles are stored in the styling context
ElementPosition = 0,
// Position of where the style sanitizer is stored within the styling context
StyleSanitizerPosition = 1,
// Position of where the initial styles are stored in the styling context
InitialStylesPosition = 2,
AnimationContext = 1,
// Position of where the style sanitizer is stored within the styling context
StyleSanitizerPosition = 2,
// Position of where the initial styles are stored in the styling context
InitialStylesPosition = 3,
// Index of location where the start of single properties are stored. (`updateStyleProp`)
MasterFlagPosition = 3,
MasterFlagPosition = 4,
// Index of location where the class index offset value is located
ClassOffsetPosition = 4,
ClassOffsetPosition = 5,
// Position of where the last string-based CSS class value was stored
CachedCssClassString = 5,
CachedCssClassString = 6,
// Location of single (prop) value entries are stored within the context
SingleStylesStartPosition = 6,
SingleStylesStartPosition = 7,
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
FlagsOffset = 0,
PropertyOffset = 1,
@ -223,6 +231,12 @@ export function allocStylingContext(
return context;
}
export function createEmptyStylingContext(
element?: LElementNode | null, sanitizer?: StyleSanitizeFn | null,
initialStylingValues?: InitialStyles): StylingContext {
return [element || null, null, sanitizer || null, initialStylingValues || [null], 0, 0, null];
}
/**
* Creates a styling context template where styling information is stored.
* Any styles that are later referenced using `updateStyleProp` must be
@ -250,7 +264,8 @@ export function createStylingContextTemplate(
initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
styleSanitizer?: StyleSanitizeFn | null): StylingContext {
const initialStylingValues: InitialStyles = [null];
const context: StylingContext = [null, styleSanitizer || null, initialStylingValues, 0, 0, null];
const context: StylingContext =
createEmptyStylingContext(null, styleSanitizer, initialStylingValues);
// we use two maps since a class name might collide with a CSS style prop
const stylesLookup: {[key: string]: number} = {};

View File

@ -7,9 +7,13 @@
*/
import {devModeEqual} from '../change_detection/change_detection_util';
import {assertLessThan} from './assert';
import {assertDefined, assertLessThan} from './assert';
import {readElementValue, readPatchedLViewData} from './context_discovery';
import {LContainerNode, LElementContainerNode, LElementNode, TNode, TNodeFlags} from './interfaces/node';
import {HEADER_OFFSET, LViewData, TData} from './interfaces/view';
import {CONTEXT, FLAGS, HEADER_OFFSET, LViewData, LViewFlags, PARENT, RootContext, TData} from './interfaces/view';
/**
* Returns whether the values are different from a change detection stand point.
@ -87,10 +91,6 @@ export function loadElementInternal(index: number, arr: LViewData): LElementNode
return readElementValue(value);
}
export function readElementValue(value: LElementNode | any[]): LElementNode {
return (Array.isArray(value) ? (value as any as any[])[0] : value) as LElementNode;
}
export function getLNode(tNode: TNode, hostView: LViewData): LElementNode|LContainerNode|
LElementContainerNode {
return readElementValue(hostView[tNode.index]);
@ -103,3 +103,21 @@ export function isContentQueryHost(tNode: TNode): boolean {
export function isComponent(tNode: TNode): boolean {
return (tNode.flags & TNodeFlags.isComponent) === TNodeFlags.isComponent;
}
/**
* Retrieve the root view from any component by walking the parent `LViewData` until
* reaching the root `LViewData`.
*
* @param component any component
*/
export function getRootView(target: LViewData | {}): LViewData {
ngDevMode && assertDefined(target, 'component');
let lViewData = Array.isArray(target) ? (target as LViewData) : readPatchedLViewData(target) !;
while (lViewData && !(lViewData[FLAGS] & LViewFlags.IsRoot)) {
lViewData = lViewData[PARENT] !;
}
return lViewData;
}
export function getRootContext(viewOrComponent: LViewData | {}): RootContext {
return getRootView(viewOrComponent)[CONTEXT] as RootContext;
}

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"
},

View File

@ -0,0 +1,60 @@
/**
* @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 {CorePlayerHandler} from '../../../src/render3/animations/core_player_handler';
import {PlayState} from '../../../src/render3/animations/interfaces';
import {MockPlayer} from './mock_player';
describe('CorePlayerHandler', () => {
it('should kick off any animation players that have been queued once flushed', () => {
const handler = new CorePlayerHandler();
const p1 = new MockPlayer();
const p2 = new MockPlayer();
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
handler.queuePlayer(p1);
handler.queuePlayer(p2);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
handler.flushPlayers();
expect(p1.state).toEqual(PlayState.Running);
expect(p2.state).toEqual(PlayState.Running);
});
it('should only kick off animation players that have not been adopted by a parent player once flushed',
() => {
const handler = new CorePlayerHandler();
const pRoot = new MockPlayer();
const p1 = new MockPlayer();
const p2 = new MockPlayer();
expect(pRoot.state).toEqual(PlayState.Pending);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
handler.queuePlayer(pRoot);
handler.queuePlayer(p1);
handler.queuePlayer(p2);
expect(pRoot.state).toEqual(PlayState.Pending);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
p1.parent = pRoot;
handler.flushPlayers();
expect(pRoot.state).toEqual(PlayState.Running);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Running);
});
});

View File

@ -0,0 +1,58 @@
/**
* @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 {PlayState, Player} from '../../../src/render3/animations/interfaces';
export class MockPlayer implements Player {
parent: Player|null = null;
log: string[] = [];
state: PlayState = PlayState.Pending;
private _listeners: {[state: string]: (() => any)[]} = {};
play(): void {
if (this.state === PlayState.Running) return;
this.state = PlayState.Running;
this._emit(PlayState.Running);
}
pause(): void {
if (this.state === PlayState.Paused) return;
this.state = PlayState.Paused;
this._emit(PlayState.Paused);
}
finish(): void {
if (this.state >= PlayState.Finished) return;
this.state = PlayState.Finished;
this._emit(PlayState.Finished);
}
destroy(): void {
if (this.state >= PlayState.Destroyed) return;
this.state = PlayState.Destroyed;
this._emit(PlayState.Destroyed);
}
addEventListener(state: PlayState|number, cb: () => any): void {
const key = state.toString();
const arr = this._listeners[key] || (this._listeners[key] = []);
arr.push(cb);
}
private _emit(state: PlayState) {
const callbacks = this._listeners[state] || [];
for (let i = 0; i < callbacks.length; i++) {
const cb = callbacks[i];
cb();
}
}
}

View File

@ -0,0 +1,311 @@
/**
* @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 {RenderFlags} from '@angular/core/src/render3';
import {AnimationContext, PlayState, Player, PlayerHandler} from '../../../src/render3/animations/interfaces';
import {addPlayer, getOrCreateAnimationContext, getPlayers} from '../../../src/render3/animations/players';
import {QUERY_READ_FROM_NODE, defineComponent, getHostElement} from '../../../src/render3/index';
import {element, elementEnd, elementStart, elementStyling, elementStylingApply, load, markDirty} from '../../../src/render3/instructions';
import {RElement} from '../../../src/render3/interfaces/renderer';
import {QueryList, query, queryRefresh} from '../../../src/render3/query';
import {ComponentFixture} from '../render_util';
import {MockPlayer} from './mock_player';
describe('animation player access', () => {
it('should add a player to the element', () => {
const element = buildElement();
expect(getPlayers(element)).toEqual([]);
const player = new MockPlayer();
addPlayer(element, player);
expect(getPlayers(element)).toEqual([player]);
});
it('should add a player to the component host element', () => {
const fixture = buildSuperComponent();
const superComp = fixture.component;
const component = superComp.query.first as Comp;
expect(component.name).toEqual('child-comp');
expect(getPlayers(component)).toEqual([]);
const player = new MockPlayer();
addPlayer(component, player);
expect(getPlayers(component)).toEqual([player]);
const hostElement = getHostElement(component);
expect(getPlayers(hostElement)).toEqual([player]);
});
it('should add a player to an element that already contains styling', () => {
const element = buildElementWithStyling();
expect(getPlayers(element)).toEqual([]);
const player = new MockPlayer();
addPlayer(element, player);
expect(getPlayers(element)).toEqual([player]);
});
it('should add a player to the element animation context and remove it once it completes', () => {
const element = buildElement();
const context = getOrCreateAnimationContext(element);
expect(context).toEqual([]);
const player = new MockPlayer();
addPlayer(element, player);
expect(readPlayers(context)).toEqual([player]);
player.destroy();
expect(readPlayers(context)).toEqual([]);
});
it('should flush all pending animation players after change detection', () => {
const fixture = buildComponent();
const element = fixture.hostElement.querySelector('div') !;
const player = new MockPlayer();
addPlayer(element, player);
expect(player.state).toEqual(PlayState.Pending);
fixture.update();
expect(player.state).toEqual(PlayState.Running);
});
it('should flush all animations in the given animation handler is apart of the component', () => {
const handler = new MockPlayerHandler();
const fixture = new ComponentFixture(Comp, {playerHandler: handler});
fixture.update();
const element = fixture.hostElement.querySelector('div') !;
const p1 = new MockPlayer();
const p2 = new MockPlayer();
addPlayer(element, p1);
addPlayer(element, p2);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
fixture.update();
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
expect(handler.lastFlushedPlayers).toEqual([p1, p2]);
});
it('should only play animation players that are not associated with a parent player', () => {
const fixture = buildComponent();
const element = fixture.hostElement.querySelector('div') !;
const p1 = new MockPlayer();
const p2 = new MockPlayer();
const pParent = new MockPlayer();
p1.parent = pParent;
addPlayer(element, p1);
addPlayer(element, p2);
addPlayer(element, pParent);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
expect(pParent.state).toEqual(PlayState.Pending);
fixture.update();
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Running);
expect(pParent.state).toEqual(PlayState.Running);
});
it('should not replay any previously queued players once change detection has run', () => {
const fixture = buildComponent();
const element = fixture.hostElement.querySelector('div') !;
const p1 = new MockPlayer();
const p2 = new MockPlayer();
const p3 = new MockPlayer();
addPlayer(element, p1);
addPlayer(element, p2);
expect(p1.state).toEqual(PlayState.Pending);
expect(p2.state).toEqual(PlayState.Pending);
expect(p3.state).toEqual(PlayState.Pending);
fixture.update();
expect(p1.state).toEqual(PlayState.Running);
expect(p2.state).toEqual(PlayState.Running);
expect(p3.state).toEqual(PlayState.Pending);
p1.pause();
p2.pause();
addPlayer(element, p3);
expect(p1.state).toEqual(PlayState.Paused);
expect(p2.state).toEqual(PlayState.Paused);
expect(p3.state).toEqual(PlayState.Pending);
fixture.update();
expect(p1.state).toEqual(PlayState.Paused);
expect(p2.state).toEqual(PlayState.Paused);
expect(p3.state).toEqual(PlayState.Running);
});
it('should not run change detection on a template if only players are being added', () => {
const fixture = buildComponent();
const element = fixture.hostElement.querySelector('div') !;
let dcCount = 0;
fixture.component.logger = () => { dcCount++; };
const p1 = new MockPlayer();
addPlayer(element, p1);
expect(p1.state).toEqual(PlayState.Pending);
expect(dcCount).toEqual(0);
fixture.requestAnimationFrame.flush();
expect(p1.state).toEqual(PlayState.Running);
expect(dcCount).toEqual(0);
const p2 = new MockPlayer();
addPlayer(element, p2);
markDirty(fixture.component);
expect(p2.state).toEqual(PlayState.Pending);
fixture.requestAnimationFrame.flush();
expect(p2.state).toEqual(PlayState.Running);
expect(p1.state).toEqual(PlayState.Running);
expect(dcCount).toEqual(1);
const p3 = new MockPlayer();
addPlayer(element, p3);
fixture.requestAnimationFrame.flush();
expect(p3.state).toEqual(PlayState.Running);
expect(p2.state).toEqual(PlayState.Running);
expect(p1.state).toEqual(PlayState.Running);
expect(dcCount).toEqual(1);
});
});
function buildElement() {
return buildComponent().hostElement.querySelector('div') as RElement;
}
function buildComponent() {
const fixture = new ComponentFixture(Comp);
fixture.update();
return fixture;
}
function buildSuperComponent() {
const fixture = new ComponentFixture(SuperComp);
fixture.update();
return fixture;
}
function buildElementWithStyling() {
const fixture = new ComponentFixture(CompWithStyling);
fixture.update();
return fixture.hostElement.querySelector('div') as RElement;
}
function readPlayers(context: AnimationContext): Player[] {
return context;
}
class Comp {
static ngComponentDef = defineComponent({
type: Comp,
exportAs: 'child',
selectors: [['child-comp']],
factory: () => new Comp(),
consts: 1,
vars: 0,
template: (rf: RenderFlags, ctx: Comp) => {
if (rf & RenderFlags.Create) {
element(0, 'div');
}
ctx.logger();
}
});
name = 'child-comp';
logger: () => any = () => {};
}
class CompWithStyling {
static ngComponentDef = defineComponent({
type: CompWithStyling,
exportAs: 'child-styled',
selectors: [['child-styled-comp']],
factory: () => new CompWithStyling(),
consts: 1,
vars: 0,
template: (rf: RenderFlags, ctx: CompWithStyling) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
elementStyling(['fooClass']);
elementEnd();
}
if (rf & RenderFlags.Update) {
elementStylingApply(0);
}
}
});
name = 'child-styled-comp';
}
class SuperComp {
static ngComponentDef = defineComponent({
type: SuperComp,
selectors: [['super-comp']],
factory: () => new SuperComp(),
consts: 3,
vars: 0,
template: (rf: RenderFlags, ctx: SuperComp) => {
if (rf & RenderFlags.Create) {
elementStart(1, 'div');
element(2, 'child-comp', ['child', ''], ['child', 'child']);
elementEnd();
}
},
viewQuery: function(rf: RenderFlags, ctx: SuperComp) {
if (rf & RenderFlags.Create) {
query(0, ['child'], true, QUERY_READ_FROM_NODE);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
}
},
directives: [Comp]
});
name = 'super-comp';
query !: QueryList<any>;
}
class MockPlayerHandler implements PlayerHandler {
players: Player[] = [];
lastFlushedPlayers: Player[] = [];
flushPlayers(): void {
this.lastFlushedPlayers = [...this.players];
this.players = [];
}
queuePlayer(player: Player): void { this.players.push(player); }
}

View File

@ -9,6 +9,7 @@
import {stringifyElement} from '@angular/platform-browser/testing/src/browser_util';
import {Injector} from '../../src/di/injector';
import {PlayerHandler} from '../../src/render3/animations/interfaces';
import {CreateComponentOptions} from '../../src/render3/component';
import {getContext, isComponentInstance} from '../../src/render3/context_discovery';
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
@ -103,9 +104,12 @@ export class ComponentFixture<T> extends BaseFixture {
component: T;
requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];};
constructor(
private componentType: ComponentType<T>,
opts: {injector?: Injector, sanitizer?: Sanitizer, rendererFactory?: RendererFactory3} = {}) {
constructor(private componentType: ComponentType<T>, opts: {
injector?: Injector,
sanitizer?: Sanitizer,
rendererFactory?: RendererFactory3,
playerHandler?: PlayerHandler
} = {}) {
super();
this.requestAnimationFrame = function(fn: () => void) {
requestAnimationFrame.queue.push(fn);
@ -122,7 +126,8 @@ export class ComponentFixture<T> extends BaseFixture {
scheduler: this.requestAnimationFrame,
injector: opts.injector,
sanitizer: opts.sanitizer,
rendererFactory: opts.rendererFactory || domRendererFactory3
rendererFactory: opts.rendererFactory || domRendererFactory3,
playerHandler: opts.playerHandler
});
}

View File

@ -109,7 +109,7 @@ describe('styling', () => {
describe('createStylingContextTemplate', () => {
it('should initialize empty template', () => {
const template = initContext();
expect(template).toEqual([element, null, [null], cleanStyle(0, 6), 0, null]);
expect(template).toEqual([element, null, null, [null], cleanStyle(0, 7), 0, null]);
});
it('should initialize static styles', () => {
@ -118,28 +118,29 @@ describe('styling', () => {
expect(template).toEqual([
element,
null,
null,
[null, 'red', '10px'],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
0,
null,
// #6
cleanStyle(1, 12),
// #7
cleanStyle(1, 13),
'color',
null,
// #9
cleanStyle(2, 15),
// #10
cleanStyle(2, 16),
'width',
null,
// #12
dirtyStyle(1, 6),
// #13
dirtyStyle(1, 7),
'color',
null,
// #15
dirtyStyle(2, 9),
// #16
dirtyStyle(2, 10),
'width',
null,
]);
@ -320,28 +321,29 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
2,
null,
// #6
cleanStyle(0, 12),
// #7
cleanStyle(0, 13),
'width',
null,
// #9
cleanStyle(0, 15),
// #10
cleanStyle(0, 16),
'height',
null,
// #12
dirtyStyle(0, 6),
// #13
dirtyStyle(0, 7),
'width',
'100px',
// #15
dirtyStyle(0, 9),
// #16
dirtyStyle(0, 10),
'height',
'100px',
]);
@ -352,33 +354,34 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
2,
null,
// #6
cleanStyle(0, 12),
// #7
cleanStyle(0, 13),
'width',
null,
// #9
cleanStyle(0, 18),
// #10
cleanStyle(0, 19),
'height',
null,
// #12
dirtyStyle(0, 6),
// #13
dirtyStyle(0, 7),
'width',
'200px',
// #15
// #16
dirtyStyle(),
'opacity',
'0',
// #18
dirtyStyle(0, 9),
// #19
dirtyStyle(0, 10),
'height',
null,
]);
@ -387,33 +390,34 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
cleanStyle(0, 12), //
cleanStyle(0, 13), //
2,
null,
// #6
cleanStyle(0, 12),
// #7
cleanStyle(0, 13),
'width',
null,
// #9
cleanStyle(0, 18),
// #10
cleanStyle(0, 19),
'height',
null,
// #12
cleanStyle(0, 6),
// #13
cleanStyle(0, 7),
'width',
'200px',
// #15
// #16
cleanStyle(),
'opacity',
'0',
// #18
cleanStyle(0, 9),
// #19
cleanStyle(0, 10),
'height',
null,
]);
@ -424,33 +428,34 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
2,
null,
// #6
dirtyStyle(0, 12),
// #7
dirtyStyle(0, 13),
'width',
'300px',
// #9
cleanStyle(0, 18),
// #10
cleanStyle(0, 19),
'height',
null,
// #12
cleanStyle(0, 6),
// #13
cleanStyle(0, 7),
'width',
null,
// #15
// #16
dirtyStyle(),
'opacity',
null,
// #18
cleanStyle(0, 9),
// #19
cleanStyle(0, 10),
'height',
null,
]);
@ -461,33 +466,34 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
2,
null,
// #6
dirtyStyle(0, 12),
// #7
dirtyStyle(0, 13),
'width',
null,
// #9
cleanStyle(0, 18),
// #10
cleanStyle(0, 19),
'height',
null,
// #12
cleanStyle(0, 6),
// #13
cleanStyle(0, 7),
'width',
null,
// #15
// #16
cleanStyle(),
'opacity',
null,
// #18
cleanStyle(0, 9),
// #19
cleanStyle(0, 10),
'height',
null,
]);
@ -503,33 +509,34 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 9), //
dirtyStyle(0, 10), //
1,
null,
// #6
cleanStyle(0, 18),
// #7
cleanStyle(0, 19),
'lineHeight',
null,
// #9
// #10
dirtyStyle(),
'width',
'100px',
// #12
// #13
dirtyStyle(),
'height',
'100px',
// #15
// #16
dirtyStyle(),
'opacity',
'0.5',
// #18
cleanStyle(0, 6),
// #19
cleanStyle(0, 7),
'lineHeight',
null,
]);
@ -540,33 +547,34 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 9), //
dirtyStyle(0, 10), //
1,
null,
// #6
cleanStyle(0, 18),
// #7
cleanStyle(0, 19),
'lineHeight',
null,
// #9
// #10
dirtyStyle(),
'width',
null,
// #12
// #13
dirtyStyle(),
'height',
null,
// #15
// #16
dirtyStyle(),
'opacity',
null,
// #18
cleanStyle(0, 6),
// #19
cleanStyle(0, 7),
'lineHeight',
null,
]);
@ -579,38 +587,39 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 9), //
dirtyStyle(0, 10), //
1,
null,
// #6
cleanStyle(0, 21),
// #7
cleanStyle(0, 22),
'lineHeight',
null,
// #9
// #10
dirtyStyle(),
'borderWidth',
'5px',
// #12
// #13
cleanStyle(),
'width',
null,
// #15
// #16
cleanStyle(),
'height',
null,
// #18
// #19
cleanStyle(),
'opacity',
null,
// #21
cleanStyle(0, 6),
// #22
cleanStyle(0, 7),
'lineHeight',
null,
]);
@ -620,38 +629,39 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 9), //
dirtyStyle(0, 10), //
1,
null,
// #6
dirtyStyle(0, 21),
// #7
dirtyStyle(0, 22),
'lineHeight',
'200px',
// #9
// #10
dirtyStyle(),
'borderWidth',
'5px',
// #12
// #13
cleanStyle(),
'width',
null,
// #15
// #16
cleanStyle(),
'height',
null,
// #18
// #19
cleanStyle(),
'opacity',
null,
// #21
cleanStyle(0, 6),
// #22
cleanStyle(0, 7),
'lineHeight',
null,
]);
@ -661,43 +671,44 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 9), //
dirtyStyle(0, 10), //
1,
null,
// #6
dirtyStyle(0, 24),
// #7
dirtyStyle(0, 25),
'lineHeight',
'200px',
// #9
// #10
dirtyStyle(),
'borderWidth',
'15px',
// #12
// #13
dirtyStyle(),
'borderColor',
'red',
// #15
// #16
cleanStyle(),
'width',
null,
// #18
// #19
cleanStyle(),
'height',
null,
// #21
// #22
cleanStyle(),
'opacity',
null,
// #24
cleanStyle(0, 6),
// #25
cleanStyle(0, 7),
'lineHeight',
null,
]);
@ -716,23 +727,24 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
dirtyStyle(0, 9), //
dirtyStyle(0, 10), //
1,
null,
// #6
dirtyStyle(0, 12),
// #7
dirtyStyle(0, 13),
'height',
'200px',
// #6
// #7
dirtyStyle(),
'width',
'100px',
// #12
cleanStyle(0, 6),
// #13
cleanStyle(0, 7),
'height',
null,
]);
@ -742,23 +754,24 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null],
cleanStyle(0, 9), //
cleanStyle(0, 10), //
1,
null,
// #6
cleanStyle(0, 12),
// #7
cleanStyle(0, 13),
'height',
'200px',
// #6
// #7
cleanStyle(),
'width',
'100px',
// #12
cleanStyle(0, 6),
// #13
cleanStyle(0, 7),
'height',
null,
]);
@ -776,29 +789,30 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
styleSanitizer,
[null],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
2,
null,
// #6
dirtyStyleWithSanitization(0, 12),
// #7
dirtyStyleWithSanitization(0, 13),
'border-image',
'url(foo.jpg)',
// #9
dirtyStyle(0, 15),
// #10
dirtyStyle(0, 16),
'border-width',
'100px',
// #12
cleanStyleWithSanitization(0, 6),
// #13
cleanStyleWithSanitization(0, 7),
'border-image',
null,
// #15
cleanStyle(0, 9),
// #16
cleanStyle(0, 10),
'border-width',
null,
]);
@ -807,34 +821,35 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
styleSanitizer,
[null],
dirtyStyle(0, 12), //
dirtyStyle(0, 13), //
2,
null,
// #6
dirtyStyleWithSanitization(0, 15),
// #7
dirtyStyleWithSanitization(0, 16),
'border-image',
'url(foo.jpg)',
// #9
dirtyStyle(0, 18),
// #10
dirtyStyle(0, 19),
'border-width',
'100px',
// #12
// #13
dirtyStyleWithSanitization(0, 0),
'background-image',
'unsafe',
// #15
cleanStyleWithSanitization(0, 6),
// #16
cleanStyleWithSanitization(0, 7),
'border-image',
null,
// #18
cleanStyle(0, 9),
// #19
cleanStyle(0, 10),
'border-width',
null,
]);
@ -843,34 +858,35 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
styleSanitizer,
[null],
cleanStyle(0, 12), //
cleanStyle(0, 13), //
2,
null,
// #6
cleanStyleWithSanitization(0, 15),
// #7
cleanStyleWithSanitization(0, 16),
'border-image',
'url(foo.jpg)',
// #9
cleanStyle(0, 18),
// #10
cleanStyle(0, 19),
'border-width',
'100px',
// #12
// #13
cleanStyleWithSanitization(0, 0),
'background-image',
'unsafe',
// #15
cleanStyleWithSanitization(0, 6),
// #16
cleanStyleWithSanitization(0, 7),
'border-image',
null,
// #18
cleanStyle(0, 9),
// #19
cleanStyle(0, 10),
'border-width',
null,
]);
@ -883,20 +899,20 @@ describe('styling', () => {
const template =
initContext(null, [InitialStylingFlags.VALUES_MODE, 'one', true, 'two', true]);
expect(template).toEqual([
element, null, [null, true, true], dirtyStyle(0, 12), //
element, null, null, [null, true, true], dirtyStyle(0, 13), //
0, null,
// #6
cleanClass(1, 12), 'one', null,
// #7
cleanClass(1, 13), 'one', null,
// #9
cleanClass(2, 15), 'two', null,
// #10
cleanClass(2, 16), 'two', null,
// #12
dirtyClass(1, 6), 'one', null,
// #13
dirtyClass(1, 7), 'one', null,
// #15
dirtyClass(2, 9), 'two', null
// #16
dirtyClass(2, 10), 'two', null
]);
});
@ -948,48 +964,49 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null, '100px', true],
dirtyStyle(0, 18), //
dirtyStyle(0, 19), //
2,
null,
// #6
cleanStyle(1, 18),
// #7
cleanStyle(1, 19),
'width',
null,
// #9
cleanStyle(0, 21),
// #10
cleanStyle(0, 22),
'height',
null,
// #12
cleanClass(2, 24),
// #13
cleanClass(2, 25),
'wide',
null,
// #15
cleanClass(0, 27),
// #16
cleanClass(0, 28),
'tall',
null,
// #18
dirtyStyle(1, 6),
// #19
dirtyStyle(1, 7),
'width',
null,
// #21
cleanStyle(0, 9),
// #22
cleanStyle(0, 10),
'height',
null,
// #24
dirtyClass(2, 12),
// #25
dirtyClass(2, 13),
'wide',
null,
// #27
cleanClass(0, 15),
// #28
cleanClass(0, 16),
'tall',
null,
]);
@ -1000,58 +1017,59 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null, '100px', true],
dirtyStyle(0, 18), //
dirtyStyle(0, 19), //
2,
'tall round',
// #6
cleanStyle(1, 18),
// #7
cleanStyle(1, 19),
'width',
null,
// #9
cleanStyle(0, 33),
// #10
cleanStyle(0, 34),
'height',
null,
// #12
cleanClass(2, 30),
// #13
cleanClass(2, 31),
'wide',
null,
// #15
cleanClass(0, 24),
// #16
cleanClass(0, 25),
'tall',
null,
// #18
dirtyStyle(1, 6),
// #19
dirtyStyle(1, 7),
'width',
'200px',
// #21
// #22
dirtyStyle(0, 0),
'opacity',
'0.5',
// #24
dirtyClass(0, 15),
// #25
dirtyClass(0, 16),
'tall',
true,
// #27
// #28
dirtyClass(0, 0),
'round',
true,
// #30
cleanClass(2, 12),
// #31
cleanClass(2, 13),
'wide',
null,
// #33
cleanStyle(0, 9),
// #34
cleanStyle(0, 10),
'height',
null,
]);
@ -1066,58 +1084,59 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
null,
null,
[null, '100px', true],
dirtyStyle(0, 18), //
dirtyStyle(0, 19), //
2,
null,
// #6
dirtyStyle(1, 18),
// #7
dirtyStyle(1, 19),
'width',
'300px',
// #9
cleanStyle(0, 33),
// #10
cleanStyle(0, 34),
'height',
null,
// #12
cleanClass(2, 24),
// #13
cleanClass(2, 25),
'wide',
null,
// #15
cleanClass(0, 21),
// #16
cleanClass(0, 22),
'tall',
null,
// #18
cleanStyle(1, 6),
// #19
cleanStyle(1, 7),
'width',
'500px',
// #21
cleanClass(0, 15),
// #22
cleanClass(0, 16),
'tall',
true,
// #24
cleanClass(2, 12),
// #25
cleanClass(2, 13),
'wide',
true,
// #27
// #28
dirtyClass(0, 0),
'round',
null,
// #30
// #31
dirtyStyle(0, 0),
'opacity',
null,
// #33
cleanStyle(0, 9),
// #34
cleanStyle(0, 10),
'height',
null,
]);