refactor(ivy): remove all old styling code prior to refactor (#31193)

In the previous patch () all the existing styling code was turned
off in favor of using the new refactored ivy styling code. This
patch is a follow up patch to that and removes all old, unused
styling code from the render3 directory.

PR Close #31193
This commit is contained in:
Matias Niemelä 2019-06-21 10:28:13 -07:00 committed by Kara Erickson
parent 0e68c7edf9
commit f50dede8f7
44 changed files with 122 additions and 6984 deletions

View File

@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime": 1440,
"main": 14912,
"main": 14021,
"polyfills": 43567
}
}

View File

@ -249,15 +249,6 @@ 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/players';
// we reexport these symbols just so that they are retained during the dead code elimination
// performed by rollup while it's creating fesm files.
//

View File

@ -17,15 +17,13 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews, renderInitialStyling} from './instructions/shared';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions/shared';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, RENDERER, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {applyOnCreateInstructions} from './node_util';
import {CONTEXT, FLAGS, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setActiveHostElement} from './state';
import {renderInitialClasses, renderInitialStyles} from './styling/class_and_style_bindings';
import {publishDefaultGlobalUtils} from './util/global_utils';
import {defaultScheduler, stringifyForError} from './util/misc_utils';
import {getRootContext} from './util/view_traversal_utils';
@ -225,17 +223,10 @@ export function createRootComponent<T>(
const expando = tView.expandoInstructions !;
invokeHostBindingsInCreationMode(
componentDef, expando, component, rootTNode, tView.firstTemplatePass);
rootTNode.onElementCreationFns && applyOnCreateInstructions(rootTNode);
setActiveHostElement(null);
}
if (rootTNode.classes !== null || rootTNode.styles !== null) {
const native = componentView[HOST] !as RElement;
const renderer = componentView[RENDERER];
renderInitialStyling(renderer, native, rootTNode);
}
return component;
}

View File

@ -16,5 +16,4 @@
*/
export {markDirty} from './instructions/all';
export {getPlayers} from './players';
export {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent} from './util/discovery_utils';

View File

