Skip to content

Commit

Permalink
feat(ivy): enhance [style] and [class] bindings to be animation aware
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko committed Oct 15, 2018
1 parent d059ef1 commit 68d6430
Show file tree
Hide file tree
Showing 21 changed files with 2,119 additions and 443 deletions.
5 changes: 5 additions & 0 deletions packages/core/src/core_render3_private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export {

export {
Player as ɵPlayer,
PlayerFactory as ɵPlayerFactory,
PlayState as ɵPlayState,
PlayerHandler as ɵPlayerHandler,
} from './render3/interfaces/player';
Expand All @@ -171,6 +172,10 @@ export {
LContext as ɵLContext,
} from './render3/interfaces/context';

export {
bindPlayerFactory as ɵbindPlayerFactory,
} from './render3/styling/player_factory';

export {
addPlayer as ɵaddPlayer,
getPlayers as ɵgetPlayers,
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/render3/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags
import {getRootView, readElementValue, readPatchedLViewData, stringify} from './util';



// Root component will always have an element index of 0 and an injector size of 1
const ROOT_EXPANDO_INSTRUCTIONS = [0, 1];

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/render3/discovery_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ export function getHostComponent<T = {}>(target: {}): T|null {
* Returns the `RootContext` instance that is associated with
* the application where the target is situated.
*/
export function getRootContext(target: {}): RootContext {
const context = loadContext(target) !;
const rootLViewData = getRootView(context.lViewData);
export function getRootContext(target: LViewData | {}): RootContext {
const lViewData = Array.isArray(target) ? target : loadContext(target) !.lViewData;
const rootLViewData = getRootView(lViewData);
return rootLViewData[CONTEXT] as RootContext;
}

Expand Down
44 changes: 27 additions & 17 deletions packages/core/src/render3/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player';
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, FLAGS, HEADER_OFFSET, HOST, 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, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory';
import {getStylingContext} from './styling/util';
import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';



Expand Down Expand Up @@ -1501,9 +1503,11 @@ function generatePropertyAliases(
* renaming as part of minification.
* @param value A value indicating if a given class should be added or removed.
*/
export function elementClassProp<T>(
index: number, stylingIndex: number, value: T | NO_CHANGE): void {
updateElementClassProp(getStylingContext(index, viewData), stylingIndex, value ? true : false);
export function elementClassProp(
index: number, stylingIndex: number, value: boolean | {} | PlayerFactory<boolean>): void {
const val =
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
updateElementClassProp(getStylingContext(index, viewData), stylingIndex, val);
}

/**
Expand Down Expand Up @@ -1534,7 +1538,7 @@ export function elementClassProp<T>(
* @param styleSanitizer An optional sanitizer function that will be used (if provided)
* to sanitize the any CSS property values that are applied to the element (during rendering).
*/
export function elementStyling<T>(
export function elementStyling(
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
styleSanitizer?: StyleSanitizeFn | null): void {
Expand Down Expand Up @@ -1565,8 +1569,13 @@ export function elementStyling<T>(
* specifically for element styling--the index must be the next index after the element
* index.)
*/
export function elementStylingApply<T>(index: number): void {
renderElementStyles(getStylingContext(index, viewData), renderer);
export function elementStylingApply(index: number): void {
const totalPlayersQueued =
renderStyleAndClassBindings(getStylingContext(index, viewData), renderer, viewData);
if (totalPlayersQueued > 0) {
const rootContext = getRootContext(viewData);
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
}
}

/**
Expand All @@ -1589,8 +1598,10 @@ export function elementStylingApply<T>(index: number): void {
* Note that when a suffix is provided then the underlying sanitizer will
* be ignored.
*/
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null, suffix?: string): void {
export function elementStyleProp(
index: number, styleIndex: number,
value: string | number | {} | PlayerFactory<{[key: string]: any}>| null,
suffix?: string): void {
let valueToAdd: string|null = null;
if (value) {
if (suffix) {
Expand Down Expand Up @@ -2386,11 +2397,7 @@ export function markViewDirty(view: LViewData): void {
ngDevMode && assertDefined(currentView[CONTEXT], 'rootContext should be defined');

const rootContext = currentView[CONTEXT] as RootContext;
const nothingScheduled = rootContext.flags === RootContextFlags.Empty;
rootContext.flags |= RootContextFlags.DetectChanges;
if (nothingScheduled) {
scheduleTick(rootContext);
}
scheduleTick(rootContext, RootContextFlags.DetectChanges);
}

/**
Expand All @@ -2404,8 +2411,11 @@ export function markViewDirty(view: LViewData): void {
* `scheduleTick` requests. The scheduling function can be overridden in
* `renderComponent`'s `scheduler` option.
*/
export function scheduleTick<T>(rootContext: RootContext) {
if (rootContext.clean == _CLEAN_PROMISE) {
export function scheduleTick<T>(rootContext: RootContext, flags: RootContextFlags) {
const nothingScheduled = rootContext.flags === RootContextFlags.Empty;
rootContext.flags |= flags;

if (nothingScheduled && rootContext.clean == _CLEAN_PROMISE) {
let res: null|((val: null) => void);
rootContext.clean = new Promise<null>((r) => res = r);
rootContext.scheduler(() => {
Expand Down
75 changes: 71 additions & 4 deletions packages/core/src/render3/interfaces/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,43 @@ export interface Player {
addEventListener(state: PlayState|string, cb: (data?: any) => any): void;
}

export const enum BindingType {
Unset = 0,
Class = 2,
Style = 3,
}

export interface BindingStore { setValue(prop: string, value: any): void; }

/**
* Used to produce a player that will be placed on an element that contains
* styling bindings that make use of the player. This function is designed
* to be used with `PlayerFactory`.
*/
export interface PlayerFactoryBuildFn {
(element: HTMLElement, type: BindingType, values: {[key: string]: any},
currentPlayer: Player|null): Player|null;
}

/**
* Used as a reference to build a player from a styling template binding
* (`[style]` and `[class]`).
*
* The `fn` function will be called once any styling-related changes are
* evaluated on an element and is expected to return a player that will
* be then run on the element.
*
* `[style]`, `[style.prop]`, `[class]` and `[class.name]` template bindings
* all accept a `PlayerFactory` as input and this player factories.
*/
export interface PlayerFactory<T> {
'__brand__': 'Brand for PlayerFactory that nothing will match';
}

export interface PlayerBuilder extends BindingStore {
buildPlayer(currentPlayer: Player|null): Player|undefined|null;
}

/**
* The state of a given player
*
Expand All @@ -29,11 +66,15 @@ export interface Player {
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.
* The context that stores all the active players and queued player factories present on an element.
*/
export declare type PlayerContext = Player[];
export declare type ComponentInstance = {};
export declare type DirectiveInstance = {};
export interface PlayerContext extends Array<null|number|Player|PlayerBuilder> {
[PlayerIndex.NonBuilderPlayersStart]: number;
[PlayerIndex.ClassMapPlayerBuilderPosition]: PlayerBuilder|null;
[PlayerIndex.ClassMapPlayerPosition]: Player|null;
[PlayerIndex.StyleMapPlayerBuilderPosition]: PlayerBuilder|null;
[PlayerIndex.StyleMapPlayerPosition]: Player|null;
}

/**
* Designed to be used as an injection service to capture all animation players.
Expand All @@ -54,3 +95,29 @@ export interface PlayerHandler {
*/
queuePlayer(player: Player, context: ComponentInstance|DirectiveInstance|HTMLElement): void;
}

export const enum PlayerIndex {
// The position where the index that reveals where players start in the PlayerContext
NonBuilderPlayersStart = 0,
// The position where the player builder lives (which handles {key:value} map expression) for
// classes
ClassMapPlayerBuilderPosition = 1,
// The position where the last player assigned to the class player builder is stored
ClassMapPlayerPosition = 2,
// The position where the player builder lives (which handles {key:value} map expression) for
// styles
StyleMapPlayerBuilderPosition = 3,
// The position where the last player assigned to the style player builder is stored
StyleMapPlayerPosition = 4,
// The position where any player builders start in the PlayerContext
PlayerBuildersStartPosition = 1,
// The position where non map-based player builders start in the PlayerContext
SinglePlayerBuildersStartPosition = 5,
// For each player builder there is a player in the player context (therefore size = 2)
PlayerAndPlayerBuildersTupleSize = 2,
// The player exists next to the player builder in the list
PlayerOffsetPosition = 1,
}

export declare type ComponentInstance = {};
export declare type DirectiveInstance = {};
26 changes: 13 additions & 13 deletions packages/core/src/render3/interfaces/styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
* 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 {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {RElement} from '../interfaces/renderer';

import {PlayerContext} from './player';



/**
* 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`
Expand Down Expand Up @@ -184,17 +181,19 @@ export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
*/
export const enum StylingFlags {
// Implies no configurations
None = 0b000,
None = 0b0000,
// Whether or not the entry or context itself is dirty
Dirty = 0b001,
Dirty = 0b0001,
// Whether or not this is a class-based assignment
Class = 0b010,
Class = 0b0010,
// Whether or not a sanitizer was applied to this property
Sanitize = 0b100,
Sanitize = 0b0100,
// Whether or not any player builders within need to produce new players
PlayerBuildersDirty = 0b1000,
// The max amount of bits used to represent these configuration values
BitCountSize = 3,
BitCountSize = 4,
// There are only three bits here
BitMask = 0b111
BitMask = 0b1111
}

/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
Expand Down Expand Up @@ -222,10 +221,11 @@ export const enum StylingIndex {
FlagsOffset = 0,
PropertyOffset = 1,
ValueOffset = 2,
// Size of each multi or single entry (flag + prop + value)
Size = 3,
PlayerBuilderIndexOffset = 3,
// Size of each multi or single entry (flag + prop + value + playerBuilderIndex)
Size = 4,
// Each flag has a binary digit length of this value
BitCountSize = 14, // (32 - 3) / 2 = ~14
BitCountSize = 14, // (32 - 4) / 2 = ~14
// The binary digit value as a mask
BitMask = 0b11111111111111 // 14 bits
BitMask = 0b11111111111111, // 14 bits
}
3 changes: 2 additions & 1 deletion packages/core/src/render3/interfaces/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
import {Injector} from '../../di/injector';
import {QueryList} from '../../linker';
import {Sanitizer} from '../../sanitization/security';
import {PlayerHandler} from '../interfaces/player';

import {LContainer} from './container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition';
import {TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player';
import {LQueries} from './query';
import {RElement, Renderer3} from './renderer';
import {StylingContext} from './styling';


/** Size of LViewData's header. Necessary to adjust for it when setting slots. */
export const HEADER_OFFSET = 17;

Expand Down

0 comments on commit 68d6430

Please sign in to comment.