Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ivy): enhance [style] and [class] bindings to be animation aware #26096

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/core/src/core_render3_private_export.ts
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,10 +172,14 @@ export {
LContext as ɵLContext,
} from './render3/interfaces/context';

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

export {
addPlayer as ɵaddPlayer,
getPlayers as ɵgetPlayers,
} from './render3/player';
} from './render3/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.
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/render3/discovery_utils.ts
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
43 changes: 26 additions & 17 deletions packages/core/src/render3/instructions.ts
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): 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,9 @@ 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 | String | PlayerFactory | null,
suffix?: string): void {
let valueToAdd: string|null = null;
if (value) {
if (suffix) {
Expand Down Expand Up @@ -2386,11 +2396,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 +2410,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
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; }

/**
* Defines the shape which produces the Player.
*
* 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 { '__brand__': 'Brand for PlayerFactory that nothing will match'; }

export interface PlayerBuilder extends BindingStore {
mhevery marked this conversation as resolved.
Show resolved Hide resolved
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
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
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
47 changes: 0 additions & 47 deletions packages/core/src/render3/player.ts

This file was deleted.