feat(ivy): provide groundwork for animations in core (#25234)
PR Close #25234
This commit is contained in:
parent
a880686081
commit
82a14dc107
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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); }
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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] = [];
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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} = {};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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 }
|
||||
}
|
|
@ -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
|
@ -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>
|
|
@ -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});
|
|
@ -416,6 +416,9 @@
|
|||
{
|
||||
"name": "createEmbeddedViewAndNode"
|
||||
},
|
||||
{
|
||||
"name": "createEmptyStylingContext"
|
||||
},
|
||||
{
|
||||
"name": "createLContainer"
|
||||
},
|
||||
|
|
|
@ -1286,6 +1286,9 @@
|
|||
{
|
||||
"name": "createEmbeddedViewAndNode"
|
||||
},
|
||||
{
|
||||
"name": "createEmptyStylingContext"
|
||||
},
|
||||
{
|
||||
"name": "createInjector"
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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); }
|
||||
}
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
|
|
Loading…
Reference in New Issue