diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 6d4990abfc..fd32d34715 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 14912, + "main": 14021, "polyfills": 43567 } } diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 8bf4c36253..7e57dd9248 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -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. // diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 6c0ca41625..7a4cb2e30e 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -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( 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; } diff --git a/packages/core/src/render3/global_utils_api.ts b/packages/core/src/render3/global_utils_api.ts index 11bec8f507..c41d44ee68 100644 --- a/packages/core/src/render3/global_utils_api.ts +++ b/packages/core/src/render3/global_utils_api.ts @@ -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'; diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 70fa75d728..ebe95e525f 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -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) { diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index 985f738ec2..09dc1273cb 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -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'; diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 8e149fc297..6a7fa36047 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -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]; diff --git a/packages/core/src/render3/instructions/element_container.ts b/packages/core/src/render3/instructions/element_container.ts index bcbe03db85..2e31723aa9 100644 --- a/packages/core/src/render3/instructions/element_container.ts +++ b/packages/core/src/render3/instructions/element_container.ts @@ -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 && diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index f34c93ac6e..b554974306 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -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 { diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 11a2c76928..239e701e34 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -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 diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts deleted file mode 100644 index 87b026c96c..0000000000 --- a/packages/core/src/render3/instructions/styling.ts +++ /dev/null @@ -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 = - [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) : - booleanOrNull(value); - const directiveStylingIndex = getActiveDirectiveStylingIndex(); - const stylingContext = getStylingContext(index, getLView()); - if (directiveStylingIndex) { - const args: ParamsOf = - [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 = [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 = [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; -} diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 024f549bbb..6296fd7b66 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -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. * diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts deleted file mode 100644 index fa1dd3ab09..0000000000 --- a/packages/core/src/render3/interfaces/styling.ts +++ /dev/null @@ -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: - * ``` - * - *
- * ``` - * - * 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: - * - * ``` - *
- * ``` - * - * 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: - * - * ``` - * - * - * @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 { [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 { - [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
elements contains a list of static styling values like so: - * - *
- * - * 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: - * ``` - *
- * ``` - * - * 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 { - [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: - * - *
- * 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 { - [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 { - [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; diff --git a/packages/core/src/render3/interfaces/type_checks.ts b/packages/core/src/render3/interfaces/type_checks.ts index d78237d863..97592a3ca7 100644 --- a/packages/core/src/render3/interfaces/type_checks.ts +++ b/packages/core/src/render3/interfaces/type_checks.ts @@ -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; } diff --git a/packages/core/src/render3/node_util.ts b/packages/core/src/render3/node_util.ts index 56ff6889d8..1640aade65 100644 --- a/packages/core/src/render3/node_util.ts +++ b/packages/core/src/render3/node_util.ts @@ -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. diff --git a/packages/core/src/render3/players.ts b/packages/core/src/render3/players.ts deleted file mode 100644 index 3c38289bd9..0000000000 --- a/packages/core/src/render3/players.ts +++ /dev/null @@ -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) : []; -} diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 37a49da87c..a62e169d08 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -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(); diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts deleted file mode 100644 index ce18612c0a..0000000000 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ /dev/null @@ -1,1943 +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, StyleSanitizeMode} from '../../sanitization/style_sanitizer'; -import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty'; -import {AttributeMarker, TAttributes} from '../interfaces/node'; -import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player'; -import {RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer'; -import {DirectiveOwnerAndPlayerBuilderIndex, DirectiveRegistryValuesIndex, InitialStylingValues, InitialStylingValuesIndex, MapBasedOffsetValues, MapBasedOffsetValuesIndex, SinglePropOffsetValues, SinglePropOffsetValuesIndex, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; -import {LView, RootContext} from '../interfaces/view'; -import {NO_CHANGE} from '../tokens'; -import {getRootContext} from '../util/view_traversal_utils'; - -import {allowFlush as allowHostInstructionsQueueFlush, flushQueue as flushHostInstructionsQueue} from './host_instructions_queue'; -import {BoundPlayerFactory} from './player_factory'; -import {addPlayerInternal, allocPlayerContext, allocateOrUpdateDirectiveIntoContext, createEmptyStylingContext, getPlayerContext} from './util'; - - -/** - * This file includes the code to power all styling-binding operations in Angular. - * - * These include: - * [style]="myStyleObj" - * [class]="myClassObj" - * [style.prop]="myPropValue" - * [class.name]="myClassValue" - * - * It also includes code that will allow style binding code to operate within host - * bindings for components/directives. - * - * There are many different ways in which these functions below are called. Please see - * `render3/interfaces/styling.ts` to get a better idea of how the styling algorithm works. - */ - - - -/** - * Creates a new StylingContext an fills it with the provided static styling attribute values. - */ -export function initializeStaticContext( - attrs: TAttributes, stylingStartIndex: number, directiveIndex: number = 0): StylingContext { - const context = createEmptyStylingContext(); - patchContextWithStaticAttrs(context, attrs, stylingStartIndex, directiveIndex); - return context; -} - -/** - * Designed to update an existing styling context with new static styling - * data (classes and styles). - * - * @param context the existing styling context - * @param attrs an array of new static styling attributes that will be - * assigned to the context - * @param attrsStylingStartIndex what index to start iterating within the - * provided `attrs` array to start reading style and class values - */ -export function patchContextWithStaticAttrs( - context: StylingContext, attrs: TAttributes, attrsStylingStartIndex: number, - directiveIndex: number): void { - // this means the context has already been set and instantiated - if (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) return; - - allocateOrUpdateDirectiveIntoContext(context, directiveIndex); - - let initialClasses: InitialStylingValues|null = null; - let initialStyles: InitialStylingValues|null = null; - let mode = -1; - for (let i = attrsStylingStartIndex; i < attrs.length; i++) { - const attr = attrs[i]; - if (typeof attr == 'number') { - mode = attr; - } else if (mode == AttributeMarker.Classes) { - initialClasses = initialClasses || context[StylingIndex.InitialClassValuesPosition]; - patchInitialStylingValue(initialClasses, attr as string, true, directiveIndex); - } else if (mode == AttributeMarker.Styles) { - initialStyles = initialStyles || context[StylingIndex.InitialStyleValuesPosition]; - patchInitialStylingValue(initialStyles, attr as string, attrs[++i], directiveIndex); - } - } -} - -/** - * Designed to add a style or class value into the existing set of initial styles. - * - * The function will search and figure out if a style/class value is already present - * within the provided initial styling array. If and when a style/class value is - * present (allocated) then the code below will set the new value depending on the - * following cases: - * - * 1) if the existing value is falsy (this happens because a `[class.prop]` or - * `[style.prop]` binding was set, but there wasn't a matching static style - * or class present on the context) - * 2) if the value was set already by the template, component or directive, but the - * new value is set on a higher level (i.e. a sub component which extends a parent - * component sets its value after the parent has already set the same one) - * 3) if the same directive provides a new set of styling values to set - * - * @param initialStyling the initial styling array where the new styling entry will be added to - * @param prop the property value of the new entry (e.g. `width` (styles) or `foo` (classes)) - * @param value the styling value of the new entry (e.g. `absolute` (styles) or `true` (classes)) - * @param directiveOwnerIndex the directive owner index value of the styling source responsible - * for these styles (see `interfaces/styling.ts#directives` for more info) - */ -function patchInitialStylingValue( - initialStyling: InitialStylingValues, prop: string, value: any, - directiveOwnerIndex: number): void { - for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyling.length; - i += InitialStylingValuesIndex.Size) { - const key = initialStyling[i + InitialStylingValuesIndex.PropOffset]; - if (key === prop) { - const existingValue = - initialStyling[i + InitialStylingValuesIndex.ValueOffset] as string | null | boolean; - const existingOwner = - initialStyling[i + InitialStylingValuesIndex.DirectiveOwnerOffset] as number; - if (allowValueChange(existingValue, value, existingOwner, directiveOwnerIndex)) { - addOrUpdateStaticStyle(i, initialStyling, prop, value, directiveOwnerIndex); - } - return; - } - } - - // We did not find existing key, add a new one. - addOrUpdateStaticStyle(null, initialStyling, prop, value, directiveOwnerIndex); -} - -/** - * Runs through the initial class values present in the provided - * context and renders them via the provided renderer on the element. - * - * @param element the element the styling will be applied to - * @param context the source styling context which contains the initial class values - * @param renderer the renderer instance that will be used to apply the class - * @returns the index that the classes were applied up until - */ -export function renderInitialClasses( - element: RElement, context: StylingContext, renderer: Renderer3, startIndex?: number): number { - const initialClasses = context[StylingIndex.InitialClassValuesPosition]; - let i = startIndex || InitialStylingValuesIndex.KeyValueStartPosition; - while (i < initialClasses.length) { - const value = initialClasses[i + InitialStylingValuesIndex.ValueOffset]; - if (value) { - setClass( - element, initialClasses[i + InitialStylingValuesIndex.PropOffset] as string, true, - renderer, null); - } - i += InitialStylingValuesIndex.Size; - } - return i; -} - -/** - * Runs through the initial styles values present in the provided - * context and renders them via the provided renderer on the element. - * - * @param element the element the styling will be applied to - * @param context the source styling context which contains the initial class values - * @param renderer the renderer instance that will be used to apply the class - * @returns the index that the styles were applied up until - */ -export function renderInitialStyles( - element: RElement, context: StylingContext, renderer: Renderer3, startIndex?: number) { - const initialStyles = context[StylingIndex.InitialStyleValuesPosition]; - let i = startIndex || InitialStylingValuesIndex.KeyValueStartPosition; - while (i < initialStyles.length) { - const value = initialStyles[i + InitialStylingValuesIndex.ValueOffset]; - if (value) { - setStyle( - element, initialStyles[i + InitialStylingValuesIndex.PropOffset] as string, - value as string, renderer, null); - } - i += InitialStylingValuesIndex.Size; - } - return i; -} - -export function allowNewBindingsForStylingContext(context: StylingContext): boolean { - return (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) === 0; -} - -/** - * Adds in new binding values to a styling context. - * - * If a directive value is provided then all provided class/style binding names will - * reference the provided directive. - * - * @param context the existing styling context - * @param classBindingNames an array of class binding names that will be added to the context - * @param styleBindingNames an array of style binding names that will be added to the context - * @param styleSanitizer an optional sanitizer that handle all sanitization on for each of - * the bindings added to the context. Note that if a directive is provided then the sanitizer - * instance will only be active if and when the directive updates the bindings that it owns. - */ -export function updateContextWithBindings( - context: StylingContext, directiveIndex: number, classBindingNames?: string[] | null, - styleBindingNames?: string[] | null, styleSanitizer?: StyleSanitizeFn | null) { - if (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) return; - - // this means the context has already been patched with the directive's bindings - const isNewDirective = - findOrPatchDirectiveIntoRegistry(context, directiveIndex, false, styleSanitizer); - if (!isNewDirective) { - // this means the directive has already been patched in ... No point in doing anything - return; - } - - if (styleBindingNames) { - styleBindingNames = hyphenateEntries(styleBindingNames); - } - - // there are alot of variables being used below to track where in the context the new - // binding values will be placed. Because the context consists of multiple types of - // entries (single classes/styles and multi classes/styles) alot of the index positions - // need to be computed ahead of time and the context needs to be extended before the values - // are inserted in. - const singlePropOffsetValues = context[StylingIndex.SinglePropOffsetPositions]; - const totalCurrentClassBindings = - singlePropOffsetValues[SinglePropOffsetValuesIndex.ClassesCountPosition]; - const totalCurrentStyleBindings = - singlePropOffsetValues[SinglePropOffsetValuesIndex.StylesCountPosition]; - - const cachedClassMapValues = context[StylingIndex.CachedMultiClasses]; - const cachedStyleMapValues = context[StylingIndex.CachedMultiStyles]; - - const classesOffset = totalCurrentClassBindings * StylingIndex.Size; - const stylesOffset = totalCurrentStyleBindings * StylingIndex.Size; - - const singleStylesStartIndex = StylingIndex.SingleStylesStartPosition; - let singleClassesStartIndex = singleStylesStartIndex + stylesOffset; - let multiStylesStartIndex = singleClassesStartIndex + classesOffset; - let multiClassesStartIndex = multiStylesStartIndex + stylesOffset; - - // because we're inserting more bindings into the context, this means that the - // binding values need to be referenced the singlePropOffsetValues array so that - // the template/directive can easily find them inside of the `styleProp` - // and the `classProp` functions without iterating through the entire context. - // The first step to setting up these reference points is to mark how many bindings - // are being added. Even if these bindings already exist in the context, the directive - // or template code will still call them unknowingly. Therefore the total values need - // to be registered so that we know how many bindings are assigned to each directive. - const currentSinglePropsLength = singlePropOffsetValues.length; - singlePropOffsetValues.push( - styleBindingNames ? styleBindingNames.length : 0, - classBindingNames ? classBindingNames.length : 0); - - // the code below will check to see if a new style binding already exists in the context - // if so then there is no point in inserting it into the context again. Whether or not it - // exists the styling offset code will now know exactly where it is - let insertionOffset = 0; - const filteredStyleBindingNames: string[] = []; - if (styleBindingNames && styleBindingNames.length) { - for (let i = 0; i < styleBindingNames.length; i++) { - const name = styleBindingNames[i]; - let singlePropIndex = - getMatchingBindingIndex(context, name, singleStylesStartIndex, singleClassesStartIndex); - if (singlePropIndex == -1) { - singlePropIndex = singleClassesStartIndex + insertionOffset; - insertionOffset += StylingIndex.Size; - filteredStyleBindingNames.push(name); - } - singlePropOffsetValues.push(singlePropIndex); - } - } - - // just like with the style binding loop above, the new class bindings get the same treatment... - const filteredClassBindingNames: string[] = []; - if (classBindingNames && classBindingNames.length) { - for (let i = 0; i < classBindingNames.length; i++) { - const name = classBindingNames[i]; - let singlePropIndex = - getMatchingBindingIndex(context, name, singleClassesStartIndex, multiStylesStartIndex); - if (singlePropIndex == -1) { - singlePropIndex = multiStylesStartIndex + insertionOffset; - insertionOffset += StylingIndex.Size; - filteredClassBindingNames.push(name); - } else { - singlePropIndex += filteredStyleBindingNames.length * StylingIndex.Size; - } - singlePropOffsetValues.push(singlePropIndex); - } - } - - // because new styles are being inserted, this means the existing collection of style offset - // index values are incorrect (they point to the wrong values). The code below will run through - // the entire offset array and update the existing set of index values to point to their new - // locations while taking the new binding values into consideration. - let i = SinglePropOffsetValuesIndex.ValueStartPosition; - if (filteredStyleBindingNames.length) { - while (i < currentSinglePropsLength) { - const totalStyles = - singlePropOffsetValues[i + SinglePropOffsetValuesIndex.StylesCountPosition]; - const totalClasses = - singlePropOffsetValues[i + SinglePropOffsetValuesIndex.ClassesCountPosition]; - if (totalClasses) { - const start = i + SinglePropOffsetValuesIndex.ValueStartPosition + totalStyles; - for (let j = start; j < start + totalClasses; j++) { - singlePropOffsetValues[j] += filteredStyleBindingNames.length * StylingIndex.Size; - } - } - - const total = totalStyles + totalClasses; - i += SinglePropOffsetValuesIndex.ValueStartPosition + total; - } - } - - const totalNewEntries = filteredClassBindingNames.length + filteredStyleBindingNames.length; - - // in the event that there are new style values being inserted, all existing class and style - // bindings need to have their pointer values offsetted with the new amount of space that is - // used for the new style/class bindings. - for (let i = singleStylesStartIndex; i < context.length; i += StylingIndex.Size) { - const isMultiBased = i >= multiStylesStartIndex; - const isClassBased = i >= (isMultiBased ? multiClassesStartIndex : singleClassesStartIndex); - const flag = getPointers(context, i); - const staticIndex = getInitialIndex(flag); - let singleOrMultiIndex = getMultiOrSingleIndex(flag); - if (isMultiBased) { - singleOrMultiIndex += - isClassBased ? (filteredStyleBindingNames.length * StylingIndex.Size) : 0; - } else { - singleOrMultiIndex += (totalNewEntries * StylingIndex.Size) + - ((isClassBased ? filteredStyleBindingNames.length : 0) * StylingIndex.Size); - } - setFlag(context, i, pointers(flag, staticIndex, singleOrMultiIndex)); - } - - // this is where we make space in the context for the new style bindings - for (let i = 0; i < filteredStyleBindingNames.length * StylingIndex.Size; i++) { - context.splice(multiClassesStartIndex, 0, null); - context.splice(singleClassesStartIndex, 0, null); - singleClassesStartIndex++; - multiStylesStartIndex++; - multiClassesStartIndex += 2; // both single + multi slots were inserted - } - - // this is where we make space in the context for the new class bindings - for (let i = 0; i < filteredClassBindingNames.length * StylingIndex.Size; i++) { - context.splice(multiStylesStartIndex, 0, null); - context.push(null); - multiStylesStartIndex++; - multiClassesStartIndex++; - } - - const initialClasses = context[StylingIndex.InitialClassValuesPosition]; - const initialStyles = context[StylingIndex.InitialStyleValuesPosition]; - - // the code below will insert each new entry into the context and assign the appropriate - // flags and index values to them. It's important this runs at the end of this function - // because the context, property offset and index values have all been computed just before. - for (let i = 0; i < totalNewEntries; i++) { - const entryIsClassBased = i >= filteredStyleBindingNames.length; - const adjustedIndex = entryIsClassBased ? (i - filteredStyleBindingNames.length) : i; - const propName = entryIsClassBased ? filteredClassBindingNames[adjustedIndex] : - filteredStyleBindingNames[adjustedIndex]; - - let multiIndex, singleIndex; - if (entryIsClassBased) { - multiIndex = multiClassesStartIndex + - ((totalCurrentClassBindings + adjustedIndex) * StylingIndex.Size); - singleIndex = singleClassesStartIndex + - ((totalCurrentClassBindings + adjustedIndex) * StylingIndex.Size); - } else { - multiIndex = - multiStylesStartIndex + ((totalCurrentStyleBindings + adjustedIndex) * StylingIndex.Size); - singleIndex = singleStylesStartIndex + - ((totalCurrentStyleBindings + adjustedIndex) * StylingIndex.Size); - } - - // if a property is not found in the initial style values list then it - // is ALWAYS added in case a follow-up directive introduces the same initial - // style/class value later on. - let initialValuesToLookup = entryIsClassBased ? initialClasses : initialStyles; - let indexForInitial = getInitialStylingValuesIndexOf(initialValuesToLookup, propName); - if (indexForInitial === -1) { - indexForInitial = addOrUpdateStaticStyle( - null, initialValuesToLookup, propName, entryIsClassBased ? false : null, - directiveIndex) + - InitialStylingValuesIndex.ValueOffset; - } else { - indexForInitial += InitialStylingValuesIndex.ValueOffset; - } - - const initialFlag = - prepareInitialFlag(context, propName, entryIsClassBased, styleSanitizer || null); - - setFlag(context, singleIndex, pointers(initialFlag, indexForInitial, multiIndex)); - setProp(context, singleIndex, propName); - setValue(context, singleIndex, null); - setPlayerBuilderIndex(context, singleIndex, 0, directiveIndex); - - setFlag(context, multiIndex, pointers(initialFlag, indexForInitial, singleIndex)); - setProp(context, multiIndex, propName); - setValue(context, multiIndex, null); - setPlayerBuilderIndex(context, multiIndex, 0, directiveIndex); - } - - // the total classes/style values are updated so the next time the context is patched - // additional style/class bindings from another directive then it knows exactly where - // to insert them in the context - singlePropOffsetValues[SinglePropOffsetValuesIndex.ClassesCountPosition] = - totalCurrentClassBindings + filteredClassBindingNames.length; - singlePropOffsetValues[SinglePropOffsetValuesIndex.StylesCountPosition] = - totalCurrentStyleBindings + filteredStyleBindingNames.length; - - // the map-based values also need to know how many entries got inserted - cachedClassMapValues[MapBasedOffsetValuesIndex.EntriesCountPosition] += - filteredClassBindingNames.length; - cachedStyleMapValues[MapBasedOffsetValuesIndex.EntriesCountPosition] += - filteredStyleBindingNames.length; - const newStylesSpaceAllocationSize = filteredStyleBindingNames.length * StylingIndex.Size; - const newClassesSpaceAllocationSize = filteredClassBindingNames.length * StylingIndex.Size; - - // update the multi styles cache with a reference for the directive that was just inserted - const directiveMultiStylesStartIndex = - multiStylesStartIndex + totalCurrentStyleBindings * StylingIndex.Size; - const cachedStyleMapIndex = cachedStyleMapValues.length; - registerMultiMapEntry( - context, directiveIndex, false, directiveMultiStylesStartIndex, - filteredStyleBindingNames.length); - - for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < cachedStyleMapIndex; - i += MapBasedOffsetValuesIndex.Size) { - // multi values start after all the single values (which is also where classes are) in the - // context therefore the new class allocation size should be taken into account - cachedStyleMapValues[i + MapBasedOffsetValuesIndex.PositionStartOffset] += - newClassesSpaceAllocationSize + newStylesSpaceAllocationSize; - } - - // update the multi classes cache with a reference for the directive that was just inserted - const directiveMultiClassesStartIndex = - multiClassesStartIndex + totalCurrentClassBindings * StylingIndex.Size; - const cachedClassMapIndex = cachedClassMapValues.length; - registerMultiMapEntry( - context, directiveIndex, true, directiveMultiClassesStartIndex, - filteredClassBindingNames.length); - - for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < cachedClassMapIndex; - i += MapBasedOffsetValuesIndex.Size) { - // the reason why both the styles + classes space is allocated to the existing offsets is - // because the styles show up before the classes in the context and any new inserted - // styles will offset any existing class entries in the context (even if there are no - // new class entries added) also the reason why it's *2 is because both single + multi - // entries for each new style have been added in the context before the multi class values - // actually start - cachedClassMapValues[i + MapBasedOffsetValuesIndex.PositionStartOffset] += - (newStylesSpaceAllocationSize * 2) + newClassesSpaceAllocationSize; - } - - // there is no initial value flag for the master index since it doesn't - // reference an initial style value - const masterFlag = pointers(0, 0, multiStylesStartIndex); - setFlag(context, StylingIndex.MasterFlagPosition, masterFlag); -} - -/** - * Searches through the existing registry of directives - */ -export function findOrPatchDirectiveIntoRegistry( - context: StylingContext, directiveIndex: number, staticModeOnly: boolean, - styleSanitizer?: StyleSanitizeFn | null): boolean { - const directiveRegistry = context[StylingIndex.DirectiveRegistryPosition]; - const index = directiveIndex * DirectiveRegistryValuesIndex.Size; - const singlePropStartPosition = index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset; - - // this means that the directive has already been registered into the registry - if (index < directiveRegistry.length && - (directiveRegistry[singlePropStartPosition] as number) >= 0) - return false; - - const singlePropsStartIndex = - staticModeOnly ? -1 : context[StylingIndex.SinglePropOffsetPositions].length; - allocateOrUpdateDirectiveIntoContext( - context, directiveIndex, singlePropsStartIndex, styleSanitizer); - return true; -} - -function getMatchingBindingIndex( - context: StylingContext, bindingName: string, start: number, end: number) { - for (let j = start; j < end; j += StylingIndex.Size) { - if (getProp(context, j) === bindingName) return j; - } - return -1; -} - -/** - * Registers the provided multi class values to the context. - * - * This function will iterate over the provided `classesInput` values and - * insert/update or remove them from the context at exactly the right spot. - * - * This function also takes in a directive which implies that the styling values will - * be evaluated for that directive with respect to any other styling that already exists - * on the context. When there are styles that conflict (e.g. say `ngClass` and `[class]` - * both update the `foo` className value at the same time) then the styling algorithm code below - * will decide which one wins based on the directive styling prioritization mechanism. (This - * mechanism is better explained in render3/interfaces/styling.ts#directives). - * - * This function will not render any styling values on screen, but is rather designed to - * prepare the context for that. `renderStyling` must be called afterwards to render any - * styling data that was set in this function (note that `updateClassProp` and - * `updateStyleProp` are designed to be run after this function is run). - * - * @param context The styling context that will be updated with the - * newly provided style values. - * @param classesInput The key/value map of CSS class names that will be used for the update. - * @param stylesInput The key/value map of CSS styles that will be used for the update. - */ -export function updateClassMap( - context: StylingContext, classesInput: {[key: string]: any} | string | - BoundPlayerFactory| null, - directiveIndex: number = 0): void { - updateStylingMap(context, classesInput, true, directiveIndex); -} - -/** - * Registers the provided multi style values to the context. - * - * This function will iterate over the provided `stylesInput` values and - * insert/update or remove them from the context at exactly the right spot. - * - * This function also takes in a directive which implies that the styling values will - * be evaluated for that directive with respect to any other styling that already exists - * on the context. When there are styles that conflict (e.g. say `ngStyle` and `[style]` - * both update the `width` property at the same time) then the styling algorithm code below - * will decide which one wins based on the directive styling prioritization mechanism. (This - * mechanism is better explained in render3/interfaces/styling.ts#directives). - * - * This function will not render any styling values on screen, but is rather designed to - * prepare the context for that. `renderStyling` must be called afterwards to render any - * styling data that was set in this function (note that `updateClassProp` and - * `updateStyleProp` are designed to be run after this function is run). - * - * @param context The styling context that will be updated with the - * newly provided style values. - * @param stylesInput The key/value map of CSS styles that will be used for the update. - */ -export function updateStyleMap( - context: StylingContext, stylesInput: {[key: string]: any} | string | - BoundPlayerFactory| null, - directiveIndex: number = 0): void { - updateStylingMap(context, stylesInput, false, directiveIndex); -} - -function updateStylingMap( - context: StylingContext, input: {[key: string]: any} | string | - BoundPlayerFactory| null, - entryIsClassBased: boolean, directiveIndex: number = 0): void { - ngDevMode && (entryIsClassBased ? ngDevMode.classMap++ : ngDevMode.styleMap++); - ngDevMode && assertValidDirectiveIndex(context, directiveIndex); - - // early exit (this is what's done to avoid using ctx.bind() to cache the value) - if (isMultiValueCacheHit(context, entryIsClassBased, directiveIndex, input)) return; - - input = - input === NO_CHANGE ? readCachedMapValue(context, entryIsClassBased, directiveIndex) : input; - - const element = context[StylingIndex.ElementPosition] !as HTMLElement; - const playerBuilder = input instanceof BoundPlayerFactory ? - new ClassAndStylePlayerBuilder( - input as any, element, entryIsClassBased ? BindingType.Class : BindingType.Style) : - null; - - const rawValue = - playerBuilder ? (input as BoundPlayerFactory<{[key: string]: any}|string>) !.value : input; - - // the position is always the same, but whether the player builder gets set - // at all (depending if its set) will be reflected in the index value below... - const playerBuilderPosition = entryIsClassBased ? PlayerIndex.ClassMapPlayerBuilderPosition : - PlayerIndex.StyleMapPlayerBuilderPosition; - let playerBuilderIndex = playerBuilder ? playerBuilderPosition : 0; - let playerBuildersAreDirty = false; - if (hasPlayerBuilderChanged(context, playerBuilder, playerBuilderPosition)) { - setPlayerBuilder(context, playerBuilder, playerBuilderPosition); - playerBuildersAreDirty = true; - } - - // each time a string-based value pops up then it shouldn't require a deep - // check of what's changed. - let startIndex: number; - let endIndex: number; - let propNames: string[]; - let applyAll = false; - if (entryIsClassBased) { - if (typeof rawValue == 'string') { - propNames = rawValue.split(/\s+/); - // this boolean is used to avoid having to create a key/value map of `true` values - // since a className string implies that all those classes are added - applyAll = true; - } else { - propNames = rawValue ? Object.keys(rawValue) : EMPTY_ARRAY; - } - startIndex = getMultiClassesStartIndex(context); - endIndex = context.length; - } else { - startIndex = getMultiStylesStartIndex(context); - endIndex = getMultiClassesStartIndex(context); - propNames = rawValue ? Object.keys(rawValue) : EMPTY_ARRAY; - } - - const values = (rawValue || EMPTY_OBJ) as{[key: string]: any}; - patchStylingMapIntoContext( - context, directiveIndex, playerBuilderIndex, startIndex, endIndex, propNames, - applyAll || values, input, entryIsClassBased); - - if (playerBuildersAreDirty) { - setContextPlayersDirty(context, true); - } - - ngDevMode && (entryIsClassBased ? ngDevMode.classMapCacheMiss++ : ngDevMode.styleMapCacheMiss++); -} - -/** - * Applies the given multi styling (styles or classes) values to the context. - * - * The styling algorithm code that applies multi-level styling (things like `[style]` and `[class]` - * values) resides here. - * - * Because this function understands that multiple directives may all write to the `[style]` and - * `[class]` bindings (through host bindings), it relies of each directive applying its binding - * value in order. This means that a directive like `classADirective` will always fire before - * `classBDirective` and therefore its styling values (classes and styles) will always be evaluated - * in the same order. Because of this consistent ordering, the first directive has a higher priority - * than the second one. It is with this prioritzation mechanism that the styling algorithm knows how - * to merge and apply redudant styling properties. - * - * The function itself applies the key/value entries (or an array of keys) to - * the context in the following steps. - * - * STEP 1: - * First check to see what properties are already set and in use by another directive in the - * context (e.g. `ngClass` set the `width` value and `[style.width]="w"` in a directive is - * attempting to set it as well). - * - * STEP 2: - * All remaining properties (that were not set prior to this directive) are now updated in - * the context. Any new properties are inserted exactly at their spot in the context and any - * previously set properties are shifted to exactly where the cursor sits while iterating over - * the context. The end result is a balanced context that includes the exact ordering of the - * styling properties/values for the provided input from the directive. - * - * STEP 3: - * Any unmatched properties in the context that belong to the directive are set to null - * - * Once the updating phase is done, then the algorithm will decide whether or not to flag the - * follow-up directives (the directives that will pass in their styling values) depending on if - * the "shape" of the multi-value map has changed (either if any keys are removed or added or - * if there are any new `null` values). If any follow-up directives are flagged as dirty then the - * algorithm will run again for them. Otherwise if the shape did not change then any follow-up - * directives will not run (so long as their binding values stay the same). - * - * @returns the total amount of new slots that were allocated into the context due to new styling - * properties that were detected. - */ -function patchStylingMapIntoContext( - context: StylingContext, directiveIndex: number, playerBuilderIndex: number, ctxStart: number, - ctxEnd: number, props: (string | null)[], values: {[key: string]: any} | true, cacheValue: any, - entryIsClassBased: boolean): number { - let dirty = false; - - const cacheIndex = MapBasedOffsetValuesIndex.ValuesStartPosition + - directiveIndex * MapBasedOffsetValuesIndex.Size; - - // the cachedValues array is the registry of all multi style values (map values). Each - // value is stored (cached) each time is updated. - const cachedValues = - context[entryIsClassBased ? StylingIndex.CachedMultiClasses : StylingIndex.CachedMultiStyles]; - - // this is the index in which this directive has ownership access to write to this - // value (anything before is owned by a previous directive that is more important) - const ownershipValuesStartIndex = - cachedValues[cacheIndex + MapBasedOffsetValuesIndex.PositionStartOffset]; - - const existingCachedValue = cachedValues[cacheIndex + MapBasedOffsetValuesIndex.ValueOffset]; - const existingCachedValueCount = - cachedValues[cacheIndex + MapBasedOffsetValuesIndex.ValueCountOffset]; - const existingCachedValueIsDirty = - cachedValues[cacheIndex + MapBasedOffsetValuesIndex.DirtyFlagOffset] === 1; - - // A shape change means the provided map value has either removed or added new properties - // compared to what were in the last time. If a shape change occurs then it means that all - // follow-up multi-styling entries are obsolete and will be examined again when CD runs - // them. If a shape change has not occurred then there is no reason to check any other - // directive values if their identity has not changed. If a previous directive set this - // value as dirty (because its own shape changed) then this means that the object has been - // offset to a different area in the context. Because its value has been offset then it - // can't write to a region that it wrote to before (which may have been a part of another - // directive) and therefore its shape changes too. - let valuesEntryShapeChange = - existingCachedValueIsDirty || ((!existingCachedValue && cacheValue) ? true : false); - - let totalUniqueValues = 0; - let totalNewAllocatedSlots = 0; - - // this is a trick to avoid building {key:value} map where all the values - // are `true` (this happens when a className string is provided instead of a - // map as an input value to this styling algorithm) - const applyAllProps = values === true; - - // STEP 1: - // loop through the earlier directives and figure out if any properties here will be placed - // in their area (this happens when the value is null because the earlier directive erased it). - let ctxIndex = ctxStart; - let totalRemainingProperties = props.length; - while (ctxIndex < ownershipValuesStartIndex) { - const currentProp = getProp(context, ctxIndex); - if (totalRemainingProperties) { - for (let i = 0; i < props.length; i++) { - const mapProp = props[i]; - const normalizedProp = mapProp ? (entryIsClassBased ? mapProp : hyphenate(mapProp)) : null; - if (normalizedProp && currentProp === normalizedProp) { - const currentValue = getValue(context, ctxIndex); - const currentDirectiveIndex = getDirectiveIndexFromEntry(context, ctxIndex); - const value = applyAllProps ? true : (values as{[key: string]: any})[normalizedProp]; - const currentFlag = getPointers(context, ctxIndex); - if (hasValueChanged(currentFlag, currentValue, value) && - allowValueChange(currentValue, value, currentDirectiveIndex, directiveIndex)) { - setValue(context, ctxIndex, value); - setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex, directiveIndex); - if (hasInitialValueChanged(context, currentFlag, value)) { - setDirty(context, ctxIndex, true); - dirty = true; - } - } - props[i] = null; - totalRemainingProperties--; - break; - } - } - } - ctxIndex += StylingIndex.Size; - } - - // STEP 2: - // apply the left over properties to the context in the correct order. - if (totalRemainingProperties) { - const sanitizer = entryIsClassBased ? null : getStyleSanitizer(context, directiveIndex); - propertiesLoop: for (let i = 0; i < props.length; i++) { - const mapProp = props[i]; - - if (!mapProp) { - // this is an early exit in case a value was already encountered above in the - // previous loop (which means that the property was applied or rejected) - continue; - } - - const value = applyAllProps ? true : (values as{[key: string]: any})[mapProp]; - const normalizedProp = entryIsClassBased ? mapProp : hyphenate(mapProp); - const isInsideOwnershipArea = ctxIndex >= ownershipValuesStartIndex; - - for (let j = ctxIndex; j < ctxEnd; j += StylingIndex.Size) { - const distantCtxProp = getProp(context, j); - if (distantCtxProp === normalizedProp) { - const distantCtxDirectiveIndex = getDirectiveIndexFromEntry(context, j); - const distantCtxPlayerBuilderIndex = getPlayerBuilderIndex(context, j); - const distantCtxValue = getValue(context, j); - const distantCtxFlag = getPointers(context, j); - - if (allowValueChange(distantCtxValue, value, distantCtxDirectiveIndex, directiveIndex)) { - // even if the entry isn't updated (by value or directiveIndex) then - // it should still be moved over to the correct spot in the array so - // the iteration loop is tighter. - if (isInsideOwnershipArea) { - swapMultiContextEntries(context, ctxIndex, j); - totalUniqueValues++; - } - - if (hasValueChanged(distantCtxFlag, distantCtxValue, value)) { - if (value === null || value === undefined && value !== distantCtxValue) { - valuesEntryShapeChange = true; - } - - setValue(context, ctxIndex, value); - - // SKIP IF INITIAL CHECK - // If the former `value` is `null` then it means that an initial value - // could be being rendered on screen. If that is the case then there is - // no point in updating the value in case it matches. In other words if the - // new value is the exact same as the previously rendered value (which - // happens to be the initial value) then do nothing. - if (distantCtxValue !== null || - hasInitialValueChanged(context, distantCtxFlag, value)) { - setDirty(context, ctxIndex, true); - dirty = true; - } - } - - if (distantCtxDirectiveIndex !== directiveIndex || - playerBuilderIndex !== distantCtxPlayerBuilderIndex) { - setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex, directiveIndex); - } - } - - ctxIndex += StylingIndex.Size; - continue propertiesLoop; - } - } - - // fallback case ... value not found at all in the context - if (value != null) { - valuesEntryShapeChange = true; - totalUniqueValues++; - const flag = prepareInitialFlag(context, normalizedProp, entryIsClassBased, sanitizer) | - StylingFlags.Dirty; - - const insertionIndex = isInsideOwnershipArea ? - ctxIndex : - (ownershipValuesStartIndex + totalNewAllocatedSlots * StylingIndex.Size); - insertNewMultiProperty( - context, insertionIndex, entryIsClassBased, normalizedProp, flag, value, directiveIndex, - playerBuilderIndex); - - totalNewAllocatedSlots++; - ctxEnd += StylingIndex.Size; - ctxIndex += StylingIndex.Size; - - dirty = true; - } - } - } - - // STEP 3: - // Remove (nullify) any existing entries in the context that were not a part of the - // map input value that was passed into this algorithm for this directive. - while (ctxIndex < ctxEnd) { - valuesEntryShapeChange = true; // some values are missing - const ctxValue = getValue(context, ctxIndex); - const ctxFlag = getPointers(context, ctxIndex); - const ctxDirective = getDirectiveIndexFromEntry(context, ctxIndex); - if (ctxValue != null) { - valuesEntryShapeChange = true; - } - if (hasValueChanged(ctxFlag, ctxValue, null)) { - setValue(context, ctxIndex, null); - // only if the initial value is falsy then - if (hasInitialValueChanged(context, ctxFlag, ctxValue)) { - setDirty(context, ctxIndex, true); - dirty = true; - } - setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex, directiveIndex); - } - ctxIndex += StylingIndex.Size; - } - - // Because the object shape has changed, this means that all follow-up directives will need to - // reapply their values into the object. For this to happen, the cached array needs to be updated - // with dirty flags so that follow-up calls to `updateStylingMap` will reapply their styling code. - // the reapplication of styling code within the context will reshape it and update the offset - // values (also follow-up directives can write new values in case earlier directives set anything - // to null due to removals or falsy values). - valuesEntryShapeChange = valuesEntryShapeChange || existingCachedValueCount !== totalUniqueValues; - updateCachedMapValue( - context, directiveIndex, entryIsClassBased, cacheValue, ownershipValuesStartIndex, ctxEnd, - totalUniqueValues, valuesEntryShapeChange); - - if (dirty) { - setContextDirty(context, true); - } - - return totalNewAllocatedSlots; -} - -/** - * Sets and resolves a single class value on the provided `StylingContext` so - * that they can be applied to the element once `renderStyling` is called. - * - * @param context The styling context that will be updated with the - * newly provided class value. - * @param offset The index of the CSS class which is being updated. - * @param addOrRemove Whether or not to add or remove the CSS class - * @param forceOverride whether or not to skip all directive prioritization - * and just apply the value regardless. - */ -export function updateClassProp( - context: StylingContext, offset: number, - input: boolean | BoundPlayerFactory| null, directiveIndex: number = 0, - forceOverride?: boolean): void { - updateSingleStylingValue(context, offset, input, true, directiveIndex, forceOverride); -} - -/** - * Sets and resolves a single style value on the provided `StylingContext` so - * that they can be applied to the element once `renderStyling` is called. - * - * Note that prop-level styling values are considered higher priority than any styling that - * has been applied using `updateStylingMap`, therefore, when styling values are rendered - * then any styles/classes that have been applied using this function will be considered first - * (then multi values second and then initial values as a backup). - * - * @param context The styling context that will be updated with the - * newly provided style value. - * @param offset The index of the property which is being updated. - * @param value The CSS style value that will be assigned - * @param forceOverride whether or not to skip all directive prioritization - * and just apply the value regardless. - */ -export function updateStyleProp( - context: StylingContext, offset: number, - input: string | boolean | null | BoundPlayerFactory, - directiveIndex: number = 0, forceOverride?: boolean): void { - updateSingleStylingValue(context, offset, input, false, directiveIndex, forceOverride); -} - -function updateSingleStylingValue( - context: StylingContext, offset: number, - input: string | boolean | null | BoundPlayerFactory, isClassBased: boolean, - directiveIndex: number, forceOverride?: boolean): void { - ngDevMode && assertValidDirectiveIndex(context, directiveIndex); - const singleIndex = getSinglePropIndexValue(context, directiveIndex, offset, isClassBased); - const currValue = getValue(context, singleIndex); - const currFlag = getPointers(context, singleIndex); - const currDirective = getDirectiveIndexFromEntry(context, singleIndex); - const value: string|boolean|null = (input instanceof BoundPlayerFactory) ? input.value : input; - - if (ngDevMode) { - if (isClassBased) { - ngDevMode.classProp++; - } else { - ngDevMode.styleProp++; - } - } - - if (hasValueChanged(currFlag, currValue, value) && - (forceOverride || allowValueChange(currValue, value, currDirective, directiveIndex))) { - const isClassBased = (currFlag & StylingFlags.Class) === StylingFlags.Class; - const element = context[StylingIndex.ElementPosition] !as HTMLElement; - const playerBuilder = input instanceof BoundPlayerFactory ? - new ClassAndStylePlayerBuilder( - input as any, element, isClassBased ? BindingType.Class : BindingType.Style) : - null; - const value = (playerBuilder ? (input as BoundPlayerFactory).value : input) as string | - boolean | null; - const currPlayerIndex = getPlayerBuilderIndex(context, singleIndex); - - let playerBuildersAreDirty = false; - let playerBuilderIndex = playerBuilder ? currPlayerIndex : 0; - if (hasPlayerBuilderChanged(context, playerBuilder, currPlayerIndex)) { - const newIndex = setPlayerBuilder(context, playerBuilder, currPlayerIndex); - playerBuilderIndex = playerBuilder ? newIndex : 0; - playerBuildersAreDirty = true; - } - - if (playerBuildersAreDirty || currDirective !== directiveIndex) { - setPlayerBuilderIndex(context, singleIndex, playerBuilderIndex, directiveIndex); - } - - if (currDirective !== directiveIndex) { - const prop = getProp(context, singleIndex); - const sanitizer = getStyleSanitizer(context, directiveIndex); - setSanitizeFlag( - context, singleIndex, - (sanitizer && sanitizer(prop, null, StyleSanitizeMode.ValidateProperty)) ? true : false); - } - - // the value will always get updated (even if the dirty flag is skipped) - setValue(context, singleIndex, value); - const indexForMulti = getMultiOrSingleIndex(currFlag); - - // if the value is the same in the multi-area then there's no point in re-assembling - const valueForMulti = getValue(context, indexForMulti); - if (!valueForMulti || hasValueChanged(currFlag, valueForMulti, value)) { - let multiDirty = false; - let singleDirty = true; - - // only when the value is set to `null` should the multi-value get flagged - if (!valueExists(value, isClassBased) && valueExists(valueForMulti, isClassBased)) { - multiDirty = true; - singleDirty = false; - } - - setDirty(context, indexForMulti, multiDirty); - setDirty(context, singleIndex, singleDirty); - setContextDirty(context, true); - } - - if (playerBuildersAreDirty) { - setContextPlayersDirty(context, true); - } - - if (ngDevMode) { - if (isClassBased) { - ngDevMode.classPropCacheMiss++; - } else { - ngDevMode.stylePropCacheMiss++; - } - } - } -} - - -/** - * Renders all queued styling using a renderer onto the given element. - * - * This function works by rendering any styles (that have been applied - * using `updateStylingMap`) and any classes (that have been applied using - * `updateStyleProp`) onto the provided element using the provided renderer. - * Just before the styles/classes are rendered a final key/value style map - * will be assembled (if `styleStore` or `classStore` are provided). - * - * @param lElement the element that the styles will be rendered on - * @param context The styling context that will be used to determine - * what styles will be rendered - * @param renderer the renderer that will be used to apply the styling - * @param classesStore if provided, the updated class values will be applied - * to this key/value map instead of being renderered via the renderer. - * @param stylesStore if provided, the updated style values will be applied - * to this key/value map instead of being renderered via the renderer. - * @returns number the total amount of players that got queued for animation (if any) - */ -export function renderStyling( - context: StylingContext, renderer: Renderer3 | null, rootOrView: RootContext | LView, - isFirstRender: boolean, classesStore?: BindingStore | null, stylesStore?: BindingStore | null, - directiveIndex: number = 0): number { - let totalPlayersQueued = 0; - ngDevMode && ngDevMode.flushStyling++; - - // this prevents multiple attempts to render style/class values on - // the same element... - if (allowHostInstructionsQueueFlush(context, directiveIndex)) { - // all styling instructions present within any hostBindings functions - // do not update the context immediately when called. They are instead - // queued up and applied to the context right at this point. Why? This - // is because Angular evaluates component/directive and directive - // sub-class code at different points and it's important that the - // styling values are applied to the context in the right order - // (see `interfaces/styling.ts` for more information). - flushHostInstructionsQueue(context); - - if (isContextDirty(context)) { - // this is here to prevent things like ... - // or if there are any host style or class bindings present in a directive set on - // a container node - const native = context[StylingIndex.ElementPosition] !as HTMLElement; - - const flushPlayerBuilders: any = - context[StylingIndex.MasterFlagPosition] & StylingFlags.PlayerBuildersDirty; - const multiStartIndex = getMultiStylesStartIndex(context); - - for (let i = StylingIndex.SingleStylesStartPosition; i < context.length; - i += StylingIndex.Size) { - // there is no point in rendering styles that have not changed on screen - if (isDirty(context, i)) { - const flag = getPointers(context, i); - const directiveIndex = getDirectiveIndexFromEntry(context, i); - const prop = getProp(context, i); - const value = getValue(context, i); - const styleSanitizer = - (flag & StylingFlags.Sanitize) ? getStyleSanitizer(context, directiveIndex) : null; - const playerBuilder = getPlayerBuilder(context, i); - const isClassBased = flag & StylingFlags.Class ? true : false; - const isInSingleRegion = i < multiStartIndex; - - let valueToApply: string|boolean|null = value; - - // VALUE DEFER CASE 1: Use a multi value instead of a null single value - // this check implies that a single value was removed and we - // should now defer to a multi value and use that (if set). - if (isInSingleRegion && !valueExists(valueToApply, isClassBased)) { - // single values ALWAYS have a reference to a multi index - const multiIndex = getMultiOrSingleIndex(flag); - valueToApply = getValue(context, multiIndex); - } - - // VALUE DEFER CASE 2: Use the initial value if all else fails (is falsy) - // the initial value will always be a string or null, - // therefore we can safely adopt it in case there's nothing else - // note that this should always be a falsy check since `false` is used - // for both class and style comparisons (styles can't be false and false - // classes are turned off and should therefore defer to their initial values) - // Note that we ignore class-based deferals because otherwise a class can never - // be removed in the case that it exists as true in the initial classes list... - if (!valueExists(valueToApply, isClassBased)) { - valueToApply = getInitialValue(context, flag); - } - - // if the first render is true then we do not want to start applying falsy - // values to the DOM element's styling. Otherwise then we know there has - // been a change and even if it's falsy then it's removing something that - // was truthy before. - const doApplyValue = renderer && (isFirstRender ? valueToApply : true); - if (doApplyValue) { - if (isClassBased) { - setClass( - native, prop, valueToApply ? true : false, renderer !, classesStore, - playerBuilder); - } else { - setStyle( - native, prop, valueToApply as string | null, renderer !, styleSanitizer, - stylesStore, playerBuilder); - } - } - - setDirty(context, i, false); - } - } - - if (flushPlayerBuilders) { - const rootContext = - Array.isArray(rootOrView) ? getRootContext(rootOrView) : rootOrView as RootContext; - const playerContext = getPlayerContext(context) !; - const playersStartIndex = playerContext[PlayerIndex.NonBuilderPlayersStart]; - for (let i = PlayerIndex.PlayerBuildersStartPosition; i < playersStartIndex; - i += PlayerIndex.PlayerAndPlayerBuildersTupleSize) { - const builder = playerContext[i] as ClassAndStylePlayerBuilder| null; - const playerInsertionIndex = i + PlayerIndex.PlayerOffsetPosition; - const oldPlayer = playerContext[playerInsertionIndex] as Player | null; - if (builder) { - const player = builder.buildPlayer(oldPlayer, isFirstRender); - if (player !== undefined) { - if (player != null) { - const wasQueued = addPlayerInternal( - playerContext, rootContext, native as HTMLElement, player, - playerInsertionIndex); - wasQueued && totalPlayersQueued++; - } - if (oldPlayer) { - oldPlayer.destroy(); - } - } - } else if (oldPlayer) { - // the player builder has been removed ... therefore we should delete the associated - // player - oldPlayer.destroy(); - } - } - setContextPlayersDirty(context, false); - } - - setContextDirty(context, false); - } - } - - return totalPlayersQueued; -} - -/** - * Assigns a style value to a style property for the given element. - * - * This function renders a given CSS prop/value entry using the - * provided renderer. If a `store` value is provided then - * that will be used a render context instead of the provided - * renderer. - * - * @param native the DOM Element - * @param prop the CSS style property that will be rendered - * @param value the CSS style value that will be rendered - * @param renderer - * @param store an optional key/value map that will be used as a context to render styles on - */ -export function setStyle( - native: any, prop: string, value: string | null, renderer: Renderer3, - sanitizer: StyleSanitizeFn | null, store?: BindingStore | null, - playerBuilder?: ClassAndStylePlayerBuilder| null) { - value = - sanitizer && value ? sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) : value; - if (store || playerBuilder) { - if (store) { - store.setValue(prop, value); - } - if (playerBuilder) { - playerBuilder.setValue(prop, value); - } - } else if (value) { - value = value.toString(); // opacity, z-index and flexbox all have number values which may not - // assign as numbers - ngDevMode && ngDevMode.rendererSetStyle++; - isProceduralRenderer(renderer) ? - renderer.setStyle(native, prop, value, RendererStyleFlags3.DashCase) : - native.style.setProperty(prop, value); - } else { - ngDevMode && ngDevMode.rendererRemoveStyle++; - isProceduralRenderer(renderer) ? - renderer.removeStyle(native, prop, RendererStyleFlags3.DashCase) : - native.style.removeProperty(prop); - } -} - -/** - * Adds/removes the provided className value to the provided element. - * - * This function renders a given CSS class value using the provided - * renderer (by adding or removing it from the provided element). - * If a `store` value is provided then that will be used a render - * context instead of the provided renderer. - * - * @param native the DOM Element - * @param prop the CSS style property that will be rendered - * @param value the CSS style value that will be rendered - * @param renderer - * @param store an optional key/value map that will be used as a context to render styles on - */ -function setClass( - native: any, className: string, add: boolean, renderer: Renderer3, store?: BindingStore | null, - playerBuilder?: ClassAndStylePlayerBuilder| null) { - if (store || playerBuilder) { - if (store) { - store.setValue(className, add); - } - if (playerBuilder) { - playerBuilder.setValue(className, add); - } - // DOMTokenList will throw if we try to add or remove an empty string. - } else if (className !== '') { - if (add) { - ngDevMode && ngDevMode.rendererAddClass++; - isProceduralRenderer(renderer) ? renderer.addClass(native, className) : - native['classList'].add(className); - } else { - ngDevMode && ngDevMode.rendererRemoveClass++; - isProceduralRenderer(renderer) ? renderer.removeClass(native, className) : - native['classList'].remove(className); - } - } -} - -function setSanitizeFlag(context: StylingContext, index: number, sanitizeYes: boolean) { - if (sanitizeYes) { - (context[index] as number) |= StylingFlags.Sanitize; - } else { - (context[index] as number) &= ~StylingFlags.Sanitize; - } -} - -function setDirty(context: StylingContext, index: number, isDirtyYes: boolean) { - const adjustedIndex = - index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index; - if (isDirtyYes) { - (context[adjustedIndex] as number) |= StylingFlags.Dirty; - } else { - (context[adjustedIndex] as number) &= ~StylingFlags.Dirty; - } -} - -function isDirty(context: StylingContext, index: number): boolean { - const adjustedIndex = - index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index; - return ((context[adjustedIndex] as number) & StylingFlags.Dirty) == StylingFlags.Dirty; -} - -export function isClassBasedValue(context: StylingContext, index: number): boolean { - const adjustedIndex = - index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index; - return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class; -} - -function isSanitizable(context: StylingContext, index: number): boolean { - const adjustedIndex = - index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index; - return ((context[adjustedIndex] as number) & StylingFlags.Sanitize) == StylingFlags.Sanitize; -} - -function pointers(configFlag: number, staticIndex: number, dynamicIndex: number) { - return (configFlag & StylingFlags.BitMask) | (staticIndex << StylingFlags.BitCountSize) | - (dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize)); -} - -function getInitialValue(context: StylingContext, flag: number): string|boolean|null { - const index = getInitialIndex(flag); - const entryIsClassBased = flag & StylingFlags.Class; - const initialValues = entryIsClassBased ? context[StylingIndex.InitialClassValuesPosition] : - context[StylingIndex.InitialStyleValuesPosition]; - return initialValues[index] as string | boolean | null; -} - -function getInitialIndex(flag: number): number { - return (flag >> StylingFlags.BitCountSize) & StylingIndex.BitMask; -} - -function getMultiOrSingleIndex(flag: number): number { - const index = - (flag >> (StylingIndex.BitCountSize + StylingFlags.BitCountSize)) & StylingIndex.BitMask; - return index >= StylingIndex.SingleStylesStartPosition ? index : -1; -} - -function getMultiStartIndex(context: StylingContext): number { - return getMultiOrSingleIndex(context[StylingIndex.MasterFlagPosition]) as number; -} - -function getMultiClassesStartIndex(context: StylingContext): number { - const classCache = context[StylingIndex.CachedMultiClasses]; - return classCache - [MapBasedOffsetValuesIndex.ValuesStartPosition + - MapBasedOffsetValuesIndex.PositionStartOffset]; -} - -function getMultiStylesStartIndex(context: StylingContext): number { - const stylesCache = context[StylingIndex.CachedMultiStyles]; - return stylesCache - [MapBasedOffsetValuesIndex.ValuesStartPosition + - MapBasedOffsetValuesIndex.PositionStartOffset]; -} - -function setProp(context: StylingContext, index: number, prop: string) { - context[index + StylingIndex.PropertyOffset] = prop; -} - -function setValue(context: StylingContext, index: number, value: string | null | boolean) { - context[index + StylingIndex.ValueOffset] = value; -} - -function hasPlayerBuilderChanged( - context: StylingContext, builder: ClassAndStylePlayerBuilder| null, index: number) { - const playerContext = context[StylingIndex.PlayerContext] !; - if (builder) { - if (!playerContext || index === 0) { - return true; - } - } else if (!playerContext) { - return false; - } - return playerContext[index] !== builder; -} - -function setPlayerBuilder( - context: StylingContext, builder: ClassAndStylePlayerBuilder| null, - insertionIndex: number): number { - let playerContext = context[StylingIndex.PlayerContext] || allocPlayerContext(context); - if (insertionIndex > 0) { - playerContext[insertionIndex] = builder; - } else { - insertionIndex = playerContext[PlayerIndex.NonBuilderPlayersStart]; - playerContext.splice(insertionIndex, 0, builder, null); - playerContext[PlayerIndex.NonBuilderPlayersStart] += - PlayerIndex.PlayerAndPlayerBuildersTupleSize; - } - return insertionIndex; -} - -export function directiveOwnerPointers(directiveIndex: number, playerIndex: number) { - return (playerIndex << DirectiveOwnerAndPlayerBuilderIndex.BitCountSize) | directiveIndex; -} - -function setPlayerBuilderIndex( - context: StylingContext, index: number, playerBuilderIndex: number, directiveIndex: number) { - const value = directiveOwnerPointers(directiveIndex, playerBuilderIndex); - context[index + StylingIndex.PlayerBuilderIndexOffset] = value; -} - -function getPlayerBuilderIndex(context: StylingContext, index: number): number { - const flag = context[index + StylingIndex.PlayerBuilderIndexOffset] as number; - const playerBuilderIndex = (flag >> DirectiveOwnerAndPlayerBuilderIndex.BitCountSize) & - DirectiveOwnerAndPlayerBuilderIndex.BitMask; - return playerBuilderIndex; -} - -function getPlayerBuilder(context: StylingContext, index: number): ClassAndStylePlayerBuilder| - null { - const playerBuilderIndex = getPlayerBuilderIndex(context, index); - if (playerBuilderIndex) { - const playerContext = context[StylingIndex.PlayerContext]; - if (playerContext) { - return playerContext[playerBuilderIndex] as ClassAndStylePlayerBuilder| null; - } - } - return null; -} - -function setFlag(context: StylingContext, index: number, flag: number) { - const adjustedIndex = - index === StylingIndex.MasterFlagPosition ? index : (index + StylingIndex.FlagsOffset); - context[adjustedIndex] = flag; -} - -function getPointers(context: StylingContext, index: number): number { - const adjustedIndex = - index === StylingIndex.MasterFlagPosition ? index : (index + StylingIndex.FlagsOffset); - return context[adjustedIndex] as number; -} - -export function getValue(context: StylingContext, index: number): string|boolean|null { - return context[index + StylingIndex.ValueOffset] as string | boolean | null; -} - -export function getProp(context: StylingContext, index: number): string { - return context[index + StylingIndex.PropertyOffset] as string; -} - -export function isContextDirty(context: StylingContext): boolean { - return isDirty(context, StylingIndex.MasterFlagPosition); -} - -export function setContextDirty(context: StylingContext, isDirtyYes: boolean): void { - setDirty(context, StylingIndex.MasterFlagPosition, isDirtyYes); -} - -export function setContextPlayersDirty(context: StylingContext, isDirtyYes: boolean): void { - if (isDirtyYes) { - (context[StylingIndex.MasterFlagPosition] as number) |= StylingFlags.PlayerBuildersDirty; - } else { - (context[StylingIndex.MasterFlagPosition] as number) &= ~StylingFlags.PlayerBuildersDirty; - } -} - -function swapMultiContextEntries(context: StylingContext, indexA: number, indexB: number) { - if (indexA === indexB) return; - - const tmpValue = getValue(context, indexA); - const tmpProp = getProp(context, indexA); - const tmpFlag = getPointers(context, indexA); - const tmpPlayerBuilderIndex = getPlayerBuilderIndex(context, indexA); - const tmpDirectiveIndex = getDirectiveIndexFromEntry(context, indexA); - - let flagA = tmpFlag; - let flagB = getPointers(context, indexB); - - const singleIndexA = getMultiOrSingleIndex(flagA); - if (singleIndexA >= 0) { - const _flag = getPointers(context, singleIndexA); - const _initial = getInitialIndex(_flag); - setFlag(context, singleIndexA, pointers(_flag, _initial, indexB)); - } - - const singleIndexB = getMultiOrSingleIndex(flagB); - if (singleIndexB >= 0) { - const _flag = getPointers(context, singleIndexB); - const _initial = getInitialIndex(_flag); - setFlag(context, singleIndexB, pointers(_flag, _initial, indexA)); - } - - setValue(context, indexA, getValue(context, indexB)); - setProp(context, indexA, getProp(context, indexB)); - setFlag(context, indexA, getPointers(context, indexB)); - const playerIndexA = getPlayerBuilderIndex(context, indexB); - const directiveIndexA = getDirectiveIndexFromEntry(context, indexB); - setPlayerBuilderIndex(context, indexA, playerIndexA, directiveIndexA); - - setValue(context, indexB, tmpValue); - setProp(context, indexB, tmpProp); - setFlag(context, indexB, tmpFlag); - setPlayerBuilderIndex(context, indexB, tmpPlayerBuilderIndex, tmpDirectiveIndex); -} - -function updateSinglePointerValues(context: StylingContext, indexStartPosition: number) { - for (let i = indexStartPosition; i < context.length; i += StylingIndex.Size) { - const multiFlag = getPointers(context, i); - const singleIndex = getMultiOrSingleIndex(multiFlag); - if (singleIndex > 0) { - const singleFlag = getPointers(context, singleIndex); - const initialIndexForSingle = getInitialIndex(singleFlag); - const flagValue = (isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None) | - (isClassBasedValue(context, singleIndex) ? StylingFlags.Class : StylingFlags.None) | - (isSanitizable(context, singleIndex) ? StylingFlags.Sanitize : StylingFlags.None); - const updatedFlag = pointers(flagValue, initialIndexForSingle, i); - setFlag(context, singleIndex, updatedFlag); - } - } -} - -function insertNewMultiProperty( - context: StylingContext, index: number, classBased: boolean, name: string, flag: number, - value: string | boolean, directiveIndex: number, playerIndex: number): void { - const doShift = index < context.length; - - // prop does not exist in the list, add it in - context.splice( - index, 0, flag | StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), - name, value, 0); - setPlayerBuilderIndex(context, index, playerIndex, directiveIndex); - - if (doShift) { - // because the value was inserted midway into the array then we - // need to update all the shifted multi values' single value - // pointers to point to the newly shifted location - updateSinglePointerValues(context, index + StylingIndex.Size); - } -} - -function valueExists(value: string | null | boolean, isClassBased?: boolean) { - return value !== null; -} - -function prepareInitialFlag( - context: StylingContext, prop: string, entryIsClassBased: boolean, - sanitizer?: StyleSanitizeFn | null) { - let flag = (sanitizer && sanitizer(prop, null, StyleSanitizeMode.ValidateProperty)) ? - StylingFlags.Sanitize : - StylingFlags.None; - - let initialIndex: number; - if (entryIsClassBased) { - flag |= StylingFlags.Class; - initialIndex = - getInitialStylingValuesIndexOf(context[StylingIndex.InitialClassValuesPosition], prop); - } else { - initialIndex = - getInitialStylingValuesIndexOf(context[StylingIndex.InitialStyleValuesPosition], prop); - } - - initialIndex = initialIndex > 0 ? (initialIndex + InitialStylingValuesIndex.ValueOffset) : 0; - return pointers(flag, initialIndex, 0); -} - -function hasInitialValueChanged(context: StylingContext, flag: number, newValue: any) { - const initialValue = getInitialValue(context, flag); - return !initialValue || hasValueChanged(flag, initialValue, newValue); -} - -function hasValueChanged( - flag: number, a: string | boolean | null, b: string | boolean | null): boolean { - const isClassBased = flag & StylingFlags.Class; - const hasValues = a && b; - const usesSanitizer = flag & StylingFlags.Sanitize; - // the toString() comparison ensures that a value is checked - // ... otherwise (during sanitization bypassing) the === comparsion - // would fail since a new String() instance is created - if (!isClassBased && hasValues && usesSanitizer) { - // we know for sure we're dealing with strings at this point - return (a as string).toString() !== (b as string).toString(); - } - - // everything else is safe to check with a normal equality check - return a !== b; -} - -export class ClassAndStylePlayerBuilder implements PlayerBuilder { - private _values: {[key: string]: string | null} = {}; - private _dirty = false; - private _factory: BoundPlayerFactory; - - constructor(factory: PlayerFactory, private _element: HTMLElement, private _type: BindingType) { - this._factory = factory as any; - } - - setValue(prop: string, value: any) { - if (this._values[prop] !== value) { - this._values[prop] = value; - this._dirty = true; - } - } - - buildPlayer(currentPlayer: Player|null, isFirstRender: boolean): Player|undefined|null { - // if no values have been set here then this means the binding didn't - // change and therefore the binding values were not updated through - // `setValue` which means no new player will be provided. - if (this._dirty) { - const player = this._factory.fn( - this._element, this._type, this._values !, isFirstRender, currentPlayer || null); - this._values = {}; - this._dirty = false; - return player; - } - - return undefined; - } -} - -/** - * Used to provide a summary of the state of the styling context. - * - * This is an internal interface that is only used inside of test tooling to - * help summarize what's going on within the styling context. None of this code - * is designed to be exported publicly and will, therefore, be tree-shaken away - * during runtime. - */ -export interface LogSummary { - name: string; // - staticIndex: number; // - dynamicIndex: number; // - value: number; // - flags: { - dirty: boolean; // - class: boolean; // - sanitize: boolean; // - playerBuildersDirty: boolean; // - bindingAllocationLocked: boolean; // - }; -} - -/** - * This function is not designed to be used in production. - * It is a utility tool for debugging and testing and it - * will automatically be tree-shaken away during production. - */ -export function generateConfigSummary(source: number): LogSummary; -export function generateConfigSummary(source: StylingContext): LogSummary; -export function generateConfigSummary(source: StylingContext, index: number): LogSummary; -export function generateConfigSummary(source: number | StylingContext, index?: number): LogSummary { - let flag, name = 'config value for '; - if (Array.isArray(source)) { - if (index) { - name += 'index: ' + index; - } else { - name += 'master config'; - } - index = index || StylingIndex.MasterFlagPosition; - flag = source[index] as number; - } else { - flag = source; - name += 'index: ' + flag; - } - const dynamicIndex = getMultiOrSingleIndex(flag); - const staticIndex = getInitialIndex(flag); - return { - name, - staticIndex, - dynamicIndex, - value: flag, - flags: { - dirty: flag & StylingFlags.Dirty ? true : false, - class: flag & StylingFlags.Class ? true : false, - sanitize: flag & StylingFlags.Sanitize ? true : false, - playerBuildersDirty: flag & StylingFlags.PlayerBuildersDirty ? true : false, - bindingAllocationLocked: flag & StylingFlags.BindingAllocationLocked ? true : false, - } - }; -} - -export function getDirectiveIndexFromEntry(context: StylingContext, index: number) { - const value = context[index + StylingIndex.PlayerBuilderIndexOffset] as number; - return value & DirectiveOwnerAndPlayerBuilderIndex.BitMask; -} - -function getInitialStylingValuesIndexOf(keyValues: InitialStylingValues, key: string): number { - for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < keyValues.length; - i += InitialStylingValuesIndex.Size) { - if (keyValues[i] === key) return i; - } - return -1; -} - -export function compareLogSummaries(a: LogSummary, b: LogSummary) { - const log: string[] = []; - const diffs: [string, any, any][] = []; - diffSummaryValues(diffs, 'staticIndex', 'staticIndex', a, b); - diffSummaryValues(diffs, 'dynamicIndex', 'dynamicIndex', a, b); - Object.keys(a.flags).forEach( - name => { diffSummaryValues(diffs, 'flags.' + name, name, a.flags, b.flags); }); - - if (diffs.length) { - log.push('Log Summaries for:'); - log.push(' A: ' + a.name); - log.push(' B: ' + b.name); - log.push('\n Differ in the following way (A !== B):'); - diffs.forEach(result => { - const [name, aVal, bVal] = result; - log.push(' => ' + name); - log.push(' => ' + aVal + ' !== ' + bVal + '\n'); - }); - } - - return log; -} - -function diffSummaryValues(result: any[], name: string, prop: string, a: any, b: any) { - const aVal = a[prop]; - const bVal = b[prop]; - if (aVal !== bVal) { - result.push([name, aVal, bVal]); - } -} - -export function getSinglePropIndexValue( - context: StylingContext, directiveIndex: number, offset: number, isClassBased: boolean) { - const singlePropOffsetRegistryIndex = - context[StylingIndex.DirectiveRegistryPosition] - [(directiveIndex * DirectiveRegistryValuesIndex.Size) + - DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] as number; - const offsets = context[StylingIndex.SinglePropOffsetPositions]; - const indexForOffset = singlePropOffsetRegistryIndex + - SinglePropOffsetValuesIndex.ValueStartPosition + - (isClassBased ? - offsets - [singlePropOffsetRegistryIndex + SinglePropOffsetValuesIndex.StylesCountPosition] : - 0) + - offset; - return offsets[indexForOffset]; -} - -function getStyleSanitizer(context: StylingContext, directiveIndex: number): StyleSanitizeFn|null { - const dirs = context[StylingIndex.DirectiveRegistryPosition]; - const value = dirs - [directiveIndex * DirectiveRegistryValuesIndex.Size + - DirectiveRegistryValuesIndex.StyleSanitizerOffset] || - dirs[DirectiveRegistryValuesIndex.StyleSanitizerOffset] || null; - return value as StyleSanitizeFn | null; -} - -function allowValueChange( - currentValue: string | boolean | null, newValue: string | boolean | null, - currentDirectiveOwner: number, newDirectiveOwner: number) { - // the code below relies the importance of directive's being tied to their - // index value. The index values for each directive are derived from being - // registered into the styling context directive registry. The most important - // directive is the parent component directive (the template) and each directive - // that is added after is considered less important than the previous entry. This - // prioritization of directives enables the styling algorithm to decide if a style - // or class should be allowed to be updated/replaced in case an earlier directive - // already wrote to the exact same style-property or className value. In other words - // this decides what to do if and when there is a collision. - if (currentValue != null) { - if (newValue != null) { - // if a directive index is lower than it always has priority over the - // previous directive's value... - return newDirectiveOwner <= currentDirectiveOwner; - } else { - // only write a null value in case it's the same owner writing it. - // this avoids having a higher-priority directive write to null - // only to have a lesser-priority directive change right to a - // non-null value immediately afterwards. - return currentDirectiveOwner === newDirectiveOwner; - } - } - return true; -} - -/** - * Returns the className string of all the initial classes for the element. - * - * This function is designed to populate and cache all the static class - * values into a className string. The caching mechanism works by placing - * the completed className string into the initial values array into a - * dedicated slot. This will prevent the function from having to populate - * the string each time an element is created or matched. - * - * @returns the className string (e.g. `on active red`) - */ -export function getInitialClassNameValue(context: StylingContext): string { - const initialClassValues = context[StylingIndex.InitialClassValuesPosition]; - let className = initialClassValues[InitialStylingValuesIndex.CachedStringValuePosition]; - if (className === null) { - className = ''; - for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialClassValues.length; - i += InitialStylingValuesIndex.Size) { - const isPresent = initialClassValues[i + 1]; - if (isPresent) { - className += (className.length ? ' ' : '') + initialClassValues[i]; - } - } - initialClassValues[InitialStylingValuesIndex.CachedStringValuePosition] = className; - } - return className; -} - -/** - * Returns the style string of all the initial styles for the element. - * - * This function is designed to populate and cache all the static style - * values into a style string. The caching mechanism works by placing - * the completed style string into the initial values array into a - * dedicated slot. This will prevent the function from having to populate - * the string each time an element is created or matched. - * - * @returns the style string (e.g. `width:100px;height:200px`) - */ -export function getInitialStyleStringValue(context: StylingContext): string { - const initialStyleValues = context[StylingIndex.InitialStyleValuesPosition]; - let styleString = initialStyleValues[InitialStylingValuesIndex.CachedStringValuePosition]; - if (styleString === null) { - styleString = ''; - for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyleValues.length; - i += InitialStylingValuesIndex.Size) { - const value = initialStyleValues[i + 1]; - if (value !== null) { - styleString += (styleString.length ? ';' : '') + `${initialStyleValues[i]}:${value}`; - } - } - initialStyleValues[InitialStylingValuesIndex.CachedStringValuePosition] = styleString; - } - return styleString; -} - -/** - * Returns the current cached multi-value for a given directiveIndex within the provided context. - */ -function readCachedMapValue( - context: StylingContext, entryIsClassBased: boolean, directiveIndex: number) { - const values: MapBasedOffsetValues = - context[entryIsClassBased ? StylingIndex.CachedMultiClasses : StylingIndex.CachedMultiStyles]; - const index = MapBasedOffsetValuesIndex.ValuesStartPosition + - directiveIndex * MapBasedOffsetValuesIndex.Size; - return values[index + MapBasedOffsetValuesIndex.ValueOffset] || null; -} - -/** - * Determines whether the provided multi styling value should be updated or not. - * - * Because `[style]` and `[class]` bindings rely on an identity change to occur before - * applying new values, the styling algorithm may not update an existing entry into - * the context if a previous directive's entry changed shape. - * - * This function will decide whether or not a value should be applied (if there is a - * cache miss) to the context based on the following rules: - * - * - If there is an identity change between the existing value and new value - * - If there is no existing value cached (first write) - * - If a previous directive flagged the existing cached value as dirty - */ -function isMultiValueCacheHit( - context: StylingContext, entryIsClassBased: boolean, directiveIndex: number, - newValue: any): boolean { - const indexOfCachedValues = - entryIsClassBased ? StylingIndex.CachedMultiClasses : StylingIndex.CachedMultiStyles; - const cachedValues = context[indexOfCachedValues] as MapBasedOffsetValues; - const index = MapBasedOffsetValuesIndex.ValuesStartPosition + - directiveIndex * MapBasedOffsetValuesIndex.Size; - if (cachedValues[index + MapBasedOffsetValuesIndex.DirtyFlagOffset]) return false; - return newValue === NO_CHANGE || - readCachedMapValue(context, entryIsClassBased, directiveIndex) === newValue; -} - -/** - * Updates the cached status of a multi-styling value in the context. - * - * The cached map array (which exists in the context) contains a manifest of - * each multi-styling entry (`[style]` and `[class]` entries) for the template - * as well as all directives. - * - * This function will update the cached status of the provided multi-style - * entry within the cache. - * - * When called, this function will update the following information: - * - The actual cached value (the raw value that was passed into `[style]` or `[class]`) - * - The total amount of unique styling entries that this value has written into the context - * - The exact position of where the multi styling entries start in the context for this binding - * - The dirty flag will be set to true - * - * If the `dirtyFutureValues` param is provided then it will update all future entries (binding - * entries that exist as a part of other directives) to be dirty as well. This will force the - * styling algorithm to reapply those values once change detection checks them (which will in - * turn cause the styling context to update itself and the correct styling values will be - * rendered on screen). - */ -function updateCachedMapValue( - context: StylingContext, directiveIndex: number, entryIsClassBased: boolean, cacheValue: any, - startPosition: number, endPosition: number, totalValues: number, dirtyFutureValues: boolean) { - const values = - context[entryIsClassBased ? StylingIndex.CachedMultiClasses : StylingIndex.CachedMultiStyles]; - - const index = MapBasedOffsetValuesIndex.ValuesStartPosition + - directiveIndex * MapBasedOffsetValuesIndex.Size; - - // in the event that this is true we assume that future values are dirty and therefore - // will be checked again in the next CD cycle - if (dirtyFutureValues) { - const nextStartPosition = startPosition + totalValues * MapBasedOffsetValuesIndex.Size; - for (let i = index + MapBasedOffsetValuesIndex.Size; i < values.length; - i += MapBasedOffsetValuesIndex.Size) { - values[i + MapBasedOffsetValuesIndex.PositionStartOffset] = nextStartPosition; - values[i + MapBasedOffsetValuesIndex.DirtyFlagOffset] = 1; - } - } - - values[index + MapBasedOffsetValuesIndex.DirtyFlagOffset] = 0; - values[index + MapBasedOffsetValuesIndex.PositionStartOffset] = startPosition; - values[index + MapBasedOffsetValuesIndex.ValueOffset] = cacheValue; - values[index + MapBasedOffsetValuesIndex.ValueCountOffset] = totalValues; - - // the code below counts the total amount of styling values that exist in - // the context up until this directive. This value will be later used to - // update the cached value map's total counter value. - let totalStylingEntries = totalValues; - for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < index; - i += MapBasedOffsetValuesIndex.Size) { - totalStylingEntries += values[i + MapBasedOffsetValuesIndex.ValueCountOffset]; - } - - // because style values come before class values in the context this means - // that if any new values were inserted then the cache values array for - // classes is out of sync. The code below will update the offsets to point - // to their new values. - if (!entryIsClassBased) { - const classCache = context[StylingIndex.CachedMultiClasses]; - const classesStartPosition = classCache - [MapBasedOffsetValuesIndex.ValuesStartPosition + - MapBasedOffsetValuesIndex.PositionStartOffset]; - const diffInStartPosition = endPosition - classesStartPosition; - for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < classCache.length; - i += MapBasedOffsetValuesIndex.Size) { - classCache[i + MapBasedOffsetValuesIndex.PositionStartOffset] += diffInStartPosition; - } - } - - values[MapBasedOffsetValuesIndex.EntriesCountPosition] = totalStylingEntries; -} - -function hyphenateEntries(entries: string[]): string[] { - const newEntries: string[] = []; - for (let i = 0; i < entries.length; i++) { - newEntries.push(hyphenate(entries[i])); - } - return newEntries; -} - -function hyphenate(value: string): string { - return value.replace( - /[a-z][A-Z]/g, match => `${match.charAt(0)}-${match.charAt(1).toLowerCase()}`); -} - -function registerMultiMapEntry( - context: StylingContext, directiveIndex: number, entryIsClassBased: boolean, - startPosition: number, count = 0) { - const cachedValues = - context[entryIsClassBased ? StylingIndex.CachedMultiClasses : StylingIndex.CachedMultiStyles]; - if (directiveIndex > 0) { - const limit = MapBasedOffsetValuesIndex.ValuesStartPosition + - (directiveIndex * MapBasedOffsetValuesIndex.Size); - while (cachedValues.length < limit) { - // this means that ONLY directive class styling (like ngClass) was used - // therefore the root directive will still need to be filled in as well - // as any other directive spaces in case they only used static values - cachedValues.push(0, startPosition, null, 0); - } - } - cachedValues.push(0, startPosition, null, count); -} - -/** - * Inserts or updates an existing entry in the provided `staticStyles` collection. - * - * @param index the index representing an existing styling entry in the collection: - * if provided (numeric): then it will update the existing entry at the given position - * if null: then it will insert a new entry within the collection - * @param staticStyles a collection of style or class entries where the value will - * be inserted or patched - * @param prop the property value of the entry (e.g. `width` (styles) or `foo` (classes)) - * @param value the styling value of the entry (e.g. `absolute` (styles) or `true` (classes)) - * @param directiveOwnerIndex the directive owner index value of the styling source responsible - * for these styles (see `interfaces/styling.ts#directives` for more info) - * @returns the index of the updated or new entry within the collection - */ -function addOrUpdateStaticStyle( - index: number | null, staticStyles: InitialStylingValues, prop: string, - value: string | boolean | null, directiveOwnerIndex: number) { - if (index === null) { - index = staticStyles.length; - staticStyles.push(null, null, null); - staticStyles[index + InitialStylingValuesIndex.PropOffset] = prop; - } - staticStyles[index + InitialStylingValuesIndex.ValueOffset] = value; - staticStyles[index + InitialStylingValuesIndex.DirectiveOwnerOffset] = directiveOwnerIndex; - return index; -} - -function assertValidDirectiveIndex(context: StylingContext, directiveIndex: number) { - const dirs = context[StylingIndex.DirectiveRegistryPosition]; - const index = directiveIndex * DirectiveRegistryValuesIndex.Size; - if (index >= dirs.length || - dirs[index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] === -1) { - throw new Error('The provided directive is not registered with the styling context'); - } -} diff --git a/packages/core/src/render3/styling/core_player_handler.ts b/packages/core/src/render3/styling/core_player_handler.ts deleted file mode 100644 index 135327fc4f..0000000000 --- a/packages/core/src/render3/styling/core_player_handler.ts +++ /dev/null @@ -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); } -} diff --git a/packages/core/src/render3/styling/host_instructions_queue.ts b/packages/core/src/render3/styling/host_instructions_queue.ts deleted file mode 100644 index f4682aaf0c..0000000000 --- a/packages/core/src/render3/styling/host_instructions_queue.ts +++ /dev/null @@ -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( - context: StylingContext, priority: number, instructionFn: T, instructionFnArgs: ParamsOf) { - 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 extends(...args: infer T) => any ? T : never; diff --git a/packages/core/src/render3/styling/player_factory.ts b/packages/core/src/render3/styling/player_factory.ts deleted file mode 100644 index dac6daf9bc..0000000000 --- a/packages/core/src/render3/styling/player_factory.ts +++ /dev/null @@ -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(factoryFn: PlayerFactoryBuildFn, value: T): PlayerFactory { - return new BoundPlayerFactory(factoryFn, value) as any; -} - -export class BoundPlayerFactory { - '__brand__': 'Brand for PlayerFactory that nothing will match'; - constructor(public fn: PlayerFactoryBuildFn, public value: T) {} -} diff --git a/packages/core/src/render3/styling/shared.ts b/packages/core/src/render3/styling/shared.ts deleted file mode 100644 index 20782d18eb..0000000000 --- a/packages/core/src/render3/styling/shared.ts +++ /dev/null @@ -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; diff --git a/packages/core/src/render3/styling/state.ts b/packages/core/src/render3/styling/state.ts deleted file mode 100644 index c000295e37..0000000000 --- a/packages/core/src/render3/styling/state.ts +++ /dev/null @@ -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; -} diff --git a/packages/core/src/render3/styling/util.ts b/packages/core/src/render3/styling/util.ts deleted file mode 100644 index 94a6adad87..0000000000 --- a/packages/core/src/render3/styling/util.ts +++ /dev/null @@ -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 `
` 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'); -} diff --git a/packages/core/src/render3/styling_next/instructions.ts b/packages/core/src/render3/styling_next/instructions.ts index 7851c90891..e8f1398216 100644 --- a/packages/core/src/render3/styling_next/instructions.ts +++ b/packages/core/src/render3/styling_next/instructions.ts @@ -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 diff --git a/packages/core/src/render3/styling_next/interfaces.ts b/packages/core/src/render3/styling_next/interfaces.ts index 671bc91bf0..e98b08ae4d 100644 --- a/packages/core/src/render3/styling_next/interfaces.ts +++ b/packages/core/src/render3/styling_next/interfaces.ts @@ -284,7 +284,7 @@ import {LView} from '../interfaces/view'; export interface TStylingContext extends Array { /** Initial value position for static styles */ - [TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray|null; + [TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray; /** Configuration data for the context */ [TStylingContextIndex.ConfigPosition]: TStylingConfigFlags; diff --git a/packages/core/src/render3/styling_next/util.ts b/packages/core/src/render3/styling_next/util.ts index 1eeccdedbf..209b51f30d 100644 --- a/packages/core/src/render3/styling_next/util.ts +++ b/packages/core/src/render3/styling_next/util.ts @@ -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; +} diff --git a/packages/core/src/render3/util/attrs_utils.ts b/packages/core/src/render3/util/attrs_utils.ts index c64e2df6ce..bdf9ffdbd1 100644 --- a/packages/core/src/render3/util/attrs_utils.ts +++ b/packages/core/src/render3/util/attrs_utils.ts @@ -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; +} diff --git a/packages/core/src/render3/util/global_utils.ts b/packages/core/src/render3/util/global_utils.ts index 9d009dcfd6..e4d48167d3 100644 --- a/packages/core/src/render3/util/global_utils.ts +++ b/packages/core/src/render3/util/global_utils.ts @@ -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); } } diff --git a/packages/core/src/render3/util/view_utils.ts b/packages/core/src/render3/util/view_utils.ts index 32343ca21f..a451367d7e 100644 --- a/packages/core/src/render3/util/view_utils.ts +++ b/packages/core/src/render3/util/view_utils.ts @@ -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. diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index 106a175173..858943f1be 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -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: ` +
+ ` + }) + 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'); }); - - }); }); diff --git a/packages/core/test/bundling/animation_world/index.ts b/packages/core/test/bundling/animation_world/index.ts index 38cd84b9b1..6c68aa888b 100644 --- a/packages/core/test/bundling/animation_world/index.ts +++ b/packages/core/test/bundling/animation_world/index.ts @@ -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]', diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index b216614fd0..d62b81cd9a 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -182,9 +182,6 @@ { "name": "appendChild" }, - { - "name": "applyOnCreateInstructions" - }, { "name": "attachPatchData" }, @@ -695,6 +692,9 @@ { "name": "unwrapRNode" }, + { + "name": "updateRawValueOnContext" + }, { "name": "viewAttachedToChangeDetector" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index fc4279505c..475281af8b 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -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" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 11e3dd5f05..da6a18a1df 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -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" }, diff --git a/packages/core/test/render3/global_utils_spec.ts b/packages/core/test/render3/global_utils_spec.ts index 712d95cf85..ff7bb39ffe 100644 --- a/packages/core/test/render3/global_utils_spec.ts +++ b/packages/core/test/render3/global_utils_spec.ts @@ -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); }); }); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index c87f1eca92..d071c9abb2 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -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'; diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index dbd6e3cdf3..3905bc96ca 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -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); diff --git a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts deleted file mode 100644 index 6dac9b8bdd..0000000000 --- a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts +++ /dev/null @@ -1,2679 +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 {createLView, createTView} from '@angular/core/src/render3/instructions/shared'; - -import {createRootContext} from '../../../src/render3/component'; -import {getLContext} from '../../../src/render3/context_discovery'; -import {ɵɵclassMap, ɵɵclassProp, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵelementEnd, ɵɵelementStart, ɵɵnamespaceSVG, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply} from '../../../src/render3/index'; -import {RenderFlags} from '../../../src/render3/interfaces/definition'; -import {AttributeMarker, TAttributes} from '../../../src/render3/interfaces/node'; -import {BindingStore, BindingType, PlayState, Player, PlayerContext, PlayerFactory, PlayerHandler} from '../../../src/render3/interfaces/player'; -import {RElement, Renderer3, domRendererFactory3} from '../../../src/render3/interfaces/renderer'; -import {StylingContext, StylingFlags, StylingIndex} from '../../../src/render3/interfaces/styling'; -import {CONTEXT, LView, LViewFlags, RootContext} from '../../../src/render3/interfaces/view'; -import {addPlayer, getPlayers} from '../../../src/render3/players'; -import {ClassAndStylePlayerBuilder, compareLogSummaries, directiveOwnerPointers, generateConfigSummary, getDirectiveIndexFromEntry, initializeStaticContext, isContextDirty, patchContextWithStaticAttrs, renderStyling as _renderStyling, setContextDirty, updateClassMap, updateClassProp, updateContextWithBindings, updateStyleMap, updateStyleProp} from '../../../src/render3/styling/class_and_style_bindings'; -import {CorePlayerHandler} from '../../../src/render3/styling/core_player_handler'; -import {registerHostDirective} from '../../../src/render3/styling/host_instructions_queue'; -import {BoundPlayerFactory, bindPlayerFactory} from '../../../src/render3/styling/player_factory'; -import {allocStylingContext, createEmptyStylingContext} from '../../../src/render3/styling/util'; -import {ɵɵdefaultStyleSanitizer} from '../../../src/sanitization/sanitization'; -import {StyleSanitizeFn, StyleSanitizeMode} from '../../../src/sanitization/style_sanitizer'; -import {ComponentFixture, renderToHtml} from '../render_util'; - -import {MockPlayer} from './mock_player'; - -describe('style and class based bindings', () => { - let element: RElement|null = null; - beforeEach(() => { element = document.createElement('div') as any; }); - - function updateStyleAndClassMaps( - context: StylingContext, classes: {} | string | null, styles?: {} | null, - directiveIndex?: number) { - updateStyleMap(context, styles || null, directiveIndex); - updateClassMap(context, classes, directiveIndex); - } - - function createMockViewData(playerHandler: PlayerHandler, context: StylingContext): LView { - const rootContext = - createRootContext(requestAnimationFrame.bind(window), playerHandler || null); - const lView = createLView( - null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, LViewFlags.IsRoot, - null, null, domRendererFactory3, domRendererFactory3.createRenderer(element, null)); - return lView; - } - - function createStylingTemplate( - initialStyles?: (number | string)[] | null, styleBindings?: string[] | null, - initialClasses?: (string | number | boolean)[] | null, classBindings?: string[] | null, - sanitizer?: StyleSanitizeFn | null): StylingContext { - const attrsWithStyling: TAttributes = []; - if (initialClasses) { - attrsWithStyling.push(AttributeMarker.Classes); - attrsWithStyling.push(...initialClasses as any); - } - if (initialStyles) { - attrsWithStyling.push(AttributeMarker.Styles); - attrsWithStyling.push(...initialStyles as any); - } - - const tpl = initializeStaticContext(attrsWithStyling, 0) !; - updateContextWithBindings(tpl, 0, classBindings || null, styleBindings || null, sanitizer); - return tpl; - } - - function createStylingContext( - initialStyles?: (number | string)[] | null, styleBindings?: string[] | null, - initialClasses?: (string | number | boolean)[] | null, classBindings?: string[] | null, - sanitizer?: StyleSanitizeFn | null): StylingContext { - const tpl = createStylingTemplate( - initialStyles, styleBindings, initialClasses, classBindings, sanitizer); - return allocStylingContext(element, tpl); - } - - function patchContext( - context: StylingContext, styles?: string[] | null, classes?: string[] | null, - directiveIndex: number = 0) { - const attrs: (string | AttributeMarker)[] = []; - if (classes && classes.length) { - attrs.push(AttributeMarker.Classes); - attrs.push(...classes); - } - if (styles && styles.length) { - attrs.push(AttributeMarker.Styles); - attrs.push(...styles); - } - patchContextWithStaticAttrs(context, attrs, 0, directiveIndex); - } - - function getRootContextInternal(lView: LView) { return lView[CONTEXT] as RootContext; } - - function renderStyles( - context: StylingContext, firstRender?: boolean, renderer?: Renderer3, lView?: LView) { - const store = new MockStylingStore(element as HTMLElement, BindingType.Style); - const handler = new CorePlayerHandler(); - _renderStyling( - context, (renderer || {}) as Renderer3, - getRootContextInternal(lView || createMockViewData(handler, context)), !!firstRender, null, - store); - return store.getValues(); - } - - function trackStylesFactory(store?: MockStylingStore) { - store = store || new MockStylingStore(element as HTMLElement, BindingType.Style); - const handler = new CorePlayerHandler(); - return function( - context: StylingContext, targetDirective?: any, firstRender?: boolean, - renderer?: Renderer3): {[key: string]: any} { - const lView = createMockViewData(handler, context); - _renderStyling( - context, (renderer || {}) as Renderer3, getRootContextInternal(lView), !!firstRender, - null, store, targetDirective); - return store !.getValues(); - }; - } - - function trackClassesFactory(store?: MockStylingStore) { - store = store || new MockStylingStore(element as HTMLElement, BindingType.Class); - const handler = new CorePlayerHandler(); - return function(context: StylingContext, firstRender?: boolean, renderer?: Renderer3): - {[key: string]: any} { - const lView = createMockViewData(handler, context); - _renderStyling( - context, (renderer || {}) as Renderer3, getRootContextInternal(lView), !!firstRender, - store); - return store !.getValues(); - }; - } - - function trackStylesAndClasses() { - const classStore = new MockStylingStore(element as HTMLElement, BindingType.Class); - const styleStore = new MockStylingStore(element as HTMLElement, BindingType.Style); - const handler = new CorePlayerHandler(); - return function(context: StylingContext, firstRender?: boolean, renderer?: Renderer3): - {[key: string]: any} { - const lView = createMockViewData(handler, context); - _renderStyling( - context, (renderer || {}) as Renderer3, getRootContextInternal(lView), !!firstRender, - classStore, styleStore); - return [classStore.getValues(), styleStore.getValues()]; - }; - } - - function updateClasses(context: StylingContext, classes: string | {[key: string]: any} | null) { - updateStyleAndClassMaps(context, classes, null); - } - - function updateStyles(context: StylingContext, styles: {[key: string]: any} | null) { - updateStyleAndClassMaps(context, null, styles); - } - - function cleanStyle(a: number = 0, b: number = 0): number { return _clean(a, b, false, false); } - - function masterConfig(multiIndexStart: number, dirty: boolean = false, locked = true) { - let num = 0; - num |= multiIndexStart << (StylingFlags.BitCountSize + StylingIndex.BitCountSize); - if (dirty) { - num |= StylingFlags.Dirty; - } - if (locked) { - num |= StylingFlags.BindingAllocationLocked; - } - return num; - } - - function cleanStyleWithSanitization(a: number = 0, b: number = 0): number { - return _clean(a, b, false, true); - } - - function cleanClass(a: number, b: number) { return _clean(a, b, true); } - - function _clean( - a: number = 0, b: number = 0, isClassBased: boolean, sanitizable?: boolean): number { - let num = 0; - if (a) { - num |= a << StylingFlags.BitCountSize; - } - if (b) { - num |= b << (StylingFlags.BitCountSize + StylingIndex.BitCountSize); - } - if (isClassBased) { - num |= StylingFlags.Class; - } - if (sanitizable) { - num |= StylingFlags.Sanitize; - } - return num; - } - - function _dirty( - a: number = 0, b: number = 0, isClassBased: boolean, sanitizable?: boolean): number { - return _clean(a, b, isClassBased, sanitizable) | StylingFlags.Dirty; - } - - function dirtyStyle(a: number = 0, b: number = 0): number { - return _dirty(a, b, false) | StylingFlags.Dirty; - } - - function dirtyStyleWithSanitization(a: number = 0, b: number = 0): number { - return _dirty(a, b, false, true); - } - - function dirtyClass(a: number, b: number) { return _dirty(a, b, true); } - - function makePlayerBuilder( - factory: PlayerFactory, isClassBased?: boolean, elm?: HTMLElement) { - return new ClassAndStylePlayerBuilder( - factory, (elm || element) as HTMLElement, - isClassBased ? BindingType.Class : BindingType.Style); - } - - describe('styles', () => { - describe('static styling properties within a context', () => { - it('should initialize empty template', () => { - const template = createStylingContext(); - assertContext(template, [ - element, - masterConfig(10), - [2, null], - [null, null], - [null, null], - [0, 0, 0, 0], - [0, 0, 10, null, 0], - [0, 0, 10, null, 0], - null, - null, - ]); - }); - - it('should initialize static styles and classes', () => { - const template = - createStylingContext(['color', 'red', 'width', '10px'], null, ['foo', 'bar']); - assertContext(template, [ - element, - masterConfig(10), - [2, null], - [null, null, 'color', 'red', 0, 'width', '10px', 0], - [null, null, 'foo', true, 0, 'bar', true, 0], - [0, 0, 0, 0], - [0, 0, 10, null, 0], - [0, 0, 10, null, 0], - null, - null, - ]); - }); - - it('should initialize and then patch static styling inline with existing static styling and also replace values if the same directive runs twice', - () => { - const template = createStylingTemplate(['color', 'red'], null, ['foo']); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - 0, - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 0, - ]); - - patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo'], 1); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - 0, - 'height', - '200px', - 1, - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 0, - 'bar', - true, - 1, - ]); - - patchContext(template, ['color', 'orange'], []); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'orange', - 0, - 'height', - '200px', - 1, - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 0, - 'bar', - true, - 1, - ]); - }); - - it('should only populate static styles for a given directive once only when the context is allocated', - () => { - const template = createStylingTemplate(['color', 'red'], null, ['foo']); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - 0, - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 0, - ]); - - patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo']); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'black', - 0, - 'height', - '200px', - 0, - ]); - - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 0, - 'bar', - true, - 0, - ]); - - const context = allocStylingContext(element, template); - - patchContext(context, ['color', 'orange', 'height', '300px'], ['bar', 'foo', 'car']); - expect(context[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'black', - 0, - 'height', - '200px', - 0, - ]); - expect(context[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 0, - 'bar', - true, - 0, - ]); - }); - }); - - describe('dynamic styling properties within a styling context', () => { - it('should initialize a context with a series of styling bindings as well as single property offsets', - () => { - const ctx = createEmptyStylingContext(); - updateContextWithBindings(ctx, 0, ['foo'], ['width']); - - assertContext(ctx, [ - null, - masterConfig(18, false, false), // - [2, null], - [null, null, 'width', null, 0], - [null, null, 'foo', false, 0], - [1, 1, 1, 1, 10, 14], - [1, 0, 22, null, 1], - [1, 0, 18, null, 1], - null, - null, - - // #10 - cleanStyle(3, 18), - 'width', - null, - 0, - - // #14 - cleanClass(3, 22), - 'foo', - null, - 0, - - // #18 - cleanStyle(3, 10), - 'width', - null, - 0, - - // #22 - cleanClass(3, 14), - 'foo', - null, - 0, - ]); - - updateContextWithBindings(ctx, 1, ['bar'], ['width', 'height']); - - assertContext(ctx, [ - null, - masterConfig(26, false, false), // - [2, null, 6, null], - [null, null, 'width', null, 0, 'height', null, 1], - [null, null, 'foo', false, 0, 'bar', false, 1], - [2, 2, 1, 1, 10, 18, 2, 1, 10, 14, 22], - [2, 0, 34, null, 1, 0, 38, null, 1], - [2, 0, 26, null, 1, 0, 30, null, 1], - null, - null, - - // #10 - cleanStyle(3, 26), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 30), - 'height', - null, - 1, - - // #18 - cleanClass(3, 34), - 'foo', - null, - 0, - - // #22 - cleanClass(6, 38), - 'bar', - null, - 1, - - // #26 - cleanStyle(3, 10), - 'width', - null, - 0, - - // #30 - cleanStyle(6, 14), - 'height', - null, - 1, - - // #34 - cleanClass(3, 18), - 'foo', - null, - 0, - - // #38 - cleanClass(6, 22), - 'bar', - null, - 1, - ]); - - updateContextWithBindings(ctx, 2, ['baz', 'bar', 'foo'], ['opacity', 'width', 'height']); - - assertContext(ctx, [ - null, - masterConfig(34, false, false), // - [2, null, 6, null, 11, null], - [null, null, 'width', null, 0, 'height', null, 1, 'opacity', null, 2], - [null, null, 'foo', false, 0, 'bar', false, 1, 'baz', false, 2], - [3, 3, 1, 1, 10, 22, 2, 1, 10, 14, 26, 3, 3, 18, 10, 14, 30, 26, 22], - [3, 0, 46, null, 1, 0, 50, null, 1, 0, 54, null, 1], - [3, 0, 34, null, 1, 0, 38, null, 1, 0, 42, null, 1], - null, - null, - - // #10 - cleanStyle(3, 34), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 38), - 'height', - null, - 1, - - // #18 - cleanStyle(9, 42), - 'opacity', - null, - 2, - - // #22 - cleanClass(3, 46), - 'foo', - null, - 0, - - // #26 - cleanClass(6, 50), - 'bar', - null, - 1, - - // #30 - cleanClass(9, 54), - 'baz', - null, - 2, - - // #34 - cleanStyle(3, 10), - 'width', - null, - 0, - - // #38 - cleanStyle(6, 14), - 'height', - null, - 1, - - // #42 - cleanStyle(9, 18), - 'opacity', - null, - 2, - - // #46 - cleanClass(3, 22), - 'foo', - null, - 0, - - // #50 - cleanClass(6, 26), - 'bar', - null, - 1, - - // #54 - cleanClass(9, 30), - 'baz', - null, - 2, - ]); - }); - - it('should only populate bindings for a given directive once', () => { - const ctx = createEmptyStylingContext(); - updateContextWithBindings(ctx, 0, ['foo'], ['width']); - expect(ctx.length).toEqual(26); - - updateContextWithBindings(ctx, 0, ['bar'], ['height']); - expect(ctx.length).toEqual(26); - - updateContextWithBindings(ctx, 1, ['bar'], ['height']); - expect(ctx.length).toEqual(42); - - updateContextWithBindings(ctx, 1, ['bar'], ['height']); - expect(ctx.length).toEqual(42); - }); - - it('should build a list of multiple styling values', () => { - const getStyles = trackStylesFactory(); - const stylingContext = createStylingContext(); - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - updateStyles(stylingContext, {height: '200px'}); - expect(getStyles(stylingContext, null, true)).toEqual({height: '200px'}); - }); - - it('should evaluate the delta between style changes when rendering occurs', () => { - const stylingContext = createStylingContext(['width', '100px'], ['width', 'height']); - updateStyles(stylingContext, { - height: '200px', - }); - expect(renderStyles(stylingContext)).toEqual({height: '200px'}); - expect(renderStyles(stylingContext)).toEqual({}); - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - expect(renderStyles(stylingContext)).toEqual({height: '100px'}); - updateStyleProp(stylingContext, 1, '100px'); - expect(renderStyles(stylingContext)).toEqual({}); - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - expect(renderStyles(stylingContext)).toEqual({}); - }); - - it('should update individual values on a set of styles', () => { - const getStyles = trackStylesFactory(); - const stylingContext = createStylingContext(null, ['width', 'height']); - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - updateStyleProp(stylingContext, 1, '200px'); - expect(getStyles(stylingContext)).toEqual({width: '100px', height: '200px'}); - }); - - it('should only mark itself as updated when one or more properties have been applied', () => { - const stylingContext = createStylingContext(); - expect(isContextDirty(stylingContext)).toBeFalsy(); - - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - expect(isContextDirty(stylingContext)).toBeTruthy(); - - setContextDirty(stylingContext, false); - - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - expect(isContextDirty(stylingContext)).toBeFalsy(); - - updateStyles(stylingContext, { - width: '200px', - height: '100px', - }); - expect(isContextDirty(stylingContext)).toBeTruthy(); - }); - - it('should only mark itself as updated when any single properties have been applied', () => { - const stylingContext = createStylingContext(null, ['height']); - updateStyles(stylingContext, { - width: '100px', - height: '100px', - }); - - setContextDirty(stylingContext, false); - - updateStyleProp(stylingContext, 0, '100px'); - expect(isContextDirty(stylingContext)).toBeFalsy(); - - setContextDirty(stylingContext, false); - - updateStyleProp(stylingContext, 0, '200px'); - expect(isContextDirty(stylingContext)).toBeTruthy(); - }); - - it('should prioritize multi and single styles over initial styles', () => { - const getStyles = trackStylesFactory(); - - const stylingContext = createStylingContext( - ['width', '100px', 'height', '100px', 'opacity', '0'], ['width', 'height', 'opacity']); - - expect(getStyles(stylingContext)).toEqual({}); - - updateStyles(stylingContext, {width: '200px', height: '200px'}); - - expect(getStyles(stylingContext)).toEqual({ - width: '200px', - height: '200px', - }); - - updateStyleProp(stylingContext, 0, '300px'); - - expect(getStyles(stylingContext)).toEqual({ - width: '300px', - height: '200px', - }); - - updateStyleProp(stylingContext, 0, null); - - expect(getStyles(stylingContext)).toEqual({ - width: '200px', - height: '200px', - }); - - updateStyles(stylingContext, {}); - - expect(getStyles(stylingContext)).toEqual({ - width: '100px', - height: '100px', - }); - }); - - it('should cleanup removed styles from the context once the styles are built', () => { - const stylingContext = createStylingContext(null, ['width', 'height']); - const getStyles = trackStylesFactory(); - updateStyles(stylingContext, {width: '100px', height: '100px'}); - - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyle(3, 18), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 22), - 'height', - null, - 0, - - // #18 - dirtyStyle(3, 10), - 'width', - '100px', - 0, - - // #22 - dirtyStyle(6, 14), - 'height', - '100px', - 0, - ]); - - getStyles(stylingContext); - updateStyles(stylingContext, {width: '200px', opacity: '0'}); - - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyle(3, 18), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 26), - 'height', - null, - 0, - - // #18 - dirtyStyle(3, 10), - 'width', - '200px', - 0, - - // #22 - dirtyStyle(), - 'opacity', - '0', - 0, - - // #26 - dirtyStyle(6, 14), - 'height', - null, - 0, - ]); - - getStyles(stylingContext); - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyle(3, 18), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 26), - 'height', - null, - 0, - - // #18 - cleanStyle(3, 10), - 'width', - '200px', - 0, - - // #22 - cleanStyle(), - 'opacity', - '0', - 0, - - // #23 - cleanStyle(6, 14), - 'height', - null, - 0, - ]); - - updateStyles(stylingContext, {width: null}); - updateStyleProp(stylingContext, 0, '300px'); - - assertContextOnlyValues(stylingContext, [ - // #10 - dirtyStyle(3, 18), - 'width', - '300px', - 0, - - // #14 - cleanStyle(6, 26), - 'height', - null, - 0, - - // #18 - cleanStyle(3, 10), - 'width', - null, - 0, - - // #22 - dirtyStyle(), - 'opacity', - null, - 0, - - // #23 - cleanStyle(6, 14), - 'height', - null, - 0, - ]); - - getStyles(stylingContext); - - updateStyleProp(stylingContext, 0, null); - assertContextOnlyValues(stylingContext, [ - // #10 - dirtyStyle(3, 18), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 26), - 'height', - null, - 0, - - // #18 - cleanStyle(3, 10), - 'width', - null, - 0, - - // #22 - cleanStyle(), - 'opacity', - null, - 0, - - // #23 - cleanStyle(6, 14), - 'height', - null, - 0, - ]); - }); - - it('should find the next available space in the context when data is added after being removed before', - () => { - const stylingContext = createStylingContext(null, ['line-height']); - const getStyles = trackStylesFactory(); - - updateStyles(stylingContext, {width: '100px', height: '100px', opacity: '0.5'}); - - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyle(3, 26), - 'line-height', - null, - 0, - - // #14 - dirtyStyle(), - 'width', - '100px', - 0, - - // #18 - dirtyStyle(), - 'height', - '100px', - 0, - - // #22 - dirtyStyle(), - 'opacity', - '0.5', - 0, - - // #23 - cleanStyle(3, 10), - 'line-height', - null, - 0, - ]); - - getStyles(stylingContext); - - updateStyles(stylingContext, {}); - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyle(3, 26), - 'line-height', - null, - 0, - - // #14 - dirtyStyle(), - 'width', - null, - 0, - - // #18 - dirtyStyle(), - 'height', - null, - 0, - - // #22 - dirtyStyle(), - 'opacity', - null, - 0, - - // #23 - cleanStyle(3, 10), - 'line-height', - null, - 0, - ]); - - getStyles(stylingContext); - updateStyles(stylingContext, {borderWidth: '5px'}); - - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyle(3, 30), - 'line-height', - null, - 0, - - // #14 - dirtyStyle(), - 'border-width', - '5px', - 0, - - // #18 - cleanStyle(), - 'width', - null, - 0, - - // #22 - cleanStyle(), - 'height', - null, - 0, - - // #23 - cleanStyle(), - 'opacity', - null, - 0, - - // #30 - cleanStyle(3, 10), - 'line-height', - null, - 0, - ]); - - updateStyleProp(stylingContext, 0, '200px'); - - assertContextOnlyValues(stylingContext, [ - // #10 - dirtyStyle(3, 30), - 'line-height', - '200px', - 0, - - // #14 - dirtyStyle(), - 'border-width', - '5px', - 0, - - // #18 - cleanStyle(), - 'width', - null, - 0, - - // #22 - cleanStyle(), - 'height', - null, - 0, - - // #23 - cleanStyle(), - 'opacity', - null, - 0, - - // #30 - cleanStyle(3, 10), - 'line-height', - null, - 0, - ]); - - updateStyles(stylingContext, {borderWidth: '15px', borderColor: 'red'}); - - assertContextOnlyValues(stylingContext, [ - // #10 - dirtyStyle(3, 34), - 'line-height', - '200px', - 0, - - // #14 - dirtyStyle(), - 'border-width', - '15px', - 0, - - // #18 - dirtyStyle(), - 'border-color', - 'red', - 0, - - // #22 - cleanStyle(), - 'width', - null, - 0, - - // #23 - cleanStyle(), - 'height', - null, - 0, - - // #30 - cleanStyle(), - 'opacity', - null, - 0, - - // #34 - cleanStyle(3, 10), - 'line-height', - null, - 0, - ]); - }); - - it('should render all data as not being dirty after the styles are built', () => { - const getStyles = trackStylesFactory(); - const stylingContext = createStylingContext(null, ['height']); - - const cachedStyleValue = {width: '100px'}; - - updateStyles(stylingContext, cachedStyleValue); - updateStyleProp(stylingContext, 0, '200px'); - - assertContext(stylingContext, [ - element, - masterConfig(14, true), // - [2, null], - [null, null, 'height', null, 0], - [null, null], - [1, 0, 1, 0, 10], - [0, 0, 22, null, 0], - [1, 0, 14, cachedStyleValue, 1], - null, - null, - - // #10 - dirtyStyle(3, 18), - 'height', - '200px', - 0, - - // #14 - dirtyStyle(), - 'width', - '100px', - 0, - - // #18 - cleanStyle(3, 10), - 'height', - null, - 0, - ]); - - getStyles(stylingContext); - - assertContext(stylingContext, [ - element, - masterConfig(14, false), // - [2, null], - [null, null, 'height', null, 0], - [null, null], - [1, 0, 1, 0, 10], - [0, 0, 22, null, 0], - [1, 0, 14, cachedStyleValue, 1], - null, - null, - - // #10 - cleanStyle(3, 18), - 'height', - '200px', - 0, - - // #14 - cleanStyle(), - 'width', - '100px', - 0, - - // #18 - cleanStyle(3, 10), - 'height', - null, - 0, - ]); - }); - - it('should mark styles that may contain url values as being sanitizable (when a sanitizer is passed in)', - () => { - const getStyles = trackStylesFactory(); - const styleBindings = ['border-image', 'border-width']; - const styleSanitizer = ɵɵdefaultStyleSanitizer; - const stylingContext = - createStylingContext(null, styleBindings, null, null, styleSanitizer); - - updateStyleProp(stylingContext, 0, 'url(foo.jpg)'); - updateStyleProp(stylingContext, 1, '100px'); - - assertContextOnlyValues(stylingContext, [ - // #10 - dirtyStyleWithSanitization(3, 18), - 'border-image', - 'url(foo.jpg)', - 0, - - // #14 - dirtyStyle(6, 22), - 'border-width', - '100px', - 0, - - // #18 - cleanStyleWithSanitization(3, 10), - 'border-image', - null, - 0, - - // #22 - cleanStyle(6, 14), - 'border-width', - null, - 0, - ]); - - updateStyles(stylingContext, {'background-image': 'unsafe'}); - - assertContextOnlyValues(stylingContext, [ - // #10 - dirtyStyleWithSanitization(3, 22), - 'border-image', - 'url(foo.jpg)', - 0, - - // #14 - dirtyStyle(6, 26), - 'border-width', - '100px', - 0, - - // #18 - dirtyStyleWithSanitization(0, 0), - 'background-image', - 'unsafe', - 0, - - // #22 - cleanStyleWithSanitization(3, 10), - 'border-image', - null, - 0, - - // #23 - cleanStyle(6, 14), - 'border-width', - null, - 0, - ]); - - getStyles(stylingContext); - - assertContextOnlyValues(stylingContext, [ - // #10 - cleanStyleWithSanitization(3, 22), - 'border-image', - 'url(foo.jpg)', - 0, - - // #14 - cleanStyle(6, 26), - 'border-width', - '100px', - 0, - - // #18 - cleanStyleWithSanitization(0, 0), - 'background-image', - 'unsafe', - 0, - - // #22 - cleanStyleWithSanitization(3, 10), - 'border-image', - null, - 0, - - // #23 - cleanStyle(6, 14), - 'border-width', - null, - 0, - ]); - }); - - it('should only update single styling values for successive directives if null in a former directive', - () => { - const template = createEmptyStylingContext(); - - const dir1 = 1; - const dir2 = 2; - const dir3 = 3; - - updateContextWithBindings(template, dir1, null, ['width', 'height']); - updateContextWithBindings(template, dir2, null, ['width', 'color']); - updateContextWithBindings(template, dir3, null, ['color', 'opacity']); - - const ctx = allocStylingContext(element, template); - - // styles 0 = width, 1 = height, 2 = color within the context - const widthIndex = StylingIndex.SingleStylesStartPosition + StylingIndex.Size * 0; - const colorIndex = StylingIndex.SingleStylesStartPosition + StylingIndex.Size * 2; - - updateStyleProp(ctx, 0, '200px', dir1); - updateStyleProp(ctx, 0, '100px', dir2); - expect(ctx[widthIndex + StylingIndex.ValueOffset]).toEqual('200px'); - expect(getDirectiveIndexFromEntry(ctx, widthIndex)).toEqual(1); - - updateStyleProp(ctx, 0, 'blue', dir3); - updateStyleProp(ctx, 1, 'red', dir2); - expect(ctx[colorIndex + StylingIndex.ValueOffset]).toEqual('red'); - expect(getDirectiveIndexFromEntry(ctx, colorIndex)).toEqual(2); - - updateStyleProp(ctx, 0, null, dir1); - updateStyleProp(ctx, 0, '100px', dir2); - expect(ctx[widthIndex + StylingIndex.ValueOffset]).toEqual('100px'); - expect(getDirectiveIndexFromEntry(ctx, widthIndex)).toEqual(2); - - updateStyleProp(ctx, 1, null, dir2); - updateStyleProp(ctx, 0, 'blue', dir3); - updateStyleProp(ctx, 1, null, dir2); - expect(ctx[colorIndex + StylingIndex.ValueOffset]).toEqual('blue'); - expect(getDirectiveIndexFromEntry(ctx, colorIndex)).toEqual(3); - }); - - it('should allow single style values to override a previous entry if a flag is passed in', - () => { - const template = createEmptyStylingContext(); - - const dir1 = 1; - const dir2 = 2; - const dir3 = 3; - - updateContextWithBindings(template, dir1, null, ['width', 'height']); - updateContextWithBindings(template, dir2, null, ['width', 'color']); - updateContextWithBindings(template, dir3, null, ['height', 'opacity']); - - const ctx = allocStylingContext(element, template); - - // styles 0 = width, 1 = height, 2 = color within the context - const widthIndex = StylingIndex.SingleStylesStartPosition + StylingIndex.Size * 0; - const heightIndex = StylingIndex.SingleStylesStartPosition + StylingIndex.Size * 1; - - updateStyleProp(ctx, 0, '100px', dir1); - updateStyleProp(ctx, 1, '100px', dir1); - expect(ctx[widthIndex + StylingIndex.ValueOffset]).toEqual('100px'); - expect(ctx[heightIndex + StylingIndex.ValueOffset]).toEqual('100px'); - expect(getDirectiveIndexFromEntry(ctx, widthIndex)).toEqual(1); - expect(getDirectiveIndexFromEntry(ctx, heightIndex)).toEqual(1); - - updateStyleProp(ctx, 0, '300px', dir1); - updateStyleProp(ctx, 1, '300px', dir1); - - updateStyleProp(ctx, 0, '900px', dir2); - updateStyleProp(ctx, 0, '900px', dir3, true); - - expect(ctx[widthIndex + StylingIndex.ValueOffset]).toEqual('300px'); - expect(ctx[heightIndex + StylingIndex.ValueOffset]).toEqual('900px'); - expect(getDirectiveIndexFromEntry(ctx, widthIndex)).toEqual(1); - expect(getDirectiveIndexFromEntry(ctx, heightIndex)).toEqual(3); - - updateStyleProp(ctx, 0, '400px', dir1); - updateStyleProp(ctx, 1, '400px', dir1); - - expect(ctx[widthIndex + StylingIndex.ValueOffset]).toEqual('400px'); - expect(ctx[heightIndex + StylingIndex.ValueOffset]).toEqual('400px'); - expect(getDirectiveIndexFromEntry(ctx, widthIndex)).toEqual(1); - expect(getDirectiveIndexFromEntry(ctx, heightIndex)).toEqual(1); - }); - - it('should only update missing multi styling values for successive directives if null in a former directive', - () => { - const template = createEmptyStylingContext(); - updateContextWithBindings(template, 0); - - const dir1 = 1; - const dir2 = 2; - const dir3 = 3; - updateContextWithBindings(template, dir1, null, ['width', 'height']); - updateContextWithBindings(template, dir2); - updateContextWithBindings(template, dir3); - - const ctx = allocStylingContext(element, template); - let s1, s2, s3; - updateStyleAndClassMaps(ctx, null, s1 = {width: '100px', height: '99px'}, dir1); - updateStyleAndClassMaps(ctx, null, s2 = {width: '200px', opacity: '0.5'}, dir2); - updateStyleAndClassMaps(ctx, null, s3 = {width: '300px', height: '999px'}, dir3); - - expect(ctx[StylingIndex.CachedMultiStyles]).toEqual([ - 3, - 0, - 18, - null, - 0, - 0, - 18, - s1, - 2, - 0, - 26, - s2, - 1, - 0, - 30, - s3, - 0, - ]); - - assertContextOnlyValues(ctx, [ - // #10 - cleanStyle(3, 18), - 'width', - null, - 1, - - // #14 - cleanStyle(6, 22), - 'height', - null, - 1, - - // #18 - dirtyStyle(3, 10), - 'width', - '100px', - 1, - - // #22 - dirtyStyle(6, 14), - 'height', - '99px', - 1, - - // #26 - dirtyStyle(0, 0), - 'opacity', - '0.5', - 2, - ]); - - updateStyleAndClassMaps(ctx, null, {opacity: '0', width: null}, dir1); - updateStyleAndClassMaps(ctx, null, {width: '200px', opacity: '0.5'}, dir2); - updateStyleAndClassMaps(ctx, null, {width: '300px', height: '999px'}, dir3); - - assertContextOnlyValues(ctx, [ - // #10 - cleanStyle(3, 22), - 'width', - null, - 1, - - // #14 - cleanStyle(6, 26), - 'height', - null, - 1, - - // #18 - dirtyStyle(0, 0), - 'opacity', - '0', - 1, - - // #22 - dirtyStyle(3, 10), - 'width', - '200px', - 2, - - // #26 - dirtyStyle(6, 14), - 'height', - '999px', - 3, - ]); - - updateStyleAndClassMaps(ctx, null, null, dir1); - updateStyleAndClassMaps(ctx, null, {width: '500px', opacity: '0.2'}, dir2); - updateStyleAndClassMaps( - ctx, null, {width: '300px', height: '999px', color: 'red'}, dir3); - - assertContextOnlyValues(ctx, [ - // #10 - cleanStyle(3, 18), - 'width', - null, - 1, - - // #14 - cleanStyle(6, 26), - 'height', - null, - 1, - - // #18 - dirtyStyle(3, 10), - 'width', - '500px', - 2, - - // #22 - dirtyStyle(0, 0), - 'opacity', - '0.2', - 2, - - // #26 - dirtyStyle(6, 14), - 'height', - '999px', - 3, - - // #30 - dirtyStyle(0, 0), - 'color', - 'red', - 3, - ]); - }); - - it('should only update missing multi class values for successive directives if null in a former directive', - () => { - const template = createEmptyStylingContext(); - updateContextWithBindings(template, 0); - - const dir1 = 1; - const dir2 = 2; - const dir3 = 3; - updateContextWithBindings(template, dir1, ['red', 'green']); - updateContextWithBindings(template, dir2); - updateContextWithBindings(template, dir3); - - const ctx = allocStylingContext(element, template); - let c1, c2, c3; - updateStyleAndClassMaps(ctx, c1 = {red: true, orange: true}, null, dir1); - updateStyleAndClassMaps(ctx, c2 = 'black red', null, dir2); - updateStyleAndClassMaps(ctx, c3 = 'silver green', null, dir3); - - expect(ctx[StylingIndex.CachedMultiClasses]).toEqual([ - 5, 0, 18, null, 0, 0, 18, c1, 2, 0, 26, c2, 1, 0, 30, c3, 2 - ]); - - assertContextOnlyValues(ctx, [ - // #10 - cleanClass(3, 18), - 'red', - null, - 1, - - // #14 - cleanClass(6, 34), - 'green', - null, - 1, - - // #18 - dirtyClass(3, 10), - 'red', - true, - 1, - - // #22 - dirtyClass(0, 0), - 'orange', - true, - 1, - - // #26 - dirtyClass(0, 0), - 'black', - true, - 2, - - // #30 - dirtyClass(0, 0), - 'silver', - true, - 3, - - // #34 - dirtyClass(6, 14), - 'green', - true, - 3, - ]); - - updateStyleAndClassMaps(ctx, c1 = {orange: true}, null, dir1); - updateStyleAndClassMaps(ctx, c2 = 'black red', null, dir2); - updateStyleAndClassMaps(ctx, c3 = 'green', null, dir3); - - assertContextOnlyValues(ctx, [ - // #10 - cleanClass(3, 26), - 'red', - null, - 1, - - // #14 - cleanClass(6, 30), - 'green', - null, - 1, - - // #18 - dirtyClass(0, 0), - 'orange', - true, - 1, - - // #22 - dirtyClass(0, 0), - 'black', - true, - 2, - - // #26 - dirtyClass(3, 10), - 'red', - true, - 2, - - // #30 - dirtyClass(6, 14), - 'green', - true, - 3, - - // #34 - dirtyClass(0, 0), - 'silver', - null, - 1, - ]); - - updateStyleAndClassMaps(ctx, c1 = 'green', null, dir1); - updateStyleAndClassMaps(ctx, c2 = null, null, dir2); - updateStyleAndClassMaps(ctx, c3 = 'red', null, dir3); - - assertContextOnlyValues(ctx, [ - // #10 - cleanClass(3, 22), - 'red', - null, - 1, - - // #14 - cleanClass(6, 18), - 'green', - null, - 1, - - // #18 - dirtyClass(6, 14), - 'green', - true, - 1, - - // #22 - dirtyClass(3, 10), - 'red', - true, - 3, - - // #26 - dirtyClass(0, 0), - 'black', - null, - 1, - - // #30 - dirtyClass(0, 0), - 'orange', - null, - 1, - - // #34 - dirtyClass(0, 0), - 'silver', - null, - 1, - ]); - }); - - it('should use a different sanitizer when a different directive\'s binding is updated', - () => { - const getStyles = trackStylesFactory(); - - const makeSanitizer = (id: string) => { - return (function(prop: string, value?: string): string | boolean { - return `${value}-${id}`; - } as StyleSanitizeFn); - }; - - const template = createEmptyStylingContext(); - const dirWithSanitizer1 = 1; - const sanitizer1 = makeSanitizer('1'); - const dirWithSanitizer2 = 2; - const sanitizer2 = makeSanitizer('2'); - const dirWithoutSanitizer = 3; - updateContextWithBindings(template, dirWithSanitizer1, null, ['color'], sanitizer1); - updateContextWithBindings(template, dirWithSanitizer2, null, ['color'], sanitizer2); - updateContextWithBindings(template, dirWithoutSanitizer, null, ['color']); - - const ctx = allocStylingContext(element, template); - expect(ctx[StylingIndex.DirectiveRegistryPosition]).toEqual([ - -1, // - null, // - 2, // - sanitizer1, // - 5, // - sanitizer2, // - 8, // - null - ]); - - const colorIndex = StylingIndex.SingleStylesStartPosition; - expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeTruthy(); - - updateStyleProp(ctx, 0, 'green', dirWithoutSanitizer); - expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeFalsy(); - expect(getStyles(ctx, dirWithoutSanitizer)).toEqual({color: 'green'}); - - updateStyleProp(ctx, 0, 'blue', dirWithSanitizer1); - expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeTruthy(); - expect(getStyles(ctx, dirWithSanitizer1)).toEqual({color: 'blue-1'}); - - updateStyleProp(ctx, 0, null, dirWithSanitizer1); - updateStyleProp(ctx, 0, 'red', dirWithSanitizer2); - expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeTruthy(); - expect(getStyles(ctx, dirWithSanitizer2)).toEqual({color: 'red-2'}); - - updateStyleProp(ctx, 0, null, dirWithSanitizer2); - updateStyleProp(ctx, 0, 'green', dirWithoutSanitizer); - expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeFalsy(); - expect(getStyles(ctx, dirWithoutSanitizer)).toEqual({color: 'green'}); - }); - - it('should not allow a foreign directive index to update values in the styling context unless it has been registered', - () => { - const template = createEmptyStylingContext(); - const knownDir = 1; - updateContextWithBindings(template, knownDir); - - const ctx = allocStylingContext(element, template); - expect(ctx[StylingIndex.DirectiveRegistryPosition]).toEqual([ - -1, // - null, // - 2, // - null, // - ]); - - expect(ctx[StylingIndex.CachedMultiClasses].length) - .toEqual(template[StylingIndex.CachedMultiClasses].length); - expect(ctx[StylingIndex.CachedMultiClasses]).toEqual([ - 0, 0, 10, null, 0, 0, 10, null, 0 - ]); - - expect(ctx[StylingIndex.CachedMultiStyles].length) - .toEqual(template[StylingIndex.CachedMultiStyles].length); - expect(ctx[StylingIndex.CachedMultiStyles]).toEqual([0, 0, 10, null, 0, 0, 10, null, 0]); - - const foreignDir = 2; - expect(() => { - updateStyleAndClassMaps(ctx, 'foo', null, foreignDir); - }).toThrowError('The provided directive is not registered with the styling context'); - }); - }); - - it('should always render styling when called regardless of the directive unless the host is set', - () => { - const stylingContext = createStylingContext(null, ['color']); - const store = new MockStylingStore(element as HTMLElement, BindingType.Class); - const getStyles = trackStylesFactory(store); - const otherDirective = 1; - - let styles: any = {'font-size': ''}; - updateStyleProp(stylingContext, 0, ''); - updateStyleAndClassMaps(stylingContext, null, styles); - - getStyles(stylingContext); - expect(store.getValues()).toEqual({'font-size': '', 'color': ''}); - - patchContextWithStaticAttrs(stylingContext, [], 0, otherDirective); - registerHostDirective(stylingContext, otherDirective); - - updateStyleProp(stylingContext, 0, 'red'); - updateStyleAndClassMaps(stylingContext, null, styles = {'font-size': '20px'}); - - getStyles(stylingContext); - expect(store.getValues()).toEqual({'font-size': '', 'color': ''}); - - getStyles(stylingContext, otherDirective); - expect(store.getValues()).toEqual({'font-size': '20px', color: 'red'}); - - updateStyleProp(stylingContext, 0, ''); - updateStyleAndClassMaps(stylingContext, null, styles = {}); - - getStyles(stylingContext, otherDirective); - expect(store.getValues()).toEqual({'font-size': null, color: ''}); - }); - }); - - describe('classes', () => { - it('should initialize with the provided class bindings', () => { - const template = createStylingContext(null, null, null, ['one', 'two']); - assertContext(template, [ - element, - masterConfig(18, false), // - [2, null], - [null, null], - [null, null, 'one', false, 0, 'two', false, 0], - [0, 2, 0, 2, 10, 14], - [2, 0, 18, null, 2], - [0, 0, 18, null, 0], - null, - null, - - // #10 - cleanClass(3, 18), - 'one', - null, - 0, - - // #14 - cleanClass(6, 22), - 'two', - null, - 0, - - // #18 - cleanClass(3, 10), - 'one', - null, - 0, - - // #22 - cleanClass(6, 14), - 'two', - null, - 0, - ]); - }); - - it('should update multi class properties against the static classes', () => { - const getClasses = trackClassesFactory(); - const stylingContext = createStylingContext(null, null, ['bar'], ['bar', 'foo']); - expect(getClasses(stylingContext)).toEqual({}); - updateClasses(stylingContext, {foo: true, bar: false}); - expect(getClasses(stylingContext)).toEqual({'foo': true, 'bar': false}); - updateClasses(stylingContext, 'bar'); - expect(getClasses(stylingContext)).toEqual({'foo': false, 'bar': true}); - }); - - it('should update single class properties despite static classes being present', () => { - const getClasses = trackClassesFactory(); - const stylingContext = createStylingContext(null, null, ['bar'], ['bar', 'foo']); - expect(getClasses(stylingContext)).toEqual({}); - - updateClassProp(stylingContext, 0, true); - updateClassProp(stylingContext, 1, true); - expect(getClasses(stylingContext)).toEqual({'bar': true, 'foo': true}); - - updateClassProp(stylingContext, 0, false); - updateClassProp(stylingContext, 1, false); - expect(getClasses(stylingContext)).toEqual({'bar': false, 'foo': false}); - }); - - it('should understand updating multi-classes using a string-based value while respecting single class-based props', - () => { - const getClasses = trackClassesFactory(); - const stylingContext = createStylingContext(null, null, null, ['baz']); - expect(getClasses(stylingContext)).toEqual({}); - - updateStyleAndClassMaps(stylingContext, 'foo bar baz'); - expect(getClasses(stylingContext)).toEqual({'foo': true, 'bar': true, 'baz': true}); - - updateStyleAndClassMaps(stylingContext, 'foo car'); - updateClassProp(stylingContext, 0, true); - expect(getClasses(stylingContext)) - .toEqual({'foo': true, 'car': true, 'bar': false, 'baz': true}); - }); - - it('should place styles within the context and work alongside style-based values in harmony', - () => { - const getStylesAndClasses = trackStylesAndClasses(); - const stylingContext = createStylingContext( - ['width', '100px'], ['width', 'height'], ['wide'], ['wide', 'tall']); - assertContext(stylingContext, [ - element, - masterConfig(26, false), // - [2, null], - [null, null, 'width', '100px', 0, 'height', null, 0], - [null, null, 'wide', true, 0, 'tall', false, 0], - [2, 2, 2, 2, 10, 14, 18, 22], - [2, 0, 34, null, 2], - [2, 0, 26, null, 2], - null, - null, - - // #10 - cleanStyle(3, 26), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 30), - 'height', - null, - 0, - - // #18 - cleanClass(3, 34), - 'wide', - null, - 0, - - // #22 - cleanClass(6, 38), - 'tall', - null, - 0, - - // #26 - cleanStyle(3, 10), - 'width', - null, - 0, - - // #30 - cleanStyle(6, 14), - 'height', - null, - 0, - - // #34 - cleanClass(3, 18), - 'wide', - null, - 0, - - // #38 - cleanClass(6, 22), - 'tall', - null, - 0, - ]); - - expect(getStylesAndClasses(stylingContext)).toEqual([{}, {}]); - - let cachedStyleMap: any = {width: '200px', opacity: '0.5'}; - updateStyleAndClassMaps(stylingContext, 'tall round', cachedStyleMap); - assertContext(stylingContext, [ - element, - masterConfig(26, true), // - [2, null], - [null, null, 'width', '100px', 0, 'height', null, 0], - [null, null, 'wide', true, 0, 'tall', false, 0], - [2, 2, 2, 2, 10, 14, 18, 22], - [2, 0, 38, 'tall round', 2], - [2, 0, 26, cachedStyleMap, 2], - null, - null, - - // #10 - cleanStyle(3, 26), - 'width', - null, - 0, - - // #14 - cleanStyle(6, 34), - 'height', - null, - 0, - - // #18 - cleanClass(3, 46), - 'wide', - null, - 0, - - // #22 - cleanClass(6, 38), - 'tall', - null, - 0, - - // #26 - dirtyStyle(3, 10), - 'width', - '200px', - 0, - - // #30 - dirtyStyle(0, 0), - 'opacity', - '0.5', - 0, - - // #34 - cleanStyle(6, 14), - 'height', - null, - 0, - - // #38 - dirtyClass(6, 22), - 'tall', - true, - 0, - - // #42 - dirtyClass(0, 0), - 'round', - true, - 0, - - // #46 - cleanClass(3, 18), - 'wide', - null, - 0, - ]); - - expect(getStylesAndClasses(stylingContext)).toEqual([ - {tall: true, round: true}, - {width: '200px', opacity: '0.5'}, - ]); - - let cachedClassMap = {tall: true, wide: true}; - cachedStyleMap = {width: '500px'}; - updateStyleAndClassMaps(stylingContext, cachedClassMap, cachedStyleMap); - updateStyleProp(stylingContext, 0, '300px'); - - assertContext(stylingContext, [ - element, - masterConfig(26, true), // - [2, null], - [null, null, 'width', '100px', 0, 'height', null, 0], - [null, null, 'wide', true, 0, 'tall', false, 0], - [2, 2, 2, 2, 10, 14, 18, 22], - [2, 0, 38, cachedClassMap, 2], - [1, 0, 26, cachedStyleMap, 1], - null, - null, - - // #10 - dirtyStyle(3, 26), - 'width', - '300px', - 0, - - // #14 - cleanStyle(6, 34), - 'height', - null, - 0, - - // #18 - cleanClass(3, 42), - 'wide', - null, - 0, - - // #22 - cleanClass(6, 38), - 'tall', - null, - 0, - - // #26 - cleanStyle(3, 10), - 'width', - '500px', - 0, - - // #30 - dirtyStyle(0, 0), - 'opacity', - null, - 0, - - // #34 - cleanStyle(6, 14), - 'height', - null, - 0, - - // #38 - cleanClass(6, 22), - 'tall', - true, - 0, - - // #42 - cleanClass(3, 18), - 'wide', - true, - 0, - - // #46 - dirtyClass(0, 0), - 'round', - null, - 0, - ]); - - expect(getStylesAndClasses(stylingContext)).toEqual([ - {tall: true, round: false}, - {width: '300px', opacity: null}, - ]); - - updateStyleAndClassMaps(stylingContext, {wide: false}); - - expect(getStylesAndClasses(stylingContext)).toEqual([ - {wide: false, tall: false, round: false}, {width: '100px', opacity: null} - ]); - }); - - it('should skip updating multi classes and styles if the input identity has not changed', - () => { - const stylingContext = createStylingContext(); - const getStylesAndClasses = trackStylesAndClasses(); - - const stylesMap = {width: '200px'}; - const classesMap = {foo: true}; - updateStyleAndClassMaps(stylingContext, classesMap, stylesMap); - - // apply the styles - getStylesAndClasses(stylingContext); - - assertContext(stylingContext, [ - element, // - masterConfig(10, false), // - [2, null], // - [null, null], // - [null, null], // - [0, 0, 0, 0], // - [1, 0, 14, classesMap, 1], // - [1, 0, 10, stylesMap, 1], // - null, // - null, // - - // #10 - cleanStyle(0, 0), 'width', '200px', 0, - - // #14 - cleanClass(0, 0), 'foo', true, 0 - ]); - - stylesMap.width = '300px'; - classesMap.foo = false; - - updateStyleAndClassMaps(stylingContext, classesMap, stylesMap); - - // apply the styles - getStylesAndClasses(stylingContext); - - assertContext(stylingContext, [ - element, // - masterConfig(10, false), // - [2, null], // - [null, null], // - [null, null], // - [0, 0, 0, 0], // - [1, 0, 14, classesMap, 1], // - [1, 0, 10, stylesMap, 1], // - null, // - null, // - - // #10 - cleanStyle(0, 0), 'width', '200px', 0, - - // #14 - cleanClass(0, 0), 'foo', true, 0 - ]); - }); - - it('should skip updating multi classes if the string-based identity has not changed', () => { - const stylingContext = createStylingContext(); - const getClasses = trackClassesFactory(); - - const classes = 'apple orange banana'; - updateStyleAndClassMaps(stylingContext, classes); - - // apply the styles - expect(getClasses(stylingContext)).toEqual({apple: true, orange: true, banana: true}); - - assertContext(stylingContext, [ - element, - masterConfig(10, false), // - [2, null], - [null, null], - [null, null], - [0, 0, 0, 0], - [3, 0, 10, 'apple orange banana', 3], - [0, 0, 10, null, 0], - null, - null, - - // #10 - cleanClass(0, 0), - 'apple', - true, - 0, - - // #14 - cleanClass(0, 0), - 'orange', - true, - 0, - - // #18 - cleanClass(0, 0), - 'banana', - true, - 0, - ]); - - stylingContext[14 + StylingIndex.ValueOffset] = false; - stylingContext[18 + StylingIndex.ValueOffset] = false; - updateStyleAndClassMaps(stylingContext, classes); - - // apply the styles - expect(getClasses(stylingContext)).toEqual({apple: true, orange: true, banana: true}); - }); - - it('should skip issuing class updates if there is nothing to update upon first render', () => { - const stylingContext = createStylingContext(null, null, ['blue'], ['blue']); - const store = new MockStylingStore(element as HTMLElement, BindingType.Class); - const getClasses = trackClassesFactory(store); - - let classes: any = {red: false}; - updateClassProp(stylingContext, 0, false); - updateStyleAndClassMaps(stylingContext, classes); - - // apply the styles - getClasses(stylingContext, true); - expect(store.getValues()).toEqual({}); - - classes = {red: true}; - updateClassProp(stylingContext, 0, true); - updateStyleAndClassMaps(stylingContext, classes); - - getClasses(stylingContext); - expect(store.getValues()).toEqual({red: true, blue: true}); - - classes = {red: false}; - updateClassProp(stylingContext, 0, false); - updateStyleAndClassMaps(stylingContext, classes); - - getClasses(stylingContext); - expect(store.getValues()).toEqual({red: false, blue: false}); - }); - }); - - describe('players', () => { - it('should build a player with the computed styles and classes', () => { - const context = createStylingContext(); - - const styles = {width: '100px', height: '200px'}; - const classes = 'foo bar'; - - let classResult: any; - const classFactory = bindPlayerFactory( - (element: HTMLElement, type: BindingType, value: any, firstRender: boolean) => { - const player = new MockPlayer(); - classResult = {player, element, type, value}; - return player; - }, - classes); - - let styleResult: any; - const styleFactory = bindPlayerFactory( - (element: HTMLElement, type: BindingType, value: any, firstRender: boolean) => { - const player = new MockPlayer(); - styleResult = {player, element, type, value}; - return player; - }, - styles); - - updateStyleAndClassMaps(context, classFactory, styleFactory); - expect(classResult).toBeFalsy(); - - renderStyles(context); - - expect(classResult.element).toBe(element); - expect(classResult.type).toBe(BindingType.Class); - expect(classResult.value).toEqual({foo: true, bar: true}); - expect(classResult.player instanceof MockPlayer).toBeTruthy(); - - expect(styleResult.element).toBe(element); - expect(styleResult.type).toBe(BindingType.Style); - expect(styleResult.value).toEqual(styles); - expect(styleResult.player instanceof MockPlayer).toBeTruthy(); - }); - - it('should only build one player for a given style map', () => { - const context = createStylingContext(null, []); - - let count = 0; - const buildFn = (element: HTMLElement, type: BindingType, value: any) => { - count++; - return new MockPlayer(); - }; - - updateStyleAndClassMaps(context, null, bindPlayerFactory(buildFn, {width: '100px'})); - renderStyles(context); - expect(count).toEqual(1); - - updateStyleAndClassMaps(context, null, bindPlayerFactory(buildFn, {height: '100px'})); - renderStyles(context); - expect(count).toEqual(2); - - updateStyleAndClassMaps( - context, null, bindPlayerFactory(buildFn, {height: '200px', width: '200px'})); - renderStyles(context); - expect(count).toEqual(3); - }); - - it('should only build one player for a given class map', () => { - const context = createStylingContext(null, []); - - let count = 0; - const buildFn = (element: HTMLElement, type: BindingType, value: any) => { - count++; - return new MockPlayer(); - }; - - updateStyleAndClassMaps(context, bindPlayerFactory(buildFn, {myClass: true})); - renderStyles(context); - expect(count).toEqual(1); - - updateStyleAndClassMaps(context, bindPlayerFactory(buildFn, {otherClass: true})); - renderStyles(context); - expect(count).toEqual(2); - - updateStyleAndClassMaps( - context, bindPlayerFactory(buildFn, {myClass: false, otherClass: false})); - renderStyles(context); - expect(count).toEqual(3); - }); - - it('should store active players in the player context and remove them once destroyed', () => { - const context = createStylingContext(null, []); - const handler = new CorePlayerHandler(); - const lView = createMockViewData(handler, context); - - let currentStylePlayer: Player; - const styleBuildFn = (element: HTMLElement, type: BindingType, value: any) => { - return currentStylePlayer = new MockPlayer(); - }; - - let currentClassPlayer: Player; - const classBuildFn = (element: HTMLElement, type: BindingType, value: any) => { - return currentClassPlayer = new MockPlayer(); - }; - - expect(context[StylingIndex.PlayerContext]).toEqual(null); - - const styleFactory = bindPlayerFactory(styleBuildFn, {width: '100px'}); - const classFactory = bindPlayerFactory(classBuildFn, 'foo'); - const stylePlayerBuilder = - new ClassAndStylePlayerBuilder(styleFactory, element as HTMLElement, BindingType.Style); - const classPlayerBuilder = - new ClassAndStylePlayerBuilder(classFactory, element as HTMLElement, BindingType.Class); - - updateStyleAndClassMaps(context, classFactory, styleFactory); - expect(context[StylingIndex.PlayerContext]).toEqual([ - 5, classPlayerBuilder, null, stylePlayerBuilder, null - ]); - - renderStyles(context, false, undefined, lView); - expect(context[StylingIndex.PlayerContext]).toEqual([ - 5, classPlayerBuilder, currentClassPlayer !, stylePlayerBuilder, currentStylePlayer ! - ]); - - expect(currentStylePlayer !.state).toEqual(PlayState.Pending); - expect(currentClassPlayer !.state).toEqual(PlayState.Pending); - handler.flushPlayers(); - - expect(currentStylePlayer !.state).toEqual(PlayState.Running); - expect(currentClassPlayer !.state).toEqual(PlayState.Running); - - expect(context[StylingIndex.PlayerContext]).toEqual([ - 5, classPlayerBuilder, currentClassPlayer !, stylePlayerBuilder, currentStylePlayer ! - ]); - - currentStylePlayer !.finish(); - expect(context[StylingIndex.PlayerContext]).toEqual([ - 5, classPlayerBuilder, currentClassPlayer !, stylePlayerBuilder, currentStylePlayer ! - ]); - - currentStylePlayer !.destroy(); - expect(context[StylingIndex.PlayerContext]).toEqual([ - 5, classPlayerBuilder, currentClassPlayer !, stylePlayerBuilder, null - ]); - - currentClassPlayer !.destroy(); - expect(context[StylingIndex.PlayerContext]).toEqual([ - 5, classPlayerBuilder, null, stylePlayerBuilder, null - ]); - }); - - it('should kick off single property change players alongside map-based ones and remove the players', - () => { - const context = createStylingContext(null, ['width', 'height'], null, ['foo', 'bar']); - const handler = new CorePlayerHandler(); - const lView = createMockViewData(handler, context); - - const capturedStylePlayers: Player[] = []; - const styleBuildFn = (element: HTMLElement, type: BindingType, value: any) => { - const player = new MockPlayer(); - capturedStylePlayers.push(player); - return player; - }; - - const capturedClassPlayers: Player[] = []; - const classBuildFn = (element: HTMLElement, type: BindingType, value: any) => { - const player = new MockPlayer(); - capturedClassPlayers.push(player); - return player; - }; - - expect(context[StylingIndex.PlayerContext]).toEqual(null); - - const styleMapFactory = bindPlayerFactory(styleBuildFn, {opacity: '1'}); - const classMapFactory = bindPlayerFactory(classBuildFn, {map: true}); - const styleMapPlayerBuilder = new ClassAndStylePlayerBuilder( - styleMapFactory, element as HTMLElement, BindingType.Style); - const classMapPlayerBuilder = new ClassAndStylePlayerBuilder( - classMapFactory, element as HTMLElement, BindingType.Class); - - updateStyleAndClassMaps(context, classMapFactory, styleMapFactory); - - const widthFactory = bindPlayerFactory(styleBuildFn, '100px'); - const barFactory = bindPlayerFactory(classBuildFn, true); - const widthPlayerBuilder = new ClassAndStylePlayerBuilder( - widthFactory, element as HTMLElement, BindingType.Style); - const barPlayerBuilder = - new ClassAndStylePlayerBuilder(barFactory, element as HTMLElement, BindingType.Class); - updateStyleProp(context, 0, widthFactory as any); - updateClassProp(context, 0, barFactory as any); - - expect(context[StylingIndex.PlayerContext]).toEqual([ - 9, classMapPlayerBuilder, null, styleMapPlayerBuilder, null, widthPlayerBuilder, null, - barPlayerBuilder, null - ]); - - renderStyles(context, false, undefined, lView); - const classMapPlayer = capturedClassPlayers.shift() !; - const barPlayer = capturedClassPlayers.shift() !; - const styleMapPlayer = capturedStylePlayers.shift() !; - const widthPlayer = capturedStylePlayers.shift() !; - - expect(context[StylingIndex.PlayerContext]).toEqual([ - 9, - classMapPlayerBuilder, - classMapPlayer, - styleMapPlayerBuilder, - styleMapPlayer, - widthPlayerBuilder, - widthPlayer, - barPlayerBuilder, - barPlayer, - ]); - - const heightFactory = bindPlayerFactory(styleBuildFn, '200px') !; - const bazFactory = bindPlayerFactory(classBuildFn, true); - const heightPlayerBuilder = new ClassAndStylePlayerBuilder( - heightFactory, element as HTMLElement, BindingType.Style); - const bazPlayerBuilder = - new ClassAndStylePlayerBuilder(bazFactory, element as HTMLElement, BindingType.Class); - updateStyleProp(context, 1, heightFactory as any); - updateClassProp(context, 1, bazFactory as any); - - expect(context[StylingIndex.PlayerContext]).toEqual([ - 13, classMapPlayerBuilder, classMapPlayer, styleMapPlayerBuilder, styleMapPlayer, - widthPlayerBuilder, widthPlayer, barPlayerBuilder, barPlayer, heightPlayerBuilder, null, - bazPlayerBuilder, null - ]); - - renderStyles(context, false, undefined, lView); - const heightPlayer = capturedStylePlayers.shift() !; - const bazPlayer = capturedClassPlayers.shift() !; - - expect(context[StylingIndex.PlayerContext]).toEqual([ - 13, classMapPlayerBuilder, classMapPlayer, styleMapPlayerBuilder, styleMapPlayer, - widthPlayerBuilder, widthPlayer, barPlayerBuilder, barPlayer, heightPlayerBuilder, - heightPlayer, bazPlayerBuilder, bazPlayer - ]); - - widthPlayer.destroy(); - bazPlayer.destroy(); - expect(context[StylingIndex.PlayerContext]).toEqual([ - 13, classMapPlayerBuilder, classMapPlayer, styleMapPlayerBuilder, styleMapPlayer, - widthPlayerBuilder, null, barPlayerBuilder, barPlayer, heightPlayerBuilder, heightPlayer, - bazPlayerBuilder, null - ]); - }); - - it('should automatically destroy existing players when the follow-up binding is not a part of a factory', - () => { - const context = createStylingContext(null, ['width'], null, ['foo', 'bar']); - const handler = new CorePlayerHandler(); - const lView = createMockViewData(handler, context); - - const players: Player[] = []; - const styleBuildFn = (element: HTMLElement, type: BindingType, value: any) => { - const player = new MockPlayer(); - players.push(player); - return player; - }; - - const classBuildFn = (element: HTMLElement, type: BindingType, value: any) => { - const player = new MockPlayer(); - players.push(player); - return player; - }; - - expect(context[StylingIndex.PlayerContext]).toEqual(null); - - const styleMapFactory = bindPlayerFactory(styleBuildFn, {opacity: '1'}); - const classMapFactory = bindPlayerFactory(classBuildFn, {map: true}); - updateStyleAndClassMaps(context, classMapFactory, styleMapFactory); - updateStyleProp(context, 0, bindPlayerFactory(styleBuildFn, '100px') as any); - updateClassProp(context, 0, bindPlayerFactory(classBuildFn, true) as any); - updateClassProp(context, 1, bindPlayerFactory(classBuildFn, true) as any); - renderStyles(context, false, undefined, lView); - handler.flushPlayers(); - - expect(players.length).toEqual(5); - const [p1, p2, p3, p4, p5] = players; - expect(p1.state).toEqual(PlayState.Running); - expect(p2.state).toEqual(PlayState.Running); - expect(p3.state).toEqual(PlayState.Running); - expect(p4.state).toEqual(PlayState.Running); - - updateStyleAndClassMaps(context, {bar: true}, {height: '200px'}); - updateStyleProp(context, 0, '200px'); - updateClassProp(context, 0, false); - expect(p1.state).toEqual(PlayState.Running); - expect(p2.state).toEqual(PlayState.Running); - expect(p3.state).toEqual(PlayState.Running); - expect(p4.state).toEqual(PlayState.Running); - expect(p5.state).toEqual(PlayState.Running); - - renderStyles(context, false, undefined, lView); - expect(p1.state).toEqual(PlayState.Destroyed); - expect(p2.state).toEqual(PlayState.Destroyed); - expect(p3.state).toEqual(PlayState.Destroyed); - expect(p4.state).toEqual(PlayState.Destroyed); - expect(p5.state).toEqual(PlayState.Running); - }); - }); -}); - -class MockStylingStore implements BindingStore { - private _values: {[key: string]: any} = {}; - - constructor(public element: HTMLElement, public type: BindingType) {} - - setValue(prop: string, value: any): void { this._values[prop] = value; } - - getValues() { return this._values; } -} - -function assertContextOnlyValues(actual: StylingContext, target: any[]) { - assertContext(actual, target as StylingContext, StylingIndex.SingleStylesStartPosition); -} - -function assertContext(actual: StylingContext, target: StylingContext, startIndex: number = 0) { - const errorPrefix = 'Assertion of styling context has failed: \n'; - if (startIndex === 0 && actual.length !== target.length) { - fail( - errorPrefix + - `=> Expected length of context to be ${target.length} (actual = ${actual.length})`); - return; - } - - const log: string[] = []; - for (let i = startIndex; i < actual.length; i++) { - const actualValue = actual[i]; - const targetValue = target[i - startIndex]; - if (isConfigValue(i)) { - const failures = compareLogSummaries( - generateConfigSummary(actualValue as number), - generateConfigSummary(targetValue as number)); - if (failures.length) { - log.push(`i=${i}: Expected config values to match`); - failures.forEach(f => { log.push(' ' + f); }); - } - } else { - let valueIsTheSame: boolean; - let stringError: string|null = null; - let fieldName: string; - switch (i) { - case StylingIndex.PlayerContext: - valueIsTheSame = valueEqualsValue(actualValue, targetValue); - stringError = !valueIsTheSame ? - generateArrayCompareError(actualValue as any[], targetValue as any[], ' ') : - null; - fieldName = 'Player Context'; - break; - case StylingIndex.ElementPosition: - valueIsTheSame = actualValue === targetValue; - stringError = !valueIsTheSame ? - generateElementCompareError(actualValue as Element, targetValue as Element) : - null; - fieldName = 'Element Position'; - break; - case StylingIndex.CachedMultiClasses: - case StylingIndex.CachedMultiStyles: - valueIsTheSame = Array.isArray(actualValue) ? - valueEqualsValue(actualValue, targetValue) : - stringMapEqualsStringMap(actualValue, targetValue); - if (!valueIsTheSame) { - stringError = '\n\n ' + generateValueCompareError(actualValue, targetValue); - if (Array.isArray(actualValue)) { - stringError += '\n ....'; - stringError += - generateArrayCompareError(actualValue as any[], targetValue as any[], ' '); - } - } - fieldName = 'Cached Style/Class Value'; - break; - default: - valueIsTheSame = valueEqualsValue(actualValue, targetValue); - stringError = - !valueIsTheSame ? generateValueCompareError(actualValue, targetValue) : null; - fieldName = i > StylingIndex.SingleStylesStartPosition ? - `styling value #${Math.floor(i / StylingIndex.Size)}` : - 'config value'; - break; - } - if (!valueIsTheSame) { - log.push(`Error: i=${i}: (${fieldName}) ${stringError}`); - } - } - } - - if (log.length) { - fail(errorPrefix + log.join('\n')); - } -} - -function generateArrayCompareError(a: any[], b: any[], tab: string) { - const values: string[] = []; - const length = Math.max(a.length, b.length); - for (let i = 0; i < length; i++) { - if (a[i] === b[i]) { - values.push(`${tab}a[${i}] === b[${i}] (${a[i]} === ${b[i]})`); - } else { - values.push(`${tab}a[${i}] !== b[${i}] (${a[i]} !== ${b[i]})`); - } - } - return values.length ? '\n' + values.join('\n') : null; -} - -function generateElementCompareError(a: Element, b: Element) { - const aName = a.nodeName.toLowerCase() + a.className.replace(/ /g, '.').trim(); - const bName = b.nodeName.toLowerCase() + b.className.replace(/ /g, '.').trim(); - return `${aName} !== ${bName} (by instance)`; -} - -function generateValueCompareError(a: any, b: any) { - return `${JSON.stringify(a)} !== ${JSON.stringify(b)}`; -} - -function isConfigValue(index: number) { - if (index == StylingIndex.MasterFlagPosition) return true; - if (index >= StylingIndex.SingleStylesStartPosition) { - return (index - StylingIndex.SingleStylesStartPosition) % StylingIndex.Size === 0; - } -} - -function valueEqualsValue(a: any, b: any): boolean { - if (Array.isArray(a)) { - if (!Array.isArray(b)) return false; - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; - } - return a === b; -} - -function stringMapEqualsStringMap(a: any, b: any): boolean { - if (a && b) { - const k1 = Object.keys(a); - const k2 = Object.keys(b); - if (k1.length === k2.length) { - return k1.every(key => { return a[key] === b[key]; }); - } - return false; - } - return a == b; -} diff --git a/packages/core/test/render3/styling/core_player_handler_spec.ts b/packages/core/test/render3/styling/core_player_handler_spec.ts deleted file mode 100644 index c6bce3ac5c..0000000000 --- a/packages/core/test/render3/styling/core_player_handler_spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/packages/core/test/render3/styling/mock_player.ts b/packages/core/test/render3/styling/mock_player.ts deleted file mode 100644 index 1c69bfbf15..0000000000 --- a/packages/core/test/render3/styling/mock_player.ts +++ /dev/null @@ -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(); - } - } -} diff --git a/packages/core/test/render3/styling/players_spec.ts b/packages/core/test/render3/styling/players_spec.ts deleted file mode 100644 index ed602b7786..0000000000 --- a/packages/core/test/render3/styling/players_spec.ts +++ /dev/null @@ -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>()) && - (ctx.query = tmp as QueryList); - } - }, - directives: [Comp] - }); - - name = 'super-comp'; - query !: QueryList; -} - -class MockPlayerHandler implements PlayerHandler { - players: Player[] = []; - lastFlushedPlayers: Player[] = []; - flushPlayers(): void { - this.lastFlushedPlayers = [...this.players]; - this.players = []; - } - queuePlayer(player: Player): void { this.players.push(player); } -} diff --git a/packages/core/test/render3/view_utils_spec.ts b/packages/core/test/render3/view_utils_spec.ts index 81cb6f41e8..841ef52918 100644 --- a/packages/core/test/render3/view_utils_spec.ts +++ b/packages/core/test/render3/view_utils_spec.ts @@ -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); }); }); diff --git a/tools/public_api_guard/global_utils.d.ts b/tools/public_api_guard/global_utils.d.ts index 499ec0e235..67c0a67566 100644 --- a/tools/public_api_guard/global_utils.d.ts +++ b/tools/public_api_guard/global_utils.d.ts @@ -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(element: Element | {}): T | null;