refactor(ivy): move all styling configurations into `TNodeFlags` (#33540)
This patch gets rid of the configuration settings present in the `TStylingContext` array that is used within the styling algorithm for `[style]`, `[style.prop]`, `[class]` and `[class.name]` bindings. These configurations now all live inside of the `TNodeFlags`. PR Close #33540
This commit is contained in:
parent
e89c2dd8d0
commit
41560b47c4
|
@ -39,7 +39,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 265613,
|
||||
"main-es2015": 268331,
|
||||
"polyfills-es2015": 36808,
|
||||
"5-es2015": 751
|
||||
}
|
||||
|
|
|
@ -375,10 +375,10 @@ export function buildDebugNode(tNode: TNode, lView: LView, nodeIndex: number): D
|
|||
const native = unwrapRNode(rawValue);
|
||||
const componentLViewDebug = toDebug(readLViewValue(rawValue));
|
||||
const styles = isStylingContext(tNode.styles) ?
|
||||
new NodeStylingDebug(tNode.styles as any as TStylingContext, lView, false) :
|
||||
new NodeStylingDebug(tNode.styles as any as TStylingContext, tNode, lView, false) :
|
||||
null;
|
||||
const classes = isStylingContext(tNode.classes) ?
|
||||
new NodeStylingDebug(tNode.classes as any as TStylingContext, lView, true) :
|
||||
new NodeStylingDebug(tNode.classes as any as TStylingContext, tNode, lView, true) :
|
||||
null;
|
||||
return {
|
||||
html: toHtml(native),
|
||||
|
|
|
@ -11,7 +11,7 @@ import {throwErrorIfNoChangesMode} from '../errors';
|
|||
import {setInputsForProperty} from '../instructions/shared';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext} from '../interfaces/styling';
|
||||
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfaces/styling';
|
||||
import {isDirectiveHost} from '../interfaces/type_checks';
|
||||
import {LView, RENDERER, TVIEW, TView} from '../interfaces/view';
|
||||
import {getActiveDirectiveId, getCheckNoChangesMode, getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, nextBindingIndex, resetCurrentStyleSanitizer, setCurrentStyleSanitizer, setElementExitFn} from '../state';
|
||||
|
@ -95,9 +95,21 @@ export function stylePropInternal(
|
|||
// 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;
|
||||
|
||||
const updated =
|
||||
stylingProp(elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false);
|
||||
// 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) {
|
||||
|
@ -127,8 +139,20 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
|
|||
// still needs to be incremented because all styling binding values
|
||||
// are stored inside of the lView.
|
||||
const bindingIndex = nextBindingIndex();
|
||||
const lView = getLView();
|
||||
const elementIndex = getSelectedIndex();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const firstUpdatePass = lView[TVIEW].firstUpdatePass;
|
||||
|
||||
const updated = stylingProp(getSelectedIndex(), bindingIndex, className, value, true);
|
||||
// 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.hasClassPropBindings);
|
||||
patchHostStylingFlag(tNode, isHostStyling(), true);
|
||||
}
|
||||
|
||||
const updated = stylingProp(tNode, firstUpdatePass, lView, bindingIndex, className, value, true);
|
||||
if (ngDevMode) {
|
||||
ngDevMode.classProp++;
|
||||
if (updated) {
|
||||
|
@ -148,25 +172,15 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
|
|||
* present together).
|
||||
*/
|
||||
function stylingProp(
|
||||
elementIndex: number, bindingIndex: number, prop: string,
|
||||
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 lView = getLView();
|
||||
const firstUpdatePass = lView[TVIEW].firstUpdatePass;
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode);
|
||||
const sanitizer = isClassBased ? null : getCurrentStyleSanitizer();
|
||||
|
||||
// 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(context, TStylingConfig.HasPropBindings);
|
||||
}
|
||||
|
||||
// [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
|
||||
|
@ -180,11 +194,12 @@ function stylingProp(
|
|||
|
||||
// Direct Apply Case: bypass context resolution and apply the
|
||||
// style/class value directly to the element
|
||||
if (allowDirectStyling(context, firstUpdatePass)) {
|
||||
if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) {
|
||||
const sanitizerToUse = isClassBased ? null : sanitizer;
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
updated = applyStylingValueDirectly(
|
||||
renderer, context, native, lView, bindingIndex, prop, value, isClassBased, sanitizerToUse);
|
||||
renderer, context, tNode, native, lView, bindingIndex, prop, value, isClassBased,
|
||||
sanitizerToUse);
|
||||
|
||||
if (sanitizerToUse) {
|
||||
// it's important we remove the current style sanitizer once the
|
||||
|
@ -199,11 +214,11 @@ function stylingProp(
|
|||
const directiveIndex = getActiveDirectiveId();
|
||||
if (isClassBased) {
|
||||
updated = updateClassViaContext(
|
||||
context, lView, native, directiveIndex, prop, bindingIndex,
|
||||
context, tNode, lView, native, directiveIndex, prop, bindingIndex,
|
||||
value as string | boolean | null, false, firstUpdatePass);
|
||||
} else {
|
||||
updated = updateStyleViaContext(
|
||||
context, lView, native, directiveIndex, prop, bindingIndex,
|
||||
context, tNode, lView, native, directiveIndex, prop, bindingIndex,
|
||||
value as string | SafeValue | null, sanitizer, false, firstUpdatePass);
|
||||
}
|
||||
|
||||
|
@ -245,15 +260,24 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu
|
|||
// 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 (!isHostStyling() && hasDirectiveInput && styles !== NO_CHANGE) {
|
||||
if (!hostBindingsMode && hasDirectiveInput && styles !== NO_CHANGE) {
|
||||
updateDirectiveInputValue(context, lView, tNode, bindingIndex, styles, false, firstUpdatePass);
|
||||
styles = 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.hasStyleMapBindings);
|
||||
patchHostStylingFlag(tNode, isHostStyling(), false);
|
||||
}
|
||||
|
||||
stylingMap(
|
||||
context, tNode, firstUpdatePass, lView, bindingIndex, styles, false, hasDirectiveInput);
|
||||
}
|
||||
|
@ -299,15 +323,24 @@ export function classMapInternal(
|
|||
// 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 (!isHostStyling() && hasDirectiveInput && classes !== NO_CHANGE) {
|
||||
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);
|
||||
}
|
||||
|
@ -336,20 +369,13 @@ function stylingMap(
|
|||
throwErrorIfNoChangesMode(false, oldValue, value);
|
||||
}
|
||||
|
||||
// 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(context, TStylingConfig.HasMapBindings);
|
||||
}
|
||||
|
||||
// Direct Apply Case: bypass context resolution and apply the
|
||||
// style/class map values directly to the element
|
||||
if (allowDirectStyling(context, firstUpdatePass)) {
|
||||
if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) {
|
||||
const sanitizerToUse = isClassBased ? null : sanitizer;
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
applyStylingMapDirectly(
|
||||
renderer, context, native, lView, bindingIndex, value, isClassBased, sanitizerToUse,
|
||||
renderer, context, tNode, native, lView, bindingIndex, value, isClassBased, sanitizerToUse,
|
||||
valueHasChanged, hasDirectiveInput);
|
||||
if (sanitizerToUse) {
|
||||
// it's important we remove the current style sanitizer once the
|
||||
|
@ -368,12 +394,12 @@ function stylingMap(
|
|||
// value to the element.
|
||||
if (isClassBased) {
|
||||
updateClassViaContext(
|
||||
context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr,
|
||||
context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr,
|
||||
valueHasChanged, firstUpdatePass);
|
||||
} else {
|
||||
updateStyleViaContext(
|
||||
context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, sanitizer,
|
||||
valueHasChanged, firstUpdatePass);
|
||||
context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr,
|
||||
sanitizer, valueHasChanged, firstUpdatePass);
|
||||
}
|
||||
|
||||
setElementExitFn(stylingApply);
|
||||
|
@ -463,7 +489,7 @@ function stylingApply(): void {
|
|||
const classesContext = isStylingContext(tNode.classes) ? tNode.classes as TStylingContext : null;
|
||||
const stylesContext = isStylingContext(tNode.styles) ? tNode.styles as TStylingContext : null;
|
||||
flushStyling(
|
||||
renderer, lView, classesContext, stylesContext, native, directiveIndex, sanitizer,
|
||||
renderer, lView, tNode, classesContext, stylesContext, native, directiveIndex, sanitizer,
|
||||
tView.firstUpdatePass);
|
||||
resetCurrentStyleSanitizer();
|
||||
}
|
||||
|
@ -541,7 +567,7 @@ function getContext(tNode: TNode, isClassBased: boolean): TStylingContext {
|
|||
const hasDirectives = isDirectiveHost(tNode);
|
||||
context = allocTStylingContext(context as StylingMapArray | null, hasDirectives);
|
||||
if (ngDevMode) {
|
||||
attachStylingDebugObject(context as TStylingContext, isClassBased);
|
||||
attachStylingDebugObject(context as TStylingContext, tNode, isClassBased);
|
||||
}
|
||||
|
||||
if (isClassBased) {
|
||||
|
@ -582,3 +608,10 @@ function resolveStylePropValue(
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -67,19 +67,121 @@ export const enum TNodeFlags {
|
|||
/** Bit #6 - This bit is set if the node has any "style" inputs */
|
||||
hasStyleInput = 0x20,
|
||||
|
||||
/** Bit #7 - This bit is set if the node has initial styling */
|
||||
hasInitialStyling = 0x40,
|
||||
|
||||
/** Bit #8 - This bit is set if the node has been detached by i18n */
|
||||
isDetached = 0x80,
|
||||
/** Bit #7 This bit is set if the node has been detached by i18n */
|
||||
isDetached = 0x40,
|
||||
|
||||
/**
|
||||
* Bit #9 - This bit is set if the node has directives with host bindings.
|
||||
* Bit #8 - This bit is set if the node has directives with host bindings.
|
||||
*
|
||||
* This flags allows us to guard host-binding logic and invoke it only on nodes
|
||||
* that actually have directives with host bindings.
|
||||
*/
|
||||
hasHostBindings = 0x100,
|
||||
hasHostBindings = 0x80,
|
||||
|
||||
/** Bit #9 - This bit is set if the node has initial styling */
|
||||
hasInitialStyling = 0x100,
|
||||
|
||||
/**
|
||||
* Bit #10 - Whether or not there are class-based map bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [class]="x">`
|
||||
* 2. `@HostBinding('class') x`
|
||||
*/
|
||||
hasClassMapBindings = 0x200,
|
||||
|
||||
/**
|
||||
* Bit #11 - Whether or not there are any class-based prop bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [class.name]="x">`
|
||||
* 2. `@HostBinding('class.name') x`
|
||||
*/
|
||||
hasClassPropBindings = 0x400,
|
||||
|
||||
/**
|
||||
* Bit #12 - whether or not there are any active [class] and [class.name] bindings
|
||||
*/
|
||||
hasClassPropAndMapBindings = hasClassMapBindings | hasClassPropBindings,
|
||||
|
||||
/**
|
||||
* Bit #13 - Whether or not the context contains one or more class-based template bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [class]="x">`
|
||||
* 2. `<div [class.name]="x">`
|
||||
*/
|
||||
hasTemplateClassBindings = 0x800,
|
||||
|
||||
/**
|
||||
* Bit #14 - Whether or not the context contains one or more class-based host bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `@HostBinding('class') x`
|
||||
* 2. `@HostBinding('class.name') x`
|
||||
*/
|
||||
hasHostClassBindings = 0x1000,
|
||||
|
||||
/**
|
||||
* Bit #15 - Whether or not there are two or more sources for a class property in the context.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. prop + prop: `<div [class.active]="x" dir-that-sets-active-class>`
|
||||
* 2. map + prop: `<div [class]="x" [class.foo]>`
|
||||
* 3. map + map: `<div [class]="x" dir-that-sets-class>`
|
||||
*/
|
||||
hasDuplicateClassBindings = 0x2000,
|
||||
|
||||
/**
|
||||
* Bit #16 - Whether or not there are style-based map bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x">`
|
||||
* 2. `@HostBinding('style') x`
|
||||
*/
|
||||
hasStyleMapBindings = 0x4000,
|
||||
|
||||
/**
|
||||
* Bit #17 - Whether or not there are any style-based prop bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style.prop]="x">`
|
||||
* 2. `@HostBinding('style.prop') x`
|
||||
*/
|
||||
hasStylePropBindings = 0x8000,
|
||||
|
||||
/**
|
||||
* Bit #18 - whether or not there are any active [style] and [style.prop] bindings
|
||||
*/
|
||||
hasStylePropAndMapBindings = hasStyleMapBindings | hasStylePropBindings,
|
||||
|
||||
/**
|
||||
* Bit #19 - Whether or not the context contains one or more style-based template bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x">`
|
||||
* 2. `<div [style.prop]="x">`
|
||||
*/
|
||||
hasTemplateStyleBindings = 0x10000,
|
||||
|
||||
/**
|
||||
* Bit #20 - Whether or not the context contains one or more style-based host bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `@HostBinding('style') x`
|
||||
* 2. `@HostBinding('style.prop') x`
|
||||
*/
|
||||
hasHostStyleBindings = 0x20000,
|
||||
|
||||
/**
|
||||
* Bit #21 - Whether or not there are two or more sources for a style property in the context.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. prop + prop: `<div [style.width]="x" dir-that-sets-width>`
|
||||
* 2. map + prop: `<div [style]="x" [style.prop]>`
|
||||
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
|
||||
*/
|
||||
hasDuplicateStyleBindings = 0x40000,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
|
||||
import {TNodeFlags} from './node';
|
||||
import {ProceduralRenderer3, RElement, Renderer3} from './renderer';
|
||||
import {LView} from './view';
|
||||
|
||||
|
||||
/**
|
||||
* --------
|
||||
*
|
||||
|
@ -75,7 +78,6 @@ import {LView} from './view';
|
|||
* // ...
|
||||
* // </div>
|
||||
* tNode.styles = [
|
||||
* 0, // the context config value (see `TStylingContextConfig`)
|
||||
* 1, // the total amount of sources present (only `1` b/c there are only template
|
||||
* bindings)
|
||||
* [null], // initial values array (an instance of `StylingMapArray`)
|
||||
|
@ -323,9 +325,6 @@ import {LView} from './view';
|
|||
*/
|
||||
export interface TStylingContext extends
|
||||
Array<number|string|number|boolean|null|StylingMapArray|{}> {
|
||||
/** Configuration data for the context */
|
||||
[TStylingContextIndex.ConfigPosition]: TStylingConfig;
|
||||
|
||||
/** The total amount of sources present in the context */
|
||||
[TStylingContextIndex.TotalSourcesPosition]: number;
|
||||
|
||||
|
@ -333,125 +332,13 @@ export interface TStylingContext extends
|
|||
[TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* A series of flags used to configure the config value present within an instance of
|
||||
* `TStylingContext`.
|
||||
*/
|
||||
export const enum TStylingConfig {
|
||||
/**
|
||||
* The initial state of the styling context config.
|
||||
*/
|
||||
Initial = 0b000000,
|
||||
|
||||
/**
|
||||
* Whether or not there are any directives on this element.
|
||||
*
|
||||
* This is used so that certain performance optimizations can
|
||||
* take place (e.g. direct style/class binding application).
|
||||
*
|
||||
* Note that the presence of this flag doesn't guarantee the
|
||||
* presence of host-level style or class bindings within any
|
||||
* of the active directives on the element.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div dir-one>`
|
||||
* 2. `<div dir-one [dir-two]="x">`
|
||||
* 3. `<comp>`
|
||||
* 4. `<comp dir-one>`
|
||||
*/
|
||||
HasDirectives = 0b000001,
|
||||
|
||||
/**
|
||||
* Whether or not there are prop-based bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style.prop]="x">`
|
||||
* 2. `<div [class.prop]="x">`
|
||||
* 3. `@HostBinding('style.prop') x`
|
||||
* 4. `@HostBinding('class.prop') x`
|
||||
*/
|
||||
HasPropBindings = 0b000010,
|
||||
|
||||
/**
|
||||
* Whether or not there are map-based bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x">`
|
||||
* 2. `<div [class]="x">`
|
||||
* 3. `@HostBinding('style') x`
|
||||
* 4. `@HostBinding('class') x`
|
||||
*/
|
||||
HasMapBindings = 0b000100,
|
||||
|
||||
/**
|
||||
* Whether or not there are map-based and prop-based bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x" [style.prop]="y">`
|
||||
* 2. `<div [class]="x" [style.prop]="y">`
|
||||
* 3. `<div [style]="x" dir-that-sets-some-prop>`
|
||||
* 4. `<div [class]="x" dir-that-sets-some-class>`
|
||||
*/
|
||||
HasPropAndMapBindings = HasPropBindings | HasMapBindings,
|
||||
|
||||
/**
|
||||
* Whether or not there are two or more sources for a single property in the context.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. prop + prop: `<div [style.width]="x" dir-that-sets-width>`
|
||||
* 2. map + prop: `<div [style]="x" [style.prop]>`
|
||||
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
|
||||
*/
|
||||
HasCollisions = 0b001000,
|
||||
|
||||
/**
|
||||
* Whether or not the context contains initial styling values.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div style="width:200px">`
|
||||
* 2. `<div class="one two three">`
|
||||
* 3. `@Directive({ host: { 'style': 'width:200px' } })`
|
||||
* 4. `@Directive({ host: { 'class': 'one two three' } })`
|
||||
*/
|
||||
HasInitialStyling = 0b0010000,
|
||||
|
||||
/**
|
||||
* Whether or not the context contains one or more template bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x">`
|
||||
* 2. `<div [style.width]="x">`
|
||||
* 3. `<div [class]="x">`
|
||||
* 4. `<div [class.name]="x">`
|
||||
*/
|
||||
HasTemplateBindings = 0b0100000,
|
||||
|
||||
/**
|
||||
* Whether or not the context contains one or more host bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `@HostBinding('style') x`
|
||||
* 2. `@HostBinding('style.width') x`
|
||||
* 3. `@HostBinding('class') x`
|
||||
* 4. `@HostBinding('class.name') x`
|
||||
*/
|
||||
HasHostBindings = 0b1000000,
|
||||
|
||||
/** A Mask of all the configurations */
|
||||
Mask = 0b1111111,
|
||||
|
||||
/** Total amount of configuration bits used */
|
||||
TotalBits = 7,
|
||||
}
|
||||
|
||||
/**
|
||||
* An index of position and offset values used to navigate the `TStylingContext`.
|
||||
*/
|
||||
export const enum TStylingContextIndex {
|
||||
ConfigPosition = 0,
|
||||
TotalSourcesPosition = 1,
|
||||
InitialStylingValuePosition = 2,
|
||||
ValuesStartPosition = 3,
|
||||
TotalSourcesPosition = 0,
|
||||
InitialStylingValuePosition = 1,
|
||||
ValuesStartPosition = 2,
|
||||
|
||||
// each tuple entry in the context
|
||||
// (config, templateBitGuard, hostBindingBitGuard, prop, ...bindings||default-value)
|
||||
|
@ -577,3 +464,10 @@ export const enum StylingMapsSyncMode {
|
|||
/** Only check to see if a value was set somewhere in each map */
|
||||
CheckValuesOnly = 0b10000,
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified `TNode` interface for styling-related code.
|
||||
*
|
||||
* The styling algorithm code only needs access to `flags`.
|
||||
*/
|
||||
export interface TStylingNode { flags: TNodeFlags; }
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||
import {global} from '../../util/global';
|
||||
import {TNodeFlags} from '../interfaces/node';
|
||||
import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from '../interfaces/styling';
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags, TStylingNode} from '../interfaces/styling';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, concatString, forceStylesAsString, getBindingValue, getConfig, getDefaultValue, getGuardMask, getInitialStylingValue, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isHostStylingActive, isSanitizationRequired, isStylingMapArray, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from '../util/styling_utils';
|
||||
import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, concatString, forceStylesAsString, getBindingValue, getDefaultValue, getGuardMask, getInitialStylingValue, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isHostStylingActive, isSanitizationRequired, isStylingMapArray, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from '../util/styling_utils';
|
||||
|
||||
import {getStylingState, resetStylingState} from './state';
|
||||
|
||||
|
@ -27,7 +28,7 @@ const VALUE_IS_EXTERNALLY_MODIFIED = {};
|
|||
*
|
||||
* When a binding is encountered (e.g. `<div [style.width]="w">`) then
|
||||
* the binding data will be populated into a `TStylingContext` data-structure.
|
||||
* There is only one `TStylingContext` per `TNode` and each element instance
|
||||
* There is only one `TStylingContext` per `TStylingNode` and each element instance
|
||||
* will update its style/class binding values in concert with the styling
|
||||
* context.
|
||||
*
|
||||
|
@ -54,8 +55,8 @@ const STYLING_INDEX_FOR_MAP_BINDING = 0;
|
|||
* and the bit mask values to be in sync).
|
||||
*/
|
||||
export function updateClassViaContext(
|
||||
context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number,
|
||||
prop: string | null, bindingIndex: number,
|
||||
context: TStylingContext, tNode: TStylingNode, data: LStylingData, element: RElement,
|
||||
directiveIndex: number, prop: string | null, bindingIndex: number,
|
||||
value: boolean | string | null | undefined | StylingMapArray | NO_CHANGE, forceUpdate: boolean,
|
||||
firstUpdatePass: boolean): boolean {
|
||||
const isMapBased = !prop;
|
||||
|
@ -67,8 +68,8 @@ export function updateClassViaContext(
|
|||
// is aware of the binding even if things change after the first update pass.
|
||||
if (firstUpdatePass || value !== NO_CHANGE) {
|
||||
const updated = updateBindingData(
|
||||
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, false,
|
||||
firstUpdatePass);
|
||||
context, tNode, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate,
|
||||
false, firstUpdatePass, true);
|
||||
if (updated || forceUpdate) {
|
||||
// We flip the bit in the bitMask to reflect that the binding
|
||||
// at the `index` slot has changed. This identifies to the flushing
|
||||
|
@ -93,8 +94,8 @@ export function updateClassViaContext(
|
|||
* and the bit mask values to be in sync).
|
||||
*/
|
||||
export function updateStyleViaContext(
|
||||
context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number,
|
||||
prop: string | null, bindingIndex: number,
|
||||
context: TStylingContext, tNode: TStylingNode, data: LStylingData, element: RElement,
|
||||
directiveIndex: number, prop: string | null, bindingIndex: number,
|
||||
value: string | number | SafeValue | null | undefined | StylingMapArray | NO_CHANGE,
|
||||
sanitizer: StyleSanitizeFn | null, forceUpdate: boolean, firstUpdatePass: boolean): boolean {
|
||||
const isMapBased = !prop;
|
||||
|
@ -109,8 +110,8 @@ export function updateStyleViaContext(
|
|||
true :
|
||||
(sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false);
|
||||
const updated = updateBindingData(
|
||||
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate,
|
||||
sanitizationRequired, firstUpdatePass);
|
||||
context, tNode, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate,
|
||||
sanitizationRequired, firstUpdatePass, false);
|
||||
if (updated || forceUpdate) {
|
||||
// We flip the bit in the bitMask to reflect that the binding
|
||||
// at the `index` slot has changed. This identifies to the flushing
|
||||
|
@ -136,11 +137,14 @@ export function updateStyleViaContext(
|
|||
* @returns whether or not the binding value was updated in the `LStylingData`.
|
||||
*/
|
||||
function updateBindingData(
|
||||
context: TStylingContext, data: LStylingData, counterIndex: number, sourceIndex: number,
|
||||
prop: string | null, bindingIndex: number,
|
||||
context: TStylingContext, tNode: TStylingNode, data: LStylingData, counterIndex: number,
|
||||
sourceIndex: number, prop: string | null, bindingIndex: number,
|
||||
value: string | SafeValue | number | boolean | null | undefined | StylingMapArray,
|
||||
forceUpdate: boolean, sanitizationRequired: boolean, firstUpdatePass: boolean): boolean {
|
||||
forceUpdate: boolean, sanitizationRequired: boolean, firstUpdatePass: boolean,
|
||||
isClassBased: boolean): boolean {
|
||||
const hostBindingsMode = isHostStylingActive(sourceIndex);
|
||||
const hostBindingsFlag =
|
||||
isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings;
|
||||
if (firstUpdatePass) {
|
||||
// this will only happen during the first update pass of the
|
||||
// context. The reason why we can't use `tView.firstCreatePass`
|
||||
|
@ -148,19 +152,18 @@ function updateBindingData(
|
|||
// update pass is executed (remember that all styling instructions
|
||||
// are run in the update phase, and, as a result, are no more
|
||||
// styling instructions that are run in the creation phase).
|
||||
registerBinding(context, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired);
|
||||
patchConfig(
|
||||
context,
|
||||
hostBindingsMode ? TStylingConfig.HasHostBindings : TStylingConfig.HasTemplateBindings);
|
||||
registerBinding(
|
||||
context, tNode, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired,
|
||||
isClassBased);
|
||||
}
|
||||
|
||||
const changed = forceUpdate || hasValueChanged(data[bindingIndex], value);
|
||||
if (changed) {
|
||||
setValue(data, bindingIndex, value);
|
||||
const doSetValuesAsStale = (getConfig(context) & TStylingConfig.HasHostBindings) &&
|
||||
!hostBindingsMode && (prop ? !value : true);
|
||||
const doSetValuesAsStale =
|
||||
hasConfig(tNode, hostBindingsFlag) && !hostBindingsMode && (prop ? !value : true);
|
||||
if (doSetValuesAsStale) {
|
||||
renderHostBindingsAsStale(context, data, prop);
|
||||
renderHostBindingsAsStale(context, tNode, data, prop, isClassBased);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
|
@ -178,10 +181,13 @@ function updateBindingData(
|
|||
* binding changes.
|
||||
*/
|
||||
function renderHostBindingsAsStale(
|
||||
context: TStylingContext, data: LStylingData, prop: string | null): void {
|
||||
context: TStylingContext, tNode: TStylingNode, data: LStylingData, prop: string | null,
|
||||
isClassBased: boolean): void {
|
||||
const valuesCount = getValuesCount(context);
|
||||
|
||||
if (prop !== null && hasConfig(context, TStylingConfig.HasPropBindings)) {
|
||||
const hostBindingsFlag =
|
||||
isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings;
|
||||
if (prop !== null && hasConfig(tNode, hostBindingsFlag)) {
|
||||
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + valuesCount;
|
||||
|
||||
let i = TStylingContextIndex.ValuesStartPosition;
|
||||
|
@ -208,7 +214,9 @@ function renderHostBindingsAsStale(
|
|||
}
|
||||
}
|
||||
|
||||
if (hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
const mapBindingsFlag =
|
||||
isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
if (hasConfig(tNode, mapBindingsFlag)) {
|
||||
const bindingsStart =
|
||||
TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset;
|
||||
const valuesStart = bindingsStart + 1; // the first column is template bindings
|
||||
|
@ -253,8 +261,9 @@ function renderHostBindingsAsStale(
|
|||
* much the same as prop-based bindings, but, their property name value is set as `[MAP]`.
|
||||
*/
|
||||
export function registerBinding(
|
||||
context: TStylingContext, countId: number, sourceIndex: number, prop: string | null,
|
||||
bindingValue: number | null | string | boolean, sanitizationRequired?: boolean): void {
|
||||
context: TStylingContext, tNode: TStylingNode, countId: number, sourceIndex: number,
|
||||
prop: string | null, bindingValue: number | null | string | boolean,
|
||||
sanitizationRequired: boolean, isClassBased: boolean): void {
|
||||
let found = false;
|
||||
prop = prop || MAP_BASED_ENTRY_PROP_NAME;
|
||||
|
||||
|
@ -268,6 +277,8 @@ export function registerBinding(
|
|||
totalSources++;
|
||||
}
|
||||
|
||||
const collisionFlag =
|
||||
isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings;
|
||||
const isBindingIndexValue = typeof bindingValue === 'number';
|
||||
const entriesPerRow = TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
|
||||
let i = TStylingContextIndex.ValuesStartPosition;
|
||||
|
@ -279,7 +290,7 @@ export function registerBinding(
|
|||
if (prop < p) {
|
||||
allocateNewContextEntry(context, i, prop, sanitizationRequired);
|
||||
} else if (isBindingIndexValue) {
|
||||
patchConfig(context, TStylingConfig.HasCollisions);
|
||||
patchConfig(tNode, collisionFlag);
|
||||
}
|
||||
addBindingIntoContext(context, i, bindingValue, countId, sourceIndex);
|
||||
found = true;
|
||||
|
@ -405,7 +416,7 @@ function addNewSourceColumn(context: TStylingContext): void {
|
|||
* (i.e. the `bitMask` and `counter` values for styles and classes will be cleared).
|
||||
*/
|
||||
export function flushStyling(
|
||||
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData,
|
||||
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, tNode: TStylingNode,
|
||||
classesContext: TStylingContext | null, stylesContext: TStylingContext | null,
|
||||
element: RElement, directiveIndex: number, styleSanitizer: StyleSanitizeFn | null,
|
||||
firstUpdatePass: boolean): void {
|
||||
|
@ -415,22 +426,22 @@ export function flushStyling(
|
|||
const hostBindingsMode = isHostStylingActive(state.sourceIndex);
|
||||
|
||||
if (stylesContext) {
|
||||
firstUpdatePass && syncContextInitialStyling(stylesContext);
|
||||
firstUpdatePass && syncContextInitialStyling(stylesContext, tNode, false);
|
||||
|
||||
if (state.stylesBitMask !== 0) {
|
||||
applyStylingViaContext(
|
||||
stylesContext, renderer, element, data, state.stylesBitMask, setStyle, styleSanitizer,
|
||||
hostBindingsMode);
|
||||
stylesContext, tNode, renderer, element, data, state.stylesBitMask, setStyle,
|
||||
styleSanitizer, hostBindingsMode, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (classesContext) {
|
||||
firstUpdatePass && syncContextInitialStyling(classesContext);
|
||||
firstUpdatePass && syncContextInitialStyling(classesContext, tNode, true);
|
||||
|
||||
if (state.classesBitMask !== 0) {
|
||||
applyStylingViaContext(
|
||||
classesContext, renderer, element, data, state.classesBitMask, setClass, null,
|
||||
hostBindingsMode);
|
||||
classesContext, tNode, renderer, element, data, state.classesBitMask, setClass, null,
|
||||
hostBindingsMode, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,10 +503,11 @@ export function flushStyling(
|
|||
* ]
|
||||
* ```
|
||||
*/
|
||||
function syncContextInitialStyling(context: TStylingContext): void {
|
||||
function syncContextInitialStyling(
|
||||
context: TStylingContext, tNode: TStylingNode, isClassBased: boolean): void {
|
||||
// the TStylingContext always has initial style/class values which are
|
||||
// stored in styling array format.
|
||||
updateInitialStylingOnContext(context, getStylingMapArray(context) !);
|
||||
updateInitialStylingOnContext(context, tNode, getStylingMapArray(context) !, isClassBased);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -513,7 +525,8 @@ function syncContextInitialStyling(context: TStylingContext): void {
|
|||
* update itself with the complete initial styling for the element.
|
||||
*/
|
||||
function updateInitialStylingOnContext(
|
||||
context: TStylingContext, initialStyling: StylingMapArray): void {
|
||||
context: TStylingContext, tNode: TStylingNode, initialStyling: StylingMapArray,
|
||||
isClassBased: boolean): void {
|
||||
// `-1` is used here because all initial styling data is not a apart
|
||||
// of a binding (since it's static)
|
||||
const COUNT_ID_FOR_STYLING = -1;
|
||||
|
@ -524,13 +537,13 @@ function updateInitialStylingOnContext(
|
|||
const value = getMapValue(initialStyling, i);
|
||||
if (value) {
|
||||
const prop = getMapProp(initialStyling, i);
|
||||
registerBinding(context, COUNT_ID_FOR_STYLING, 0, prop, value, false);
|
||||
registerBinding(context, tNode, COUNT_ID_FOR_STYLING, 0, prop, value, false, isClassBased);
|
||||
hasInitialStyling = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInitialStyling) {
|
||||
patchConfig(context, TStylingConfig.HasInitialStyling);
|
||||
patchConfig(tNode, TNodeFlags.hasInitialStyling);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,14 +575,17 @@ function updateInitialStylingOnContext(
|
|||
* the styles and classes contexts).
|
||||
*/
|
||||
export function applyStylingViaContext(
|
||||
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||
bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn,
|
||||
sanitizer: StyleSanitizeFn | null, hostBindingsMode: boolean): void {
|
||||
context: TStylingContext, tNode: TStylingNode, renderer: Renderer3 | ProceduralRenderer3 | null,
|
||||
element: RElement, bindingData: LStylingData, bitMaskValue: number | boolean,
|
||||
applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null, hostBindingsMode: boolean,
|
||||
isClassBased: boolean): void {
|
||||
const bitMask = normalizeBitMaskValue(bitMaskValue);
|
||||
|
||||
let stylingMapsSyncFn: SyncStylingMapsFn|null = null;
|
||||
let applyAllValues = false;
|
||||
if (hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
const mapBindingsFlag =
|
||||
isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
if (hasConfig(tNode, mapBindingsFlag)) {
|
||||
stylingMapsSyncFn = getStylingMapsSyncFn();
|
||||
const mapsGuardMask =
|
||||
getGuardMask(context, TStylingContextIndex.ValuesStartPosition, hostBindingsMode);
|
||||
|
@ -585,7 +601,7 @@ export function applyStylingViaContext(
|
|||
totalBindingsToVisit = valuesCount - 1;
|
||||
}
|
||||
|
||||
let i = getPropValuesStartPosition(context);
|
||||
let i = getPropValuesStartPosition(context, tNode, isClassBased);
|
||||
while (i < context.length) {
|
||||
const guardMask = getGuardMask(context, i, hostBindingsMode);
|
||||
if (bitMask & guardMask) {
|
||||
|
@ -681,14 +697,13 @@ export function applyStylingViaContext(
|
|||
* @returns whether or not the styling map was applied to the element.
|
||||
*/
|
||||
export function applyStylingMapDirectly(
|
||||
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
|
||||
bindingIndex: number, value: {[key: string]: any} | string | null, isClassBased: boolean,
|
||||
sanitizer?: StyleSanitizeFn | null, forceUpdate?: boolean,
|
||||
bindingValueContainsInitial?: boolean): void {
|
||||
renderer: any, context: TStylingContext, tNode: TStylingNode, element: RElement,
|
||||
data: LStylingData, bindingIndex: number, value: {[key: string]: any} | string | null,
|
||||
isClassBased: boolean, sanitizer: StyleSanitizeFn | null, forceUpdate: boolean,
|
||||
bindingValueContainsInitial: boolean): void {
|
||||
const oldValue = getValue(data, bindingIndex);
|
||||
if (forceUpdate || hasValueChanged(oldValue, value)) {
|
||||
const config = getConfig(context);
|
||||
const hasInitial = config & TStylingConfig.HasInitialStyling;
|
||||
const hasInitial = hasConfig(tNode, TNodeFlags.hasInitialStyling);
|
||||
const initialValue =
|
||||
hasInitial && !bindingValueContainsInitial ? getInitialStylingValue(context) : null;
|
||||
setValue(data, bindingIndex, value);
|
||||
|
@ -707,7 +722,9 @@ export function applyStylingMapDirectly(
|
|||
// fast pass cannot guarantee that the external values are retained.
|
||||
// When this happens, the algorithm will bail out and not write to
|
||||
// the style or className attribute directly.
|
||||
let writeToAttrDirectly = !(config & TStylingConfig.HasPropBindings);
|
||||
const propBindingsFlag =
|
||||
isClassBased ? TNodeFlags.hasClassPropBindings : TNodeFlags.hasStylePropBindings;
|
||||
let writeToAttrDirectly = !hasConfig(tNode, propBindingsFlag);
|
||||
if (writeToAttrDirectly &&
|
||||
checkIfExternallyModified(element as HTMLElement, cachedValue, isClassBased)) {
|
||||
writeToAttrDirectly = false;
|
||||
|
@ -818,8 +835,8 @@ export function writeStylingValueDirectly(
|
|||
* @returns whether or not the prop/value styling was applied to the element.
|
||||
*/
|
||||
export function applyStylingValueDirectly(
|
||||
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
|
||||
bindingIndex: number, prop: string, value: any, isClassBased: boolean,
|
||||
renderer: any, context: TStylingContext, tNode: TStylingNode, element: RElement,
|
||||
data: LStylingData, bindingIndex: number, prop: string, value: any, isClassBased: boolean,
|
||||
sanitizer?: StyleSanitizeFn | null): boolean {
|
||||
let applied = false;
|
||||
if (hasValueChanged(data[bindingIndex], value)) {
|
||||
|
@ -830,7 +847,9 @@ export function applyStylingValueDirectly(
|
|||
applied = applyStylingValue(renderer, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
|
||||
// case 2: find the matching property in a styling map and apply the detected value
|
||||
if (!applied && hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
const mapBindingsFlag =
|
||||
isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
if (!applied && hasConfig(tNode, mapBindingsFlag)) {
|
||||
const state = getStylingState(element, TEMPLATE_DIRECTIVE_INDEX);
|
||||
const map = isClassBased ? state.lastDirectClassMap : state.lastDirectStyleMap;
|
||||
applied = map ?
|
||||
|
@ -839,7 +858,7 @@ export function applyStylingValueDirectly(
|
|||
}
|
||||
|
||||
// case 3: apply the initial value (if it exists)
|
||||
if (!applied && hasConfig(context, TStylingConfig.HasInitialStyling)) {
|
||||
if (!applied && hasConfig(tNode, TNodeFlags.hasInitialStyling)) {
|
||||
const map = getStylingMapArray(context);
|
||||
applied =
|
||||
map ? findAndApplyMapValue(renderer, element, applyFn, map, prop, bindingIndex) : false;
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
*/
|
||||
import {createProxy} from '../../debug/proxy';
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {TNodeFlags} from '../interfaces/node';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from '../interfaces/styling';
|
||||
import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex, TStylingNode} from '../interfaces/styling';
|
||||
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';
|
||||
|
@ -52,7 +53,7 @@ export interface DebugStylingContext {
|
|||
|
||||
|
||||
/**
|
||||
* A debug/testing-oriented summary of `TStylingConfig`.
|
||||
* A debug/testing-oriented summary of all styling information in `TNode.flags`.
|
||||
*/
|
||||
export interface DebugStylingConfig {
|
||||
hasMapBindings: boolean; //
|
||||
|
@ -150,8 +151,9 @@ export interface DebugNodeStylingEntry {
|
|||
/**
|
||||
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context
|
||||
*/
|
||||
export function attachStylingDebugObject(context: TStylingContext, isClassBased: boolean) {
|
||||
const debug = new TStylingContextDebug(context, isClassBased);
|
||||
export function attachStylingDebugObject(
|
||||
context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) {
|
||||
const debug = new TStylingContextDebug(context, tNode, isClassBased);
|
||||
attachDebugObject(context, debug);
|
||||
return debug;
|
||||
}
|
||||
|
@ -163,9 +165,11 @@ export function attachStylingDebugObject(context: TStylingContext, isClassBased:
|
|||
* application has `ngDevMode` activated.
|
||||
*/
|
||||
class TStylingContextDebug implements DebugStylingContext {
|
||||
constructor(public readonly context: TStylingContext, private _isClassBased: boolean) {}
|
||||
constructor(
|
||||
public readonly context: TStylingContext, private _tNode: TStylingNode,
|
||||
private _isClassBased: boolean) {}
|
||||
|
||||
get config(): DebugStylingConfig { return buildConfig(this.context); }
|
||||
get config(): DebugStylingConfig { return buildConfig(this._tNode, this._isClassBased); }
|
||||
|
||||
/**
|
||||
* Returns a detailed summary of each styling entry in the context.
|
||||
|
@ -176,7 +180,7 @@ class TStylingContextDebug implements DebugStylingContext {
|
|||
const context = this.context;
|
||||
const totalColumns = getValuesCount(context);
|
||||
const entries: {[prop: string]: DebugStylingContextEntry} = {};
|
||||
const start = getPropValuesStartPosition(context);
|
||||
const start = getPropValuesStartPosition(context, this._tNode, this._isClassBased);
|
||||
let i = start;
|
||||
while (i < context.length) {
|
||||
const prop = getProp(context, i);
|
||||
|
@ -351,10 +355,10 @@ export class NodeStylingDebug implements DebugNodeStyling {
|
|||
private _debugContext: DebugStylingContext;
|
||||
|
||||
constructor(
|
||||
context: TStylingContext|DebugStylingContext, private _data: LStylingData,
|
||||
private _isClassBased: boolean) {
|
||||
context: TStylingContext|DebugStylingContext, private _tNode: TStylingNode,
|
||||
private _data: LStylingData, private _isClassBased: boolean) {
|
||||
this._debugContext = isStylingContext(context) ?
|
||||
new TStylingContextDebug(context as TStylingContext, _isClassBased) :
|
||||
new TStylingContextDebug(context as TStylingContext, _tNode, _isClassBased) :
|
||||
(context as DebugStylingContext);
|
||||
}
|
||||
|
||||
|
@ -421,7 +425,7 @@ export class NodeStylingDebug implements DebugNodeStyling {
|
|||
});
|
||||
}
|
||||
|
||||
get config() { return buildConfig(this.context.context); }
|
||||
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.
|
||||
|
@ -447,7 +451,7 @@ export class NodeStylingDebug implements DebugNodeStyling {
|
|||
|
||||
private _convertMapBindingsToStylingMapArrays(data: LStylingData) {
|
||||
const context = this.context.context;
|
||||
const limit = getPropValuesStartPosition(context);
|
||||
const limit = getPropValuesStartPosition(context, this._tNode, this._isClassBased);
|
||||
for (let i =
|
||||
TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset;
|
||||
i < limit; i++) {
|
||||
|
@ -467,7 +471,9 @@ export class NodeStylingDebug implements DebugNodeStyling {
|
|||
// 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 hasMaps = hasConfig(this.context.context, TStylingConfig.HasMapBindings);
|
||||
const mapBindingsFlag =
|
||||
this._isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
const hasMaps = hasConfig(this._tNode, mapBindingsFlag);
|
||||
if (hasMaps) {
|
||||
activateStylingMapFeature();
|
||||
}
|
||||
|
@ -480,25 +486,34 @@ export class NodeStylingDebug implements DebugNodeStyling {
|
|||
|
||||
// run the template bindings
|
||||
applyStylingViaContext(
|
||||
this.context.context, null, mockElement, data, true, mapFn, sanitizer, false);
|
||||
this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, false,
|
||||
this._isClassBased);
|
||||
|
||||
// and also the host bindings
|
||||
applyStylingViaContext(
|
||||
this.context.context, null, mockElement, data, true, mapFn, sanitizer, true);
|
||||
this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, true,
|
||||
this._isClassBased);
|
||||
}
|
||||
}
|
||||
|
||||
function buildConfig(context: TStylingContext) {
|
||||
const hasMapBindings = hasConfig(context, TStylingConfig.HasMapBindings);
|
||||
const hasPropBindings = hasConfig(context, TStylingConfig.HasPropBindings);
|
||||
const hasCollisions = hasConfig(context, TStylingConfig.HasCollisions);
|
||||
const hasTemplateBindings = hasConfig(context, TStylingConfig.HasTemplateBindings);
|
||||
const hasHostBindings = hasConfig(context, TStylingConfig.HasHostBindings);
|
||||
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(context, false);
|
||||
const allowDirectStyling = _allowDirectStyling(tNode, isClassBased, false);
|
||||
return {
|
||||
hasMapBindings, //
|
||||
hasPropBindings, //
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 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 {PropertyAliases, TNode, TNodeFlags} from '../interfaces/node';
|
||||
import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from '../interfaces/styling';
|
||||
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]';
|
||||
|
@ -44,17 +44,9 @@ export const DEFAULT_GUARD_MASK_VALUE = 0b1;
|
|||
export function allocTStylingContext(
|
||||
initialStyling: StylingMapArray | null, hasDirectives: boolean): TStylingContext {
|
||||
initialStyling = initialStyling || allocStylingMapArray(null);
|
||||
let config = TStylingConfig.Initial;
|
||||
if (hasDirectives) {
|
||||
config |= TStylingConfig.HasDirectives;
|
||||
}
|
||||
if (initialStyling.length > StylingMapArrayIndex.ValuesStartPosition) {
|
||||
config |= TStylingConfig.HasInitialStyling;
|
||||
}
|
||||
return [
|
||||
config, // 1) config for the styling context
|
||||
DEFAULT_TOTAL_SOURCES, // 2) total amount of styling sources (template, directives, etc...)
|
||||
initialStyling, // 3) initial styling values
|
||||
DEFAULT_TOTAL_SOURCES, // 1) total amount of styling sources (template, directives, etc...)
|
||||
initialStyling, // 2) initial styling values
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -62,12 +54,8 @@ export function allocStylingMapArray(value: {} | string | null): StylingMapArray
|
|||
return [value];
|
||||
}
|
||||
|
||||
export function getConfig(context: TStylingContext) {
|
||||
return context[TStylingContextIndex.ConfigPosition];
|
||||
}
|
||||
|
||||
export function hasConfig(context: TStylingContext, flag: TStylingConfig) {
|
||||
return (getConfig(context) & flag) !== 0;
|
||||
export function hasConfig(tNode: TStylingNode, flag: TNodeFlags) {
|
||||
return (tNode.flags & flag) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,36 +69,35 @@ export function hasConfig(context: TStylingContext, flag: TStylingConfig) {
|
|||
* 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(context: TStylingContext, firstUpdatePass: boolean): boolean {
|
||||
export function allowDirectStyling(
|
||||
tNode: TStylingNode, isClassBased: boolean, firstUpdatePass: boolean): boolean {
|
||||
let allow = false;
|
||||
const config = getConfig(context);
|
||||
const hasNoDirectives = (config & TStylingConfig.HasDirectives) === 0;
|
||||
|
||||
// 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...
|
||||
if (hasNoDirectives) {
|
||||
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 hasNoCollisions = (config & TStylingConfig.HasCollisions) === 0;
|
||||
const hasOnlyMapsOrOnlyProps =
|
||||
(config & TStylingConfig.HasPropAndMapBindings) !== TStylingConfig.HasPropAndMapBindings;
|
||||
allow = hasNoCollisions && hasOnlyMapsOrOnlyProps;
|
||||
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 setConfig(context: TStylingContext, value: TStylingConfig): void {
|
||||
context[TStylingContextIndex.ConfigPosition] = value;
|
||||
}
|
||||
|
||||
export function patchConfig(context: TStylingContext, flag: TStylingConfig): void {
|
||||
context[TStylingContextIndex.ConfigPosition] |= flag;
|
||||
export function patchConfig(tNode: TStylingNode, flag: TNodeFlags): void {
|
||||
tNode.flags |= flag;
|
||||
}
|
||||
|
||||
export function getProp(context: TStylingContext, index: number): string {
|
||||
|
@ -173,9 +160,11 @@ export function getValue<T = any>(data: LStylingData, bindingIndex: number): T|n
|
|||
return bindingIndex !== 0 ? data[bindingIndex] as T : null;
|
||||
}
|
||||
|
||||
export function getPropValuesStartPosition(context: TStylingContext) {
|
||||
export function getPropValuesStartPosition(
|
||||
context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) {
|
||||
let startPosition = TStylingContextIndex.ValuesStartPosition;
|
||||
if (hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
const flag = isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings;
|
||||
if (hasConfig(tNode, flag)) {
|
||||
startPosition += TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
|
||||
}
|
||||
return startPosition;
|
||||
|
@ -247,11 +236,11 @@ export function getInitialStylingValue(context: TStylingContext | StylingMapArra
|
|||
return map && (map[StylingMapArrayIndex.RawValuePosition] as string | null) || '';
|
||||
}
|
||||
|
||||
export function hasClassInput(tNode: TNode) {
|
||||
export function hasClassInput(tNode: TStylingNode) {
|
||||
return (tNode.flags & TNodeFlags.hasClassInput) !== 0;
|
||||
}
|
||||
|
||||
export function hasStyleInput(tNode: TNode) {
|
||||
export function hasStyleInput(tNode: TStylingNode) {
|
||||
return (tNode.flags & TNodeFlags.hasStyleInput) !== 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -632,9 +632,6 @@
|
|||
{
|
||||
"name": "getComponentViewByInstance"
|
||||
},
|
||||
{
|
||||
"name": "getConfig"
|
||||
},
|
||||
{
|
||||
"name": "getConstant"
|
||||
},
|
||||
|
@ -932,6 +929,9 @@
|
|||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
{
|
||||
"name": "isHostStyling"
|
||||
},
|
||||
{
|
||||
"name": "isHostStylingActive"
|
||||
},
|
||||
|
@ -1067,6 +1067,9 @@
|
|||
{
|
||||
"name": "patchConfig"
|
||||
},
|
||||
{
|
||||
"name": "patchHostStylingFlag"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
|
|
|
@ -5,11 +5,22 @@
|
|||
* 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 {registerBinding} from '@angular/core/src/render3/styling/bindings';
|
||||
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);
|
||||
|
@ -111,7 +122,9 @@ describe('styling context', () => {
|
|||
|
||||
function makeContextWithDebug(isClassBased: boolean) {
|
||||
const ctx = allocTStylingContext(null, false);
|
||||
return attachStylingDebugObject(ctx, isClassBased);
|
||||
const tNode: TStylingNode = {flags: 0};
|
||||
(ctx as any).tNode = ctx;
|
||||
return attachStylingDebugObject(ctx, tNode, isClassBased);
|
||||
}
|
||||
|
||||
function buildGuardMask(...bindingIndices: number[]) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 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';
|
||||
|
@ -15,12 +16,14 @@ describe('styling debugging tools', () => {
|
|||
() => {
|
||||
if (isIE()) return;
|
||||
|
||||
const debug = makeContextWithDebug(false);
|
||||
const context = debug.context;
|
||||
const data: any[] = [];
|
||||
const d = new NodeStylingDebug(context, data, false);
|
||||
const values = makeContextWithDebug(false);
|
||||
const context = values.context;
|
||||
const tNode = values.tNode;
|
||||
|
||||
registerBinding(context, 0, 0, 'width', null);
|
||||
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',
|
||||
|
@ -29,7 +32,7 @@ describe('styling debugging tools', () => {
|
|||
},
|
||||
});
|
||||
|
||||
registerBinding(context, 0, 0, 'width', '100px');
|
||||
registerBinding(context, tNode, 0, 0, 'width', '100px', false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
@ -41,7 +44,7 @@ describe('styling debugging tools', () => {
|
|||
const someBindingIndex1 = 1;
|
||||
data[someBindingIndex1] = '200px';
|
||||
|
||||
registerBinding(context, 0, 0, 'width', someBindingIndex1);
|
||||
registerBinding(context, tNode, 0, 0, 'width', someBindingIndex1, false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
@ -53,7 +56,7 @@ describe('styling debugging tools', () => {
|
|||
const someBindingIndex2 = 2;
|
||||
data[someBindingIndex2] = '500px';
|
||||
|
||||
registerBinding(context, 0, 1, 'width', someBindingIndex2);
|
||||
registerBinding(context, tNode, 0, 1, 'width', someBindingIndex2, false, false);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
@ -66,8 +69,14 @@ describe('styling debugging tools', () => {
|
|||
});
|
||||
|
||||
function makeContextWithDebug(isClassBased: boolean) {
|
||||
const ctx = allocTStylingContext(null, false);
|
||||
return attachStylingDebugObject(ctx, isClassBased);
|
||||
const context = allocTStylingContext(null, false);
|
||||
const tNode = createTStylingNode();
|
||||
attachStylingDebugObject(context, tNode, isClassBased);
|
||||
return {context, tNode};
|
||||
}
|
||||
|
||||
function createTStylingNode(): TStylingNode {
|
||||
return {flags: 0};
|
||||
}
|
||||
|
||||
function isIE() {
|
||||
|
|
Loading…
Reference in New Issue