@ -24,7 +24,6 @@ import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18
import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node';
import {RComment, RElement, RText} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {StylingContext} from './interfaces/styling';
import {isLContainer} from './interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view';
import {appendChild, appendProjectedNodes, createTextNode, nativeRemoveNode} from './node_manipulation';
@ -915,7 +914,7 @@ function removeNode(index: number, viewData: LView) {
nativeRemoveNode(viewData[RENDERER], removedPhRNode);
}
const slotValue = ɵɵload(index) as RElement | RComment | LContainer | StylingContext;
const slotValue = ɵɵload(index) as RElement | RComment | LContainer;
if (isLContainer(slotValue)) {
const lContainer = slotValue as LContainer;
if (removedPhTNode.type !== TNodeType.Container) {

View File

@ -43,7 +43,7 @@ export * from './projection';
export * from './property';
export * from './property_interpolation';
export * from './select';
export {ɵɵstyling, ɵɵstyleSanitizer, ɵɵstyleMap, ɵɵclassMap, ɵɵstyleProp, ɵɵclassProp, ɵɵstylingApply} from '../styling_next/instructions';
export * from '../styling_next/instructions';
export * from './text';
export * from './text_interpolation';
export * from './class_map_interpolation';

View File

@ -16,7 +16,6 @@ import {isContentQueryHost} from '../interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util';
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
import {StylingMapArray, TStylingContext} from '../styling_next/interfaces';
@ -120,12 +119,8 @@ export function ɵɵelementEnd(): void {
}
const tNode = previousOrParentTNode;
// this is required for all host-level styling-related instructions to run
// in the correct order
tNode.onElementCreationFns && applyOnCreateInstructions(tNode);
ngDevMode && assertNodeType(tNode, TNodeType.Element);
const lView = getLView();
const tView = lView[TVIEW];

View File

@ -14,7 +14,6 @@ import {isContentQueryHost} from '../interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util';
import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
@ -95,10 +94,6 @@ export function ɵɵelementContainerEnd(): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
// this is required for all host-level styling-related instructions to run
// in the correct order
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
registerPostOrderHooks(tView, previousOrParentTNode);
if (tView.firstTemplatePass && tView.queries !== null &&

View File

@ -17,8 +17,6 @@ import {PropertyAliases, TContainerNode, TElementNode, TNode as ITNode, TNode, T
import {SelectorFlags} from '../interfaces/projection';
import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
import {TStylingContext} from '../styling_next/interfaces';
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug';
@ -129,9 +127,7 @@ export const TNodeConstructor = class TNode implements ITNode {
public projectionNext: ITNode|null, //
public child: ITNode|null, //
public parent: TElementNode|TContainerNode|null, //
public stylingTemplate: StylingContext|null, //
public projection: number|(ITNode|RNode[])[]|null, //
public onElementCreationFns: Function[]|null, //
public styles: TStylingContext|null, //
public classes: TStylingContext|null, //
) {}
@ -331,7 +327,6 @@ export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|nul
const rawValue = lView[tNode.index];
const native = unwrapRNode(rawValue);
const componentLViewDebug = toDebug(readLViewValue(rawValue));
const styles = isStylingContext(tNode.styles) ?
new NodeStylingDebug(tNode.styles as any as TStylingContext, lView) :
null;
@ -362,7 +357,7 @@ export class LContainerDebug {
}
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lContainer[PARENT]); }
get movedViews(): LView[]|null { return this._raw_lContainer[MOVED_VIEWS]; }
get host(): RElement|RComment|StylingContext|LView { return this._raw_lContainer[HOST]; }
get host(): RElement|RComment|LView { return this._raw_lContainer[HOST]; }
get native(): RComment { return this._raw_lContainer[NATIVE]; }
get __other__() {
return {

View File

@ -25,17 +25,14 @@ import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/inj
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {StylingContext} from '../interfaces/styling';
import {isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {initializeStaticContext as initializeStaticStylingContext} from '../styling/class_and_style_bindings';
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../styling/util';
import {renderStylingMap} from '../styling_next/bindings';
import {getInitialStylingValue, getStylingMapArray} from '../styling_next/util';
import {NO_CHANGE} from '../tokens';
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
@ -760,11 +757,9 @@ export function createTNode(
null, // projectionNext: ITNode|null
null, // child: ITNode|null
tParent, // parent: TElementNode|TContainerNode|null
null, // stylingTemplate: StylingContext|null
null, // projection: number|(ITNode|RNode[])[]|null
null, // onElementCreationFns: Function[]|null
null, // newStyles: TStylingContext|null
null, // newClasses: TStylingContext|null
null, // styles: TStylingContext|null
null, // classes: TStylingContext|null
) :
{
type: type,
@ -787,9 +782,7 @@ export function createTNode(
projectionNext: null,
child: null,
parent: tParent,
stylingTemplate: null,
projection: null,
onElementCreationFns: null,
styles: null,
classes: null,
};
@ -1474,8 +1467,8 @@ const LContainerArray: any = ngDevMode && createNamedArrayType('LContainer');
* @returns LContainer
*/
export function createLContainer(
hostNative: RElement | RComment | StylingContext | LView, currentView: LView, native: RComment,
tNode: TNode, isForViewContainerRef?: boolean): LContainer {
hostNative: RElement | RComment | LView, currentView: LView, native: RComment, tNode: TNode,
isForViewContainerRef?: boolean): LContainer {
ngDevMode && assertDomNode(native);
ngDevMode && assertLView(currentView);
// https://jsperf.com/array-literal-vs-new-array-really

View File

@ -1,376 +0,0 @@
/**
* @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 {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {assertEqual} from '../../util/assert';
import {TNode, TNodeType} from '../interfaces/node';
import {PlayerFactory} from '../interfaces/player';
import {FLAGS, HEADER_OFFSET, LView, LViewFlags, RENDERER, RootContextFlags} from '../interfaces/view';
import {getActiveDirectiveId, getActiveDirectiveSuperClassDepth, getLView, getPreviousOrParentTNode, getSelectedIndex} from '../state';
import {getInitialClassNameValue, renderStyling, updateClassMap, updateClassProp as updateclassProp, updateContextWithBindings, updateStyleMap, updateStyleProp as updatestyleProp} from '../styling/class_and_style_bindings';
import {ParamsOf, enqueueHostInstruction, registerHostDirective} from '../styling/host_instructions_queue';
import {BoundPlayerFactory} from '../styling/player_factory';
import {DEFAULT_TEMPLATE_DIRECTIVE_INDEX} from '../styling/shared';
import {getCachedStylingContext, setCachedStylingContext} from '../styling/state';
import {allocateOrUpdateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContextFromLView} from '../styling/util';
import {hasClassInput, hasStyleInput} from '../styling_next/util';
import {NO_CHANGE} from '../tokens';
import {renderStringify} from '../util/misc_utils';
import {getRootContext} from '../util/view_traversal_utils';
import {getTNode} from '../util/view_utils';
import {scheduleTick, setInputsForProperty} from './shared';
/*
* The contents of this file include the instructions for all styling-related
* operations in Angular.
*
* The instructions present in this file are:
*
* Template level styling instructions:
* - styling
* - styleMap
* - classMap
* - styleProp
* - classProp
* - stylingApply
*/
/**
* Allocates style and class binding properties on the element during creation mode.
*
* This instruction is meant to be called during creation mode to register all
* dynamic style and class bindings on the element. Note that this is only used
* for binding values (see `elementStart` to learn how to assign static styling
* values to an element).
*
* @param classBindingNames An array containing bindable class names.
* The `classProp` instruction refers to the class name by index in
* this array (i.e. `['foo', 'bar']` means `foo=0` and `bar=1`).
* @param styleBindingNames An array containing bindable style properties.
* The `styleProp` instruction refers to the class name by index in
* this array (i.e. `['width', 'height']` means `width=0` and `height=1`).
* @param styleSanitizer An optional sanitizer function that will be used to sanitize any CSS
* style values that are applied to the element (during rendering).
*
* Note that this will allocate the provided style/class bindings to the host element if
* this function is called within a host binding.
*
* @codeGenApi
*/
export function ɵɵstyling(
classBindingNames?: string[] | null, styleBindingNames?: string[] | null,
styleSanitizer?: StyleSanitizeFn | null): void {
const tNode = getPreviousOrParentTNode();
if (!tNode.stylingTemplate) {
tNode.stylingTemplate = createEmptyStylingContext();
}
const directiveStylingIndex = getActiveDirectiveStylingIndex();
if (directiveStylingIndex) {
// despite the binding being applied in a queue (below), the allocation
// of the directive into the context happens right away. The reason for
// this is to retain the ordering of the directives (which is important
// for the prioritization of bindings).
allocateOrUpdateDirectiveIntoContext(tNode.stylingTemplate, directiveStylingIndex);
const fns = tNode.onElementCreationFns = tNode.onElementCreationFns || [];
fns.push(() => {
initStyling(
tNode, classBindingNames, styleBindingNames, styleSanitizer, directiveStylingIndex);
registerHostDirective(tNode.stylingTemplate !, directiveStylingIndex);
});
} else {
// calling the function below ensures that the template's binding values
// are applied as the first set of bindings into the context. If any other
// styling bindings are set on the same element (by directives and/or
// components) then they will be applied at the end of the `elementEnd`
// instruction (because directives are created first before styling is
// executed for a new element).
initStyling(
tNode, classBindingNames, styleBindingNames, styleSanitizer,
DEFAULT_TEMPLATE_DIRECTIVE_INDEX);
}
}
function initStyling(
tNode: TNode, classBindingNames: string[] | null | undefined,
styleBindingNames: string[] | null | undefined,
styleSanitizer: StyleSanitizeFn | null | undefined, directiveStylingIndex: number): void {
updateContextWithBindings(
tNode.stylingTemplate !, directiveStylingIndex, classBindingNames, styleBindingNames,
styleSanitizer);
}
/**
* Update a style binding on an element with the provided value.
*
* If the style value is falsy then it will be removed from the element
* (or assigned a different value depending if there are any styles placed
* on the element with `styleMap` or any static styles that are
* present from when the element was created with `styling`).
*
* Note that the styling element is updated as part of `stylingApply`.
*
* @param styleIndex Index of style to update. This index value refers to the
* index of the style in the style bindings array that was passed into
* `styling`.
* @param value New value to write (falsy to remove).
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
* Note that when a suffix is provided then the underlying sanitizer will
* be ignored.
* @param forceOverride Whether or not to update the styling value immediately
* (despite the other bindings possibly having priority)
*
* Note that this will apply the provided style value to the host element if this function is called
* within a host binding.
*
* @codeGenApi
*/
export function ɵɵstyleProp(
styleIndex: number, value: string | number | String | PlayerFactory | null,
suffix?: string | null, forceOverride?: boolean): void {
stylePropInternal(
getLView(), getSelectedIndex(), styleIndex, getActiveDirectiveStylingIndex(), value, suffix,
forceOverride);
}
export function stylePropInternal(
lView: LView, selectedIndex: number, styleIndex: number, directiveStylingIndex: number,
value: string | number | String | PlayerFactory | null, suffix?: string | null,
forceOverride?: boolean): void {
const valueToAdd = resolveStylePropValue(value, suffix);
const stylingContext = getStylingContext(selectedIndex, lView);
if (directiveStylingIndex) {
const args: ParamsOf<typeof updatestyleProp> =
[stylingContext, styleIndex, valueToAdd, directiveStylingIndex, forceOverride];
enqueueHostInstruction(stylingContext, directiveStylingIndex, updatestyleProp, args);
} else {
updatestyleProp(
stylingContext, styleIndex, valueToAdd, DEFAULT_TEMPLATE_DIRECTIVE_INDEX, forceOverride);
}
}
function resolveStylePropValue(
value: string | number | String | PlayerFactory | null, suffix: string | null | undefined) {
let valueToAdd: string|null = null;
if (value !== null) {
if (suffix) {
// when a suffix is applied then it will bypass
// sanitization entirely (b/c a new string is created)
valueToAdd = renderStringify(value) + suffix;
} else {
// sanitization happens by dealing with a String value
// this means that the string value will be passed through
// into the style rendering later (which is where the value
// will be sanitized before it is applied)
valueToAdd = value as any as string;
}
}
return valueToAdd;
}
/**
* Update a class binding on an element with the provided value.
*
* This instruction is meant to handle the `[class.foo]="exp"` case and,
* therefore, the class binding itself must already be allocated using
* `styling` within the creation block.
*
* @param classIndex Index of class to toggle. This index value refers to the
* index of the class in the class bindings array that was passed into
* `styling` (which is meant to be called before this
* function is).
* @param value A true/false value which will turn the class on or off.
* @param forceOverride Whether or not this value will be applied regardless
* of where it is being set within the styling priority structure.
*
* Note that this will apply the provided class value to the host element if this function
* is called within a host binding.
*
* @codeGenApi
*/
export function ɵɵclassProp(
classIndex: number, value: boolean | PlayerFactory, forceOverride?: boolean): void {
const index = getSelectedIndex();
const input = (value instanceof BoundPlayerFactory) ?
(value as BoundPlayerFactory<boolean|null>) :
booleanOrNull(value);
const directiveStylingIndex = getActiveDirectiveStylingIndex();
const stylingContext = getStylingContext(index, getLView());
if (directiveStylingIndex) {
const args: ParamsOf<typeof updateclassProp> =
[stylingContext, classIndex, input, directiveStylingIndex, forceOverride];
enqueueHostInstruction(stylingContext, directiveStylingIndex, updateclassProp, args);
} else {
updateclassProp(
stylingContext, classIndex, input, DEFAULT_TEMPLATE_DIRECTIVE_INDEX, forceOverride);
}
}
function booleanOrNull(value: any): boolean|null {
if (typeof value === 'boolean') return value;
return value ? true : null;
}
/**
* Update style bindings using an object literal on an element.
*
* This instruction is meant to apply styling via the `[style]="exp"` template bindings.
* When styles are applied to the element they will then be updated with respect to
* any styles/classes set via `styleProp`. If any styles are set to falsy
* then they will be removed from the element.
*
* Note that the styling instruction will not be applied until `stylingApply` is called.
*
* @param styles A key/value style map of the styles that will be applied to the given element.
* Any missing styles (that have already been applied to the element beforehand) will be
* removed (unset) from the element's styling.
*
* Note that this will apply the provided styleMap value to the host element if this function
* is called within a host binding.
*
* @codeGenApi
*/
export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | null): void {
const index = getSelectedIndex();
const lView = getLView();
const stylingContext = getStylingContext(index, lView);
const directiveStylingIndex = getActiveDirectiveStylingIndex();
if (directiveStylingIndex) {
const args: ParamsOf<typeof updateStyleMap> = [stylingContext, styles, directiveStylingIndex];
enqueueHostInstruction(stylingContext, directiveStylingIndex, updateStyleMap, args);
} else {
const tNode = getTNode(index, lView);
// inputs are only evaluated from a template binding into a directive, therefore,
// there should not be a situation where a directive host bindings function
// evaluates the inputs (this should only happen in the template function)
if (hasStyleInput(tNode) && styles !== NO_CHANGE) {
const initialStyles = getInitialClassNameValue(stylingContext);
const styleInputVal =
(initialStyles.length ? (initialStyles + ' ') : '') + forceStylesAsString(styles);
setInputsForProperty(lView, tNode.inputs !['style'] !, styleInputVal);
styles = NO_CHANGE;
}
updateStyleMap(stylingContext, styles);
}
}
/**
* Update class bindings using an object literal or class-string on an element.
*
* This instruction is meant to apply styling via the `[class]="exp"` template bindings.
* When classes are applied to the element they will then be updated with
* respect to any styles/classes set via `classProp`. If any
* classes are set to falsy then they will be removed from the element.
*
* Note that the styling instruction will not be applied until `stylingApply` is called.
* Note that this will the provided classMap value to the host element if this function is called
* within a host binding.
*
* @param classes A key/value map or string of CSS classes that will be added to the
* given element. Any missing classes (that have already been applied to the element
* beforehand) will be removed (unset) from the element's list of CSS classes.
*
* @codeGenApi
*/
export function ɵɵclassMap(classes: {[styleName: string]: any} | string | null): void {
classMapInternal(getLView(), getSelectedIndex(), getActiveDirectiveStylingIndex(), classes);
}
export function classMapInternal(
lView: LView, selectedIndex: number, directiveStylingIndex: number,
classes: {[styleName: string]: any} | string | null) {
const stylingContext = getStylingContext(selectedIndex, lView);
if (directiveStylingIndex) {
const args: ParamsOf<typeof updateClassMap> = [stylingContext, classes, directiveStylingIndex];
enqueueHostInstruction(stylingContext, directiveStylingIndex, updateClassMap, args);
} else {
const tNode = getTNode(selectedIndex, lView);
// inputs are only evaluated from a template binding into a directive, therefore,
// there should not be a situation where a directive host bindings function
// evaluates the inputs (this should only happen in the template function)
if (hasClassInput(tNode)) {
const initialClasses = getInitialClassNameValue(stylingContext);
const classInputVal =
(initialClasses.length ? (initialClasses + ' ') : '') + forceClassesAsString(classes);
setInputsForProperty(lView, tNode.inputs !['class'] !, classInputVal);
classes = NO_CHANGE;
}
updateClassMap(stylingContext, classes);
}
}
/**
* Apply all style and class binding values to the element.
*
* This instruction is meant to be run after `styleMap`, `classMap`,
* `styleProp` or `classProp` instructions have been run and will
* only apply styling to the element if any styling bindings have been updated.
*
* @codeGenApi
*/
export function ɵɵstylingApply(): void {
const index = getSelectedIndex();
const directiveStylingIndex =
getActiveDirectiveStylingIndex() || DEFAULT_TEMPLATE_DIRECTIVE_INDEX;
const lView = getLView();
const tNode = getTNode(index, lView);
// if a non-element value is being processed then we can't render values
// on the element at all therefore by setting the renderer to null then
// the styling apply code knows not to actually apply the values...
const renderer = tNode.type === TNodeType.Element ? lView[RENDERER] : null;
const isFirstRender = (lView[FLAGS] & LViewFlags.FirstLViewPass) !== 0;
const stylingContext = getStylingContext(index, lView);
const totalPlayersQueued = renderStyling(
stylingContext, renderer, lView, isFirstRender, null, null, directiveStylingIndex);
if (totalPlayersQueued > 0) {
const rootContext = getRootContext(lView);
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
}
// because select(n) may not run between every instruction, the cached styling
// context may not get cleared between elements. The reason for this is because
// styling bindings (like `[style]` and `[class]`) are not recognized as property
// bindings by default so a select(n) instruction is not generated. To ensure the
// context is loaded correctly for the next element the cache below is pre-emptively
// cleared because there is no code in Angular that applies more styling code after a
// styling flush has occurred. Note that this will be fixed once FW-1254 lands.
setCachedStylingContext(null);
}
export function getActiveDirectiveStylingIndex() {
// whenever a directive's hostBindings function is called a uniqueId value
// is assigned. Normally this is enough to help distinguish one directive
// from another for the styling context, but there are situations where a
// sub-class directive could inherit and assign styling in concert with a
// parent directive. To help the styling code distinguish between a parent
// sub-classed directive the inheritance depth is taken into account as well.
return getActiveDirectiveId() + getActiveDirectiveSuperClassDepth();
}
function getStylingContext(index: number, lView: LView) {
let context = getCachedStylingContext();
if (!context) {
context = getStylingContextFromLView(index + HEADER_OFFSET, lView);
setCachedStylingContext(context);
} else if (ngDevMode) {
const actualContext = getStylingContextFromLView(index + HEADER_OFFSET, lView);
assertEqual(context, actualContext, 'The cached styling context is invalid');
}
return context;
}

View File

@ -6,14 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StylingMapArray, TStylingContext} from '../styling_next/interfaces';
import {CssSelector} from './projection';
import {RNode} from './renderer';
import {StylingContext} from './styling';
import {LView, TView} from './view';
/**
* TNodeType corresponds to the {@link TNode} `type` property.
*/
@ -405,7 +402,6 @@ export interface TNode {
*/
parent: TElementNode|TContainerNode|null;
stylingTemplate: StylingContext|null;
/**
* List of projected TNodes for a given component host element OR index into the said nodes.
*
@ -447,19 +443,6 @@ export interface TNode {
*/
projection: (TNode|RNode[])[]|number|null;
/**
* A buffer of functions that will be called once `elementEnd` (or `element`) completes.
*
* Due to the nature of how directives work in Angular, some directive code may
* need to fire after any template-level code runs. If present, this array will
* be flushed (each function will be invoked) once the associated element is
* created.
*
* If an element is created multiple times then this function will be populated
* with functions each time the creation block is called.
*/
onElementCreationFns: Function[]|null;
/**
* A collection of all style bindings and/or static style values for an element.
*

View File

@ -1,790 +0,0 @@
/**
* @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 {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {RElement} from '../interfaces/renderer';
import {LContainer} from './container';
import {PlayerContext} from './player';
import {LView} from './view';
/**
* 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`
* and `updateClassProp` functions. It also stores the static style/class values that were
* extracted from the template by the compiler.
*
* A context is created by Angular when:
* 1. An element contains static styling values (like style="..." or class="...")
* 2. An element contains single property binding values (like [style.prop]="x" or
* [class.prop]="y")
* 3. An element contains multi property binding values (like [style]="x" or [class]="y")
* 4. A directive contains host bindings for static, single or multi styling properties/bindings.
* 5. An animation player is added to an element via `addPlayer`
*
* Note that even if an element contains static styling then this context will be created and
* attached to it. The reason why this happens (instead of treating styles/classes as regular
* HTML attributes) is because the style/class bindings must be able to default themselves back
* to their respective static values when they are set to null.
*
* Say for example we have this:
* ```
* <!-- when `myWidthExp=null` then a width of `100px`
* will be used a default value for width -->
* <div style="width:100px" [style.width]="myWidthExp"></div>
* ```
*
* Even in the situation where there are no bindings, the static styling is still placed into the
* context because there may be another directive on the same element that has styling.
*
* When Angular initializes styling data for an element then it will first register the static
* styling values on the element using one of these two instructions:
*
* 1. elementStart or element (within the template function of a component)
* 2. elementHostAttrs (for directive host bindings)
*
* In either case, a styling context will be created and stored within an element's `LViewData`.
* Once the styling context is created then single and multi properties can be stored within it.
* For this to happen, the following function needs to be called:
*
* `styling` (called with style properties, class properties and a sanitizer + a directive
* instance).
*
* When this instruction is called it will populate the styling context with the provided style
* and class names into the context.
*
* The context itself looks like this:
*
* context = [
* // 0-8: header values (about 8 entries of configuration data)
* // 9+: this is where each entry is stored:
* ]
*
* Let's say we have the following template code:
*
* ```
* <div class="foo bar"
* style="width:200px; color:red"
* [style.width]="myWidthExp"
* [style.height]="myHeightExp"
* [class.baz]="myBazExp">
* ```
*
* The context generated from these values will look like this (note that
* for each binding name (the class and style bindings) the values will
* be inserted twice into the array (once for single property entries and
* again for multi property entries).
*
* context = [
* // 0-8: header values (about 8 entries of configuration data)
* // 9+: this is where each entry is stored:
*
* // SINGLE PROPERTIES
* configForWidth,
* 'width'
* myWidthExp, // the binding value not the binding itself
* 0, // the directive owner
*
* configForHeight,
* 'height'
* myHeightExp, // the binding value not the binding itself
* 0, // the directive owner
*
* configForBazClass,
* 'baz
* myBazClassExp, // the binding value not the binding itself
* 0, // the directive owner
*
* // MULTI PROPERTIES
* configForWidth,
* 'width'
* myWidthExp, // the binding value not the binding itself
* 0, // the directive owner
*
* configForHeight,
* 'height'
* myHeightExp, // the binding value not the binding itself
* 0, // the directive owner
*
* configForBazClass,
* 'baz
* myBazClassExp, // the binding value not the binding itself
* 0, // the directive owner
* ]
*
* The configuration values are left out of the example above because
* the ordering of them could change between code patches. Please read the
* documentation below to get a better understand of what the configuration
* values are and how they work.
*
* Each time a binding property is updated (whether it be through a single
* property instruction like `styleProp`, `classProp`,
* `styleMap` or `classMap`) then the values in the context will be updated as
* well.
*
* If for example `[style.width]` updates to `555px` then its value will be reflected
* in the context as so:
*
* context = [
* // ...
* configForWidth, // this will be marked DIRTY
* 'width'
* '555px',
* 0,
* //..
* ]
*
* The context and directive data will also be marked dirty.
*
* Despite the context being updated, nothing has been rendered on screen (not styles or
* classes have been set on the element). To kick off rendering for an element the following
* function needs to be run `stylingApply`.
*
* `stylingApply` will run through the context and find each dirty value and render them onto
* the element. Once complete, all styles/classes will be set to clean. Because of this, the render
* function will now know not to rerun itself again if called again unless new style/class values
* have changed.
*
* ## Directives
* Directive style/class values (which are provided through host bindings) are also supported and
* housed within the same styling context as are template-level style/class properties/bindings
* So long as they are all assigned to the same element, both directive-level and template-level
* styling bindings share the same context.
*
* Each of the following instructions supports accepting a directive instance as an input parameter:
*
* - `elementHostAttrs`
* - `styling`
* - `styleProp`
* - `classProp`
* - `styleMap`
* - `classMap`
* - `stylingApply`
*
* Each time a directive value is passed in, it will be converted into an index by examining the
* directive registry (which lives in the context configuration area). The index is then used
* to help single style properties figure out where a value is located in the context.
*
*
* ## Single-level styling bindings (`[style.prop]` and `[class.name]`)
*
* Both `[style.prop]` and `[class.name]` bindings are run through the `updateStyleProp`
* and `updateClassProp` functions respectively. They work by examining the provided
* `offset` value and are able to locate the exact spot in the context where the
* matching style is located.
*
* Both `[style.prop]` and `[class.name]` bindings are able to process these values
* from directive host bindings. When evaluated (from the host binding function) the
* `directiveRef` value is then passed in.
*
* If two directives or a directive + a template binding both write to the same style/class
* binding then the styling context code will decide which one wins based on the following
* rule:
*
* 1. If the template binding has a value then it always wins
* 2. Otherwise whichever first-registered directive that has that value first will win
*
* The code example helps make this clear:
*
* ```
* <!--
* <div [style.width]="myWidth"
* [my-width-directive]="'600px'">
* -->
*
* @Directive({
* selector: '[my-width-directive']
* })
* class MyWidthDirective {
* @Input('my-width-directive')
* @HostBinding('style.width')
* public width = null;
* }
* ```
*
* Since there is a style binding for width present on the element (`[style.width]`) then
* it will always win over the width binding that is present as a host binding within
* the `MyWidthDirective`. However, if `[style.width]` renders as `null` (so `myWidth=null`)
* then the `MyWidthDirective` will be able to write to the `width` style within the context.
* Simply put, whichever directive writes to a value first ends up having ownership of it as
* long as the template didn't set anything.
*
* The way in which the ownership is facilitated is through index value. The earliest directives
* get the smallest index values (with 0 being reserved for the template element bindings). Each
* time a value is written from a directive or the template bindings, the value itself gets
* assigned the directive index value in its data. If another directive writes a value again then
* its directive index gets compared against the directive index that exists on the element. Only
* when the new value's directive index is less than the existing directive index then the new
* value will be written to the context. But, if the existing value is null then the new value is
* written by the less important directive.
*
* Each directive also has its own sanitizer and dirty flags. These values are consumed within the
* rendering function.
*
*
* ## Multi-level styling bindings (`[style]` and `[class]`)
*
* Multi-level styling bindings are treated as less important (less specific) as single-level
* bindings (things like `[style.prop]` and `[class.name]`).
*
* Multi-level bindings are still applied to the context in a similar way as are single level
* bindings, but this process works by diffing the new multi-level values (which are key/value
* maps) against the existing set of styles that live in the context. Each time a new map value
* is detected (via identity check) then it will loop through the values and figure out what
* has changed and reorder the context array to match the ordering of the keys. This reordering
* of the context makes sure that follow-up traversals of the context when updated against the
* key/value map are as close as possible to o(n) (where "n" is the size of the key/value map).
*
* If a `directiveRef` value is passed in then the styling algorithm code will take the directive's
* prioritization index into account and update the values with respect to more important
* directives. This means that if a value such as `width` is updated in two different `[style]`
* bindings (say one on the template and another within a directive that sits on the same element)
* then the algorithm will decide how to update the value based on the following heuristic:
*
* 1. If the template binding has a value then it always wins
* 2. If not then whichever first-registered directive that has that value first will win
*
* It will also update the value if it was set to `null` by a previous directive (or the template).
*
* Each time a value is updated (or removed) then the context will change shape to better match
* the ordering of the styling data as well as the ordering of each directive that contains styling
* data. (See `patchStylingMapIntoContext` inside of class_and_style_bindings.ts to better
* understand how this works.)
*
* ## Rendering
* The rendering mechanism (when the styling data is applied on screen) occurs via the
* `stylingApply` function and is designed to run after **all** styling functions have been
* evaluated. The rendering algorithm will loop over the context and only apply the styles that are
* flagged as dirty (either because they are new, updated or have been removed via multi or
* single bindings).
*/
export interface StylingContext extends
Array<{[key: string]: any}|number|string|boolean|RElement|StyleSanitizeFn|PlayerContext|null> {
/**
* Location of element that is used as a target for this context.
*/
[StylingIndex.ElementPosition]: LContainer|LView|RElement|null;
/**
* 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.
*/
[StylingIndex.MasterFlagPosition]: number;
/**
* Location of the collection of directives for this context
*/
[StylingIndex.DirectiveRegistryPosition]: DirectiveRegistryValues;
/**
* Location of all static styles values
*/
[StylingIndex.InitialStyleValuesPosition]: InitialStylingValues;
/**
* Location of all static class values
*/
[StylingIndex.InitialClassValuesPosition]: InitialStylingValues;
/**
* A numeric value representing the class index offset value. Whenever a single class is
* applied (using `classProp`) it should have an styling index value that doesn't
* need to take into account any style values that exist in the context.
*/
[StylingIndex.SinglePropOffsetPositions]: SinglePropOffsetValues;
/**
* The last class value that was interpreted by `styleMap`. This is cached
* So that the algorithm can exit early incase the value has not changed.
*/
[StylingIndex.CachedMultiClasses]: any|MapBasedOffsetValues;
/**
* The last style value that was interpreted by `classMap`. This is cached
* So that the algorithm can exit early incase the value has not changed.
*/
[StylingIndex.CachedMultiStyles]: any|MapBasedOffsetValues;
/**
* A queue of all hostStyling instructions.
*
* This array (queue) is populated only when host-level styling instructions
* (e.g. `hostStyleMap` and `hostClassProp`) are used to apply style and
* class values via host bindings to the host element. Despite these being
* standard angular instructions, they are not designed to immediately apply
* their values to the styling context when executed. What happens instead is
* a queue is constructed and each instruction is populated into the queue.
* Then, once the style/class values are set to flush (via `stylingApply` or
* `hostStylingApply`), the queue is flushed and the values are rendered onto
* the host element.
*/
[StylingIndex.HostInstructionsQueue]: HostInstructionsQueue|null;
/**
* Location of animation context (which contains the active players) for this element styling
* context.
*/
[StylingIndex.PlayerContext]: PlayerContext|null;
}
/**
* A queue of all host-related styling instructions (these are buffered and evaluated just before
* the styling is applied).
*
* This queue is used when any `hostStyling` instructions are executed from the `hostBindings`
* function. Template-level styling functions (e.g. `styleMap` and `classProp`)
* do not make use of this queue (they are applied to the styling context immediately).
*
* Due to the nature of how components/directives are evaluated, directives (both parent and
* subclass directives) may not apply their styling at the right time for the styling
* algorithm code to prioritize them. Therefore, all host-styling instructions are queued up
* (buffered) into the array below and are automatically sorted in terms of priority. The
* priority for host-styling is as follows:
*
* 1. The template (this doesn't get queued, but gets evaluated immediately)
* 2. Any directives present on the host
* 2a) first child directive styling bindings are updated
* 2b) then any parent directives
* 3. Component host bindings
*
* Angular runs change detection for each of these cases in a different order. Because of this
* the array below is populated with each of the host styling functions + their arguments.
*
* context[HostInstructionsQueue] = [
* directiveIndex,
* hostStylingFn,
* [argumentsForFn],
* ...
* anotherDirectiveIndex, <-- this has a lower priority (a higher directive index)
* anotherHostStylingFn,
* [argumentsForFn],
* ]
*
* When `renderStyling` is called (within `class_and_host_bindings.ts`) then the queue is
* drained and each of the instructions are executed. Once complete the queue is empty then
* the style/class binding code is rendered on the element (which is what happens normally
* inside of `renderStyling`).
*
* Right now each directive's hostBindings function, as well the template function, both
* call `stylingApply()` and `hostStylingApply()`. The fact that this is called
* multiple times for the same element (b/c of change detection) causes some issues. To avoid
* having styling code be rendered on an element multiple times, the `HostInstructionsQueue`
* reserves a slot for a reference pointing to the very last directive that was registered and
* only allows for styling to be applied once that directive is encountered (which will happen
* as the last update for that element).
*/
export interface HostInstructionsQueue extends Array<number|Function|any[]> { [0]: number; }
/**
* Used as a reference for any values contained within `HostInstructionsQueue`.
*/
export const enum HostInstructionsQueueIndex {
LastRegisteredDirectiveIndexPosition = 0,
ValuesStartPosition = 1,
DirectiveIndexOffset = 0,
InstructionFnOffset = 1,
ParamsOffset = 2,
Size = 3,
}
/**
* Used as a styling array to house static class and style values that were extracted
* by the compiler and placed in the animation context via `elementStart` and
* `elementHostAttrs`.
*
* See [InitialStylingValuesIndex] for a breakdown of how all this works.
*/
export interface InitialStylingValues extends Array<string|boolean|number|null> {
[InitialStylingValuesIndex.DefaultNullValuePosition]: null;
[InitialStylingValuesIndex.CachedStringValuePosition]: string|null;
}
/**
* Used as an offset/position index to figure out where initial styling
* values are located.
*
* Used as a reference point to provide markers to all static styling
* values (the initial style and class values on an element) within an
* array within the `StylingContext`. This array contains key/value pairs
* where the key is the style property name or className and the value is
* the style value or whether or not a class is present on the elment.
*
* The first value is always null so that a initial index value of
* `0` will always point to a null value.
*
* The second value is also always null unless a string-based representation
* of the styling data was constructed (it gets cached in this slot).
*
* If a <div> elements contains a list of static styling values like so:
*
* <div class="foo bar baz" style="width:100px; height:200px;">
*
* Then the initial styles for that will look like so:
*
* Styles:
* ```
* StylingContext[InitialStylesIndex] = [
* null, null, 'width', '100px', height, '200px'
* ]
* ```
*
* Classes:
* ```
* StylingContext[InitialClassesIndex] = [
* null, null, 'foo', true, 'bar', true, 'baz', true
* ]
* ```
*
* Initial style and class entries have their own arrays. This is because
* it's easier to add to the end of one array and not then have to update
* every context entries' pointer index to the newly offseted values.
*
* When property bindinds are added to a context then initial style/class
* values will also be inserted into the array. This is to create a space
* in the situation when a follow-up directive inserts static styling into
* the array. By default, style values are `null` and class values are
* `false` when inserted by property bindings.
*
* For example:
* ```
* <div class="foo bar baz"
* [class.car]="myCarExp"
* style="width:100px; height:200px;"
* [style.opacity]="myOpacityExp">
* ```
*
* Will construct initial styling values that look like:
*
* Styles:
* ```
* StylingContext[InitialStylesIndex] = [
* null, null, 'width', '100px', height, '200px', 'opacity', null
* ]
* ```
*
* Classes:
* ```
* StylingContext[InitialClassesIndex] = [
* null, null, 'foo', true, 'bar', true, 'baz', true, 'car', false
* ]
* ```
*
* Now if a directive comes along and introduces `car` as a static
* class value or `opacity` then those values will be filled into
* the initial styles array.
*
* For example:
*
* ```
* @Directive({
* selector: 'opacity-car-directive',
* host: {
* 'style': 'opacity:0.5',
* 'class': 'car'
* }
* })
* class OpacityCarDirective {}
* ```
*
* This will render itself as:
*
* Styles:
* ```
* StylingContext[InitialStylesIndex] = [
* null, null, 'width', '100px', height, '200px', 'opacity', '0.5'
* ]
* ```
*
* Classes:
* ```
* StylingContext[InitialClassesIndex] = [
* null, null, 'foo', true, 'bar', true, 'baz', true, 'car', true
* ]
* ```
*/
export const enum InitialStylingValuesIndex {
/**
* The first value is always `null` so that `styles[0] == null` for unassigned values
*/
DefaultNullValuePosition = 0,
/**
* Used for non-styling code to examine what the style or className string is:
* styles: ['width', '100px', 0, 'opacity', null, 0, 'height', '200px', 0]
* => initialStyles[CachedStringValuePosition] = 'width:100px;height:200px';
* classes: ['foo', true, 0, 'bar', false, 0, 'baz', true, 0]
* => initialClasses[CachedStringValuePosition] = 'foo bar';
*
* Note that this value is `null` by default and it will only be populated
* once `getInitialStyleStringValue` or `getInitialClassNameValue` is executed.
*/
CachedStringValuePosition = 1,
/**
* Where the style or class values start in the tuple
*/
KeyValueStartPosition = 2,
/**
* The offset value (index + offset) for the property value for each style/class entry
*/
PropOffset = 0,
/**
* The offset value (index + offset) for the style/class value for each style/class entry
*/
ValueOffset = 1,
/**
* The offset value (index + offset) for the style/class directive owner for each style/class
entry
*/
DirectiveOwnerOffset = 2,
/**
* The first bit set aside to mark if the initial style was already rendere
*/
AppliedFlagBitPosition = 0b0,
AppliedFlagBitLength = 1,
/**
* The total size for each style/class entry (prop + value + directiveOwner)
*/
Size = 3
}
/**
* An array located in the StylingContext that houses all directive instances and additional
* data about them.
*
* Each entry in this array represents a source of where style/class binding values could
* come from. By default, there is always at least one directive here with a null value and
* that represents bindings that live directly on an element in the template (not host bindings).
*
* Each successive entry in the array is an actual instance of a directive as well as some
* additional info about that entry.
*
* An entry within this array has the following values:
* [0] = The instance of the directive (the first entry is null because its reserved for the
* template)
* [1] = The pointer that tells where the single styling (stuff like [class.foo] and [style.prop])
* offset values are located. This value will allow for a binding instruction to find exactly
* where a style is located.
* [2] = Whether or not the directive has any styling values that are dirty. This is used as
* reference within the `renderStyling` function to decide whether to skip iterating
* through the context when rendering is executed.
* [3] = The styleSanitizer instance that is assigned to the directive. Although it's unlikely,
* a directive could introduce its own special style sanitizer and for this reach each
* directive will get its own space for it (if null then the very first sanitizer is used).
*
* Each time a new directive is added it will insert these four values at the end of the array.
* When this array is examined then the resulting directiveIndex will be resolved by dividing the
* index value by the size of the array entries (so if DirA is at spot 8 then its index will be 2).
*/
export interface DirectiveRegistryValues extends Array<null|{}|boolean|number|StyleSanitizeFn> {
[DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset]: number;
[DirectiveRegistryValuesIndex.StyleSanitizerOffset]: StyleSanitizeFn|null;
}
/**
* An enum that outlines the offset/position values for each directive entry and its data
* that are housed inside of [DirectiveRegistryValues].
*/
export const enum DirectiveRegistryValuesIndex {
SinglePropValuesIndexOffset = 0,
StyleSanitizerOffset = 1,
Size = 2
}
/**
* An array that contains the index pointer values for every single styling property
* that exists in the context and for every directive. It also contains the total
* single styles and single classes that exists in the context as the first two values.
*
* Let's say we have the following template code:
*
* <div [style.width]="myWidth"
* [style.height]="myHeight"
* [class.flipped]="flipClass"
* directive-with-opacity>
* directive-with-foo-bar-classes>
*
* We have two directive and template-binding sources,
* 2 + 1 styles and 1 + 1 classes. When the bindings are
* registered the SinglePropOffsets array will look like so:
*
* s_0/c_0 = template directive value
* s_1/c_1 = directive one (directive-with-opacity)
* s_2/c_2 = directive two (directive-with-foo-bar-classes)
*
* [3, 2, 2, 1, s_00, s01, c_01, 1, 0, s_10, 0, 1, c_20
*/
export interface SinglePropOffsetValues extends Array<number> {
[SinglePropOffsetValuesIndex.StylesCountPosition]: number;
[SinglePropOffsetValuesIndex.ClassesCountPosition]: number;
}
/**
* An enum that outlines the offset/position values for each single prop/class entry
* that are housed inside of [SinglePropOffsetValues].
*/
export const enum SinglePropOffsetValuesIndex {
StylesCountPosition = 0,
ClassesCountPosition = 1,
ValueStartPosition = 2
}
/**
* Used a reference for all multi styling values (values that are assigned via the
* `[style]` and `[class]` bindings).
*
* Single-styling properties (things set via `[style.prop]` and `[class.name]` bindings)
* are not handled using the same approach as multi-styling bindings (such as `[style]`
* `[class]` bindings).
*
* Multi-styling bindings rely on a diffing algorithm to figure out what properties have been added,
* removed and modified. Multi-styling properties are also evaluated across directives--which means
* that Angular supports having multiple directives all write to the same `[style]` and `[class]`
* bindings (using host bindings) even if the `[style]` and/or `[class]` bindings are being written
* to on the template element.
*
* All multi-styling values that are written to an element (whether it be from the template or any
* directives attached to the element) are all written into the `MapBasedOffsetValues` array. (Note
* that there are two arrays: one for styles and another for classes.)
*
* This array is shaped in the following way:
*
* [0] = The total amount of unique multi-style or multi-class entries that exist currently in the
* context.
* [1+] = Contains an entry of four values ... Each entry is a value assigned by a
* `[style]`/`[class]`
* binding (we call this a **source**).
*
* An example entry looks like so (at a given `i` index):
* [i + 0] = Whether or not the value is dirty
*
* [i + 1] = The index of where the map-based values
* (for this **source**) start within the context
*
* [i + 2] = The untouched, last set value of the binding
*
* [i + 3] = The total amount of unqiue binding values that were
* extracted and set into the context. (Note that this value does
* not reflect the total amount of values within the binding
* value (since it's a map), but instead reflects the total values
* that were not used by another directive).
*
* Each time a directive (or template) writes a value to a `[class]`/`[style]` binding then the
* styling diffing algorithm code will decide whether or not to update the value based on the
* following rules:
*
* 1. If a more important directive (either the template or a directive that was registered
* beforehand) has written a specific styling value into the context then any follow-up styling
* values (set by another directive via its `[style]` and/or `[class]` host binding) will not be
* able to set it. This is because the former directive has priorty.
* 2. Only if a former directive has set a specific styling value to null (whether by actually
* setting it to null or not including it in is map value) then a less imporatant directive can
* set its own value.
*
* ## How the map-based styling algorithm updates itself
*/
export interface MapBasedOffsetValues extends Array<any> {
[MapBasedOffsetValuesIndex.EntriesCountPosition]: number;
}
export const enum MapBasedOffsetValuesIndex {
EntriesCountPosition = 0,
ValuesStartPosition = 1,
DirtyFlagOffset = 0,
PositionStartOffset = 1,
ValueOffset = 2,
ValueCountOffset = 3,
Size = 4
}
/**
* Used to set the context to be dirty or not both on the master flag (position 1)
* or for each single/multi property that exists in the context.
*/
export const enum StylingFlags {
// Implies no configurations
None = 0b00000,
// Whether or not the entry or context itself is dirty
Dirty = 0b00001,
// Whether or not this is a class-based assignment
Class = 0b00010,
// Whether or not a sanitizer was applied to this property
Sanitize = 0b00100,
// Whether or not any player builders within need to produce new players
PlayerBuildersDirty = 0b01000,
// The max amount of bits used to represent these configuration values
BindingAllocationLocked = 0b10000,
BitCountSize = 5,
// There are only five bits here
BitMask = 0b11111
}
/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
export const enum StylingIndex {
// Position of where the initial styles are stored in the styling context
// This index must align with HOST, see interfaces/view.ts
ElementPosition = 0,
// Index of location where the start of single properties are stored. (`updateStyleProp`)
MasterFlagPosition = 1,
// Position of where the registered directives exist for this styling context
DirectiveRegistryPosition = 2,
// Position of where the initial styles are stored in the styling context
InitialStyleValuesPosition = 3,
InitialClassValuesPosition = 4,
// Index of location where the class index offset value is located
SinglePropOffsetPositions = 5,
// Position of where the last string-based CSS class value was stored (or a cached version of the
// initial styles when a [class] directive is present)
CachedMultiClasses = 6,
// Position of where the last string-based CSS class value was stored
CachedMultiStyles = 7,
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
// Position of where the initial styles are stored in the styling context
HostInstructionsQueue = 8,
PlayerContext = 9,
// Location of single (prop) value entries are stored within the context
SingleStylesStartPosition = 10,
FlagsOffset = 0,
PropertyOffset = 1,
ValueOffset = 2,
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 - 4) / 2 = ~14
// The binary digit value as a mask
BitMask = 0b11111111111111, // 14 bits
}
/**
* An enum that outlines the bit flag data for directive owner and player index
* values that exist within en entry that lives in the StylingContext.
*
* The values here split a number value into two sets of bits:
* - The first 16 bits are used to store the directiveIndex that owns this style value
* - The other 16 bits are used to store the playerBuilderIndex that is attached to this style
*/
export const enum DirectiveOwnerAndPlayerBuilderIndex {
BitCountSize = 16,
BitMask = 0b1111111111111111
}
/**
* The default directive styling index value for template-based bindings.
*
* All host-level bindings (e.g. `hostStyleProp` and `hostClassMap`) are
* assigned a directive styling index value based on the current directive
* uniqueId and the directive super-class inheritance depth. But for template
* bindings they always have the same directive styling index value.
*/
export const DEFAULT_TEMPLATE_DIRECTIVE_INDEX = 0;

View File

@ -11,37 +11,25 @@ import {ComponentDef, DirectiveDef} from '..';
import {LContainer, TYPE} from './container';
import {TNode, TNodeFlags} from './node';
import {RNode} from './renderer';
import {StylingContext} from './styling';
import {FLAGS, LView, LViewFlags} from './view';
/**
* True if `value` is `LView`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext`
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
export function isLView(value: RNode | LView | LContainer | StylingContext | {} | null):
value is LView {
export function isLView(value: RNode | LView | LContainer | {} | null): value is LView {
return Array.isArray(value) && typeof value[TYPE] === 'object';
}
/**
* True if `value` is `LContainer`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext`
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
export function isLContainer(value: RNode | LView | LContainer | StylingContext | {} | null):
value is LContainer {
export function isLContainer(value: RNode | LView | LContainer | {} | null): value is LContainer {
return Array.isArray(value) && value[TYPE] === true;
}
/**
* True if `value` is `StylingContext`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext`
*/
export function isStylingContext(value: RNode | LView | LContainer | StylingContext | {} | null):
value is StylingContext {
return Array.isArray(value) && typeof value[TYPE] === 'number';
}
export function isContentQueryHost(tNode: TNode): boolean {
return (tNode.flags & TNodeFlags.hasContentQuery) !== 0;
}

View File

@ -11,21 +11,6 @@ import {TContainerNode, TElementNode, TNode} from './interfaces/node';
import {DECLARATION_VIEW, LView, T_HOST} from './interfaces/view';
import {getParentInjectorViewOffset} from './util/injector_utils';
export function applyOnCreateInstructions(tNode: TNode) {
// there may be some instructions that need to run in a specific
// order because the CREATE block in a directive runs before the
// CREATE block in a template. To work around this instructions
// can get access to the function array below and defer any code
// to run after the element is created.
let fns: Function[]|null;
if (fns = tNode.onElementCreationFns) {
for (let i = 0; i < fns.length; i++) {
fns[i]();
}
tNode.onElementCreationFns = null;
}
}
/**
* Unwraps a parent injector location number to find the view offset from the current injector,
* then walks up the declaration view tree until the TNode of the parent injector is found.

View File

@ -1,67 +0,0 @@
/**
* @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 '../util/ng_dev_mode';
import {getLContext} from './context_discovery';
import {scheduleTick} from './instructions/shared';
import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player';
import {RootContextFlags} from './interfaces/view';
import {addPlayerInternal, getOrCreatePlayerContext, getPlayerContext, getPlayersInternal, getStylingContextFromLView, throwInvalidRefError} from './styling/util';
import {getRootContext} from './util/view_traversal_utils';
/**
* Adds a player to an element, directive or component instance that will later be
* animated once change detection has passed.
*
* When a player is added to a reference it will stay active until `player.destroy()`
* is called. Once called then the player will be removed from the active players
* present on the associated ref instance.
*
* To get a list of all the active players on an element see [getPlayers].
*
* @param ref The element, directive or component that the player will be placed on.
* @param player The player that will be triggered to play once change detection has run.
*/
export function addPlayer(
ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void {
const context = getLContext(ref);
if (!context) {
ngDevMode && throwInvalidRefError();
return;
}
const element = context.native as HTMLElement;
const lView = context.lView;
const playerContext = getOrCreatePlayerContext(element, context) !;
const rootContext = getRootContext(lView);
addPlayerInternal(playerContext, rootContext, element, player, 0, ref);
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
}
/**
* Returns a list of all the active players present on the provided ref instance (which can
* be an instance of a directive, component or element).
*
* This function will only return players that have been added to the ref instance using
* `addPlayer` or any players that are active through any template styling bindings
* (`[style]`, `[style.prop]`, `[class]` and `[class.name]`).
*
* @publicApi
*/
export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] {
const context = getLContext(ref);
if (!context) {
ngDevMode && throwInvalidRefError();
return [];
}
const stylingContext = getStylingContextFromLView(context.nodeIndex, context.lView);
const playerContext = stylingContext ? getPlayerContext(stylingContext) : null;
return playerContext ? getPlayersInternal(playerContext) : [];
}

View File

@ -14,7 +14,6 @@ import {executeHooks} from './hooks';
import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TViewNode} from './interfaces/node';
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
import {setCachedStylingContext} from './styling/state';
import {resetAllStylingState, resetStylingState} from './styling_next/state';
import {resetPreOrderHookFlags} from './util/view_utils';
@ -489,7 +488,6 @@ export function leaveView(newView: LView, safeToRunHooks: boolean): void {
lView[BINDING_INDEX] = tView.bindingStartIndex;
}
}
setCachedStylingContext(null);
enterView(newView, null);
}
@ -514,10 +512,6 @@ export function getSelectedIndex() {
export function setSelectedIndex(index: number) {
_selectedIndex = index;
// remove the styling context from the cache
// because we are now on a different element
setCachedStylingContext(null);
// we have now jumped to another element
// therefore the state is stale
resetStylingState();

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
/**
* @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, PlayerHandler} from '../interfaces/player';
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.state === PlayState.Pending) {
player.play();
}
}
this._players.length = 0;
}
queuePlayer(player: Player) { this._players.push(player); }
}

View File

@ -1,98 +0,0 @@
/**
* @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 {HostInstructionsQueue, HostInstructionsQueueIndex, StylingContext, StylingIndex} from '../interfaces/styling';
import {DEFAULT_TEMPLATE_DIRECTIVE_INDEX} from '../styling/shared';
/*
* This file contains the logic to defer all hostBindings-related styling code to run
* at a later point, instead of immediately (as is the case with how template-level
* styling instructions are run).
*
* Certain styling instructions, present within directives, components and sub-classed
* directives, are evaluated at different points (depending on priority) and will therefore
* not be applied to the styling context of an element immediately. They are instead
* designed to be applied just before styling is applied to an element.
*
* (The priority for when certain host-related styling operations are executed is discussed
* more within `interfaces/styling.ts`.)
*/
export function registerHostDirective(context: StylingContext, directiveIndex: number) {
let buffer = context[StylingIndex.HostInstructionsQueue];
if (!buffer) {
buffer = context[StylingIndex.HostInstructionsQueue] = [DEFAULT_TEMPLATE_DIRECTIVE_INDEX];
}
buffer[HostInstructionsQueueIndex.LastRegisteredDirectiveIndexPosition] = directiveIndex;
}
/**
* Queues a styling instruction to be run just before `renderStyling()` is executed.
*/
export function enqueueHostInstruction<T extends Function>(
context: StylingContext, priority: number, instructionFn: T, instructionFnArgs: ParamsOf<T>) {
const buffer: HostInstructionsQueue|null = context[StylingIndex.HostInstructionsQueue];
// Buffer may be null if host element is a template node. In this case, just ignore the style.
if (buffer != null) {
const index = findNextInsertionIndex(buffer, priority);
buffer.splice(index, 0, priority, instructionFn, instructionFnArgs);
}
}
/**
* Figures out where exactly to to insert the next host instruction queue entry.
*/
function findNextInsertionIndex(buffer: HostInstructionsQueue, priority: number): number {
for (let i = HostInstructionsQueueIndex.ValuesStartPosition; i < buffer.length;
i += HostInstructionsQueueIndex.Size) {
const p = buffer[i + HostInstructionsQueueIndex.DirectiveIndexOffset] as number;
if (p > priority) {
return i;
}
}
return buffer.length;
}
/**
* Iterates through the host instructions queue (if present within the provided
* context) and executes each queued instruction entry.
*/
export function flushQueue(this: unknown, context: StylingContext): void {
const buffer = context[StylingIndex.HostInstructionsQueue];
if (buffer) {
for (let i = HostInstructionsQueueIndex.ValuesStartPosition; i < buffer.length;
i += HostInstructionsQueueIndex.Size) {
const fn = buffer[i + HostInstructionsQueueIndex.InstructionFnOffset] as Function;
const args = buffer[i + HostInstructionsQueueIndex.ParamsOffset] as any[];
fn.apply(this, args);
}
buffer.length = HostInstructionsQueueIndex.ValuesStartPosition;
}
}
/**
* Determines whether or not to allow the host instructions queue to be flushed or not.
*
* Because the hostBindings function code is unaware of the presence of other host bindings
* (as well as the template function) then styling is evaluated multiple times per element.
* To prevent style and class values from being applied to the element multiple times, a
* flush is only allowed when the last directive (the directive that was registered into
* the styling context) attempts to render its styling.
*/
export function allowFlush(context: StylingContext, directiveIndex: number): boolean {
const buffer = context[StylingIndex.HostInstructionsQueue];
if (buffer) {
return buffer[HostInstructionsQueueIndex.LastRegisteredDirectiveIndexPosition] ===
directiveIndex;
}
return true;
}
/**
* Infers the parameters of a given function into a typed array.
*/
export type ParamsOf<T> = T extends(...args: infer T) => any ? T : never;

View File

@ -1,33 +0,0 @@
/**
* @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 {PlayerFactory, PlayerFactoryBuildFn} from '../interfaces/player';
/**
* Combines the binding value and a factory for an animation player.
*
* Used to bind a player to an element template binding (currently only
* `[style]`, `[style.prop]`, `[class]` and `[class.name]` bindings
* supported). The provided `factoryFn` function will be run once all
* the associated bindings have been evaluated on the element and is
* designed to return a player which will then be placed on the element.
*
* @param factoryFn The function that is used to create a player
* once all the rendering-related (styling values) have been
* processed for the element binding.
* @param value The raw value that will be exposed to the binding
* so that the binding can update its internal values when
* any changes are evaluated.
*/
export function bindPlayerFactory<T>(factoryFn: PlayerFactoryBuildFn, value: T): PlayerFactory {
return new BoundPlayerFactory(factoryFn, value) as any;
}
export class BoundPlayerFactory<T> {
'__brand__': 'Brand for PlayerFactory that nothing will match';
constructor(public fn: PlayerFactoryBuildFn, public value: T) {}
}

View File

@ -1,17 +0,0 @@
/**
* @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
*/
/**
* The default directive styling index value for template-based bindings.
*
* All host-level bindings (e.g. `hostStyleProp` and `hostStyleMap`) are
* assigned a directive styling index value based on the current directive
* uniqueId and the directive super-class inheritance depth. But for template
* bindings they always have the same directive styling index value.
*/
export const DEFAULT_TEMPLATE_DIRECTIVE_INDEX = 0;

View File

@ -1,30 +0,0 @@
/**
* @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 {StylingContext} from '../interfaces/styling';
let stylingContext: StylingContext|null = null;
/**
* Gets the most recent styling context value.
*
* Note that only one styling context is stored at a given time.
*/
export function getCachedStylingContext() {
return stylingContext;
}
/**
* Sets the most recent styling context value.
*
* Note that only one styling context is stored at a given time.
*
* @param context The styling context value that will be stored
*/
export function setCachedStylingContext(context: StylingContext | null) {
stylingContext = context;
}

View File

@ -1,263 +0,0 @@
/**
* @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 '../../util/ng_dev_mode';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {getLContext} from '../context_discovery';
import {LContainer} from '../interfaces/container';
import {LContext} from '../interfaces/context';
import {TNode, TNodeFlags} from '../interfaces/node';
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
import {RElement} from '../interfaces/renderer';
import {DirectiveRegistryValuesIndex, InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
import {isStylingContext} from '../interfaces/type_checks';
import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
import {getTNode} from '../util/view_utils';
import {CorePlayerHandler} from './core_player_handler';
import {DEFAULT_TEMPLATE_DIRECTIVE_INDEX} from './shared';
export const ANIMATION_PROP_PREFIX = '@';
export function createEmptyStylingContext(
wrappedElement?: LContainer | LView | RElement | null, sanitizer?: StyleSanitizeFn | null,
initialStyles?: InitialStylingValues | null,
initialClasses?: InitialStylingValues | null): StylingContext {
const context: StylingContext = [
wrappedElement || null, // Element
0, // MasterFlags
[] as any, // DirectiveRefs (this gets filled below)
initialStyles || [null, null], // InitialStyles
initialClasses || [null, null], // InitialClasses
[0, 0], // SinglePropOffsets
[0], // CachedMultiClassValue
[0], // CachedMultiStyleValue
null, // HostBuffer
null, // PlayerContext
];
// whenever a context is created there is always a `null` directive
// that is registered (which is a placeholder for the "template").
allocateOrUpdateDirectiveIntoContext(context, DEFAULT_TEMPLATE_DIRECTIVE_INDEX);
return context;
}
/**
* Allocates (registers) a directive into the directive registry within the provided styling
* context.
*
* For each and every `[style]`, `[style.prop]`, `[class]`, `[class.name]` binding
* (as well as static style and class attributes) a directive, component or template
* is marked as the owner. When an owner is determined (this happens when the template
* is first passed over) the directive owner is allocated into the styling context. When
* this happens, each owner gets its own index value. This then ensures that once any
* style and/or class binding are assigned into the context then they are marked to
* that directive's index value.
*
* @param context the target StylingContext
* @param directiveRef the directive that will be allocated into the context
* @returns the index where the directive was inserted into
*/
export function allocateOrUpdateDirectiveIntoContext(
context: StylingContext, directiveIndex: number, singlePropValuesIndex: number = -1,
styleSanitizer?: StyleSanitizeFn | null | undefined): void {
const directiveRegistry = context[StylingIndex.DirectiveRegistryPosition];
const index = directiveIndex * DirectiveRegistryValuesIndex.Size;
// we preemptively make space into the directives array and then
// assign values slot-by-slot to ensure that if the directive ordering
// changes then it will still function
const limit = index + DirectiveRegistryValuesIndex.Size;
for (let i = directiveRegistry.length; i < limit; i += DirectiveRegistryValuesIndex.Size) {
// -1 is used to signal that the directive has been allocated, but
// no actual style or class bindings have been registered yet...
directiveRegistry.push(-1, null);
}
const propValuesStartPosition = index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset;
if (singlePropValuesIndex >= 0 && directiveRegistry[propValuesStartPosition] === -1) {
directiveRegistry[propValuesStartPosition] = singlePropValuesIndex;
directiveRegistry[index + DirectiveRegistryValuesIndex.StyleSanitizerOffset] =
styleSanitizer || null;
}
}
/**
* Used clone a copy of a pre-computed template of a styling context.
*
* A pre-computed template is designed to be computed once for a given element
* (instructions.ts has logic for caching this).
*/
export function allocStylingContext(
element: RElement | null, templateStyleContext: StylingContext): StylingContext {
// each instance gets a copy
const context = templateStyleContext.slice() as any as StylingContext;
// the HEADER values contain arrays which also need
// to be copied over into the new context
for (let i = 0; i < StylingIndex.SingleStylesStartPosition; i++) {
const value = templateStyleContext[i];
if (Array.isArray(value)) {
context[i] = value.slice();
}
}
context[StylingIndex.ElementPosition] = element;
// this will prevent any other directives from extending the context
context[StylingIndex.MasterFlagPosition] |= StylingFlags.BindingAllocationLocked;
return context;
}
/**
* Retrieve the `StylingContext` at a given index.
*
* This method lazily creates the `StylingContext`. This is because in most cases
* we have styling without any bindings. Creating `StylingContext` eagerly would mean that
* every style declaration such as `<div style="color: red">` would result `StyleContext`
* which would create unnecessary memory pressure.
*
* @param index Index of the style allocation. See: `styling`.
* @param viewData The view to search for the styling context
*/
export function getStylingContextFromLView(index: number, viewData: LView): StylingContext {
let storageIndex = index;
let slotValue: LContainer|LView|StylingContext|RElement = viewData[storageIndex];
let wrapper: LContainer|LView|StylingContext = viewData;
while (Array.isArray(slotValue)) {
wrapper = slotValue;
slotValue = slotValue[HOST] as LView | StylingContext | RElement;
}
if (isStylingContext(wrapper)) {
return wrapper;
} else {
// This is an LView or an LContainer
const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate;
if (wrapper !== viewData) {
storageIndex = HOST;
}
return wrapper[storageIndex] = stylingTemplate ?
allocStylingContext(slotValue, stylingTemplate) :
createEmptyStylingContext(slotValue);
}
}
export function isAnimationProp(name: string): boolean {
return name[0] === ANIMATION_PROP_PREFIX;
}
export function forceClassesAsString(classes: string | {[key: string]: any} | null | undefined):
string {
if (classes && typeof classes !== 'string') {
classes = Object.keys(classes).join(' ');
}
return (classes as string) || '';
}
export function forceStylesAsString(styles: {[key: string]: any} | null | undefined): string {
let str = '';
if (styles) {
const props = Object.keys(styles);
for (let i = 0; i < props.length; i++) {
const prop = props[i];
str += (i ? ';' : '') + `${prop}:${styles[prop]}`;
}
}
return str;
}
export function addPlayerInternal(
playerContext: PlayerContext, rootContext: RootContext, element: HTMLElement,
player: Player | null, playerContextIndex: number, ref?: any): boolean {
ref = ref || element;
if (playerContextIndex) {
playerContext[playerContextIndex] = player;
} else {
playerContext.push(player);
}
if (player) {
player.addEventListener(PlayState.Destroyed, () => {
const index = playerContext.indexOf(player);
const nonFactoryPlayerIndex = playerContext[PlayerIndex.NonBuilderPlayersStart];
// if the player is being removed from the factory side of the context
// (which is where the [style] and [class] bindings do their thing) then
// that side of the array cannot be resized since the respective bindings
// have pointer index values that point to the associated factory instance
if (index) {
if (index < nonFactoryPlayerIndex) {
playerContext[index] = null;
} else {
playerContext.splice(index, 1);
}
}
player.destroy();
});
const playerHandler =
rootContext.playerHandler || (rootContext.playerHandler = new CorePlayerHandler());
playerHandler.queuePlayer(player, ref);
return true;
}
return false;
}
export function getPlayersInternal(playerContext: PlayerContext): Player[] {
const players: Player[] = [];
const nonFactoryPlayersStart = playerContext[PlayerIndex.NonBuilderPlayersStart];
// add all factory-based players (which are a part of [style] and [class] bindings)
for (let i = PlayerIndex.PlayerBuildersStartPosition + PlayerIndex.PlayerOffsetPosition;
i < nonFactoryPlayersStart; i += PlayerIndex.PlayerAndPlayerBuildersTupleSize) {
const player = playerContext[i] as Player | null;
if (player) {
players.push(player);
}
}
// add all custom players (not a part of [style] and [class] bindings)
for (let i = nonFactoryPlayersStart; i < playerContext.length; i++) {
players.push(playerContext[i] as Player);
}
return players;
}
export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext|
null {
context = context || getLContext(target) !;
if (!context) {
ngDevMode && throwInvalidRefError();
return null;
}
const {lView, nodeIndex} = context;
const stylingContext = getStylingContextFromLView(nodeIndex, lView);
return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext);
}
export function getPlayerContext(stylingContext: StylingContext): PlayerContext|null {
return stylingContext[StylingIndex.PlayerContext];
}
export function allocPlayerContext(data: StylingContext): PlayerContext {
return data[StylingIndex.PlayerContext] =
[PlayerIndex.SinglePlayerBuildersStartPosition, null, null, null, null];
}
export function throwInvalidRefError() {
throw new Error('Only elements that exist in an Angular application can be used for animations');
}

View File

@ -11,16 +11,15 @@ import {AttributeMarker, TAttributes, TNode, TNodeType} from '../interfaces/node
import {RElement} from '../interfaces/renderer';
import {BINDING_INDEX, LView, RENDERER, TVIEW} from '../interfaces/view';
import {getActiveDirectiveId, getActiveDirectiveSuperClassDepth, getActiveDirectiveSuperClassHeight, getCurrentStyleSanitizer, getLView, getPreviousOrParentTNode, getSelectedIndex, setCurrentStyleSanitizer} from '../state';
import {forceClassesAsString, forceStylesAsString} from '../styling/util';
import {NO_CHANGE} from '../tokens';
import {renderStringify} from '../util/misc_utils';
import {getNativeByTNode, getTNode} from '../util/view_utils';
import {flushStyling, updateClassBinding, updateStyleBinding} from './bindings';
import {StylingMapArrayIndex, TStylingContext} from './interfaces';
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from './interfaces';
import {activateStylingMapFeature, addItemToStylingMap, normalizeIntoStylingMap, stylingMapToString} from './map_based_bindings';
import {attachStylingDebugObject} from './styling_debug';
import {allocTStylingContext, concatString, getInitialStylingValue, getStylingMapArray, hasClassInput, hasStyleInput, hasValueChanged, isContextLocked, isStylingContext, updateLastDirectiveIndex as _updateLastDirectiveIndex} from './util';
import {allocTStylingContext, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, hasClassInput, hasStyleInput, hasValueChanged, isContextLocked, isStylingContext, updateLastDirectiveIndex as _updateLastDirectiveIndex} from './util';
@ -49,19 +48,9 @@ import {allocTStylingContext, concatString, getInitialStylingValue, getStylingMa
* @codeGenApi
*/
export function ɵɵstyling() {
const lView = getLView();
const tView = lView[TVIEW];
const tView = getLView()[TVIEW];
if (tView.firstTemplatePass) {
const tNode = getPreviousOrParentTNode();
const directiveStylingIndex = getActiveDirectiveStylingIndex();
// temporary workaround until `select(n)` is fully compatible
if (directiveStylingIndex) {
const fns = tNode.onElementCreationFns = tNode.onElementCreationFns || [];
fns.push(() => updateLastDirectiveIndex(tNode, directiveStylingIndex));
} else {
updateLastDirectiveIndex(tNode, directiveStylingIndex);
}
updateLastDirectiveIndex(getPreviousOrParentTNode(), getActiveDirectiveStylingIndex());
}
}
@ -389,16 +378,12 @@ function normalizeStylingDirectiveInputValue(
}
/**
* Temporary function to bridge styling functionality between this new
* refactor (which is here inside of `styling_next/`) and the old
* implementation (which lives inside of `styling/`).
* Flushes all styling code to the element.
*
* The new styling refactor ensures that styling flushing is called
* automatically when a template function exits or a follow-up element
* is visited (i.e. when `select(n)` is called). Because the `select(n)`
* instruction is not fully implemented yet (it doesn't actually execute
* host binding instruction code at the right time), this means that a
* styling apply function is still needed.
* This function is designed to be called from the template and hostBindings
* functions and may be called multiple times depending whether multiple
* sources of styling exist. If called multiple times, only the last call
* to `stlyingApply()` will render styling to the element.
*
* @codeGenApi
*/
@ -447,18 +432,27 @@ export function registerInitialStylingOnTNode(
}
if (classes && classes.length > StylingMapArrayIndex.ValuesStartPosition) {
classes[StylingMapArrayIndex.RawValuePosition] = stylingMapToString(classes, true);
tNode.classes = classes;
if (!tNode.classes) {
tNode.classes = classes;
}
updateRawValueOnContext(tNode.classes, stylingMapToString(classes, true));
}
if (styles && styles.length > StylingMapArrayIndex.ValuesStartPosition) {
styles[StylingMapArrayIndex.RawValuePosition] = stylingMapToString(styles, false);
tNode.styles = styles;
if (!tNode.styles) {
tNode.styles = styles;
}
updateRawValueOnContext(tNode.styles, stylingMapToString(styles, false));
}
return hasAdditionalInitialStyling;
}
function updateRawValueOnContext(context: TStylingContext | StylingMapArray, value: string) {
const stylingMapArr = getStylingMapArray(context) !;
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value;
}
export function getActiveDirectiveStylingIndex(): number {
// whenever a directive's hostBindings function is called a uniqueId value
// is assigned. Normally this is enough to help distinguish one directive

View File

@ -284,7 +284,7 @@ import {LView} from '../interfaces/view';
export interface TStylingContext extends
Array<number|string|number|boolean|null|StylingMapArray|{}> {
/** Initial value position for static styles */
[TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray|null;
[TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray;
/** Configuration data for the context */
[TStylingContextIndex.ConfigPosition]: TStylingConfigFlags;

View File

@ -11,6 +11,7 @@ import {isDifferent} from '../util/misc_utils';
import {StylingMapArray, StylingMapArrayIndex, TStylingConfigFlags, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
const MAP_BASED_ENTRY_PROP_NAME = '--MAP--';
const TEMPLATE_DIRECTIVE_INDEX = 0;
/**
* Creates a new instance of the `TStylingContext`.
@ -27,17 +28,14 @@ export function allocTStylingContext(initialStyling?: StylingMapArray | null): T
// (this means that when map-based values are applied then sanitization will
// be checked against each property).
const mapBasedConfig = TStylingContextPropConfigFlags.SanitizationRequired;
const context: TStylingContext = [
initialStyling || null,
return [
initialStyling || [''], // empty initial-styling map value
TStylingConfigFlags.Initial,
// the LastDirectiveIndex value in the context is used to track which directive is the last
// to call `stylingApply()`. The `-1` value implies that no directive has been set yet.
-1,
TEMPLATE_DIRECTIVE_INDEX,
mapBasedConfig,
0,
MAP_BASED_ENTRY_PROP_NAME,
];
return context;
}
/**
@ -54,12 +52,17 @@ export function allocTStylingContext(initialStyling?: StylingMapArray | null): T
*/
export function updateLastDirectiveIndex(
context: TStylingContext, lastDirectiveIndex: number): void {
const currentValue = context[TStylingContextIndex.LastDirectiveIndexPosition];
if (lastDirectiveIndex !== currentValue) {
context[TStylingContextIndex.LastDirectiveIndexPosition] = lastDirectiveIndex;
if (currentValue === 0 && lastDirectiveIndex > 0) {
if (lastDirectiveIndex === TEMPLATE_DIRECTIVE_INDEX) {
const currentValue = context[TStylingContextIndex.LastDirectiveIndexPosition];
if (currentValue > TEMPLATE_DIRECTIVE_INDEX) {
// This means that a directive or two contained a host bindings function, but
// now the template function also contains styling. When this combination of sources
// comes up then we need to tell the context to store the state between updates
// (because host bindings evaluation happens after template binding evaluation).
markContextToPersistState(context);
}
} else {
context[TStylingContextIndex.LastDirectiveIndexPosition] = lastDirectiveIndex;
}
}
@ -228,3 +231,23 @@ export function setMapValue(
export function getMapValue(map: StylingMapArray, index: number): string|null {
return map[index + StylingMapArrayIndex.ValueOffset] as string | null;
}
export function forceClassesAsString(classes: string | {[key: string]: any} | null | undefined):
string {
if (classes && typeof classes !== 'string') {
classes = Object.keys(classes).join(' ');
}
return (classes as string) || '';
}
export function forceStylesAsString(styles: {[key: string]: any} | null | undefined): string {
let str = '';
if (styles) {
const props = Object.keys(styles);
for (let i = 0; i < props.length; i++) {
const prop = props[i];
str = concatString(str, `${prop}:${styles[prop]}`, ';');
}
}
return str;
}

View File

@ -10,8 +10,6 @@ import {CssSelector} from '../interfaces/projection';
import {ProceduralRenderer3, RElement, isProceduralRenderer} from '../interfaces/renderer';
import {RENDERER} from '../interfaces/view';
import {getLView} from '../state';
import {isAnimationProp} from '../styling/util';
/**
@ -115,3 +113,9 @@ export function isNameOnlyAttributeMarker(marker: string | AttributeMarker | Css
return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template ||
marker === AttributeMarker.I18n;
}
export const ANIMATION_PROP_PREFIX = '@';
export function isAnimationProp(name: string): boolean {
return name[0] === ANIMATION_PROP_PREFIX;
}

View File

@ -8,7 +8,7 @@
import {assertDefined} from '../../util/assert';
import {global} from '../../util/global';
import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getPlayers, getRootComponents, getViewComponent, markDirty} from '../global_utils_api';
import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent, markDirty} from '../global_utils_api';
@ -48,7 +48,6 @@ export function publishDefaultGlobalUtils() {
publishGlobalUtil('getInjector', getInjector);
publishGlobalUtil('getRootComponents', getRootComponents);
publishGlobalUtil('getDirectives', getDirectives);
publishGlobalUtil('getPlayers', getPlayers);
publishGlobalUtil('markDirty', markDirty);
}
}

View File

@ -12,25 +12,21 @@ import {LContainer, TYPE} from '../interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
import {TNode} from '../interfaces/node';
import {RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling';
import {isLContainer, isLView} from '../interfaces/type_checks';
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, TData, TVIEW} from '../interfaces/view';
/**
* For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`,
* `StylingContext`) in same location in `LView`. This is because we don't want to pre-allocate
* space for it because the storage is sparse. This file contains utilities for dealing with such
* data types.
* For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`)
* in same location in `LView`. This is because we don't want to pre-allocate space for it
* because the storage is sparse. This file contains utilities for dealing with such data types.
*
* How do we know what is stored at a given location in `LView`.
* - `Array.isArray(value) === false` => `RNode` (The normal storage value)
* - `Array.isArray(value) === true` => then the `value[0]` represents the wrapped value.
* - `typeof value[TYPE] === 'object'` => `LView`
* - This happens when we have a component at a given location
* - `typeof value[TYPE] === 'number'` => `StylingContext`
* - This happens when we have style/class binding at a given location.
* - `typeof value[TYPE] === true` => `LContainer`
* - This happens when we have `LContainer` binding at a given location.
*
@ -77,22 +73,6 @@ export function unwrapLContainer(value: RNode | LView | LContainer): LContainer|
return null;
}
/**
* Returns `StylingContext` or `null` if not found.
* @param value wrapped value of `RNode`, `LView`, `LContainer`, `StylingContext`
*/
export function unwrapStylingContext(value: RNode | LView | LContainer | StylingContext):
StylingContext|null {
while (Array.isArray(value)) {
// This check is same as `isStylingContext()` but we don't call at as we don't want to call
// `Array.isArray()` twice and give JITer more work for inlining.
if (typeof value[TYPE] === 'number') return value as StylingContext;
value = value[HOST] as any;
}
return null;
}
/**
* Retrieves an element value from the provided `viewData`, by unwrapping
* from any containers, component views, or style contexts.

View File

@ -555,6 +555,35 @@ describe('styling', () => {
expect(capturedMyClassBindingValue !).toEqual('foo');
});
onlyInIvy('only ivy balances styling across directives and component host bindings')
.it('should allow multiple directives to set dynamic and static classes independent of one another',
() => {
@Component({
template: `
<div dir-one dir-two></div>
`
})
class Cmp {
}
@Directive({selector: '[dir-one]', host: {'[class.dir-one]': 'dirOneExp'}})
class DirOne {
dirOneExp = true;
}
@Directive({selector: '[dir-two]', host: {'class': 'dir-two'}})
class DirTwo {
}
TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
const element = fixture.nativeElement.querySelector('div');
expect(element.classList.contains('dir-one')).toBeTruthy();
expect(element.classList.contains('dir-two')).toBeTruthy();
});
describe('NgClass', () => {
// We had a bug where NgClass would not allocate sufficient slots for host bindings,
@ -601,7 +630,5 @@ describe('styling', () => {
expect(fixture.debugElement.nativeElement.textContent).toContain('Hello');
});
});
});

View File

@ -9,7 +9,7 @@
import '@angular/core/test/bundling/util/src/reflect_metadata';
import {CommonModule} from '@angular/common';
import {Component, Directive, ElementRef, HostBinding, HostListener, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵbindPlayerFactory as bindPlayerFactory, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
import {Component, Directive, ElementRef, HostBinding, HostListener, NgModule, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
@Directive({
selector: '[make-color-grey]',

View File

@ -182,9 +182,6 @@
{
"name": "appendChild"
},
{
"name": "applyOnCreateInstructions"
},
{
"name": "attachPatchData"
},
@ -695,6 +692,9 @@
{
"name": "unwrapRNode"
},
{
"name": "updateRawValueOnContext"
},
{
"name": "viewAttachedToChangeDetector"
},

View File

@ -104,9 +104,6 @@
{
"name": "RENDERER_FACTORY"
},
{
"name": "RendererStyleFlags3"
},
{
"name": "SANITIZER"
},
@ -158,9 +155,6 @@
{
"name": "appendChild"
},
{
"name": "applyOnCreateInstructions"
},
{
"name": "attachPatchData"
},
@ -287,12 +281,6 @@
{
"name": "getLViewParent"
},
{
"name": "getMapProp"
},
{
"name": "getMapValue"
},
{
"name": "getNativeAnchorNode"
},
@ -347,9 +335,6 @@
{
"name": "getSelectedIndex"
},
{
"name": "getStylingMapArray"
},
{
"name": "hasParentInjector"
},
@ -395,9 +380,6 @@
{
"name": "isRootView"
},
{
"name": "isStylingContext"
},
{
"name": "leaveView"
},
@ -458,15 +440,9 @@
{
"name": "renderEmbeddedTemplate"
},
{
"name": "renderInitialStyling"
},
{
"name": "renderStringify"
},
{
"name": "renderStylingMap"
},
{
"name": "resetAllStylingState"
},
@ -488,9 +464,6 @@
{
"name": "setBindingRoot"
},
{
"name": "setClass"
},
{
"name": "setCurrentDirectiveDef"
},
@ -518,9 +491,6 @@
{
"name": "setSelectedIndex"
},
{
"name": "setStyle"
},
{
"name": "setTNodeAndViewData"
},

View File

@ -230,6 +230,9 @@
{
"name": "SkipSelf"
},
{
"name": "TEMPLATE_DIRECTIVE_INDEX"
},
{
"name": "TNODE"
},
@ -461,9 +464,6 @@
{
"name": "appendChild"
},
{
"name": "applyOnCreateInstructions"
},
{
"name": "applyStyling"
},
@ -1400,6 +1400,9 @@
{
"name": "updateLastDirectiveIndex"
},
{
"name": "updateRawValueOnContext"
},
{
"name": "updateStyleBinding"
},

View File

@ -7,7 +7,6 @@
*/
import {ɵmarkDirty as markDirty} from '@angular/core';
import {getPlayers} from '../../src/render3/players';
import {getComponent, getContext, getDirectives, getHostElement, getInjector, getListeners, getRootComponents, getViewComponent} from '../../src/render3/util/discovery_utils';
import {GLOBAL_PUBLISH_EXPANDO_KEY, GlobalDevModeContainer, publishDefaultGlobalUtils, publishGlobalUtil} from '../../src/render3/util/global_utils';
import {global} from '../../src/util/global';
@ -46,8 +45,6 @@ describe('global utils', () => {
it('should publish getInjector', () => { assertPublished('getInjector', getInjector); });
it('should publish markDirty', () => { assertPublished('markDirty', markDirty); });
it('should publish getPlayers', () => { assertPublished('getPlayers', getPlayers); });
});
});

View File

@ -13,7 +13,6 @@ import {ɵɵallocHostVars, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainer
import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
import {StylingIndex} from '../../src/render3/interfaces/styling';
import {CONTEXT, HEADER_OFFSET} from '../../src/render3/interfaces/view';
import {ɵɵsanitizeUrl} from '../../src/sanitization/sanitization';
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';

View File

@ -11,7 +11,6 @@ import {createTNode} from '@angular/core/src/render3/instructions/shared';
import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/interfaces/node';
import {CssSelector, CssSelectorList, SelectorFlags} from '../../src/render3/interfaces/projection';
import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorList} from '../../src/render3/node_selector_matcher';
import {initializeStaticContext} from '../../src/render3/styling/class_and_style_bindings';
function testLStaticData(tagName: string, attrs: TAttributes | null): TNode {
return createTNode(null !, null, TNodeType.Element, 0, tagName, attrs);

View File

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

View File

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

View File

@ -1,312 +0,0 @@
/**
* @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 {QueryList} from '@angular/core';
import {RenderFlags} from '@angular/core/src/render3';
import {getHostElement, ɵɵdefineComponent, ɵɵloadViewQuery, ɵɵviewQuery} from '../../../src/render3/index';
import {markDirty, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵselect, ɵɵstyling, ɵɵstylingApply} from '../../../src/render3/instructions/all';
import {PlayState, Player, PlayerHandler} from '../../../src/render3/interfaces/player';
import {RElement} from '../../../src/render3/interfaces/renderer';
import {addPlayer, getPlayers} from '../../../src/render3/players';
import {ɵɵqueryRefresh} from '../../../src/render3/query';
import {getOrCreatePlayerContext} from '../../../src/render3/styling/util';
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 = getOrCreatePlayerContext(element);
expect(getPlayers(element)).toEqual([]);
const player = new MockPlayer();
addPlayer(element, player);
expect(getPlayers(element)).toEqual([player]);
player.destroy();
expect(getPlayers(element)).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;
}
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');
ɵɵstyling();
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵstylingApply();
}
}
});
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(0, 'div');
ɵɵelement(1, 'child-comp', ['child', ''], ['child', 'child']);
ɵɵelementEnd();
}
},
viewQuery: function(rf: RenderFlags, ctx: SuperComp) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['child'], true, null);
}
if (rf & RenderFlags.Update) {
let tmp: any;
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.query = tmp as QueryList<any>);
}
},
directives: [Comp]
});
name = 'super-comp';
query !: QueryList<any>;
}
class MockPlayerHandler implements PlayerHandler {
players: Player[] = [];
lastFlushedPlayers: Player[] = [];
flushPlayers(): void {
this.lastFlushedPlayers = [...this.players];
this.players = [];
}
queuePlayer(player: Player): void { this.players.push(player); }
}

View File

@ -7,9 +7,7 @@
*/
import {createLContainer, createLView, createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
import {isLContainer, isLView, isStylingContext} from '@angular/core/src/render3/interfaces/type_checks';
import {createEmptyStylingContext} from '@angular/core/src/render3/styling/util';
import {unwrapLContainer, unwrapLView, unwrapRNode, unwrapStylingContext} from '@angular/core/src/render3/util/view_utils';
import {isLContainer, isLView} from '@angular/core/src/render3/interfaces/type_checks';
describe('view_utils', () => {
it('should verify unwrap methods', () => {
@ -18,18 +16,11 @@ describe('view_utils', () => {
const lView = createLView(null, tView, {}, 0, div, null, {} as any, {} as any, null, null);
const tNode = createTNode(null !, null, 3, 0, 'div', []);
const lContainer = createLContainer(lView, lView, div, tNode, true);
const styleContext = createEmptyStylingContext(lContainer, null, null, null);
expect(isLView(lView)).toBe(true);
expect(isLView(lContainer)).toBe(false);
expect(isLView(styleContext)).toBe(false);
expect(isLContainer(lView)).toBe(false);
expect(isLContainer(lContainer)).toBe(true);
expect(isLContainer(styleContext)).toBe(false);
expect(isStylingContext(lView)).toBe(false);
expect(isStylingContext(lContainer)).toBe(false);
expect(isStylingContext(styleContext)).toBe(true);
});
});

View File

@ -10,8 +10,6 @@ export declare function getInjector(target: {}): Injector;
export declare function getListeners(element: Element): Listener[];
export declare function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[];
export declare function getRootComponents(target: {}): any[];
export declare function getViewComponent<T = {}>(element: Element | {}): T | null;