refactor(ivy): Delete all styling code. (Part of next SHA) (#34616)
NOTE: This change deletes code and creates a BROKEN SHA. If reverting this SHA needs to be reverted with the next SHA to get back into a valid state. PR Close #34616
This commit is contained in:
parent
69de7680f5
commit
b4a711ea9f
|
@ -1994,31 +1994,3 @@ export function textBindingInternal(lView: LView, index: number, value: string):
|
|||
isProceduralRenderer(renderer) ? renderer.setValue(element, value) : element.textContent = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all initial styling (class and style values) on to the element from the tNode.
|
||||
*
|
||||
* All initial styling data (i.e. any values extracted from the `style` or `class` attributes
|
||||
* on an element) are collected into the `tNode.styles` and `tNode.classes` data structures.
|
||||
* These values are populated during the creation phase of an element and are then later
|
||||
* applied once the element is instantiated. This function applies each of the static
|
||||
* style and class entries to the element.
|
||||
*/
|
||||
export function renderInitialStyling(
|
||||
renderer: Renderer3, native: RElement, tNode: TNode, append: boolean) {
|
||||
if (tNode.classes !== null) {
|
||||
if (append) {
|
||||
renderStylingMap(renderer, native, tNode.classes, true);
|
||||
} else {
|
||||
const classes = getInitialStylingValue(tNode.classes);
|
||||
writeStylingValueDirectly(renderer, native, classes, true, null);
|
||||
}
|
||||
}
|
||||
if (tNode.styles !== null) {
|
||||
if (append) {
|
||||
renderStylingMap(renderer, native, tNode.styles, false);
|
||||
} else {
|
||||
const styles = getInitialStylingValue(tNode.styles);
|
||||
writeStylingValueDirectly(renderer, native, styles, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,6 @@ import {getNativeByTNode, getTNode} from '../util/view_utils';
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* --------
|
||||
*
|
||||
* This file contains the core logic for how styling instructions are processed in Angular.
|
||||
*
|
||||
* To learn more about the algorithm see `TStylingContext`.
|
||||
*
|
||||
* --------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the current style sanitizer function which will then be used
|
||||
|
@ -83,43 +74,6 @@ export function ɵɵstyleProp(
|
|||
return ɵɵstyleProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function for applying a single style to an element.
|
||||
*
|
||||
* The reason why this function has been separated from `ɵɵstyleProp` is because
|
||||
* it is also called from `ɵɵstylePropInterpolate`.
|
||||
*/
|
||||
export function stylePropInternal(
|
||||
elementIndex: number, prop: string, value: string | number | SafeValue | null,
|
||||
suffix?: string | null | undefined): void {
|
||||
// if a value is interpolated then it may render a `NO_CHANGE` value.
|
||||
// in this case we do not need to do anything, but the binding index
|
||||
// still needs to be incremented because all styling binding values
|
||||
// are stored inside of the lView.
|
||||
const bindingIndex = nextBindingIndex();
|
||||
const lView = getLView();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const firstUpdatePass = lView[TVIEW].firstUpdatePass;
|
||||
|
||||
// we check for this in the instruction code so that the context can be notified
|
||||
// about prop or map bindings so that the direct apply check can decide earlier
|
||||
// if it allows for context resolution to be bypassed.
|
||||
if (firstUpdatePass) {
|
||||
patchConfig(tNode, TNodeFlags.hasStylePropBindings);
|
||||
patchHostStylingFlag(tNode, isHostStyling(), false);
|
||||
}
|
||||
|
||||
const updated = stylingProp(
|
||||
tNode, firstUpdatePass, lView, bindingIndex, prop, resolveStylePropValue(value, suffix),
|
||||
false);
|
||||
if (ngDevMode) {
|
||||
ngDevMode.styleProp++;
|
||||
if (updated) {
|
||||
ngDevMode.stylePropCacheMiss++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a class binding on an element with the provided value.
|
||||
*
|
||||
|
@ -164,73 +118,6 @@ export function ɵɵclassProp(className: string, value: boolean | null): typeof
|
|||
return ɵɵclassProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared function used to update a prop-based styling binding for an element.
|
||||
*
|
||||
* Depending on the state of the `tNode.styles` styles context, the style/prop
|
||||
* value may be applied directly to the element instead of being processed
|
||||
* through the context. The reason why this occurs is for performance and fully
|
||||
* depends on the state of the context (i.e. whether or not there are duplicate
|
||||
* bindings or whether or not there are map-based bindings and property bindings
|
||||
* present together).
|
||||
*/
|
||||
function stylingProp(
|
||||
tNode: TNode, firstUpdatePass: boolean, lView: LView, bindingIndex: number, prop: string,
|
||||
value: boolean | number | SafeValue | string | null | undefined | NO_CHANGE,
|
||||
isClassBased: boolean): boolean {
|
||||
let updated = false;
|
||||
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode);
|
||||
const sanitizer = isClassBased ? null : getCurrentStyleSanitizer();
|
||||
|
||||
// [style.prop] and [class.name] bindings do not use `bind()` and will
|
||||
// therefore manage accessing and updating the new value in the lView directly.
|
||||
// For this reason, the checkNoChanges situation must also be handled here
|
||||
// as well.
|
||||
if (ngDevMode && getCheckNoChangesMode()) {
|
||||
const oldValue = getValue(lView, bindingIndex);
|
||||
if (hasValueChangedUnwrapSafeValue(oldValue, value)) {
|
||||
const field = isClassBased ? `class.${prop}` : `style.${prop}`;
|
||||
throwErrorIfNoChangesMode(false, oldValue, value, field);
|
||||
}
|
||||
}
|
||||
|
||||
// Direct Apply Case: bypass context resolution and apply the
|
||||
// style/class value directly to the element
|
||||
if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) {
|
||||
const sanitizerToUse = isClassBased ? null : sanitizer;
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
updated = applyStylingValueDirectly(
|
||||
renderer, context, tNode, native, lView, bindingIndex, prop, value, isClassBased,
|
||||
sanitizerToUse);
|
||||
|
||||
if (sanitizerToUse) {
|
||||
// it's important we remove the current style sanitizer once the
|
||||
// element exits, otherwise it will be used by the next styling
|
||||
// instructions for the next element.
|
||||
setElementExitFn(stylingApply);
|
||||
}
|
||||
} else {
|
||||
// Context Resolution (or first update) Case: save the value
|
||||
// and defer to the context to flush and apply the style/class binding
|
||||
// value to the element.
|
||||
const directiveIndex = getActiveDirectiveId();
|
||||
if (isClassBased) {
|
||||
updated = updateClassViaContext(
|
||||
context, tNode, lView, native, directiveIndex, prop, bindingIndex,
|
||||
value as string | boolean | null, false, firstUpdatePass);
|
||||
} else {
|
||||
updated = updateStyleViaContext(
|
||||
context, tNode, lView, native, directiveIndex, prop, bindingIndex,
|
||||
value as string | SafeValue | null, sanitizer, false, firstUpdatePass);
|
||||
}
|
||||
|
||||
setElementExitFn(stylingApply);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update style bindings using an object literal on an element.
|
||||
|
@ -308,319 +195,3 @@ export function ɵɵclassMap(classes: {[className: string]: any} | NO_CHANGE | s
|
|||
classMapInternal(getSelectedIndex(), classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function for applying a class string or key/value map of classes to an element.
|
||||
*
|
||||
* The reason why this function has been separated from `ɵɵclassMap` is because
|
||||
* it is also called from `ɵɵclassMapInterpolate`.
|
||||
*/
|
||||
export function classMapInternal(
|
||||
elementIndex: number, classes: {[className: string]: any} | NO_CHANGE | string | null): void {
|
||||
const lView = getLView();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const firstUpdatePass = lView[TVIEW].firstUpdatePass;
|
||||
const context = getClassesContext(tNode);
|
||||
const hasDirectiveInput = hasClassInput(tNode);
|
||||
|
||||
// if a value is interpolated then it may render a `NO_CHANGE` value.
|
||||
// in this case we do not need to do anything, but the binding index
|
||||
// still needs to be incremented because all styling binding values
|
||||
// are stored inside of the lView.
|
||||
const bindingIndex = incrementBindingIndex(2);
|
||||
const hostBindingsMode = isHostStyling();
|
||||
|
||||
// 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 (!hostBindingsMode && hasDirectiveInput && classes !== NO_CHANGE) {
|
||||
updateDirectiveInputValue(context, lView, tNode, bindingIndex, classes, true, firstUpdatePass);
|
||||
classes = NO_CHANGE;
|
||||
}
|
||||
|
||||
// we check for this in the instruction code so that the context can be notified
|
||||
// about prop or map bindings so that the direct apply check can decide earlier
|
||||
// if it allows for context resolution to be bypassed.
|
||||
if (firstUpdatePass) {
|
||||
patchConfig(tNode, TNodeFlags.hasClassMapBindings);
|
||||
patchHostStylingFlag(tNode, isHostStyling(), true);
|
||||
}
|
||||
|
||||
stylingMap(
|
||||
context, tNode, firstUpdatePass, lView, bindingIndex, classes, true, hasDirectiveInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared function used to update a map-based styling binding for an element.
|
||||
*
|
||||
* When this function is called it will activate support for `[style]` and
|
||||
* `[class]` bindings in Angular.
|
||||
*/
|
||||
function stylingMap(
|
||||
context: TStylingContext, tNode: TNode, firstUpdatePass: boolean, lView: LView,
|
||||
bindingIndex: number, value: {[key: string]: any} | string | null, isClassBased: boolean,
|
||||
hasDirectiveInput: boolean): void {
|
||||
const directiveIndex = getActiveDirectiveId();
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const oldValue = getValue(lView, bindingIndex);
|
||||
const sanitizer = getCurrentStyleSanitizer();
|
||||
const valueHasChanged = hasValueChanged(oldValue, value);
|
||||
|
||||
// [style] and [class] bindings do not use `bind()` and will therefore
|
||||
// manage accessing and updating the new value in the lView directly.
|
||||
// For this reason, the checkNoChanges situation must also be handled here
|
||||
// as well.
|
||||
if (ngDevMode && valueHasChanged && getCheckNoChangesMode()) {
|
||||
// check if the value is a StylingMapArray, in which case take the first value (which stores raw
|
||||
// value) from the array
|
||||
const previousValue =
|
||||
isStylingMapArray(oldValue) ? oldValue[StylingMapArrayIndex.RawValuePosition] : oldValue;
|
||||
throwErrorIfNoChangesMode(false, previousValue, value);
|
||||
}
|
||||
|
||||
// Direct Apply Case: bypass context resolution and apply the
|
||||
// style/class map values directly to the element
|
||||
if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) {
|
||||
const sanitizerToUse = isClassBased ? null : sanitizer;
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
applyStylingMapDirectly(
|
||||
renderer, context, tNode, native, lView, bindingIndex, value, isClassBased, sanitizerToUse,
|
||||
valueHasChanged, hasDirectiveInput);
|
||||
if (sanitizerToUse) {
|
||||
// it's important we remove the current style sanitizer once the
|
||||
// element exits, otherwise it will be used by the next styling
|
||||
// instructions for the next element.
|
||||
setElementExitFn(stylingApply);
|
||||
}
|
||||
} else {
|
||||
const stylingMapArr =
|
||||
value === NO_CHANGE ? NO_CHANGE : normalizeIntoStylingMap(oldValue, value, !isClassBased);
|
||||
|
||||
activateStylingMapFeature();
|
||||
|
||||
// Context Resolution (or first update) Case: save the map value
|
||||
// and defer to the context to flush and apply the style/class binding
|
||||
// value to the element.
|
||||
if (isClassBased) {
|
||||
updateClassViaContext(
|
||||
context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr,
|
||||
valueHasChanged, firstUpdatePass);
|
||||
} else {
|
||||
updateStyleViaContext(
|
||||
context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr,
|
||||
sanitizer, valueHasChanged, firstUpdatePass);
|
||||
}
|
||||
|
||||
setElementExitFn(stylingApply);
|
||||
}
|
||||
|
||||
if (ngDevMode) {
|
||||
isClassBased ? ngDevMode.classMap : ngDevMode.styleMap++;
|
||||
if (valueHasChanged) {
|
||||
isClassBased ? ngDevMode.classMapCacheMiss : ngDevMode.styleMapCacheMiss++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a value to a directive's `style` or `class` input binding (if it has changed).
|
||||
*
|
||||
* If a directive has a `@Input` binding that is set on `style` or `class` then that value
|
||||
* will take priority over the underlying style/class styling bindings. This value will
|
||||
* be updated for the binding each time during change detection.
|
||||
*
|
||||
* When this occurs this function will attempt to write the value to the input binding
|
||||
* depending on the following situations:
|
||||
*
|
||||
* - If `oldValue !== newValue`
|
||||
* - If `newValue` is `null` (but this is skipped if it is during the first update pass)
|
||||
*/
|
||||
function updateDirectiveInputValue(
|
||||
context: TStylingContext, lView: LView, tNode: TNode, bindingIndex: number, newValue: any,
|
||||
isClassBased: boolean, firstUpdatePass: boolean): void {
|
||||
const oldValue = getValue(lView, bindingIndex);
|
||||
if (hasValueChanged(oldValue, newValue)) {
|
||||
// even if the value has changed we may not want to emit it to the
|
||||
// directive input(s) in the event that it is falsy during the
|
||||
// first update pass.
|
||||
if (isStylingValueDefined(newValue) || !firstUpdatePass) {
|
||||
const inputName: string = isClassBased ? selectClassBasedInputName(tNode.inputs !) : 'style';
|
||||
const inputs = tNode.inputs ![inputName] !;
|
||||
const initialValue = getInitialStylingValue(context);
|
||||
const value = normalizeStylingDirectiveInputValue(initialValue, newValue, isClassBased);
|
||||
setInputsForProperty(lView, inputs, inputName, value);
|
||||
setElementExitFn(stylingApply);
|
||||
}
|
||||
setValue(lView, bindingIndex, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate directive input value for `style` or `class`.
|
||||
*
|
||||
* Earlier versions of Angular expect a binding value to be passed into directive code
|
||||
* exactly as it is unless there is a static value present (in which case both values
|
||||
* will be stringified and concatenated).
|
||||
*/
|
||||
function normalizeStylingDirectiveInputValue(
|
||||
initialValue: string, bindingValue: string | {[key: string]: any} | null,
|
||||
isClassBased: boolean) {
|
||||
let value = bindingValue;
|
||||
|
||||
// we only concat values if there is an initial value, otherwise we return the value as is.
|
||||
// Note that this is to satisfy backwards-compatibility in Angular.
|
||||
if (initialValue.length) {
|
||||
if (isClassBased) {
|
||||
value = concatString(initialValue, forceClassesAsString(bindingValue));
|
||||
} else {
|
||||
value = concatString(initialValue, forceStylesAsString(bindingValue, true), ';');
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all styling code to the element.
|
||||
*
|
||||
* This function is designed to be scheduled from any of the four styling instructions
|
||||
* in this file. When called it will flush all style and class bindings to the element
|
||||
* via the context resolution algorithm.
|
||||
*/
|
||||
function stylingApply(): void {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const elementIndex = getSelectedIndex();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const directiveIndex = getActiveDirectiveId();
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
const sanitizer = getCurrentStyleSanitizer();
|
||||
const classesContext = isStylingContext(tNode.classes) ? tNode.classes as TStylingContext : null;
|
||||
const stylesContext = isStylingContext(tNode.styles) ? tNode.styles as TStylingContext : null;
|
||||
flushStyling(
|
||||
renderer, lView, tNode, classesContext, stylesContext, native, directiveIndex, sanitizer,
|
||||
tView.firstUpdatePass);
|
||||
resetCurrentStyleSanitizer();
|
||||
}
|
||||
|
||||
function getRenderer(tNode: TNode, lView: LView) {
|
||||
return tNode.type === TNodeType.Element ? lView[RENDERER] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches and assigns provided all static style/class entries (found in the `attrs` value)
|
||||
* and registers them in their respective styling contexts.
|
||||
*/
|
||||
export function registerInitialStylingOnTNode(
|
||||
tNode: TNode, attrs: TAttributes, startIndex: number): boolean {
|
||||
let hasAdditionalInitialStyling = false;
|
||||
let styles = getStylingMapArray(tNode.styles);
|
||||
let classes = getStylingMapArray(tNode.classes);
|
||||
let mode = -1;
|
||||
for (let i = startIndex; i < attrs.length; i++) {
|
||||
const attr = attrs[i] as string;
|
||||
if (typeof attr == 'number') {
|
||||
mode = attr;
|
||||
} else if (mode == AttributeMarker.Classes) {
|
||||
classes = classes || allocStylingMapArray(null);
|
||||
addItemToStylingMap(classes, attr, true);
|
||||
hasAdditionalInitialStyling = true;
|
||||
} else if (mode == AttributeMarker.Styles) {
|
||||
const value = attrs[++i] as string | null;
|
||||
styles = styles || allocStylingMapArray(null);
|
||||
addItemToStylingMap(styles, attr, value);
|
||||
hasAdditionalInitialStyling = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (classes && classes.length > StylingMapArrayIndex.ValuesStartPosition) {
|
||||
if (!tNode.classes) {
|
||||
tNode.classes = classes;
|
||||
}
|
||||
updateRawValueOnContext(tNode.classes, stylingMapToString(classes, true));
|
||||
}
|
||||
|
||||
if (styles && styles.length > StylingMapArrayIndex.ValuesStartPosition) {
|
||||
if (!tNode.styles) {
|
||||
tNode.styles = styles;
|
||||
}
|
||||
updateRawValueOnContext(tNode.styles, stylingMapToString(styles, false));
|
||||
}
|
||||
|
||||
if (hasAdditionalInitialStyling) {
|
||||
tNode.flags |= TNodeFlags.hasInitialStyling;
|
||||
}
|
||||
|
||||
return hasAdditionalInitialStyling;
|
||||
}
|
||||
|
||||
function updateRawValueOnContext(
|
||||
context: TStylingContext | StylingMapArray | string, value: string) {
|
||||
const stylingMapArr = getStylingMapArray(context) !;
|
||||
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value;
|
||||
}
|
||||
|
||||
function getStylesContext(tNode: TNode): TStylingContext {
|
||||
return getContext(tNode, false);
|
||||
}
|
||||
|
||||
function getClassesContext(tNode: TNode): TStylingContext {
|
||||
return getContext(tNode, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns/instantiates a styling context from/to a `tNode` instance.
|
||||
*/
|
||||
function getContext(tNode: TNode, isClassBased: boolean): TStylingContext {
|
||||
let context = isClassBased ? tNode.classes : tNode.styles;
|
||||
if (!isStylingContext(context)) {
|
||||
const hasDirectives = isDirectiveHost(tNode);
|
||||
context = allocTStylingContext(context as StylingMapArray | null, hasDirectives);
|
||||
if (ngDevMode) {
|
||||
attachStylingDebugObject(context as TStylingContext, tNode, isClassBased);
|
||||
}
|
||||
|
||||
if (isClassBased) {
|
||||
tNode.classes = context;
|
||||
} else {
|
||||
tNode.styles = context;
|
||||
}
|
||||
}
|
||||
return context as TStylingContext;
|
||||
}
|
||||
|
||||
function resolveStylePropValue(
|
||||
value: string | number | SafeValue | null | NO_CHANGE,
|
||||
suffix: string | null | undefined): string|SafeValue|null|undefined|NO_CHANGE {
|
||||
if (value === NO_CHANGE) return value;
|
||||
|
||||
let resolvedValue: string|null = null;
|
||||
if (isStylingValueDefined(value)) {
|
||||
if (suffix) {
|
||||
// when a suffix is applied then it will bypass
|
||||
// sanitization entirely (b/c a new string is created)
|
||||
resolvedValue = 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)
|
||||
resolvedValue = value as any as string;
|
||||
}
|
||||
}
|
||||
return resolvedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the style/class binding being applied was executed within a host bindings
|
||||
* function.
|
||||
*/
|
||||
function isHostStyling(): boolean {
|
||||
return isHostStylingActive(getActiveDirectiveId());
|
||||
}
|
||||
|
||||
function patchHostStylingFlag(tNode: TNode, hostBindingsMode: boolean, isClassBased: boolean) {
|
||||
const flag = hostBindingsMode ?
|
||||
isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings :
|
||||
isClassBased ? TNodeFlags.hasTemplateClassBindings : TNodeFlags.hasTemplateStyleBindings;
|
||||
patchConfig(tNode, flag);
|
||||
}
|
||||
|
|
|
@ -99,15 +99,6 @@ interface LFrame {
|
|||
*/
|
||||
currentDirectiveDef: DirectiveDef<any>|ComponentDef<any>|null;
|
||||
|
||||
/**
|
||||
* Used as the starting directive id value.
|
||||
*
|
||||
* All subsequent directives are incremented from this value onwards.
|
||||
* The reason why this value is `1` instead of `0` is because the `0`
|
||||
* value is reserved for the template.
|
||||
*/
|
||||
activeDirectiveId: number;
|
||||
|
||||
/**
|
||||
* The root index from which pure function instructions should calculate their binding
|
||||
* indices. In component views, this is TView.bindingStartIndex. In a host binding
|
||||
|
@ -273,12 +264,6 @@ export const enum ActiveElementFlags {
|
|||
Size = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a flag is currently set for the active element.
|
||||
*/
|
||||
export function hasActiveElementFlag(flag: ActiveElementFlags) {
|
||||
return (instructionState.lFrame.selectedIndex & flag) === flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag is for the active element.
|
||||
|
@ -329,54 +314,6 @@ export function setElementExitFn(fn: () => void): void {
|
|||
assertEqual(instructionState.elementExitFn, fn, 'Expecting to always get the same function');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current id value of the current directive.
|
||||
*
|
||||
* For example we have an element that has two directives on it:
|
||||
* <div dir-one dir-two></div>
|
||||
*
|
||||
* dirOne->hostBindings() (id == 1)
|
||||
* dirTwo->hostBindings() (id == 2)
|
||||
*
|
||||
* Note that this is only active when `hostBinding` functions are being processed.
|
||||
*
|
||||
* Note that directive id values are specific to an element (this means that
|
||||
* the same id value could be present on another element with a completely
|
||||
* different set of directives).
|
||||
*/
|
||||
export function getActiveDirectiveId() {
|
||||
return instructionState.lFrame.activeDirectiveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current directive id value.
|
||||
*
|
||||
* For example we have an element that has two directives on it:
|
||||
* <div dir-one dir-two></div>
|
||||
*
|
||||
* dirOne->hostBindings() (index = 1)
|
||||
* // increment
|
||||
* dirTwo->hostBindings() (index = 2)
|
||||
*
|
||||
* Depending on whether or not a previous directive had any inherited
|
||||
* directives present, that value will be incremented in addition
|
||||
* to the id jumping up by one.
|
||||
*
|
||||
* Note that this is only active when `hostBinding` functions are being processed.
|
||||
*
|
||||
* Note that directive id values are specific to an element (this means that
|
||||
* the same id value could be present on another element with a completely
|
||||
* different set of directives).
|
||||
*/
|
||||
export function incrementActiveDirectiveId() {
|
||||
// Each directive gets a uniqueId value that is the same for both
|
||||
// create and update calls when the hostBindings function is called. The
|
||||
// directive uniqueId is not set anywhere--it is just incremented between
|
||||
// each hostBindings call and is useful for helping instruction code
|
||||
// uniquely determine which directive is currently active when executed.
|
||||
instructionState.lFrame.activeDirectiveId += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores `contextViewData` to the given OpaqueViewState instance.
|
||||
*
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,362 +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 {unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex} from '../interfaces/styling';
|
||||
import {getBindingValue, getMapProp, getMapValue, getValue, getValuesCount, isStylingValueDefined} from '../util/styling_utils';
|
||||
|
||||
import {setStylingMapsSyncFn} from './bindings';
|
||||
|
||||
|
||||
/**
|
||||
* --------
|
||||
*
|
||||
* This file contains the algorithm logic for applying map-based bindings
|
||||
* such as `[style]` and `[class]`.
|
||||
*
|
||||
* --------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enables support for map-based styling bindings (e.g. `[style]` and `[class]` bindings).
|
||||
*/
|
||||
export function activateStylingMapFeature() {
|
||||
setStylingMapsSyncFn(syncStylingMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to apply styling values presently within any map-based bindings on an element.
|
||||
*
|
||||
* Angular supports map-based styling bindings which can be applied via the
|
||||
* `[style]` and `[class]` bindings which can be placed on any HTML element.
|
||||
* These bindings can work independently, together or alongside prop-based
|
||||
* styling bindings (e.g. `<div [style]="x" [style.width]="w">`).
|
||||
*
|
||||
* If a map-based styling binding is detected by the compiler, the following
|
||||
* AOT code is produced:
|
||||
*
|
||||
* ```typescript
|
||||
* styleMap(ctx.styles); // styles = {key:value}
|
||||
* classMap(ctx.classes); // classes = {key:value}|string
|
||||
* ```
|
||||
*
|
||||
* If and when either of the instructions above are evaluated, then the code
|
||||
* present in this file is included into the bundle. The mechanism used, to
|
||||
* activate support for map-based bindings at runtime is possible via the
|
||||
* `activeStylingMapFeature` function (which is also present in this file).
|
||||
*
|
||||
* # The Algorithm
|
||||
* Whenever a map-based binding updates (which is when the identity of the
|
||||
* map-value changes) then the map is iterated over and a `StylingMapArray` array
|
||||
* is produced. The `StylingMapArray` instance is stored in the binding location
|
||||
* where the `BINDING_INDEX` is situated when the `styleMap()` or `classMap()`
|
||||
* instruction were called. Once the binding changes, then the internal `bitMask`
|
||||
* value is marked as dirty.
|
||||
*
|
||||
* Styling values are applied once CD exits the element (which happens when
|
||||
* the `advance(n)` instruction is called or the template function exits). When
|
||||
* this occurs, all prop-based bindings are applied. If a map-based binding is
|
||||
* present then a special flushing function (called a sync function) is made
|
||||
* available and it will be called each time a styling property is flushed.
|
||||
*
|
||||
* The flushing algorithm is designed to apply styling for a property (which is
|
||||
* a CSS property or a className value) one by one. If map-based bindings
|
||||
* are present, then the flushing algorithm will keep calling the maps styling
|
||||
* sync function each time a property is visited. This way, the flushing
|
||||
* behavior of map-based bindings will always be at the same property level
|
||||
* as the current prop-based property being iterated over (because everything
|
||||
* is alphabetically sorted).
|
||||
*
|
||||
* Let's imagine we have the following HTML template code:
|
||||
*
|
||||
* ```html
|
||||
* <div [style]="{width:'100px', height:'200px', 'z-index':'10'}"
|
||||
* [style.width.px]="200">...</div>
|
||||
* ```
|
||||
*
|
||||
* When CD occurs, both the `[style]` and `[style.width]` bindings
|
||||
* are evaluated. Then when the styles are flushed on screen, the
|
||||
* following operations happen:
|
||||
*
|
||||
* 1. `[style.width]` is attempted to be written to the element.
|
||||
*
|
||||
* 2. Once that happens, the algorithm instructs the map-based
|
||||
* entries (`[style]` in this case) to "catch up" and apply
|
||||
* all values up to the `width` value. When this happens the
|
||||
* `height` value is applied to the element (since it is
|
||||
* alphabetically situated before the `width` property).
|
||||
*
|
||||
* 3. Since there are no more prop-based entries anymore, the
|
||||
* loop exits and then, just before the flushing ends, it
|
||||
* instructs all map-based bindings to "finish up" applying
|
||||
* their values.
|
||||
*
|
||||
* 4. The only remaining value within the map-based entries is
|
||||
* the `z-index` value (`width` got skipped because it was
|
||||
* successfully applied via the prop-based `[style.width]`
|
||||
* binding). Since all map-based entries are told to "finish up",
|
||||
* the `z-index` value is iterated over and it is then applied
|
||||
* to the element.
|
||||
*
|
||||
* The most important thing to take note of here is that prop-based
|
||||
* bindings are evaluated in order alongside map-based bindings.
|
||||
* This allows all styling across an element to be applied in O(n)
|
||||
* time (a similar algorithm is that of the array merge algorithm
|
||||
* in merge sort).
|
||||
*/
|
||||
export const syncStylingMap: SyncStylingMapsFn =
|
||||
(context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||
data: LStylingData, sourceIndex: number, applyStylingFn: ApplyStylingFn,
|
||||
sanitizer: StyleSanitizeFn | null, mode: StylingMapsSyncMode, targetProp?: string | null,
|
||||
defaultValue?: string | boolean | null): boolean => {
|
||||
let targetPropValueWasApplied = false;
|
||||
|
||||
// once the map-based styling code is activate it is never deactivated. For this reason a
|
||||
// check to see if the current styling context has any map based bindings is required.
|
||||
const totalMaps = getValuesCount(context);
|
||||
if (totalMaps) {
|
||||
let runTheSyncAlgorithm = true;
|
||||
const loopUntilEnd = !targetProp;
|
||||
|
||||
// If the code is told to finish up (run until the end), but the mode
|
||||
// hasn't been flagged to apply values (it only traverses values) then
|
||||
// there is no point in iterating over the array because nothing will
|
||||
// be applied to the element.
|
||||
if (loopUntilEnd && (mode & StylingMapsSyncMode.ApplyAllValues) === 0) {
|
||||
runTheSyncAlgorithm = false;
|
||||
targetPropValueWasApplied = true;
|
||||
}
|
||||
|
||||
if (runTheSyncAlgorithm) {
|
||||
targetPropValueWasApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp || null,
|
||||
sourceIndex, defaultValue || null);
|
||||
}
|
||||
|
||||
if (loopUntilEnd) {
|
||||
resetSyncCursors();
|
||||
}
|
||||
}
|
||||
|
||||
return targetPropValueWasApplied;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursive function designed to apply map-based styling to an element one map at a time.
|
||||
*
|
||||
* This function is designed to be called from the `syncStylingMap` function and will
|
||||
* apply map-based styling data one map at a time to the provided `element`.
|
||||
*
|
||||
* This function is recursive and it will call itself if a follow-up map value is to be
|
||||
* processed. To learn more about how the algorithm works, see `syncStylingMap`.
|
||||
*/
|
||||
function innerSyncStylingMap(
|
||||
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
|
||||
mode: StylingMapsSyncMode, targetProp: string | null, currentMapIndex: number,
|
||||
defaultValue: string | boolean | null): boolean {
|
||||
const totalMaps = getValuesCount(context) - 1; // maps have no default value
|
||||
const mapsLimit = totalMaps - 1;
|
||||
const recurseInnerMaps =
|
||||
currentMapIndex < mapsLimit && (mode & StylingMapsSyncMode.RecurseInnerMaps) !== 0;
|
||||
const checkValuesOnly = (mode & StylingMapsSyncMode.CheckValuesOnly) !== 0;
|
||||
|
||||
if (checkValuesOnly) {
|
||||
// inner modes do not check values ever (that can only happen
|
||||
// when sourceIndex === 0)
|
||||
mode &= ~StylingMapsSyncMode.CheckValuesOnly;
|
||||
}
|
||||
|
||||
let targetPropValueWasApplied = false;
|
||||
if (currentMapIndex <= mapsLimit) {
|
||||
let cursor = getCurrentSyncCursor(currentMapIndex);
|
||||
const bindingIndex = getBindingValue(
|
||||
context, TStylingContextIndex.ValuesStartPosition, currentMapIndex) as number;
|
||||
const stylingMapArr = getValue<StylingMapArray>(data, bindingIndex);
|
||||
|
||||
if (stylingMapArr) {
|
||||
while (cursor < stylingMapArr.length) {
|
||||
const prop = getMapProp(stylingMapArr, cursor);
|
||||
const iteratedTooFar = targetProp && prop > targetProp;
|
||||
const isTargetPropMatched = !iteratedTooFar && prop === targetProp;
|
||||
const value = getMapValue(stylingMapArr, cursor);
|
||||
const valueIsDefined = isStylingValueDefined(value);
|
||||
|
||||
// the recursive code is designed to keep applying until
|
||||
// it reaches or goes past the target prop. If and when
|
||||
// this happens then it will stop processing values, but
|
||||
// all other map values must also catch up to the same
|
||||
// point. This is why a recursive call is still issued
|
||||
// even if the code has iterated too far.
|
||||
const innerMode =
|
||||
iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched);
|
||||
|
||||
const innerProp = iteratedTooFar ? targetProp : prop;
|
||||
let valueApplied = recurseInnerMaps ?
|
||||
innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp,
|
||||
currentMapIndex + 1, defaultValue) :
|
||||
false;
|
||||
|
||||
if (iteratedTooFar) {
|
||||
if (!targetPropValueWasApplied) {
|
||||
targetPropValueWasApplied = valueApplied;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valueApplied && isValueAllowedToBeApplied(mode, isTargetPropMatched)) {
|
||||
valueApplied = true;
|
||||
|
||||
if (!checkValuesOnly) {
|
||||
const useDefault = isTargetPropMatched && !valueIsDefined;
|
||||
const bindingIndexToApply = isTargetPropMatched ? bindingIndex : null;
|
||||
|
||||
let finalValue: any;
|
||||
if (useDefault) {
|
||||
finalValue = defaultValue;
|
||||
} else {
|
||||
finalValue = sanitizer ?
|
||||
sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) :
|
||||
(value ? unwrapSafeValue(value) : null);
|
||||
}
|
||||
|
||||
applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply);
|
||||
}
|
||||
}
|
||||
|
||||
targetPropValueWasApplied = valueApplied && isTargetPropMatched;
|
||||
cursor += StylingMapArrayIndex.TupleSize;
|
||||
}
|
||||
setCurrentSyncCursor(currentMapIndex, cursor);
|
||||
|
||||
// this is a fallback case in the event that the styling map is `null` for this
|
||||
// binding but there are other map-based bindings that need to be evaluated
|
||||
// afterwards. If the `prop` value is falsy then the intention is to cycle
|
||||
// through all of the properties in the remaining maps as well. If the current
|
||||
// styling map is too short then there are no values to iterate over. In either
|
||||
// case the follow-up maps need to be iterated over.
|
||||
if (recurseInnerMaps &&
|
||||
(stylingMapArr.length === StylingMapArrayIndex.ValuesStartPosition || !targetProp)) {
|
||||
targetPropValueWasApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp,
|
||||
currentMapIndex + 1, defaultValue);
|
||||
}
|
||||
} else if (recurseInnerMaps) {
|
||||
targetPropValueWasApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp,
|
||||
currentMapIndex + 1, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
return targetPropValueWasApplied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine the mode for the inner recursive call.
|
||||
*
|
||||
* If an inner map is iterated on then this is done so for one
|
||||
* of two reasons:
|
||||
*
|
||||
* - value is being applied:
|
||||
* if the value is being applied from this current styling
|
||||
* map then there is no need to apply it in a deeper map
|
||||
* (i.e. the `SkipTargetProp` flag is set)
|
||||
*
|
||||
* - value is being not applied:
|
||||
* apply the value if it is found in a deeper map.
|
||||
* (i.e. the `SkipTargetProp` flag is unset)
|
||||
*
|
||||
* When these reasons are encountered the flags will for the
|
||||
* inner map mode will be configured.
|
||||
*/
|
||||
function resolveInnerMapMode(
|
||||
currentMode: number, valueIsDefined: boolean, isTargetPropMatched: boolean): number {
|
||||
let innerMode = currentMode;
|
||||
|
||||
// the statements below figures out whether or not an inner styling map
|
||||
// is allowed to apply its value or not. The main thing to keep note
|
||||
// of is that if the target prop isn't matched then its expected that
|
||||
// all values before it are allowed to be applied so long as "apply all values"
|
||||
// is set to true.
|
||||
const applyAllValues = currentMode & StylingMapsSyncMode.ApplyAllValues;
|
||||
const applyTargetProp = currentMode & StylingMapsSyncMode.ApplyTargetProp;
|
||||
const allowInnerApply =
|
||||
!valueIsDefined && (isTargetPropMatched ? applyTargetProp : applyAllValues);
|
||||
|
||||
if (allowInnerApply) {
|
||||
// case 1: set the mode to apply the targeted prop value if it
|
||||
// ends up being encountered in another map value
|
||||
innerMode |= StylingMapsSyncMode.ApplyTargetProp;
|
||||
innerMode &= ~StylingMapsSyncMode.SkipTargetProp;
|
||||
} else {
|
||||
// case 2: set the mode to skip the targeted prop value if it
|
||||
// ends up being encountered in another map value
|
||||
innerMode |= StylingMapsSyncMode.SkipTargetProp;
|
||||
innerMode &= ~StylingMapsSyncMode.ApplyTargetProp;
|
||||
}
|
||||
|
||||
return innerMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether or not a prop/value entry will be applied to an element.
|
||||
*
|
||||
* To determine whether or not a value is to be applied,
|
||||
* the following procedure is evaluated:
|
||||
*
|
||||
* First check to see the current `mode` status:
|
||||
* 1. If the mode value permits all props to be applied then allow.
|
||||
* - But do not allow if the current prop is set to be skipped.
|
||||
* 2. Otherwise if the current prop is permitted then allow.
|
||||
*/
|
||||
function isValueAllowedToBeApplied(mode: StylingMapsSyncMode, isTargetPropMatched: boolean) {
|
||||
let doApplyValue = (mode & StylingMapsSyncMode.ApplyAllValues) !== 0;
|
||||
if (!doApplyValue) {
|
||||
if (mode & StylingMapsSyncMode.ApplyTargetProp) {
|
||||
doApplyValue = isTargetPropMatched;
|
||||
}
|
||||
} else if ((mode & StylingMapsSyncMode.SkipTargetProp) && isTargetPropMatched) {
|
||||
doApplyValue = false;
|
||||
}
|
||||
return doApplyValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to keep track of concurrent cursor values for multiple map-based styling bindings present on
|
||||
* an element.
|
||||
*/
|
||||
const MAP_CURSORS: number[] = [];
|
||||
|
||||
/**
|
||||
* Used to reset the state of each cursor value being used to iterate over map-based styling
|
||||
* bindings.
|
||||
*/
|
||||
function resetSyncCursors() {
|
||||
for (let i = 0; i < MAP_CURSORS.length; i++) {
|
||||
MAP_CURSORS[i] = StylingMapArrayIndex.ValuesStartPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an active cursor value at a given mapIndex location.
|
||||
*/
|
||||
function getCurrentSyncCursor(mapIndex: number) {
|
||||
if (mapIndex >= MAP_CURSORS.length) {
|
||||
MAP_CURSORS.push(StylingMapArrayIndex.ValuesStartPosition);
|
||||
}
|
||||
return MAP_CURSORS[mapIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cursor value at a given mapIndex location.
|
||||
*/
|
||||
function setCurrentSyncCursor(mapIndex: number, indexValue: number) {
|
||||
MAP_CURSORS[mapIndex] = indexValue;
|
||||
}
|
|
@ -1,139 +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 {RElement} from '../interfaces/renderer';
|
||||
import {StylingMapArray} from '../interfaces/styling';
|
||||
import {TEMPLATE_DIRECTIVE_INDEX} from '../util/styling_utils';
|
||||
|
||||
/**
|
||||
* --------
|
||||
*
|
||||
* This file contains all state-based logic for styling in Angular.
|
||||
*
|
||||
* Styling in Angular is evaluated with a series of styling-specific
|
||||
* template instructions which are called one after another each time
|
||||
* change detection occurs in Angular.
|
||||
*
|
||||
* Styling makes use of various temporary, state-based variables between
|
||||
* instructions so that it can better cache and optimize its values.
|
||||
* These values are usually populated and cleared when an element is
|
||||
* exited in change detection (once all the instructions are run for
|
||||
* that element).
|
||||
*
|
||||
* To learn more about the algorithm see `TStylingContext`.
|
||||
*
|
||||
* --------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used as a state reference for update values between style/class binding instructions.
|
||||
*
|
||||
* In addition to storing the element and bit-mask related values, the state also
|
||||
* stores the `sourceIndex` value. The `sourceIndex` value is an incremented value
|
||||
* that identifies what "source" (i.e. the template, a specific directive by index or
|
||||
* component) is currently applying its styling bindings to the element.
|
||||
*/
|
||||
export interface StylingState {
|
||||
/** The element that is currently being processed */
|
||||
element: RElement|null;
|
||||
|
||||
/** The directive index that is currently active (`0` === template) */
|
||||
directiveIndex: number;
|
||||
|
||||
/** The source (column) index that is currently active (`0` === template) */
|
||||
sourceIndex: number;
|
||||
|
||||
/** The classes update bit mask value that is processed during each class binding */
|
||||
classesBitMask: number;
|
||||
|
||||
/** The classes update bit index value that is processed during each class binding */
|
||||
classesIndex: number;
|
||||
|
||||
/** The styles update bit mask value that is processed during each style binding */
|
||||
stylesBitMask: number;
|
||||
|
||||
/** The styles update bit index value that is processed during each style binding */
|
||||
stylesIndex: number;
|
||||
|
||||
/**
|
||||
* The last class map that was applied (i.e. `[class]="x"`).
|
||||
*
|
||||
* Note that this property is only populated when direct class values are applied
|
||||
* (i.e. context resolution is not used).
|
||||
*
|
||||
* See `allowDirectStyling` for more info.
|
||||
*/
|
||||
lastDirectClassMap: StylingMapArray|null;
|
||||
|
||||
/**
|
||||
* The last style map that was applied (i.e. `[style]="x"`)
|
||||
*
|
||||
* Note that this property is only populated when direct style values are applied
|
||||
* (i.e. context resolution is not used).
|
||||
*
|
||||
* See `allowDirectStyling` for more info.
|
||||
*/
|
||||
lastDirectStyleMap: StylingMapArray|null;
|
||||
}
|
||||
|
||||
// these values will get filled in the very first time this is accessed...
|
||||
const _state: StylingState = {
|
||||
element: null,
|
||||
directiveIndex: -1,
|
||||
sourceIndex: -1,
|
||||
classesBitMask: -1,
|
||||
classesIndex: -1,
|
||||
stylesBitMask: -1,
|
||||
stylesIndex: -1,
|
||||
lastDirectClassMap: null,
|
||||
lastDirectStyleMap: null,
|
||||
};
|
||||
|
||||
const BIT_MASK_START_VALUE = 0;
|
||||
|
||||
// the `0` start value is reserved for [map]-based entries
|
||||
const INDEX_START_VALUE = 1;
|
||||
|
||||
/**
|
||||
* Returns (or instantiates) the styling state for the given element.
|
||||
*
|
||||
* Styling state is accessed and processed each time a style or class binding
|
||||
* is evaluated.
|
||||
*
|
||||
* If and when the provided `element` doesn't match the current element in the
|
||||
* state then this means that styling was recently cleared or the element has
|
||||
* changed in change detection. In both cases the styling state is fully reset.
|
||||
*
|
||||
* If and when the provided `directiveIndex` doesn't match the current directive
|
||||
* index in the state then this means that a new source has introduced itself into
|
||||
* the styling code (or, in other words, another directive or component has started
|
||||
* to apply its styling host bindings to the element).
|
||||
*/
|
||||
export function getStylingState(element: RElement, directiveIndex: number): StylingState {
|
||||
if (_state.element !== element) {
|
||||
_state.element = element;
|
||||
_state.directiveIndex = directiveIndex;
|
||||
_state.sourceIndex = directiveIndex === TEMPLATE_DIRECTIVE_INDEX ? 0 : 1;
|
||||
_state.classesBitMask = BIT_MASK_START_VALUE;
|
||||
_state.classesIndex = INDEX_START_VALUE;
|
||||
_state.stylesBitMask = BIT_MASK_START_VALUE;
|
||||
_state.stylesIndex = INDEX_START_VALUE;
|
||||
_state.lastDirectClassMap = null;
|
||||
_state.lastDirectStyleMap = null;
|
||||
} else if (_state.directiveIndex !== directiveIndex) {
|
||||
_state.directiveIndex = directiveIndex;
|
||||
_state.sourceIndex++;
|
||||
}
|
||||
return _state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the styling state so that it can be used by another element's styling code.
|
||||
*/
|
||||
export function resetStylingState() {
|
||||
_state.element = null;
|
||||
}
|
|
@ -12,10 +12,6 @@ import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex, TSt
|
|||
import {TData} from '../interfaces/view';
|
||||
import {getCurrentStyleSanitizer} from '../state';
|
||||
import {attachDebugObject} from '../util/debug_utils';
|
||||
import {MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, allowDirectStyling as _allowDirectStyling, getBindingValue, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValue, getValuesCount, hasConfig, isSanitizationRequired, isStylingContext, normalizeIntoStylingMap, setValue} from '../util/styling_utils';
|
||||
|
||||
import {applyStylingViaContext} from './bindings';
|
||||
import {activateStylingMapFeature} from './map_based_bindings';
|
||||
|
||||
|
||||
|
||||
|
@ -148,356 +144,6 @@ export interface DebugNodeStylingEntry {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context
|
||||
*/
|
||||
export function attachStylingDebugObject(
|
||||
context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) {
|
||||
const debug = new TStylingContextDebug(context, tNode, isClassBased);
|
||||
attachDebugObject(context, debug);
|
||||
return debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* A human-readable debug summary of the styling data present within `TStylingContext`.
|
||||
*
|
||||
* This class is designed to be used within testing code or when an
|
||||
* application has `ngDevMode` activated.
|
||||
*/
|
||||
class TStylingContextDebug implements DebugStylingContext {
|
||||
constructor(
|
||||
public readonly context: TStylingContext, private _tNode: TStylingNode,
|
||||
private _isClassBased: boolean) {}
|
||||
|
||||
get config(): DebugStylingConfig { return buildConfig(this._tNode, this._isClassBased); }
|
||||
|
||||
/**
|
||||
* Returns a detailed summary of each styling entry in the context.
|
||||
*
|
||||
* See `DebugStylingContextEntry`.
|
||||
*/
|
||||
get entries(): {[prop: string]: DebugStylingContextEntry} {
|
||||
const context = this.context;
|
||||
const totalColumns = getValuesCount(context);
|
||||
const entries: {[prop: string]: DebugStylingContextEntry} = {};
|
||||
const start = getPropValuesStartPosition(context, this._tNode, this._isClassBased);
|
||||
let i = start;
|
||||
while (i < context.length) {
|
||||
const prop = getProp(context, i);
|
||||
const templateBitMask = getGuardMask(context, i, false);
|
||||
const hostBindingsBitMask = getGuardMask(context, i, true);
|
||||
const defaultValue = getDefaultValue(context, i);
|
||||
const sanitizationRequired = isSanitizationRequired(context, i);
|
||||
const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset;
|
||||
|
||||
const sources: (number | string | null)[] = [];
|
||||
|
||||
for (let j = 0; j < totalColumns; j++) {
|
||||
const bindingIndex = context[bindingsStartPosition + j] as number | string | null;
|
||||
if (bindingIndex !== 0) {
|
||||
sources.push(bindingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
entries[prop] = {
|
||||
prop,
|
||||
templateBitMask,
|
||||
hostBindingsBitMask,
|
||||
sanitizationRequired,
|
||||
valuesCount: sources.length, defaultValue, sources,
|
||||
};
|
||||
|
||||
i += TStylingContextIndex.BindingsStartOffset + totalColumns;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a detailed summary of each styling source grouped together with each binding index in
|
||||
* the context.
|
||||
*/
|
||||
printSources(): void {
|
||||
let output = '\n';
|
||||
|
||||
const context = this.context;
|
||||
const prefix = this._isClassBased ? 'class' : 'style';
|
||||
const bindingsBySource: {
|
||||
type: string,
|
||||
entries: {binding: string, bindingIndex: number, value: any, bitMask: number}[]
|
||||
}[] = [];
|
||||
|
||||
const totalColumns = getValuesCount(context);
|
||||
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + totalColumns;
|
||||
|
||||
for (let i = 0; i < totalColumns; i++) {
|
||||
const isDefaultColumn = i === totalColumns - 1;
|
||||
const hostBindingsMode = i !== TEMPLATE_DIRECTIVE_INDEX;
|
||||
const type = getTypeFromColumn(i, totalColumns);
|
||||
const entries: {binding: string, value: any, bindingIndex: number, bitMask: number}[] = [];
|
||||
|
||||
let j = TStylingContextIndex.ValuesStartPosition;
|
||||
while (j < context.length) {
|
||||
const value = getBindingValue(context, j, i);
|
||||
if (isDefaultColumn || value > 0) {
|
||||
const bitMask = getGuardMask(context, j, hostBindingsMode);
|
||||
const bindingIndex = isDefaultColumn ? -1 : value as number;
|
||||
const prop = getProp(context, j);
|
||||
const isMapBased = prop === MAP_BASED_ENTRY_PROP_NAME;
|
||||
const binding = `${prefix}${isMapBased ? '' : '.' + prop}`;
|
||||
entries.push({binding, value, bindingIndex, bitMask});
|
||||
}
|
||||
j += itemsPerRow;
|
||||
}
|
||||
|
||||
bindingsBySource.push(
|
||||
{type, entries: entries.sort((a, b) => a.bindingIndex - b.bindingIndex)});
|
||||
}
|
||||
|
||||
bindingsBySource.forEach(entry => {
|
||||
output += `[${entry.type.toUpperCase()}]\n`;
|
||||
output += repeat('-', entry.type.length + 2) + '\n';
|
||||
|
||||
let tab = ' ';
|
||||
entry.entries.forEach(entry => {
|
||||
const isDefault = typeof entry.value !== 'number';
|
||||
const value = entry.value;
|
||||
if (!isDefault || value !== null) {
|
||||
output += `${tab}[${entry.binding}] = \`${value}\``;
|
||||
output += '\n';
|
||||
}
|
||||
});
|
||||
output += '\n';
|
||||
});
|
||||
|
||||
/* tslint:disable */
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a detailed table of the entire styling context.
|
||||
*/
|
||||
printTable(): void {
|
||||
// IE (not Edge) is the only browser that doesn't support this feature. Because
|
||||
// these debugging tools are not apart of the core of Angular (they are just
|
||||
// extra tools) we can skip-out on older browsers.
|
||||
if (!console.table) {
|
||||
throw new Error('This feature is not supported in your browser');
|
||||
}
|
||||
|
||||
const context = this.context;
|
||||
const table: any[] = [];
|
||||
const totalColumns = getValuesCount(context);
|
||||
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + totalColumns;
|
||||
const totalProps = Math.floor(context.length / itemsPerRow);
|
||||
|
||||
let i = TStylingContextIndex.ValuesStartPosition;
|
||||
while (i < context.length) {
|
||||
const prop = getProp(context, i);
|
||||
const isMapBased = prop === MAP_BASED_ENTRY_PROP_NAME;
|
||||
const entry: {[key: string]: any} = {
|
||||
prop,
|
||||
'tpl mask': generateBitString(getGuardMask(context, i, false), isMapBased, totalProps),
|
||||
'host mask': generateBitString(getGuardMask(context, i, true), isMapBased, totalProps),
|
||||
};
|
||||
|
||||
for (let j = 0; j < totalColumns; j++) {
|
||||
const key = getTypeFromColumn(j, totalColumns);
|
||||
const value = getBindingValue(context, i, j);
|
||||
entry[key] = value;
|
||||
}
|
||||
|
||||
i += itemsPerRow;
|
||||
table.push(entry);
|
||||
}
|
||||
|
||||
/* tslint:disable */
|
||||
console.table(table);
|
||||
}
|
||||
}
|
||||
|
||||
function generateBitString(value: number, isMapBased: boolean, totalProps: number) {
|
||||
if (isMapBased || value > 1) {
|
||||
return `0b${leftPad(value.toString(2), totalProps, '0')}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function leftPad(value: string, max: number, pad: string) {
|
||||
return repeat(pad, max - value.length) + value;
|
||||
}
|
||||
|
||||
function getTypeFromColumn(index: number, totalColumns: number) {
|
||||
if (index === TEMPLATE_DIRECTIVE_INDEX) {
|
||||
return 'template';
|
||||
} else if (index === totalColumns - 1) {
|
||||
return 'defaults';
|
||||
} else {
|
||||
return `dir #${index}`;
|
||||
}
|
||||
}
|
||||
|
||||
function repeat(c: string, times: number) {
|
||||
let s = '';
|
||||
for (let i = 0; i < times; i++) {
|
||||
s += c;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* A human-readable debug summary of the styling data present for a `DebugNode` instance.
|
||||
*
|
||||
* This class is designed to be used within testing code or when an
|
||||
* application has `ngDevMode` activated.
|
||||
*/
|
||||
export class NodeStylingDebug implements DebugNodeStyling {
|
||||
private _sanitizer: StyleSanitizeFn|null = null;
|
||||
private _debugContext: DebugStylingContext;
|
||||
|
||||
constructor(
|
||||
context: TStylingContext|DebugStylingContext, private _tNode: TStylingNode,
|
||||
private _data: LStylingData, private _isClassBased: boolean) {
|
||||
this._debugContext = isStylingContext(context) ?
|
||||
new TStylingContextDebug(context as TStylingContext, _tNode, _isClassBased) :
|
||||
(context as DebugStylingContext);
|
||||
}
|
||||
|
||||
get context() { return this._debugContext; }
|
||||
|
||||
/**
|
||||
* Overrides the sanitizer used to process styles.
|
||||
*/
|
||||
overrideSanitizer(sanitizer: StyleSanitizeFn|null) { this._sanitizer = sanitizer; }
|
||||
|
||||
/**
|
||||
* Returns a detailed summary of each styling entry in the context and
|
||||
* what their runtime representation is.
|
||||
*
|
||||
* See `LStylingSummary`.
|
||||
*/
|
||||
get summary(): {[key: string]: DebugNodeStylingEntry} {
|
||||
const entries: {[key: string]: DebugNodeStylingEntry} = {};
|
||||
const config = this.config;
|
||||
|
||||
let data = this._data;
|
||||
|
||||
// the direct pass code doesn't convert [style] or [class] values
|
||||
// into StylingMapArray instances. For this reason, the values
|
||||
// need to be converted ahead of time since the styling debug
|
||||
// relies on context resolution to figure out what styling
|
||||
// values have been added/removed on the element.
|
||||
if (config.allowDirectStyling && config.hasMapBindings) {
|
||||
data = data.concat([]); // make a copy
|
||||
this._convertMapBindingsToStylingMapArrays(data);
|
||||
}
|
||||
|
||||
this._mapValues(data, (prop: string, value: any, bindingIndex: number | null) => {
|
||||
entries[prop] = {prop, value, bindingIndex};
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
get config() { return buildConfig(this._tNode, this._isClassBased); }
|
||||
|
||||
/**
|
||||
* Returns a key/value map of all the styles/classes that were last applied to the element.
|
||||
*/
|
||||
get values(): {[key: string]: any} {
|
||||
const entries: {[key: string]: any} = {};
|
||||
const config = this.config;
|
||||
let data = this._data;
|
||||
|
||||
// the direct pass code doesn't convert [style] or [class] values
|
||||
// into StylingMapArray instances. For this reason, the values
|
||||
// need to be converted ahead of time since the styling debug
|
||||
// relies on context resolution to figure out what styling
|
||||
// values have been added/removed on the element.
|
||||
if (config.allowDirectStyling && config.hasMapBindings) {
|
||||
data = data.concat([]); // make a copy
|
||||
this._convertMapBindingsToStylingMapArrays(data);
|
||||
}
|
||||
|
||||
this._mapValues(data, (prop: string, value: any) => { entries[prop] = value; });
|
||||
return entries;
|
||||
}
|
||||
|
||||
private _convertMapBindingsToStylingMapArrays(data: LStylingData) {
|
||||
const context = this.context.context;
|
||||
const limit = getPropValuesStartPosition(context, this._tNode, this._isClassBased);
|
||||
for (let i =
|
||||
TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset;
|
||||
i < limit; i++) {
|
||||
const bindingIndex = context[i] as number;
|
||||
const bindingValue = bindingIndex !== 0 ? getValue(data, bindingIndex) : null;
|
||||
if (bindingValue && !Array.isArray(bindingValue)) {
|
||||
const stylingMapArray = normalizeIntoStylingMap(null, bindingValue, !this._isClassBased);
|
||||
setValue(data, bindingIndex, stylingMapArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _mapValues(
|
||||
data: LStylingData,
|
||||
fn: (prop: string, value: string|null, bindingIndex: number|null) => any) {
|
||||
// there is no need to store/track an element instance. The
|
||||
// element is only used when the styling algorithm attempts to
|
||||
// style the value (and we mock out the stylingApplyFn anyway).
|
||||
const mockElement = {} as any;
|
||||
const mapBindingsFlag =
|
||||
this._isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
const hasMaps = hasConfig(this._tNode, mapBindingsFlag);
|
||||
if (hasMaps) {
|
||||
activateStylingMapFeature();
|
||||
}
|
||||
|
||||
const mapFn: ApplyStylingFn =
|
||||
(renderer: any, element: RElement, prop: string, value: string | null,
|
||||
bindingIndex?: number | null) => fn(prop, value, bindingIndex || null);
|
||||
|
||||
const sanitizer = this._isClassBased ? null : (this._sanitizer || getCurrentStyleSanitizer());
|
||||
|
||||
// run the template bindings
|
||||
applyStylingViaContext(
|
||||
this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, false,
|
||||
this._isClassBased);
|
||||
|
||||
// and also the host bindings
|
||||
applyStylingViaContext(
|
||||
this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, true,
|
||||
this._isClassBased);
|
||||
}
|
||||
}
|
||||
|
||||
function buildConfig(tNode: TStylingNode, isClassBased: boolean): DebugStylingConfig {
|
||||
const hasMapBindings = hasConfig(
|
||||
tNode, isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings);
|
||||
const hasPropBindings = hasConfig(
|
||||
tNode, isClassBased ? TNodeFlags.hasClassPropBindings : TNodeFlags.hasStylePropBindings);
|
||||
const hasCollisions = hasConfig(
|
||||
tNode,
|
||||
isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings);
|
||||
const hasTemplateBindings = hasConfig(
|
||||
tNode,
|
||||
isClassBased ? TNodeFlags.hasTemplateClassBindings : TNodeFlags.hasTemplateStyleBindings);
|
||||
const hasHostBindings = hasConfig(
|
||||
tNode, isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings);
|
||||
|
||||
// `firstTemplatePass` here is false because the context has already been constructed
|
||||
// directly within the behavior of the debugging tools (outside of style/class debugging,
|
||||
// the context is constructed during the first template pass).
|
||||
const allowDirectStyling = _allowDirectStyling(tNode, isClassBased, false);
|
||||
return {
|
||||
hasMapBindings, //
|
||||
hasPropBindings, //
|
||||
hasCollisions, //
|
||||
hasTemplateBindings, //
|
||||
hasHostBindings, //
|
||||
allowDirectStyling, //
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the head of the styling binding linked list.
|
||||
*/
|
||||
|
|
|
@ -1,463 +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 {unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {CharCode} from '../../util/char_code';
|
||||
import {PropertyAliases, TNodeFlags} from '../interfaces/node';
|
||||
import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags, TStylingNode} from '../interfaces/styling';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
|
||||
export const MAP_BASED_ENTRY_PROP_NAME = '[MAP]';
|
||||
export const TEMPLATE_DIRECTIVE_INDEX = 0;
|
||||
|
||||
/**
|
||||
* Default fallback value for a styling binding.
|
||||
*
|
||||
* A value of `null` is used here which signals to the styling algorithm that
|
||||
* the styling value is not present. This way if there are no other values
|
||||
* detected then it will be removed once the style/class property is dirty and
|
||||
* diffed within the styling algorithm present in `flushStyling`.
|
||||
*/
|
||||
export const DEFAULT_BINDING_VALUE = null;
|
||||
|
||||
export const DEFAULT_BINDING_INDEX = 0;
|
||||
|
||||
const DEFAULT_TOTAL_SOURCES = 1;
|
||||
|
||||
// The first bit value reflects a map-based binding value's bit.
|
||||
// The reason why it's always activated for every entry in the map
|
||||
// is so that if any map-binding values update then all other prop
|
||||
// based bindings will pass the guard check automatically without
|
||||
// any extra code or flags.
|
||||
export const DEFAULT_GUARD_MASK_VALUE = 0b1;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the `TStylingContext`.
|
||||
*
|
||||
* The `TStylingContext` is used as a manifest of all style or all class bindings on
|
||||
* an element. Because it is a T-level data-structure, it is only created once per
|
||||
* tNode for styles and for classes. This function allocates a new instance of a
|
||||
* `TStylingContext` with the initial values (see `interfaces.ts` for more info).
|
||||
*/
|
||||
export function allocTStylingContext(
|
||||
initialStyling: StylingMapArray | null, hasDirectives: boolean): TStylingContext {
|
||||
initialStyling = initialStyling || allocStylingMapArray(null);
|
||||
return [
|
||||
DEFAULT_TOTAL_SOURCES, // 1) total amount of styling sources (template, directives, etc...)
|
||||
initialStyling, // 2) initial styling values
|
||||
];
|
||||
}
|
||||
|
||||
export function allocStylingMapArray(value: {} | string | null): StylingMapArray {
|
||||
return [value];
|
||||
}
|
||||
|
||||
export function hasConfig(tNode: TStylingNode, flag: TNodeFlags) {
|
||||
return (tNode.flags & flag) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not to apply styles/classes directly or via context resolution.
|
||||
*
|
||||
* There are three cases that are matched here:
|
||||
* 1. there are no directives present AND `ngDevMode` is falsy
|
||||
* 2. the `firstUpdatePass` has not already run (which means that
|
||||
* there are more bindings to register and, therefore, direct
|
||||
* style/class application is not yet possible)
|
||||
* 3. There are no collisions (i.e. properties with more than one binding) across multiple
|
||||
* sources (i.e. template + directive, directive + directive, directive + component)
|
||||
*/
|
||||
export function allowDirectStyling(
|
||||
tNode: TStylingNode, isClassBased: boolean, firstUpdatePass: boolean): boolean {
|
||||
let allow = false;
|
||||
|
||||
// if no directives are present then we do not need populate a context at all. This
|
||||
// is because duplicate prop bindings cannot be registered through the template. If
|
||||
// and when this happens we can safely apply the value directly without context
|
||||
// resolution...
|
||||
const hasDirectives = hasConfig(tNode, TNodeFlags.hasHostBindings);
|
||||
if (!hasDirectives) {
|
||||
// `ngDevMode` is required to be checked here because tests/debugging rely on the context being
|
||||
// populated. If things are in production mode then there is no need to build a context
|
||||
// therefore the direct apply can be allowed (even on the first update).
|
||||
allow = ngDevMode ? !firstUpdatePass : true;
|
||||
} else if (!firstUpdatePass) {
|
||||
const duplicateStylingFlag =
|
||||
isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings;
|
||||
const hasDuplicates = hasConfig(tNode, duplicateStylingFlag);
|
||||
const hasOnlyMapOrPropsFlag = isClassBased ? TNodeFlags.hasClassPropAndMapBindings :
|
||||
TNodeFlags.hasStylePropAndMapBindings;
|
||||
const hasOnlyMapsOrOnlyProps = (tNode.flags & hasOnlyMapOrPropsFlag) !== hasOnlyMapOrPropsFlag;
|
||||
allow = !hasDuplicates && hasOnlyMapsOrOnlyProps;
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
export function patchConfig(tNode: TStylingNode, flag: TNodeFlags): void {
|
||||
tNode.flags |= flag;
|
||||
}
|
||||
|
||||
export function getProp(context: TStylingContext, index: number): string {
|
||||
return context[index + TStylingContextIndex.PropOffset] as string;
|
||||
}
|
||||
|
||||
function getPropConfig(context: TStylingContext, index: number): number {
|
||||
return (context[index + TStylingContextIndex.ConfigOffset] as number) &
|
||||
TStylingContextPropConfigFlags.Mask;
|
||||
}
|
||||
|
||||
export function isSanitizationRequired(context: TStylingContext, index: number): boolean {
|
||||
return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) !==
|
||||
0;
|
||||
}
|
||||
|
||||
export function getGuardMask(
|
||||
context: TStylingContext, index: number, isHostBinding: boolean): number {
|
||||
const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset :
|
||||
TStylingContextIndex.TemplateBitGuardOffset);
|
||||
return context[position] as number;
|
||||
}
|
||||
|
||||
export function setGuardMask(
|
||||
context: TStylingContext, index: number, maskValue: number, isHostBinding: boolean) {
|
||||
const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset :
|
||||
TStylingContextIndex.TemplateBitGuardOffset);
|
||||
context[position] = maskValue;
|
||||
}
|
||||
|
||||
export function getValuesCount(context: TStylingContext): number {
|
||||
return getTotalSources(context) + 1;
|
||||
}
|
||||
|
||||
export function getTotalSources(context: TStylingContext): number {
|
||||
return context[TStylingContextIndex.TotalSourcesPosition];
|
||||
}
|
||||
|
||||
export function getBindingValue(context: TStylingContext, index: number, offset: number) {
|
||||
return context[index + TStylingContextIndex.BindingsStartOffset + offset] as number | string;
|
||||
}
|
||||
|
||||
export function getDefaultValue(context: TStylingContext, index: number): string|boolean|null {
|
||||
return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] as
|
||||
string |
|
||||
boolean | null;
|
||||
}
|
||||
|
||||
export function setDefaultValue(
|
||||
context: TStylingContext, index: number, value: string | boolean | null) {
|
||||
return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] =
|
||||
value;
|
||||
}
|
||||
|
||||
export function setValue(data: LStylingData, bindingIndex: number, value: any) {
|
||||
data[bindingIndex] = value;
|
||||
}
|
||||
|
||||
export function getValue<T = any>(data: LStylingData, bindingIndex: number): T|null {
|
||||
return bindingIndex !== 0 ? data[bindingIndex] as T : null;
|
||||
}
|
||||
|
||||
export function getPropValuesStartPosition(
|
||||
context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) {
|
||||
let startPosition = TStylingContextIndex.ValuesStartPosition;
|
||||
const flag = isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
if (hasConfig(tNode, flag)) {
|
||||
startPosition += TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
|
||||
}
|
||||
return startPosition;
|
||||
}
|
||||
|
||||
export function hasValueChangedUnwrapSafeValue(
|
||||
a: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined | {},
|
||||
b: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined |
|
||||
{}): boolean {
|
||||
return hasValueChanged(unwrapSafeValue(a), unwrapSafeValue(b));
|
||||
}
|
||||
|
||||
|
||||
export function hasValueChanged(
|
||||
a: NO_CHANGE | StylingMapArray | number | string | null | boolean | undefined | {},
|
||||
b: NO_CHANGE | StylingMapArray | number | string | null | boolean | undefined | {}): boolean {
|
||||
if (b === NO_CHANGE) return false;
|
||||
|
||||
const compareValueA = Array.isArray(a) ? a[StylingMapArrayIndex.RawValuePosition] : a;
|
||||
const compareValueB = Array.isArray(b) ? b[StylingMapArrayIndex.RawValuePosition] : b;
|
||||
return !Object.is(compareValueA, compareValueB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided styling value is truthy or falsy.
|
||||
*/
|
||||
export function isStylingValueDefined<T extends string|number|{}|null|undefined>(value: T):
|
||||
value is NonNullable<T> {
|
||||
// the reason why null is compared against is because
|
||||
// a CSS class value that is set to `false` must be
|
||||
// respected (otherwise it would be treated as falsy).
|
||||
// Empty string values are because developers usually
|
||||
// set a value to an empty string to remove it.
|
||||
return value != null && value !== '';
|
||||
}
|
||||
|
||||
export function concatString(a: string, b: string, separator = ' '): string {
|
||||
return a + ((b.length && a.length) ? separator : '') + b;
|
||||
}
|
||||
|
||||
export function hyphenate(value: string): string {
|
||||
return value.replace(/[a-z][A-Z]/g, v => v.charAt(0) + '-' + v.charAt(1)).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of `StylingMapArray`.
|
||||
*
|
||||
* This function is designed to find an instance of `StylingMapArray` in case it is stored
|
||||
* inside of an instance of `TStylingContext`. When a styling context is created it
|
||||
* will copy over an initial styling values from the tNode (which are stored as a
|
||||
* `StylingMapArray` on the `tNode.classes` or `tNode.styles` values).
|
||||
*/
|
||||
export function getStylingMapArray(value: TStylingContext | StylingMapArray | string | null):
|
||||
StylingMapArray|null {
|
||||
// TODO(misko): remove after TNode.classes/styles becomes `string` only
|
||||
if (typeof value === 'string') return null;
|
||||
return isStylingContext(value) ?
|
||||
(value as TStylingContext)[TStylingContextIndex.InitialStylingValuePosition] :
|
||||
value as StylingMapArray;
|
||||
}
|
||||
|
||||
export function isStylingContext(value: any): boolean {
|
||||
// the StylingMapArray is in the format of [initial, prop, string, prop, string]
|
||||
// and this is the defining value to distinguish between arrays
|
||||
return Array.isArray(value) && value.length >= TStylingContextIndex.ValuesStartPosition &&
|
||||
typeof value[1] !== 'string';
|
||||
}
|
||||
|
||||
export function isStylingMapArray(value: any): boolean {
|
||||
// the StylingMapArray is in the format of [initial, prop, string, prop, string]
|
||||
// and this is the defining value to distinguish between arrays
|
||||
return Array.isArray(value) &&
|
||||
(typeof(value as StylingMapArray)[StylingMapArrayIndex.ValuesStartPosition] === 'string');
|
||||
}
|
||||
|
||||
export function getInitialStylingValue(context: TStylingContext | StylingMapArray | string | null):
|
||||
string {
|
||||
// TODO(misko): remove after TNode.classes/styles becomes `string` only
|
||||
if (typeof context === 'string') return context;
|
||||
const map = getStylingMapArray(context);
|
||||
return map && (map[StylingMapArrayIndex.RawValuePosition] as string | null) || '';
|
||||
}
|
||||
|
||||
export function hasClassInput(tNode: TStylingNode) {
|
||||
return (tNode.flags & TNodeFlags.hasClassInput) !== 0;
|
||||
}
|
||||
|
||||
export function hasStyleInput(tNode: TStylingNode) {
|
||||
return (tNode.flags & TNodeFlags.hasStyleInput) !== 0;
|
||||
}
|
||||
|
||||
export function getMapProp(map: StylingMapArray, index: number): string {
|
||||
return map[index + StylingMapArrayIndex.PropOffset] as string;
|
||||
}
|
||||
|
||||
const MAP_DIRTY_VALUE =
|
||||
typeof ngDevMode !== 'undefined' && ngDevMode ? {} : {MAP_DIRTY_VALUE: true};
|
||||
|
||||
export function setMapAsDirty(map: StylingMapArray): void {
|
||||
map[StylingMapArrayIndex.RawValuePosition] = MAP_DIRTY_VALUE;
|
||||
}
|
||||
|
||||
export function setMapValue(
|
||||
map: StylingMapArray, index: number, value: string | boolean | null): void {
|
||||
map[index + StylingMapArrayIndex.ValueOffset] = value;
|
||||
}
|
||||
|
||||
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} | string | null | undefined, hyphenateProps: boolean): string {
|
||||
if (typeof styles == 'string') return styles;
|
||||
let str = '';
|
||||
if (styles) {
|
||||
const props = Object.keys(styles);
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i];
|
||||
const propLabel = hyphenateProps ? hyphenate(prop) : prop;
|
||||
const value = styles[prop];
|
||||
if (value !== null) {
|
||||
str = concatString(str, `${propLabel}:${value}`, ';');
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function isHostStylingActive(directiveOrSourceId: number): boolean {
|
||||
return directiveOrSourceId !== TEMPLATE_DIRECTIVE_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided styling map array into a string.
|
||||
*
|
||||
* Classes => `one two three`
|
||||
* Styles => `prop:value; prop2:value2`
|
||||
*/
|
||||
export function stylingMapToString(map: StylingMapArray, isClassBased: boolean): string {
|
||||
let str = '';
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i) as string;
|
||||
const attrValue = concatString(prop, isClassBased ? '' : value, ':');
|
||||
str = concatString(str, attrValue, isClassBased ? ' ' : '; ');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided styling map array into a key value map.
|
||||
*/
|
||||
export function stylingMapToStringMap(map: StylingMapArray | null): {[key: string]: any} {
|
||||
let stringMap: {[key: string]: any} = {};
|
||||
if (map) {
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i) as string;
|
||||
stringMap[prop] = value;
|
||||
}
|
||||
}
|
||||
return stringMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the provided item into the provided styling array at the right spot.
|
||||
*
|
||||
* The `StylingMapArray` type is a sorted key/value array of entries. This means
|
||||
* that when a new entry is inserted it must be placed at the right spot in the
|
||||
* array. This function figures out exactly where to place it.
|
||||
*/
|
||||
export function addItemToStylingMap(
|
||||
stylingMapArr: StylingMapArray, prop: string, value: string | boolean | null,
|
||||
allowOverwrite?: boolean) {
|
||||
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
|
||||
j += StylingMapArrayIndex.TupleSize) {
|
||||
const propAtIndex = getMapProp(stylingMapArr, j);
|
||||
if (prop <= propAtIndex) {
|
||||
let applied = false;
|
||||
if (propAtIndex === prop) {
|
||||
const valueAtIndex = stylingMapArr[j];
|
||||
if (allowOverwrite || !isStylingValueDefined(valueAtIndex)) {
|
||||
applied = true;
|
||||
setMapValue(stylingMapArr, j, value);
|
||||
}
|
||||
} else {
|
||||
applied = true;
|
||||
stylingMapArr.splice(j, 0, prop, value);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
}
|
||||
|
||||
stylingMapArr.push(prop, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert a {key:value} map into a `StylingMapArray` array.
|
||||
*
|
||||
* This function will either generate a new `StylingMapArray` instance
|
||||
* or it will patch the provided `newValues` map value into an
|
||||
* existing `StylingMapArray` value (this only happens if `bindingValue`
|
||||
* is an instance of `StylingMapArray`).
|
||||
*
|
||||
* If a new key/value map is provided with an old `StylingMapArray`
|
||||
* value then all properties will be overwritten with their new
|
||||
* values or with `null`. This means that the array will never
|
||||
* shrink in size (but it will also not be created and thrown
|
||||
* away whenever the `{key:value}` map entries change).
|
||||
*/
|
||||
export function normalizeIntoStylingMap(
|
||||
bindingValue: null | StylingMapArray,
|
||||
newValues: {[key: string]: any} | string | null | undefined,
|
||||
normalizeProps?: boolean): StylingMapArray {
|
||||
const stylingMapArr: StylingMapArray =
|
||||
Array.isArray(bindingValue) ? bindingValue : allocStylingMapArray(null);
|
||||
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = newValues;
|
||||
|
||||
// because the new values may not include all the properties
|
||||
// that the old ones had, all values are set to `null` before
|
||||
// the new values are applied. This way, when flushed, the
|
||||
// styling algorithm knows exactly what style/class values
|
||||
// to remove from the element (since they are `null`).
|
||||
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
|
||||
j += StylingMapArrayIndex.TupleSize) {
|
||||
setMapValue(stylingMapArr, j, null);
|
||||
}
|
||||
|
||||
let props: string[]|null = null;
|
||||
let map: {[key: string]: any}|undefined|null;
|
||||
let allValuesTrue = false;
|
||||
if (typeof newValues === 'string') { // [class] bindings allow string values
|
||||
props = splitOnWhitespace(newValues);
|
||||
allValuesTrue = props !== null;
|
||||
} else {
|
||||
props = newValues ? Object.keys(newValues) : null;
|
||||
map = newValues;
|
||||
}
|
||||
|
||||
if (props) {
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i];
|
||||
const newProp = normalizeProps ? hyphenate(prop) : prop;
|
||||
const value = allValuesTrue ? true : map ![prop];
|
||||
addItemToStylingMap(stylingMapArr, newProp, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
return stylingMapArr;
|
||||
}
|
||||
|
||||
export function splitOnWhitespace(text: string): string[]|null {
|
||||
let array: string[]|null = null;
|
||||
let length = text.length;
|
||||
let start = 0;
|
||||
let foundChar = false;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const char = text.charCodeAt(i);
|
||||
if (char <= CharCode.SPACE) {
|
||||
if (foundChar) {
|
||||
if (array === null) array = [];
|
||||
array.push(text.substring(start, i));
|
||||
foundChar = false;
|
||||
}
|
||||
start = i + 1;
|
||||
} else {
|
||||
foundChar = true;
|
||||
}
|
||||
}
|
||||
if (foundChar) {
|
||||
if (array === null) array = [];
|
||||
array.push(text.substring(start, length));
|
||||
foundChar = false;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// TODO (matsko|AndrewKushnir): refactor this once we figure out how to generate separate
|
||||
// `input('class') + classMap()` instructions.
|
||||
export function selectClassBasedInputName(inputs: PropertyAliases): string {
|
||||
return inputs.hasOwnProperty('class') ? 'class' : 'className';
|
||||
}
|
|
@ -42,7 +42,6 @@ declare global {
|
|||
rendererSetClassName: number;
|
||||
rendererAddClass: number;
|
||||
rendererRemoveClass: number;
|
||||
rendererCssText: number;
|
||||
rendererSetStyle: number;
|
||||
rendererRemoveStyle: number;
|
||||
rendererDestroy: number;
|
||||
|
@ -52,17 +51,7 @@ declare global {
|
|||
rendererAppendChild: number;
|
||||
rendererInsertBefore: number;
|
||||
rendererCreateComment: number;
|
||||
styleMap: number;
|
||||
styleMapCacheMiss: number;
|
||||
classMap: number;
|
||||
classMapCacheMiss: number;
|
||||
styleProp: number;
|
||||
stylePropCacheMiss: number;
|
||||
classProp: number;
|
||||
classPropCacheMiss: number;
|
||||
flushStyling: number;
|
||||
classesApplied: number;
|
||||
stylesApplied: number;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +72,6 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
|
|||
rendererSetClassName: 0,
|
||||
rendererAddClass: 0,
|
||||
rendererRemoveClass: 0,
|
||||
rendererCssText: 0,
|
||||
rendererSetStyle: 0,
|
||||
rendererRemoveStyle: 0,
|
||||
rendererDestroy: 0,
|
||||
|
@ -93,17 +81,7 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
|
|||
rendererAppendChild: 0,
|
||||
rendererInsertBefore: 0,
|
||||
rendererCreateComment: 0,
|
||||
styleMap: 0,
|
||||
styleMapCacheMiss: 0,
|
||||
classMap: 0,
|
||||
classMapCacheMiss: 0,
|
||||
styleProp: 0,
|
||||
stylePropCacheMiss: 0,
|
||||
classProp: 0,
|
||||
classPropCacheMiss: 0,
|
||||
flushStyling: 0,
|
||||
classesApplied: 0,
|
||||
stylesApplied: 0,
|
||||
};
|
||||
|
||||
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
|
||||
|
|
|
@ -1,85 +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 {normalizeIntoStylingMap as createMap} from '../../../src/render3/util/styling_utils';
|
||||
|
||||
describe('map-based bindings', () => {
|
||||
describe('StylingMapArray construction', () => {
|
||||
it('should create a new StylingMapArray instance from a given value', () => {
|
||||
createAndAssertValues(null, []);
|
||||
createAndAssertValues(undefined, []);
|
||||
createAndAssertValues({}, []);
|
||||
createAndAssertValues({foo: 'bar'}, ['foo', 'bar']);
|
||||
createAndAssertValues({bar: null}, ['bar', null]);
|
||||
createAndAssertValues('', []);
|
||||
createAndAssertValues('abc xyz', ['abc', true, 'xyz', true]);
|
||||
createAndAssertValues([], []);
|
||||
});
|
||||
|
||||
it('should list each entry in the context in alphabetical order', () => {
|
||||
const value1 = {width: '200px', color: 'red', zIndex: -1};
|
||||
const map1 = createMap(null, value1);
|
||||
expect(map1).toEqual([value1, 'color', 'red', 'width', '200px', 'zIndex', -1]);
|
||||
|
||||
const value2 = 'yes no maybe';
|
||||
const map2 = createMap(null, value2);
|
||||
expect(map2).toEqual([value2, 'maybe', true, 'no', true, 'yes', true]);
|
||||
});
|
||||
|
||||
it('should patch an existing StylingMapArray entry with new values and retain the alphabetical order',
|
||||
() => {
|
||||
const value1 = {color: 'red'};
|
||||
const map1 = createMap(null, value1);
|
||||
expect(map1).toEqual([value1, 'color', 'red']);
|
||||
|
||||
const value2 = {backgroundColor: 'red', color: 'blue', opacity: '0.5'};
|
||||
const map2 = createMap(map1, value2);
|
||||
expect(map1).toBe(map2);
|
||||
expect(map1).toEqual(
|
||||
[value2, 'backgroundColor', 'red', 'color', 'blue', 'opacity', '0.5']);
|
||||
|
||||
const value3 = 'myClass';
|
||||
const map3 = createMap(null, value3);
|
||||
expect(map3).toEqual([value3, 'myClass', true]);
|
||||
|
||||
const value4 = 'yourClass everyonesClass myClass';
|
||||
const map4 = createMap(map3, value4);
|
||||
expect(map3).toBe(map4);
|
||||
expect(map4).toEqual([value4, 'everyonesClass', true, 'myClass', true, 'yourClass', true]);
|
||||
});
|
||||
|
||||
it('should nullify old values that are not a part of the new set of values', () => {
|
||||
const value1 = {color: 'red', fontSize: '20px'};
|
||||
const map1 = createMap(null, value1);
|
||||
expect(map1).toEqual([value1, 'color', 'red', 'fontSize', '20px']);
|
||||
|
||||
const value2 = {color: 'blue', borderColor: 'purple', opacity: '0.5'};
|
||||
const map2 = createMap(map1, value2);
|
||||
expect(map2).toEqual(
|
||||
[value2, 'borderColor', 'purple', 'color', 'blue', 'fontSize', null, 'opacity', '0.5']);
|
||||
|
||||
const value3 = 'orange';
|
||||
const map3 = createMap(null, value3);
|
||||
expect(map3).toEqual([value3, 'orange', true]);
|
||||
|
||||
const value4 = 'apple banana';
|
||||
const map4 = createMap(map3, value4);
|
||||
expect(map4).toEqual([value4, 'apple', true, 'banana', true, 'orange', null]);
|
||||
});
|
||||
|
||||
it('should hyphenate property names ', () => {
|
||||
const value1 = {fontSize: '50px', paddingTopLeft: '20px'};
|
||||
const map1 = createMap(null, value1, true);
|
||||
expect(map1).toEqual([value1, 'font-size', '50px', 'padding-top-left', '20px']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createAndAssertValues(newValue: any, entries: any[]) {
|
||||
const result = createMap(null, newValue);
|
||||
expect(result).toEqual([newValue, ...entries]);
|
||||
}
|
|
@ -1,136 +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 {TStylingContext, TStylingNode} from '@angular/core/src/render3/interfaces/styling';
|
||||
import {registerBinding as _registerBinding} from '@angular/core/src/render3/styling/bindings';
|
||||
import {attachStylingDebugObject} from '@angular/core/src/render3/styling/styling_debug';
|
||||
|
||||
import {DEFAULT_GUARD_MASK_VALUE, allocTStylingContext} from '../../../src/render3/util/styling_utils';
|
||||
|
||||
function registerBinding(
|
||||
context: TStylingContext, countId: number, sourceIndex: number, prop: string | null,
|
||||
value: any) {
|
||||
let tNode: TStylingNode = (context as any).tNode;
|
||||
if (!tNode) {
|
||||
tNode = (context as any).tNode = {flags: 0};
|
||||
}
|
||||
_registerBinding(context, tNode, countId, sourceIndex, prop, value, false, false);
|
||||
}
|
||||
|
||||
describe('styling context', () => {
|
||||
it('should register a series of entries into the context', () => {
|
||||
const debug = makeContextWithDebug(false);
|
||||
const context = debug.context;
|
||||
expect(debug.entries).toEqual({});
|
||||
|
||||
registerBinding(context, 1, 0, 'width', '100px');
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: ['100px'],
|
||||
});
|
||||
|
||||
registerBinding(context, 2, 0, 'width', 20);
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
sanitizationRequired: false,
|
||||
valuesCount: 2,
|
||||
templateBitMask: buildGuardMask(2),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: [20, '100px'],
|
||||
});
|
||||
|
||||
registerBinding(context, 3, 0, 'height', 10);
|
||||
registerBinding(context, 4, 1, 'height', 15);
|
||||
expect(debug.entries['height']).toEqual({
|
||||
prop: 'height',
|
||||
valuesCount: 3,
|
||||
sanitizationRequired: false,
|
||||
templateBitMask: buildGuardMask(3),
|
||||
hostBindingsBitMask: buildGuardMask(4),
|
||||
defaultValue: null,
|
||||
sources: [10, 15, null],
|
||||
});
|
||||
});
|
||||
|
||||
it('should only register the same binding index once per property', () => {
|
||||
const debug = makeContextWithDebug(false);
|
||||
const context = debug.context;
|
||||
expect(debug.entries).toEqual({});
|
||||
|
||||
registerBinding(context, 1, 0, 'width', 123);
|
||||
registerBinding(context, 1, 0, 'width', 123);
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 2,
|
||||
sanitizationRequired: false,
|
||||
templateBitMask: buildGuardMask(1),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: null,
|
||||
sources: [123, null],
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite a default value for an entry only if it is non-null', () => {
|
||||
const debug = makeContextWithDebug(false);
|
||||
const context = debug.context;
|
||||
|
||||
registerBinding(context, 1, 0, 'width', null);
|
||||
const x = debug.entries['width'];
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: null,
|
||||
sources: [null]
|
||||
});
|
||||
|
||||
registerBinding(context, 1, 0, 'width', '100px');
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: ['100px']
|
||||
});
|
||||
|
||||
registerBinding(context, 1, 0, 'width', '200px');
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: ['100px']
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeContextWithDebug(isClassBased: boolean) {
|
||||
const ctx = allocTStylingContext(null, false);
|
||||
const tNode: TStylingNode = {flags: 0};
|
||||
(ctx as any).tNode = ctx;
|
||||
return attachStylingDebugObject(ctx, tNode, isClassBased);
|
||||
}
|
||||
|
||||
function buildGuardMask(...bindingIndices: number[]) {
|
||||
let mask = DEFAULT_GUARD_MASK_VALUE;
|
||||
for (let i = 0; i < bindingIndices.length; i++) {
|
||||
mask |= 1 << bindingIndices[i];
|
||||
}
|
||||
return mask;
|
||||
}
|
|
@ -1,85 +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 {TStylingNode} from '@angular/core/src/render3/interfaces/styling';
|
||||
import {registerBinding} from '@angular/core/src/render3/styling/bindings';
|
||||
import {NodeStylingDebug, attachStylingDebugObject} from '@angular/core/src/render3/styling/styling_debug';
|
||||
import {allocTStylingContext} from '@angular/core/src/render3/util/styling_utils';
|
||||
|
||||
describe('styling debugging tools', () => {
|
||||
describe('NodeStylingDebug', () => {
|
||||
it('should list out each of the values in the context paired together with the provided data',
|
||||
() => {
|
||||
if (isIE()) return;
|
||||
|
||||
const values = makeContextWithDebug(false);
|
||||
const context = values.context;
|
||||
const tNode = values.tNode;
|
||||
|
||||
const data: any[] = [];
|
||||
const d = new NodeStylingDebug(context, tNode, data, false);
|
||||
|
||||
registerBinding(context, tNode, 0, 0, 'width', null, false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
value: null,
|
||||
bindingIndex: null,
|
||||
},
|
||||
});
|
||||
|
||||
registerBinding(context, tNode, 0, 0, 'width', '100px', false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
value: '100px',
|
||||
bindingIndex: null,
|
||||
},
|
||||
});
|
||||
|
||||
const someBindingIndex1 = 1;
|
||||
data[someBindingIndex1] = '200px';
|
||||
|
||||
registerBinding(context, tNode, 0, 0, 'width', someBindingIndex1, false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
value: '200px',
|
||||
bindingIndex: someBindingIndex1,
|
||||
},
|
||||
});
|
||||
|
||||
const someBindingIndex2 = 2;
|
||||
data[someBindingIndex2] = '500px';
|
||||
|
||||
registerBinding(context, tNode, 0, 1, 'width', someBindingIndex2, false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
value: '200px',
|
||||
bindingIndex: someBindingIndex1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeContextWithDebug(isClassBased: boolean) {
|
||||
const context = allocTStylingContext(null, false);
|
||||
const tNode = createTStylingNode();
|
||||
attachStylingDebugObject(context, tNode, isClassBased);
|
||||
return {context, tNode};
|
||||
}
|
||||
|
||||
function createTStylingNode(): TStylingNode {
|
||||
return {flags: 0};
|
||||
}
|
||||
|
||||
function isIE() {
|
||||
// note that this only applies to older IEs (not edge)
|
||||
return typeof window !== 'undefined' && (window as any).document['documentMode'] ? true : false;
|
||||
}
|
|
@ -1,26 +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 {splitOnWhitespace} from '@angular/core/src/render3/util/styling_utils';
|
||||
|
||||
describe('styling_utils', () => {
|
||||
describe('splitOnWhitespace', () => {
|
||||
it('should treat empty strings as null', () => {
|
||||
expect(splitOnWhitespace('')).toEqual(null);
|
||||
expect(splitOnWhitespace(' ')).toEqual(null);
|
||||
expect(splitOnWhitespace(' \n\r\t ')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should split strings into parts', () => {
|
||||
expect(splitOnWhitespace('a\nb\rc')).toEqual(['a', 'b', 'c']);
|
||||
expect(splitOnWhitespace('\ta-long\nb-long\rc-long ')).toEqual([
|
||||
'a-long', 'b-long', 'c-long'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue