feat(ivy): Change static priority resolution to be same level as directive it belongs to (#34938)
This change changes the priority order of static styling. Current priority: ``` (least priority) - Static - Component - Directives - Template - Dynamic Binding - Component - Map/Interpolation - Property - Directives - Map/Interpolation - Property - Template - Map/Interpolation - Property (highest priority) ``` The issue with the above priority is this use case: ``` <div style="color: red;" directive-which-sets-color-blue> ``` In the above case the directive will win and the resulting color will be `blue`. However a small change of adding interpolation to the example like so. (Style interpolation is coming in https://github.com/angular/angular/pull/34202) ``` <div style="color: red; width: {{exp}}px" directive-which-sets-color-blue> ``` Changes the priority from static binding to interpolated binding which means now the resulting color is `red`. It is very surprising that adding an unrelated interpolation and style can change the `color` which was not changed. To fix that we need to make sure that the static values are associated with priority of the source (directive or template) where they were declared. The new resulting priority is: ``` (least priority) - Component - Static - Map/Interpolation - Property - Directives - Static - Map/Interpolation - Property - Template - Static - Map/Interpolation - Property (highest priority) ``` PR Close #34938
This commit is contained in:
parent
cf07d428a1
commit
ee8b8f52aa
|
@ -12,7 +12,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2987,
|
||||
"main-es2015": 448306,
|
||||
"main-es2015": 448928,
|
||||
"polyfills-es2015": 52195
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {initNgDevMode} from '../../util/ng_dev_mode';
|
|||
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
||||
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
|
||||
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {PropertyAliases, TConstants, TContainerNode, TDirectiveDefs, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {SelectorFlags} from '../interfaces/projection';
|
||||
import {TQueries} from '../interfaces/query';
|
||||
import {RComment, RElement, RNode} from '../interfaces/renderer';
|
||||
|
@ -176,11 +176,12 @@ class TNode implements ITNode {
|
|||
public parent: TElementNode|TContainerNode|null, //
|
||||
public projection: number|(ITNode|RNode[])[]|null, //
|
||||
public styles: string|null, //
|
||||
public stylesMap: ArrayMap<any>|undefined|null, //
|
||||
public residualStyles: ArrayMap<any>|undefined|null, //
|
||||
public classes: string|null, //
|
||||
public classesMap: ArrayMap<any>|undefined|null, //
|
||||
public residualClasses: ArrayMap<any>|undefined|null, //
|
||||
public classBindings: TStylingRange, //
|
||||
public styleBindings: TStylingRange, //
|
||||
public directives: TDirectiveDefs|null, //
|
||||
) {}
|
||||
|
||||
get type_(): string {
|
||||
|
@ -240,9 +241,7 @@ class TNode implements ITNode {
|
|||
export const TNodeDebug = TNode;
|
||||
export type TNodeDebug = TNode;
|
||||
|
||||
export interface DebugStyleBindings extends Array<DebugStyleBinding|string|null> {
|
||||
[0]: string|null;
|
||||
}
|
||||
export interface DebugStyleBindings extends Array<ArrayMap<any>|DebugStyleBinding|string|null> {}
|
||||
export interface DebugStyleBinding {
|
||||
key: TStylingKey;
|
||||
index: number;
|
||||
|
@ -276,7 +275,7 @@ function toDebugStyleBinding(tNode: TNode, isClassBased: boolean): DebugStyleBin
|
|||
if (cursor === prev) isTemplate = false;
|
||||
cursor = getTStylingRangePrev(itemRange);
|
||||
}
|
||||
bindings.unshift(isClassBased ? tNode.classes : tNode.styles);
|
||||
bindings.push((isClassBased ? tNode.residualClasses : tNode.residualStyles) || null);
|
||||
return bindings;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} fr
|
|||
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||
import {AttributeMarker, DirectiveDefsValues, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||
import {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||
|
@ -37,7 +37,6 @@ import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
|
|||
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
||||
import {getLViewParent} from '../util/view_traversal_utils';
|
||||
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
|
||||
import {selectIndexInternal} from './advance';
|
||||
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
|
||||
|
||||
|
@ -98,7 +97,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
|
|||
} else {
|
||||
// If it's not a number, it's a host binding function that needs to be executed.
|
||||
if (instruction !== null) {
|
||||
setBindingRootForHostBindings(bindingRootIndex);
|
||||
setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex);
|
||||
const hostCtx = lView[currentDirectiveIndex];
|
||||
instruction(RenderFlags.Update, hostCtx);
|
||||
}
|
||||
|
@ -824,11 +823,12 @@ export function createTNode(
|
|||
tParent, // parent: TElementNode|TContainerNode|null
|
||||
null, // projection: number|(ITNode|RNode[])[]|null
|
||||
null, // styles: string|null
|
||||
undefined, // stylesMap: string|null
|
||||
undefined, // residualStyles: string|null
|
||||
null, // classes: string|null
|
||||
undefined, // classesMap: string|null
|
||||
undefined, // residualClasses: string|null
|
||||
0 as any, // classBindings: TStylingRange;
|
||||
0 as any, // styleBindings: TStylingRange;
|
||||
null, // directives: TDirectiveDefs|null;
|
||||
) :
|
||||
{
|
||||
type: type,
|
||||
|
@ -853,11 +853,12 @@ export function createTNode(
|
|||
parent: tParent,
|
||||
projection: null,
|
||||
styles: null,
|
||||
stylesMap: undefined,
|
||||
residualStyles: undefined,
|
||||
classes: null,
|
||||
classesMap: undefined,
|
||||
residualClasses: undefined,
|
||||
classBindings: 0 as any,
|
||||
styleBindings: 0 as any,
|
||||
directives: null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1111,6 +1112,7 @@ export function resolveDirectives(
|
|||
const exportsMap: ({[key: string]: number} | null) = localRefs === null ? null : {'': -1};
|
||||
|
||||
if (directiveDefs !== null) {
|
||||
tNode.directives = [DirectiveDefsValues.INITIAL_STYLING_CURSOR_VALUE];
|
||||
let totalDirectiveHostVars = 0;
|
||||
hasDirectives = true;
|
||||
initTNodeFlags(tNode, tView.data.length, directiveDefs.length);
|
||||
|
@ -1129,6 +1131,7 @@ export function resolveDirectives(
|
|||
let preOrderCheckHooksFound = false;
|
||||
for (let i = 0; i < directiveDefs.length; i++) {
|
||||
const def = directiveDefs[i];
|
||||
tNode.directives.push(def);
|
||||
// Merge the attrs in the order of matches. This assumes that the first directive is the
|
||||
// component itself, so that the component has the least priority.
|
||||
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
|
||||
|
|
|
@ -10,18 +10,19 @@ import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass';
|
|||
import {stylePropNeedsSanitization, ɵɵsanitizeStyle} from '../../sanitization/sanitization';
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {ArrayMap, arrayMapGet, arrayMapSet} from '../../util/array_utils';
|
||||
import {assertDefined, assertEqual, assertLessThan, throwError} from '../../util/assert';
|
||||
import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual, assertNotSame, throwError} from '../../util/assert';
|
||||
import {EMPTY_ARRAY} from '../../util/empty';
|
||||
import {concatStringsWithSpace, stringify} from '../../util/stringify';
|
||||
import {assertFirstUpdatePass} from '../assert';
|
||||
import {bindingUpdated} from '../bindings';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {DirectiveDef} from '../interfaces/definition';
|
||||
import {AttributeMarker, DirectiveDefs, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {RElement, Renderer3} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate} from '../interfaces/styling';
|
||||
import {HEADER_OFFSET, LView, RENDERER, TData, TVIEW, TView} from '../interfaces/view';
|
||||
import {applyStyling} from '../node_manipulation';
|
||||
import {getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, setCurrentStyleSanitizer} from '../state';
|
||||
import {getCurrentDirectiveIndex, getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, setCurrentStyleSanitizer} from '../state';
|
||||
import {insertTStylingBinding} from '../styling/style_binding_list';
|
||||
import {getLastParsedKey, getLastParsedValue, parseClassName, parseClassNameNext, parseStyle, parseStyleNext} from '../styling/styling_parser';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
|
@ -239,6 +240,14 @@ export function checkStylingMap(
|
|||
// if so as not to read unnecessarily.
|
||||
const tNode = tView.data[getSelectedIndex() + HEADER_OFFSET] as TNode;
|
||||
if (hasStylingInputShadow(tNode, isClassBased) && !isInHostBindings(tView, bindingIndex)) {
|
||||
if (ngDevMode) {
|
||||
// verify that if we are shadowing then `TData` is appropriately marked so that we skip
|
||||
// processing this binding in styling resolution.
|
||||
const tStylingKey = tView.data[bindingIndex];
|
||||
assertEqual(
|
||||
Array.isArray(tStylingKey) ? tStylingKey[1] : tStylingKey, false,
|
||||
'Styling linked list shadow input should be marked as \'false\'');
|
||||
}
|
||||
// VE does not concatenate the static portion like we are doing here.
|
||||
// Instead VE just ignores the static completely if dynamic binding is present.
|
||||
// Because of locality we have already set the static portion because we don't know if there
|
||||
|
@ -305,10 +314,216 @@ function stylingPropertyFirstUpdatePass(
|
|||
// We turn this into a noop by setting the key to `false`
|
||||
tStylingKey = false;
|
||||
}
|
||||
tStylingKey = wrapInStaticStylingKey(tData, tNode, tStylingKey, isClassBased);
|
||||
insertTStylingBinding(tData, tNode, tStylingKey, bindingIndex, isHostBindings, isClassBased);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds static styling information to the binding if applicable.
|
||||
*
|
||||
* The linked list of styles not only stores the list and keys, but also stores static styling
|
||||
* information on some of the keys. This function determines if the key should contain the styling
|
||||
* information and computes it.
|
||||
*
|
||||
* See `TStylingStatic` for more details.
|
||||
*
|
||||
* @param tData `TData` where the linked list is stored.
|
||||
* @param tNode `TNode` for which the styling is being computed.
|
||||
* @param stylingKey `TStylingKeyPrimitive` which may need to be wrapped into `TStylingKey`
|
||||
* @param isClassBased `true` if `class` (`false` if `style`)
|
||||
*/
|
||||
export function wrapInStaticStylingKey(
|
||||
tData: TData, tNode: TNode, stylingKey: TStylingKey, isClassBased: boolean): TStylingKey {
|
||||
const hostDirectiveDef = getHostDirectiveDef(tData);
|
||||
let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles;
|
||||
if (hostDirectiveDef === null) {
|
||||
// We are in template node.
|
||||
// If template node already had styling instruction then it has already collected the static
|
||||
// styling and there is no need to collect them again. We know that we are the first styling
|
||||
// instruction because the `TNode.*Bindings` points to 0 (nothing has been inserted yet).
|
||||
const isFirstStylingInstructionInTemplate =
|
||||
(isClassBased ? tNode.classBindings : tNode.styleBindings) as any as number === 0;
|
||||
if (isFirstStylingInstructionInTemplate) {
|
||||
// It would be nice to be able to get the statics from `mergeAttrs`, however, at this point
|
||||
// they are already merged and it would not be possible to figure which property belongs where
|
||||
// in the priority.
|
||||
stylingKey = collectStylingFromDirectives(null, tData, tNode, stylingKey, isClassBased);
|
||||
stylingKey = collectStylingFromTAttrs(stylingKey, tNode.attrs, isClassBased);
|
||||
// We know that if we have styling binding in template we can't have residual.
|
||||
residual = null;
|
||||
}
|
||||
} else {
|
||||
// We are in host binding node and there was no binding instruction in template node.
|
||||
// This means that we need to compute the residual.
|
||||
const directives = tNode.directives;
|
||||
const isFirstStylingInstructionInHostBinding = directives !== null &&
|
||||
directives[directives[DirectiveDefs.STYLING_CURSOR]] !== hostDirectiveDef;
|
||||
if (isFirstStylingInstructionInHostBinding) {
|
||||
stylingKey =
|
||||
collectStylingFromDirectives(hostDirectiveDef, tData, tNode, stylingKey, isClassBased);
|
||||
if (residual === null) {
|
||||
// - If `null` than either:
|
||||
// - Template styling instruction already ran and it has consumed the static
|
||||
// styling into its `TStylingKey` and so there is no need to update residual. Instead
|
||||
// we need to update the `TStylingKey` associated with the first template node
|
||||
// instruction. OR
|
||||
// - Some other styling instruction ran and determined that there are no residuals
|
||||
let templateStylingKey = getTemplateHeadTStylingKey(tData, tNode, isClassBased);
|
||||
if (templateStylingKey !== undefined && Array.isArray(templateStylingKey)) {
|
||||
// Only recompute if `templateStylingKey` had static values. (If no static value found
|
||||
// then there is nothing to do since this operation can only produce less static keys, not
|
||||
// more.)
|
||||
templateStylingKey = collectStylingFromDirectives(
|
||||
null, tData, tNode, templateStylingKey[1] /* unwrap previous statics */,
|
||||
isClassBased);
|
||||
templateStylingKey =
|
||||
collectStylingFromTAttrs(templateStylingKey, tNode.attrs, isClassBased);
|
||||
setTemplateHeadTStylingKey(tData, tNode, isClassBased, templateStylingKey);
|
||||
}
|
||||
} else {
|
||||
// We only need to recompute residual if it is not `null`.
|
||||
// - If existing residual (implies there was no template styling). This means that some of
|
||||
// the statics may have moved from the residual to the `stylingKey` and so we have to
|
||||
// recompute.
|
||||
// - If `undefined` this is the first time we are running.
|
||||
residual = collectResidual(tNode, isClassBased);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (residual !== undefined) {
|
||||
isClassBased ? (tNode.residualClasses = residual) : (tNode.residualStyles = residual);
|
||||
}
|
||||
return stylingKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the `TStylingKey` for the template styling instruction.
|
||||
*
|
||||
* This is needed since `hostBinding` styling instructions are inserted after the template
|
||||
* instruction. While the template instruction needs to update the residual in `TNode` the
|
||||
* `hostBinding` instructions need to update the `TStylingKey` of the template instruction because
|
||||
* the template instruction is downstream from the `hostBindings` instructions.
|
||||
*
|
||||
* @param tData `TData` where the linked list is stored.
|
||||
* @param tNode `TNode` for which the styling is being computed.
|
||||
* @param isClassBased `true` if `class` (`false` if `style`)
|
||||
* @return `TStylingKey` if found or `undefined` if not found.
|
||||
*/
|
||||
function getTemplateHeadTStylingKey(tData: TData, tNode: TNode, isClassBased: boolean): TStylingKey|
|
||||
undefined {
|
||||
const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings;
|
||||
if (getTStylingRangeNext(bindings) === 0) {
|
||||
// There does not seem to be a styling instruction in the `template`.
|
||||
return undefined;
|
||||
}
|
||||
return tData[getTStylingRangePrev(bindings)] as TStylingKey;
|
||||
}
|
||||
|
||||
function setTemplateHeadTStylingKey(
|
||||
tData: TData, tNode: TNode, isClassBased: boolean, tStylingKey: TStylingKey): void {
|
||||
const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings;
|
||||
ngDevMode && assertNotEqual(
|
||||
getTStylingRangeNext(bindings), 0,
|
||||
'Expecting to have at least one template styling binding.');
|
||||
tData[getTStylingRangePrev(bindings)] = tStylingKey;
|
||||
}
|
||||
|
||||
function collectResidual(tNode: TNode, isClassBased: boolean): ArrayMap<any>|null {
|
||||
let residual: ArrayMap<any>|null|undefined = undefined;
|
||||
const directives = tNode.directives;
|
||||
if (directives) {
|
||||
for (let i = directives[DirectiveDefs.STYLING_CURSOR] + 1; i < directives.length; i++) {
|
||||
const attrs = (directives[i] as DirectiveDef<any>).hostAttrs;
|
||||
residual = collectStylingFromTAttrs(residual, attrs, isClassBased) as ArrayMap<any>| null;
|
||||
}
|
||||
}
|
||||
return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased) as ArrayMap<any>| null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the static styling information with lower priority than `hostDirectiveDef`.
|
||||
*
|
||||
* (This is opposite of residual styling.)
|
||||
*
|
||||
* @param hostDirectiveDef `DirectiveDef` for which we want to collect lower priority static
|
||||
* styling. (Or `null` if template styling)
|
||||
* @param tData `TData` where the linked list is stored.
|
||||
* @param tNode `TNode` for which the styling is being computed.
|
||||
* @param stylingKey Existing `TStylingKey` to update or wrap.
|
||||
* @param isClassBased `true` if `class` (`false` if `style`)
|
||||
*/
|
||||
function collectStylingFromDirectives(
|
||||
hostDirectiveDef: DirectiveDef<any>| null, tData: TData, tNode: TNode, stylingKey: TStylingKey,
|
||||
isClassBased: boolean): TStylingKey {
|
||||
const directives = tNode.directives;
|
||||
if (directives != null) {
|
||||
ngDevMode && hostDirectiveDef &&
|
||||
assertGreaterThanOrEqual(
|
||||
directives.indexOf(hostDirectiveDef, directives[DirectiveDefs.STYLING_CURSOR]), 0,
|
||||
'Expecting that the current directive is in the directive list');
|
||||
// We need to loop because there can be directives which have `hostAttrs` but don't have
|
||||
// `hostBindings` so this loop catches up up to the current directive..
|
||||
let currentDirective: DirectiveDef<any>|null = null;
|
||||
let index = directives[DirectiveDefs.STYLING_CURSOR];
|
||||
while (index + 1 < directives.length) {
|
||||
index++;
|
||||
currentDirective = directives[index] as DirectiveDef<any>;
|
||||
ngDevMode && assertDefined(currentDirective, 'expected to be defined');
|
||||
stylingKey = collectStylingFromTAttrs(stylingKey, currentDirective.hostAttrs, isClassBased);
|
||||
if (currentDirective === hostDirectiveDef) break;
|
||||
}
|
||||
if (hostDirectiveDef !== null) {
|
||||
// we only advance the styling cursor if we are collecting data from host bindings.
|
||||
// Template executes before host bindings and so if we would update the index,
|
||||
// host bindings would not get their statics.
|
||||
directives[DirectiveDefs.STYLING_CURSOR] = index;
|
||||
}
|
||||
}
|
||||
return stylingKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert `TAttrs` into `TStylingStatic`.
|
||||
*
|
||||
* @param stylingKey existing `TStylingKey` to update or wrap.
|
||||
* @param attrs `TAttributes` to process.
|
||||
* @param isClassBased `true` if `class` (`false` if `style`)
|
||||
*/
|
||||
function collectStylingFromTAttrs(
|
||||
stylingKey: TStylingKey | undefined, attrs: TAttributes | null,
|
||||
isClassBased: boolean): TStylingKey {
|
||||
const desiredMarker = isClassBased ? AttributeMarker.Classes : AttributeMarker.Styles;
|
||||
let currentMarker = AttributeMarker.ImplicitAttributes;
|
||||
if (attrs !== null) {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const item = attrs[i] as number | string;
|
||||
if (typeof item === 'number') {
|
||||
currentMarker = item;
|
||||
} else {
|
||||
if (currentMarker === desiredMarker) {
|
||||
if (!Array.isArray(stylingKey)) {
|
||||
stylingKey = stylingKey === undefined ? [] : ['', stylingKey] as any;
|
||||
}
|
||||
arrayMapSet(stylingKey as ArrayMap<any>, item, isClassBased ? true : attrs[++i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return stylingKey === undefined ? null : stylingKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current `DirectiveDef` which is active when `hostBindings` style instruction is
|
||||
* being executed (or `null` if we are in `template`.)
|
||||
*
|
||||
* @param tData Current `TData` where the `DirectiveDef` will be looked up at.
|
||||
*/
|
||||
export function getHostDirectiveDef(tData: TData): DirectiveDef<any>|null {
|
||||
const currentDirectiveIndex = getCurrentDirectiveIndex();
|
||||
return currentDirectiveIndex === -1 ? null : tData[currentDirectiveIndex] as DirectiveDef<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert user input to `ArrayMap`.
|
||||
*
|
||||
|
@ -468,7 +683,7 @@ function updateStyling(
|
|||
const tData = tView.data;
|
||||
const tRange = tData[bindingIndex + 1] as TStylingRange;
|
||||
const higherPriorityValue = getTStylingRangeNextDuplicate(tRange) ?
|
||||
findStylingValue(tData, null, lView, prop, getTStylingRangeNext(tRange), isClassBased) :
|
||||
findStylingValue(tData, tNode, lView, prop, getTStylingRangeNext(tRange), isClassBased) :
|
||||
undefined;
|
||||
if (!isStylingValuePresent(higherPriorityValue)) {
|
||||
// We don't have a next duplicate, or we did not find a duplicate value.
|
||||
|
@ -476,8 +691,7 @@ function updateStyling(
|
|||
// We should delete current value or restore to lower priority value.
|
||||
if (getTStylingRangePrevDuplicate(tRange)) {
|
||||
// We have a possible prev duplicate, let's retrieve it.
|
||||
value =
|
||||
findStylingValue(tData, tNode, lView, prop, getTStylingRangePrev(tRange), isClassBased);
|
||||
value = findStylingValue(tData, null, lView, prop, bindingIndex, isClassBased);
|
||||
}
|
||||
}
|
||||
const rNode = getNativeByIndex(getSelectedIndex(), lView) as RElement;
|
||||
|
@ -505,9 +719,9 @@ function updateStyling(
|
|||
*
|
||||
* @param tData `TData` used for traversing the priority.
|
||||
* @param tNode `TNode` to use for resolving static styling. Also controls search direction.
|
||||
* - `TNode` search previous and quit as soon as `isStylingValuePresent(value)` is true.
|
||||
* If no value found consult `tNode.styleMap`/`tNode.classMap` for default value.
|
||||
* - `null` search next and go all the way to end. Return last value where
|
||||
* - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true.
|
||||
* If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value.
|
||||
* - `null` search prev and go all the way to end. Return last value where
|
||||
* `isStylingValuePresent(value)` is true.
|
||||
* @param lView `LView` used for retrieving the actual values.
|
||||
* @param prop Property which we are interested in.
|
||||
|
@ -519,29 +733,30 @@ function findStylingValue(
|
|||
isClassBased: boolean): any {
|
||||
let value: any = undefined;
|
||||
while (index > 0) {
|
||||
const key = tData[index] as TStylingKey;
|
||||
const currentValue = key === null ? arrayMapGet(lView[index + 1], prop) :
|
||||
key === prop ? lView[index + 1] : undefined;
|
||||
const rawKey = tData[index] as TStylingKey;
|
||||
const containsStatics = Array.isArray(rawKey);
|
||||
// Unwrap the key if we contain static values.
|
||||
const key = containsStatics ? (rawKey as string[])[1] : rawKey;
|
||||
let currentValue = key === null ? arrayMapGet(lView[index + 1], prop) :
|
||||
key === prop ? lView[index + 1] : undefined;
|
||||
if (containsStatics && !isStylingValuePresent(currentValue)) {
|
||||
currentValue = arrayMapGet(rawKey as ArrayMap<any>, prop);
|
||||
}
|
||||
if (isStylingValuePresent(currentValue)) {
|
||||
value = currentValue;
|
||||
if (tNode !== null) {
|
||||
if (tNode === null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
const tRange = tData[index + 1] as TStylingRange;
|
||||
index = tNode !== null ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange);
|
||||
index = tNode === null ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange);
|
||||
}
|
||||
if (tNode !== null) {
|
||||
// in case where we are going in previous direction AND we did not find anything, we need to
|
||||
// consult static styling
|
||||
let staticArrayMap = isClassBased ? tNode.classesMap : tNode.stylesMap;
|
||||
if (staticArrayMap === undefined) {
|
||||
// This is the first time we are here, and we need to initialize it.
|
||||
initializeStylingStaticArrayMap(tNode);
|
||||
staticArrayMap = isClassBased ? tNode.classesMap : tNode.stylesMap;
|
||||
}
|
||||
if (staticArrayMap !== null) {
|
||||
value = arrayMapGet(staticArrayMap !, prop);
|
||||
// in case where we are going in next direction AND we did not find anything, we need to
|
||||
// consult residual styling
|
||||
let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles;
|
||||
if (residual != null /** OR residual !=== undefined */) {
|
||||
value = arrayMapGet(residual !, prop);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
@ -571,8 +786,8 @@ function isStylingValuePresent(value: any): boolean {
|
|||
* @param tNode `TNode` to initialize.
|
||||
*/
|
||||
export function initializeStylingStaticArrayMap(tNode: TNode) {
|
||||
ngDevMode && assertEqual(tNode.classesMap, undefined, 'Already initialized!');
|
||||
ngDevMode && assertEqual(tNode.stylesMap, undefined, 'Already initialized!');
|
||||
ngDevMode && assertEqual(tNode.residualClasses, undefined, 'Already initialized!');
|
||||
ngDevMode && assertEqual(tNode.residualStyles, undefined, 'Already initialized!');
|
||||
let styleMap: ArrayMap<any>|null = null;
|
||||
let classMap: ArrayMap<any>|null = null;
|
||||
const mergeAttrs = tNode.mergedAttrs || EMPTY_ARRAY as TAttributes;
|
||||
|
@ -589,8 +804,8 @@ export function initializeStylingStaticArrayMap(tNode: TNode) {
|
|||
arrayMapSet(styleMap !, item as string, mergeAttrs[++i] as string);
|
||||
}
|
||||
}
|
||||
tNode.classesMap = classMap;
|
||||
tNode.stylesMap = styleMap;
|
||||
tNode.residualClasses = classMap;
|
||||
tNode.residualStyles = styleMap;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import {ArrayMap} from '../../util/array_utils';
|
||||
import {TStylingRange} from '../interfaces/styling';
|
||||
|
||||
import {DirectiveDef} from './definition';
|
||||
import {CssSelector} from './projection';
|
||||
import {RNode} from './renderer';
|
||||
import {LView, TView} from './view';
|
||||
|
@ -345,6 +346,13 @@ export interface TNode {
|
|||
*/
|
||||
mergedAttrs: TAttributes|null;
|
||||
|
||||
// TODO(misko): pre discussion with Kara, it seems that we don't need `directives` since the same
|
||||
// information is already present in the TData. Maybe worth refactoring.
|
||||
/**
|
||||
* Stores the directive defs matched on the current TNode (along with style cursor.)
|
||||
*/
|
||||
directives: TDirectiveDefs|null;
|
||||
|
||||
/**
|
||||
* A set of local names under which a given element is exported in a template and
|
||||
* visible to queries. An entry in this array can be created for different reasons:
|
||||
|
@ -481,7 +489,7 @@ export interface TNode {
|
|||
projection: (TNode|RNode[])[]|number|null;
|
||||
|
||||
/**
|
||||
* A collection of all style bindings and/or static style values for an element.
|
||||
* A collection of all style static values for an element.
|
||||
*
|
||||
* This field will be populated if and when:
|
||||
*
|
||||
|
@ -490,21 +498,36 @@ export interface TNode {
|
|||
styles: string|null;
|
||||
|
||||
/**
|
||||
* An `ArrayMap` version of `styles.
|
||||
* An `ArrayMap` version of residual `styles`.
|
||||
*
|
||||
* We need this when style bindings are resolving. This gets populated only if there are styling
|
||||
* binding instructions. The laziness is important since we don't want to allocate the memory
|
||||
* because most styling is static. For tree shaking purposes the code to create these only comes
|
||||
* with styling.
|
||||
* When there are styling instructions than each instruction stores the static styling
|
||||
* which is of lower priority than itself. This means that there may be a higher priority styling
|
||||
* than the instruction.
|
||||
*
|
||||
* Imagine:
|
||||
* ```
|
||||
* <div style="color: highest;" my-dir>
|
||||
*
|
||||
* @Directive({
|
||||
* host: {
|
||||
* style: 'color: lowest; ',
|
||||
* '[styles.color]': 'exp' // ɵɵstyleProp('color', ctx.exp);
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* In the above case:
|
||||
* - `color: lowest` is stored with `ɵɵstyleProp('color', ctx.exp);` instruction
|
||||
* - `color: highest` is the residual and is stored here.
|
||||
*
|
||||
* - `undefined': not initialized.
|
||||
* - `null`: initialized but `styles` is `null`
|
||||
* - `ArrayMap`: parsed version of `styles`.
|
||||
*/
|
||||
stylesMap: ArrayMap<any>|undefined|null;
|
||||
residualStyles: ArrayMap<any>|undefined|null;
|
||||
|
||||
/**
|
||||
* A collection of all class bindings and/or static class values for an element.
|
||||
* A collection of all class static values for an element.
|
||||
*
|
||||
* This field will be populated if and when:
|
||||
*
|
||||
|
@ -513,18 +536,15 @@ export interface TNode {
|
|||
classes: string|null;
|
||||
|
||||
/**
|
||||
* An `ArrayMap` version of `classes`.
|
||||
* An `ArrayMap` version of residual `classes`.
|
||||
*
|
||||
* We need this when style bindings are resolving. This gets populated only if there are styling
|
||||
* binding instructions. The laziness is important since we don't want to allocate the memory
|
||||
* because most styling is static. For tree shaking purposes the code to create these only comes
|
||||
* with styling.
|
||||
* Same as `TNode.residualStyles` but for classes.
|
||||
*
|
||||
* - `undefined': not initialized.
|
||||
* - `null`: initialized but `classes` is `null`
|
||||
* - `ArrayMap`: parsed version of `S`.
|
||||
*/
|
||||
classesMap: ArrayMap<any>|undefined|null;
|
||||
residualClasses: ArrayMap<any>|undefined|null;
|
||||
|
||||
/**
|
||||
* Stores the head/tail index of the class bindings.
|
||||
|
@ -793,4 +813,55 @@ export function hasClassInput(tNode: TNode) {
|
|||
*/
|
||||
export function hasStyleInput(tNode: TNode) {
|
||||
return (tNode.flags & TNodeFlags.hasStyleInput) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant enums for accessing data in the `TDirectiveDefs`
|
||||
*/
|
||||
export const enum DirectiveDefs {
|
||||
/// Location where the STYLING_CURSOR is stored.
|
||||
STYLING_CURSOR = 0,
|
||||
/// Header offset from which iterating over `DirectiveDefs` should start.
|
||||
HEADER_OFFSET = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant enums for initial values in the `TDirectiveDefs`
|
||||
*/
|
||||
export const enum DirectiveDefsValues {
|
||||
// Initial value for the `STYLING_CURSOR`
|
||||
INITIAL_STYLING_CURSOR_VALUE = 0,
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores `DirectiveDefs` associated with the current `TNode` as well as styling cursor.
|
||||
*/
|
||||
export interface TDirectiveDefs extends Array<number|DirectiveDef<any>> {
|
||||
/**
|
||||
* As styling instructions (`ɵɵstyleProp`/`ɵɵclassProp`/`ɵɵstyleMap`/`ɵɵclassMap`) are executing
|
||||
* they also need to get a hold of the `DirectiveDef.hostAttrs` and so that they know what
|
||||
* static styling values to use. The styling instructions need this information so that they can
|
||||
* lazily create `TStylingStatic`.
|
||||
*
|
||||
* When styling is executing it can get a hold of its `DirectiveDefs` but that alone is not
|
||||
* sufficient for two reasons:
|
||||
* 1. Styling instruction needs to coalesce other directives which came before it and which have
|
||||
* static value but may not have a styling instruction to attach the static values to.
|
||||
* 2. There may be more than one styling instruction per `hostBindings` and only the first
|
||||
* styling instruction should create the `TStylingStatic`.
|
||||
*
|
||||
* The algorithm for doing this is:
|
||||
* - look up the current `DirectiveDef` associated with the current instruction.
|
||||
* - If `STYLING_CURSOR === 0 || tDirectiveDefs[stylingCursor] !== currentDirectiveDef` than
|
||||
* create `TStylingStatic` and:
|
||||
* - iterate over `TDirectiveDefs[++stylingCursor]` and insert them into the `TStylingStatic`
|
||||
* until you reach `DirectiveDef` associated with the current instruction.
|
||||
* - If new `TStylingStatic` was created, recompute the residual styling values.
|
||||
*
|
||||
* The above algorithm will ensure that the styling instructions consume static styling values
|
||||
* associated until a given instruction. After consuming instructions, it is always important to
|
||||
* clear the residual (See `TNode.residualClass`/`TNode.residualStyle`), since this may be the
|
||||
* last styling instruction, and we need to lazily recreate the residual value on as needed basis.
|
||||
*/
|
||||
[DirectiveDefs.STYLING_CURSOR]: number;
|
||||
}
|
|
@ -6,16 +6,90 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ArrayMap} from '../../util/array_utils';
|
||||
import {assertNumber, assertNumberInRange} from '../../util/assert';
|
||||
|
||||
/**
|
||||
* Value stored in the `TData` which is needed to re-concatenate the styling.
|
||||
*
|
||||
* See: `TStylingKeyPrimitive` and `TStylingStatic`
|
||||
*/
|
||||
export type TStylingKey = TStylingKeyPrimitive | TStylingStatic;
|
||||
|
||||
|
||||
/**
|
||||
* The primitive portion (`TStylingStatic` removed) of the value stored in the `TData` which is
|
||||
* needed to re-concatenate the styling.
|
||||
*
|
||||
* - `string`: Stores the property name. Used with `ɵɵstyleProp`/`ɵɵclassProp` instruction.
|
||||
* - `null`: Represents map, so there is no name. Used with `ɵɵstyleMap`/`ɵɵclassMap`.
|
||||
* - `false`: Represents an ignore case. This happens when `ɵɵstyleProp`/`ɵɵclassProp` instruction
|
||||
* is combined with directive which shadows its input `@Input('class')`. That way the binding
|
||||
* should not participate in the styling resolution.
|
||||
*/
|
||||
export type TStylingKey = string | null | false;
|
||||
export type TStylingKeyPrimitive = string | null | false;
|
||||
|
||||
/**
|
||||
* Store the static values for the styling binding.
|
||||
*
|
||||
* The `TStylingStatic` is just `ArrayMap` where key `""` (stored at location 0) contains the
|
||||
* `TStylingKey` (stored at location 1). In other words this wraps the `TStylingKey` such that the
|
||||
* `""` contains the wrapped value.
|
||||
*
|
||||
* When instructions are resolving styling they may need to look forward or backwards in the linked
|
||||
* list to resolve the value. For this reason we have to make sure that he linked list also contains
|
||||
* the static values. However the list only has space for one item per styling instruction. For this
|
||||
* reason we store the static values here as part of the `TStylingKey`. This means that the
|
||||
* resolution function when looking for a value needs to first look at the binding value, and than
|
||||
* at `TStylingKey` (if it exists).
|
||||
*
|
||||
* Imagine we have:
|
||||
*
|
||||
* ```
|
||||
* <div class="TEMPLATE" my-dir>
|
||||
*
|
||||
* @Directive({
|
||||
* host: {
|
||||
* class: 'DIR',
|
||||
* '[class.dynamic]': 'exp' // ɵɵclassProp('dynamic', ctx.exp);
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* In the above case the linked list will contain one item:
|
||||
*
|
||||
* ```
|
||||
* // assume binding location: 10 for `ɵɵclassProp('dynamic', ctx.exp);`
|
||||
* tData[10] = <TStylingStatic>[
|
||||
* '': 'dynamic', // This is the wrapped value of `TStylingKey`
|
||||
* 'DIR': true, // This is the default static value of directive binding.
|
||||
* ];
|
||||
* tData[10 + 1] = 0; // We don't have prev/next.
|
||||
*
|
||||
* lView[10] = undefined; // assume `ctx.exp` is `undefined`
|
||||
* lView[10 + 1] = undefined; // Just normalized `lView[10]`
|
||||
* ```
|
||||
*
|
||||
* So when the function is resolving styling value, it first needs to look into the linked list
|
||||
* (there is none) and than into the static `TStylingStatic` too see if there is a default value for
|
||||
* `dynamic` (there is not). Therefore it is safe to remove it.
|
||||
*
|
||||
* If setting `true` case:
|
||||
* ```
|
||||
* lView[10] = true; // assume `ctx.exp` is `true`
|
||||
* lView[10 + 1] = true; // Just normalized `lView[10]`
|
||||
* ```
|
||||
* So when the function is resolving styling value, it first needs to look into the linked list
|
||||
* (there is none) and than into `TNode.residualClass` (TNode.residualStyle) which contains
|
||||
* ```
|
||||
* tNode.residualClass = [
|
||||
* 'TEMPLATE': true,
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* This means that it is safe to add class.
|
||||
*/
|
||||
export interface TStylingStatic extends ArrayMap<any> {}
|
||||
|
||||
/**
|
||||
* This is a branded number which contains previous and next index.
|
||||
|
@ -52,14 +126,17 @@ export interface TStylingRange { __brand__: 'TStylingRange'; }
|
|||
*/
|
||||
export const enum StylingRange {
|
||||
/// Number of bits to shift for the previous pointer
|
||||
PREV_SHIFT = 18,
|
||||
PREV_SHIFT = 17,
|
||||
/// Previous pointer mask.
|
||||
PREV_MASK = 0xFFFC0000,
|
||||
PREV_MASK = 0xFFFE0000,
|
||||
|
||||
/// Number of bits to shift for the next pointer
|
||||
NEXT_SHIFT = 2,
|
||||
/// Next pointer mask.
|
||||
NEXT_MASK = 0x0003FFC,
|
||||
NEXT_MASK = 0x001FFFC,
|
||||
|
||||
// Mask to remove nagative bit. (interpret number as positive)
|
||||
UNSIGNED_MASK = 0x7FFF,
|
||||
|
||||
/**
|
||||
* This bit is set if the previous bindings contains a binding which could possibly cause a
|
||||
|
@ -80,49 +157,62 @@ export const enum StylingRange {
|
|||
|
||||
|
||||
export function toTStylingRange(prev: number, next: number): TStylingRange {
|
||||
ngDevMode && assertNumberInRange(prev, 0, StylingRange.UNSIGNED_MASK);
|
||||
ngDevMode && assertNumberInRange(next, 0, StylingRange.UNSIGNED_MASK);
|
||||
return (prev << StylingRange.PREV_SHIFT | next << StylingRange.NEXT_SHIFT) as any;
|
||||
}
|
||||
|
||||
export function getTStylingRangePrev(tStylingRange: TStylingRange): number {
|
||||
return (tStylingRange as any as number) >> StylingRange.PREV_SHIFT;
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
return ((tStylingRange as any as number) >> StylingRange.PREV_SHIFT) & StylingRange.UNSIGNED_MASK;
|
||||
}
|
||||
|
||||
export function getTStylingRangePrevDuplicate(tStylingRange: TStylingRange): boolean {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
return ((tStylingRange as any as number) & StylingRange.PREV_DUPLICATE) ==
|
||||
StylingRange.PREV_DUPLICATE;
|
||||
}
|
||||
|
||||
export function setTStylingRangePrev(
|
||||
tStylingRange: TStylingRange, previous: number): TStylingRange {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
ngDevMode && assertNumberInRange(previous, 0, StylingRange.UNSIGNED_MASK);
|
||||
return (
|
||||
((tStylingRange as any as number) & ~StylingRange.PREV_MASK) |
|
||||
(previous << StylingRange.PREV_SHIFT)) as any;
|
||||
}
|
||||
|
||||
export function setTStylingRangePrevDuplicate(tStylingRange: TStylingRange): TStylingRange {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
return ((tStylingRange as any as number) | StylingRange.PREV_DUPLICATE) as any;
|
||||
}
|
||||
|
||||
export function getTStylingRangeNext(tStylingRange: TStylingRange): number {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
return ((tStylingRange as any as number) & StylingRange.NEXT_MASK) >> StylingRange.NEXT_SHIFT;
|
||||
}
|
||||
|
||||
export function setTStylingRangeNext(tStylingRange: TStylingRange, next: number): TStylingRange {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
ngDevMode && assertNumberInRange(next, 0, StylingRange.UNSIGNED_MASK);
|
||||
return (
|
||||
((tStylingRange as any as number) & ~StylingRange.NEXT_MASK) | //
|
||||
next << StylingRange.NEXT_SHIFT) as any;
|
||||
}
|
||||
|
||||
export function getTStylingRangeNextDuplicate(tStylingRange: TStylingRange): boolean {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
return ((tStylingRange as any as number) & StylingRange.NEXT_DUPLICATE) ===
|
||||
StylingRange.NEXT_DUPLICATE;
|
||||
}
|
||||
|
||||
export function setTStylingRangeNextDuplicate(tStylingRange: TStylingRange): TStylingRange {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
return ((tStylingRange as any as number) | StylingRange.NEXT_DUPLICATE) as any;
|
||||
}
|
||||
|
||||
export function getTStylingRangeTail(tStylingRange: TStylingRange): number {
|
||||
ngDevMode && assertNumber(tStylingRange, 'expected number');
|
||||
const next = getTStylingRangeNext(tStylingRange);
|
||||
return next === 0 ? getTStylingRangePrev(tStylingRange) : next;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
import {assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
|
||||
import {assertDefined} from '../util/assert';
|
||||
import {assertLViewOrUndefined} from './assert';
|
||||
import {TNode} from './interfaces/node';
|
||||
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view';
|
||||
|
@ -104,6 +104,13 @@ interface LFrame {
|
|||
* We iterate over the list of Queries and increment current query index at every step.
|
||||
*/
|
||||
currentQueryIndex: number;
|
||||
|
||||
/**
|
||||
* When host binding is executing this points to the directive index.
|
||||
* `TView.data[currentDirectiveIndex]` is `DirectiveDef`
|
||||
* `LView[currentDirectiveIndex]` is directive instance.
|
||||
*/
|
||||
currentDirectiveIndex: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,11 +339,25 @@ export function incrementBindingIndex(count: number): number {
|
|||
* Bindings inside the host template are 0 index. But because we don't know ahead of time
|
||||
* how many host bindings we have we can't pre-compute them. For this reason they are all
|
||||
* 0 index and we just shift the root so that they match next available location in the LView.
|
||||
* @param value
|
||||
*
|
||||
* @param bindingRootIndex Root index for `hostBindings`
|
||||
* @param currentDirectiveIndex `TData[currentDirectiveIndex]` will point to the current directive
|
||||
* whose `hostBindings` are being processed.
|
||||
*/
|
||||
export function setBindingRootForHostBindings(value: number) {
|
||||
const lframe = instructionState.lFrame;
|
||||
lframe.bindingIndex = lframe.bindingRootIndex = value;
|
||||
export function setBindingRootForHostBindings(
|
||||
bindingRootIndex: number, currentDirectiveIndex: number) {
|
||||
const lFrame = instructionState.lFrame;
|
||||
lFrame.bindingIndex = lFrame.bindingRootIndex = bindingRootIndex;
|
||||
lFrame.currentDirectiveIndex = currentDirectiveIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* When host binding is executing this points to the directive index.
|
||||
* `TView.data[getCurrentDirectiveIndex()]` is `DirectiveDef`
|
||||
* `LView[getCurrentDirectiveIndex()]` is directive instance.
|
||||
*/
|
||||
export function getCurrentDirectiveIndex(): number {
|
||||
return instructionState.lFrame.currentDirectiveIndex;
|
||||
}
|
||||
|
||||
export function getCurrentQueryIndex(): number {
|
||||
|
@ -403,6 +424,7 @@ export function enterView(newView: LView, tNode: TNode | null): void {
|
|||
newLFrame.selectedIndex = 0;
|
||||
newLFrame.contextLView = newView !;
|
||||
newLFrame.elementDepthCount = 0;
|
||||
newLFrame.currentDirectiveIndex = -1;
|
||||
newLFrame.currentNamespace = null;
|
||||
newLFrame.currentSanitizer = null;
|
||||
newLFrame.bindingRootIndex = -1;
|
||||
|
@ -430,6 +452,7 @@ function createLFrame(parent: LFrame | null): LFrame {
|
|||
elementDepthCount: 0, //
|
||||
currentNamespace: null, //
|
||||
currentSanitizer: null, //
|
||||
currentDirectiveIndex: -1, //
|
||||
bindingRootIndex: -1, //
|
||||
bindingIndex: -1, //
|
||||
currentQueryIndex: 0, //
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertEqual} from '../../util/assert';
|
||||
import {ArrayMap, arrayMapIndexOf} from '../../util/array_utils';
|
||||
import {assertDataInRange, assertEqual, assertNotEqual} from '../../util/assert';
|
||||
import {assertFirstUpdatePass} from '../assert';
|
||||
import {TNode} from '../interfaces/node';
|
||||
import {TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangePrev, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange} from '../interfaces/styling';
|
||||
import {TStylingKey, TStylingKeyPrimitive, TStylingRange, getTStylingRangeNext, getTStylingRangePrev, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange} from '../interfaces/styling';
|
||||
import {TData, TVIEW} from '../interfaces/view';
|
||||
import {getLView} from '../state';
|
||||
import {getLastParsedKey, parseClassName, parseClassNameNext, parseStyle, parseStyleNext} from './styling_parser';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -191,14 +192,28 @@ let __unused_const_as_closure_does_not_like_standalone_comment_blocks__: undefin
|
|||
* `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.)
|
||||
*/
|
||||
export function insertTStylingBinding(
|
||||
tData: TData, tNode: TNode, tStylingKey: TStylingKey, index: number, isHostBinding: boolean,
|
||||
isClassBinding: boolean): void {
|
||||
tData: TData, tNode: TNode, tStylingKeyWithStatic: TStylingKey, index: number,
|
||||
isHostBinding: boolean, isClassBinding: boolean): void {
|
||||
ngDevMode && assertFirstUpdatePass(getLView()[TVIEW]);
|
||||
let tBindings = isClassBinding ? tNode.classBindings : tNode.styleBindings;
|
||||
let tmplHead = getTStylingRangePrev(tBindings);
|
||||
let tmplTail = getTStylingRangeNext(tBindings);
|
||||
|
||||
tData[index] = tStylingKey;
|
||||
tData[index] = tStylingKeyWithStatic;
|
||||
let isKeyDuplicateOfStatic = false;
|
||||
let tStylingKey: TStylingKeyPrimitive;
|
||||
if (Array.isArray(tStylingKeyWithStatic)) {
|
||||
// We are case when the `TStylingKey` contains static fields as well.
|
||||
const staticArrayMap = tStylingKeyWithStatic as ArrayMap<any>;
|
||||
tStylingKey = staticArrayMap[1]; // unwrap.
|
||||
// We need to check if our key is present in the static so that we can mark it as duplicate.
|
||||
if (tStylingKey === null || arrayMapIndexOf(staticArrayMap, tStylingKey as string) > 0) {
|
||||
// tStylingKey is present in the statics, need to mark it as duplicate.
|
||||
isKeyDuplicateOfStatic = true;
|
||||
}
|
||||
} else {
|
||||
tStylingKey = tStylingKeyWithStatic;
|
||||
}
|
||||
if (isHostBinding) {
|
||||
// We are inserting host bindings
|
||||
|
||||
|
@ -248,10 +263,12 @@ export function insertTStylingBinding(
|
|||
|
||||
// Now we need to update / compute the duplicates.
|
||||
// Starting with our location search towards head (least priority)
|
||||
markDuplicates(
|
||||
tData, tStylingKey, index, (isClassBinding ? tNode.classes : tNode.styles) || '', true,
|
||||
isClassBinding);
|
||||
markDuplicates(tData, tStylingKey, index, '', false, isClassBinding);
|
||||
if (isKeyDuplicateOfStatic) {
|
||||
tData[index + 1] = setTStylingRangePrevDuplicate(tData[index + 1] as TStylingRange);
|
||||
}
|
||||
markDuplicates(tData, tStylingKey, index, true, isClassBinding);
|
||||
markDuplicates(tData, tStylingKey, index, false, isClassBinding);
|
||||
markDuplicateOfResidualStyling(tNode, tStylingKey, tData, index, isClassBinding);
|
||||
|
||||
tBindings = toTStylingRange(tmplHead, tmplTail);
|
||||
if (isClassBinding) {
|
||||
|
@ -261,6 +278,27 @@ export function insertTStylingBinding(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look into the residual styling to see if the current `tStylingKey` is duplicate of residual.
|
||||
*
|
||||
* @param tNode `TNode` where the residual is stored.
|
||||
* @param tStylingKey `TStylingKey` to store.
|
||||
* @param tData `TData` associated with the current `LView`.
|
||||
* @param index location of where `tStyleValue` should be stored (and linked into list.)
|
||||
* @param isClassBinding True if the associated `tStylingKey` as a `class` styling.
|
||||
* `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.)
|
||||
*/
|
||||
function markDuplicateOfResidualStyling(
|
||||
tNode: TNode, tStylingKey: TStylingKey, tData: TData, index: number, isClassBinding: boolean) {
|
||||
const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles;
|
||||
if (residual != null /* or undefined */ && typeof tStylingKey == 'string' &&
|
||||
arrayMapIndexOf(residual, tStylingKey) >= 0) {
|
||||
// We have duplicate in the residual so mark ourselves as duplicate.
|
||||
tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1] as TStylingRange);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marks `TStyleValue`s as duplicates if another style binding in the list has the same
|
||||
* `TStyleValue`.
|
||||
|
@ -309,14 +347,16 @@ export function insertTStylingBinding(
|
|||
* NOTE: Once `[style]` (Map) is added into the system all things are mapped as duplicates.
|
||||
* NOTE: We use `style` as example, but same logic is applied to `class`es as well.
|
||||
*
|
||||
* @param tData
|
||||
* @param tStylingKey
|
||||
* @param index
|
||||
* @param staticValues
|
||||
* @param isPrevDir
|
||||
* @param tData `TData` where the linked list is stored.
|
||||
* @param tStylingKey `TStylingKeyPrimitive` which contains the value to compare to other keys in
|
||||
* the linked list.
|
||||
* @param index Starting location in the linked list to search from
|
||||
* @param isPrevDir Direction.
|
||||
* - `true` for previous (lower priority);
|
||||
* - `false` for next (higher priority).
|
||||
*/
|
||||
function markDuplicates(
|
||||
tData: TData, tStylingKey: TStylingKey, index: number, staticValues: string, isPrevDir: boolean,
|
||||
tData: TData, tStylingKey: TStylingKeyPrimitive, index: number, isPrevDir: boolean,
|
||||
isClassBinding: boolean) {
|
||||
const tStylingAtIndex = tData[index + 1] as TStylingRange;
|
||||
const isMap = tStylingKey === null;
|
||||
|
@ -324,14 +364,15 @@ function markDuplicates(
|
|||
isPrevDir ? getTStylingRangePrev(tStylingAtIndex) : getTStylingRangeNext(tStylingAtIndex);
|
||||
let foundDuplicate = false;
|
||||
// We keep iterating as long as we have a cursor
|
||||
// AND either: We found what we are looking for, or we are a map in which case we have to
|
||||
// continue searching even after we find what we were looking for since we are a wild card
|
||||
// and everything needs to be flipped to duplicate.
|
||||
// AND either:
|
||||
// - we found what we are looking for, OR
|
||||
// - we are a map in which case we have to continue searching even after we find what we were
|
||||
// looking for since we are a wild card and everything needs to be flipped to duplicate.
|
||||
while (cursor !== 0 && (foundDuplicate === false || isMap)) {
|
||||
ngDevMode && assertDataInRange(tData, cursor);
|
||||
const tStylingValueAtCursor = tData[cursor] as TStylingKey;
|
||||
const tStyleRangeAtCursor = tData[cursor + 1] as TStylingRange;
|
||||
if (tStylingValueAtCursor === null || tStylingKey == null ||
|
||||
tStylingValueAtCursor === tStylingKey) {
|
||||
if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) {
|
||||
foundDuplicate = true;
|
||||
tData[cursor + 1] = isPrevDir ? setTStylingRangeNextDuplicate(tStyleRangeAtCursor) :
|
||||
setTStylingRangePrevDuplicate(tStyleRangeAtCursor);
|
||||
|
@ -339,31 +380,47 @@ function markDuplicates(
|
|||
cursor = isPrevDir ? getTStylingRangePrev(tStyleRangeAtCursor) :
|
||||
getTStylingRangeNext(tStyleRangeAtCursor);
|
||||
}
|
||||
// We also need to process the static values.
|
||||
if (staticValues !== '' && // If we have static values to search
|
||||
!foundDuplicate // If we have duplicate don't bother since we are already marked as
|
||||
// duplicate
|
||||
) {
|
||||
if (isMap) {
|
||||
// if we are a Map (and we have statics) we must assume duplicate
|
||||
foundDuplicate = true;
|
||||
} else if (staticValues != null) {
|
||||
// If we found non-map then we iterate over its keys to determine if any of them match ours
|
||||
// If we find a match than we mark it as duplicate.
|
||||
for (let i = isClassBinding ? parseClassName(staticValues) : parseStyle(staticValues); //
|
||||
i >= 0; //
|
||||
i = isClassBinding ? parseClassNameNext(staticValues, i) :
|
||||
parseStyleNext(staticValues, i)) {
|
||||
if (getLastParsedKey(staticValues) === tStylingKey) {
|
||||
foundDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundDuplicate) {
|
||||
// if we found a duplicate, than mark ourselves.
|
||||
tData[index + 1] = isPrevDir ? setTStylingRangePrevDuplicate(tStylingAtIndex) :
|
||||
setTStylingRangeNextDuplicate(tStylingAtIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two `TStylingKey`s are a match.
|
||||
*
|
||||
* When computing weather a binding contains a duplicate, we need to compare if the instruction
|
||||
* `TStylingKey` has a match.
|
||||
*
|
||||
* Here are examples of `TStylingKey`s which match given `tStylingKeyCursor` is:
|
||||
* - `color`
|
||||
* - `color` // Match another color
|
||||
* - `null` // That means that `tStylingKey` is a `classMap`/`styleMap` instruction
|
||||
* - `['', 'color', 'other', true]` // wrapped `color` so match
|
||||
* - `['', null, 'other', true]` // wrapped `null` so match
|
||||
* - `['', 'width', 'color', 'value']` // wrapped static value contains a match on `'color'`
|
||||
* - `null` // `tStylingKeyCursor` always match as it is `classMap`/`styleMap` instruction
|
||||
*
|
||||
* @param tStylingKeyCursor
|
||||
* @param tStylingKey
|
||||
*/
|
||||
function isStylingMatch(tStylingKeyCursor: TStylingKey, tStylingKey: TStylingKeyPrimitive) {
|
||||
ngDevMode &&
|
||||
assertNotEqual(
|
||||
Array.isArray(tStylingKey), true, 'Expected that \'tStylingKey\' has been unwrapped');
|
||||
if (tStylingKeyCursor === null || // If the cursor is `null` it means that we have map at that
|
||||
// location so we must assume that we have a match.
|
||||
tStylingKey == null || // If `tStylingKey` is `null` then it is a map therefor assume that it
|
||||
// contains a match.
|
||||
(Array.isArray(tStylingKeyCursor) ? tStylingKeyCursor[1] : tStylingKeyCursor) ===
|
||||
tStylingKey // If the keys match explicitly than we are a match.
|
||||
) {
|
||||
return true;
|
||||
} else if (Array.isArray(tStylingKeyCursor) && typeof tStylingKey === 'string') {
|
||||
// if we did not find a match, but `tStylingKeyCursor` is `ArrayMap` that means cursor has
|
||||
// statics and we need to check those as well.
|
||||
return arrayMapIndexOf(tStylingKeyCursor, tStylingKey) >= 0; // see if we are matching the key
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,12 @@ export function assertNumber(actual: any, msg: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export function assertNumberInRange(actual: any, minInclusive: number, maxInclusive: number) {
|
||||
assertNumber(actual, 'Expected a number');
|
||||
assertLessThanOrEqual(actual, maxInclusive, 'Expected number to be less than or equal to');
|
||||
assertGreaterThanOrEqual(actual, minInclusive, 'Expected number to be greater than or equal to');
|
||||
}
|
||||
|
||||
export function assertString(actual: any, msg: string) {
|
||||
if (!(typeof actual === 'string')) {
|
||||
throwError(msg, actual === null ? 'null' : typeof actual, 'string', '===');
|
||||
|
|
|
@ -1184,7 +1184,7 @@ describe('acceptance integration tests', () => {
|
|||
|
||||
@Component({
|
||||
template: `
|
||||
<div DirWithSingleStylingBindings class="abc" style="width:100px; height:200px"></div>
|
||||
<div DirWithSingleStylingBindings class="abc" style="width:100px;"></div>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
|
@ -1198,7 +1198,7 @@ describe('acceptance integration tests', () => {
|
|||
const dirInstance = fixture.componentInstance.dirInstance;
|
||||
const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('200px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeFalsy();
|
||||
|
@ -1208,7 +1208,7 @@ describe('acceptance integration tests', () => {
|
|||
dirInstance.activateXYZClass = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('444px');
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('999px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
|
@ -1219,7 +1219,7 @@ describe('acceptance integration tests', () => {
|
|||
fixture.detectChanges();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('200px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeTruthy();
|
||||
|
|
|
@ -1642,7 +1642,7 @@ describe('styling', () => {
|
|||
fixture.detectChanges();
|
||||
|
||||
expectStyle(element).toEqual({
|
||||
'width': '777px',
|
||||
'width': '200px',
|
||||
'color': 'red',
|
||||
'font-size': '99px',
|
||||
});
|
||||
|
@ -1659,7 +1659,8 @@ describe('styling', () => {
|
|||
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
|
||||
.it('should only apply each styling property once per CD across templates, components, directives',
|
||||
() => {
|
||||
@Directive({selector: '[dir-that-sets-styling]'})
|
||||
@Directive(
|
||||
{selector: '[dir-that-sets-styling]', host: {'style': 'width:0px; height:0px'}})
|
||||
class DirThatSetsStyling {
|
||||
@HostBinding('style') public map: any = {width: '999px', height: '999px'};
|
||||
}
|
||||
|
@ -1667,7 +1668,6 @@ describe('styling', () => {
|
|||
@Component({
|
||||
template: `
|
||||
<div #dir
|
||||
style="width:0px; height:0px"
|
||||
[style.width]="width"
|
||||
[style.height]="height"
|
||||
[style]="map"
|
||||
|
@ -2987,7 +2987,7 @@ describe('styling', () => {
|
|||
@Component({
|
||||
template: `
|
||||
<my-comp-with-styling
|
||||
style="height:1px; width:1px"
|
||||
style="height:1px; width:2px"
|
||||
my-dir-with-styling
|
||||
[style.height]="myHeight"
|
||||
[style]="myStyles">
|
||||
|
@ -3015,22 +3015,46 @@ describe('styling', () => {
|
|||
comp.myStyles = {};
|
||||
comp.myHeight = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(elm.style.width).toEqual('200px');
|
||||
expect(elm.style.height).toEqual('205px');
|
||||
|
||||
comp.dir.myStyles = {};
|
||||
comp.dir.myHeight = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(elm.style.width).toEqual('300px');
|
||||
expect(elm.style.height).toEqual('305px');
|
||||
expect(elm.style.width).toEqual('2px');
|
||||
expect(elm.style.height).toEqual('1px');
|
||||
|
||||
comp.comp.myStyles = {};
|
||||
comp.comp.myHeight = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(elm.style.width).toEqual('1px');
|
||||
expect(elm.style.width).toEqual('2px');
|
||||
expect(elm.style.height).toEqual('1px');
|
||||
|
||||
comp.dir.myStyles = {};
|
||||
comp.dir.myHeight = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(elm.style.width).toEqual('2px');
|
||||
expect(elm.style.height).toEqual('1px');
|
||||
});
|
||||
|
||||
onlyInIvy('Prioritization works in Ivy only')
|
||||
.it('should prioritize directive static bindings over components', () => {
|
||||
@Component({selector: 'my-comp-with-styling', host: {style: 'color: blue'}, template: ''})
|
||||
class MyCompWithStyling {
|
||||
}
|
||||
|
||||
@Directive({selector: '[my-dir-with-styling]', host: {style: 'color: red'}})
|
||||
class MyDirWithStyling {
|
||||
}
|
||||
|
||||
@Component({template: `<my-comp-with-styling my-dir-with-styling></my-comp-with-styling>`})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, MyCompWithStyling, MyDirWithStyling]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
const elm = fixture.nativeElement.querySelector('my-comp-with-styling') !;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(elm.style.color).toEqual('red');
|
||||
});
|
||||
|
||||
|
||||
it('should combine host class.foo bindings from multiple directives', () => {
|
||||
|
||||
@Directive({
|
||||
|
|
|
@ -47,9 +47,6 @@
|
|||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY_OBJ"
|
||||
},
|
||||
|
@ -404,30 +401,21 @@
|
|||
{
|
||||
"name": "collectNativeNodes"
|
||||
},
|
||||
{
|
||||
"name": "collectResidual"
|
||||
},
|
||||
{
|
||||
"name": "collectStylingFromDirectives"
|
||||
},
|
||||
{
|
||||
"name": "collectStylingFromTAttrs"
|
||||
},
|
||||
{
|
||||
"name": "computeStaticStyling"
|
||||
},
|
||||
{
|
||||
"name": "concatStringsWithSpace"
|
||||
},
|
||||
{
|
||||
"name": "consumeClassToken"
|
||||
},
|
||||
{
|
||||
"name": "consumeQuotedText"
|
||||
},
|
||||
{
|
||||
"name": "consumeSeparator"
|
||||
},
|
||||
{
|
||||
"name": "consumeStyleKey"
|
||||
},
|
||||
{
|
||||
"name": "consumeStyleValue"
|
||||
},
|
||||
{
|
||||
"name": "consumeWhitespace"
|
||||
},
|
||||
{
|
||||
"name": "createContainerRef"
|
||||
},
|
||||
|
@ -611,6 +599,9 @@
|
|||
{
|
||||
"name": "getContextLView"
|
||||
},
|
||||
{
|
||||
"name": "getCurrentDirectiveIndex"
|
||||
},
|
||||
{
|
||||
"name": "getCurrentStyleSanitizer"
|
||||
},
|
||||
|
@ -635,6 +626,9 @@
|
|||
{
|
||||
"name": "getFirstNativeNode"
|
||||
},
|
||||
{
|
||||
"name": "getHostDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "getInjectableDef"
|
||||
},
|
||||
|
@ -653,9 +647,6 @@
|
|||
{
|
||||
"name": "getLViewParent"
|
||||
},
|
||||
{
|
||||
"name": "getLastParsedKey"
|
||||
},
|
||||
{
|
||||
"name": "getNameOnlyMarkerIndex"
|
||||
},
|
||||
|
@ -746,6 +737,9 @@
|
|||
{
|
||||
"name": "getTViewCleanup"
|
||||
},
|
||||
{
|
||||
"name": "getTemplateHeadTStylingKey"
|
||||
},
|
||||
{
|
||||
"name": "getTypeName"
|
||||
},
|
||||
|
@ -791,9 +785,6 @@
|
|||
{
|
||||
"name": "initializeInputAndOutputAliases"
|
||||
},
|
||||
{
|
||||
"name": "initializeStylingStaticArrayMap"
|
||||
},
|
||||
{
|
||||
"name": "injectElementRef"
|
||||
},
|
||||
|
@ -902,6 +893,9 @@
|
|||
{
|
||||
"name": "isRootView"
|
||||
},
|
||||
{
|
||||
"name": "isStylingMatch"
|
||||
},
|
||||
{
|
||||
"name": "isStylingValuePresent"
|
||||
},
|
||||
|
@ -947,6 +941,9 @@
|
|||
{
|
||||
"name": "markDirtyIfOnPush"
|
||||
},
|
||||
{
|
||||
"name": "markDuplicateOfResidualStyling"
|
||||
},
|
||||
{
|
||||
"name": "markDuplicates"
|
||||
},
|
||||
|
@ -998,21 +995,6 @@
|
|||
{
|
||||
"name": "normalizeAndApplySuffixOrSanitizer"
|
||||
},
|
||||
{
|
||||
"name": "parseClassName"
|
||||
},
|
||||
{
|
||||
"name": "parseClassNameNext"
|
||||
},
|
||||
{
|
||||
"name": "parseStyle"
|
||||
},
|
||||
{
|
||||
"name": "parseStyleNext"
|
||||
},
|
||||
{
|
||||
"name": "parserState"
|
||||
},
|
||||
{
|
||||
"name": "readPatchedData"
|
||||
},
|
||||
|
@ -1073,9 +1055,6 @@
|
|||
{
|
||||
"name": "renderView"
|
||||
},
|
||||
{
|
||||
"name": "resetParserState"
|
||||
},
|
||||
{
|
||||
"name": "resetPreOrderHookFlags"
|
||||
},
|
||||
|
@ -1157,6 +1136,9 @@
|
|||
{
|
||||
"name": "setTStylingRangePrevDuplicate"
|
||||
},
|
||||
{
|
||||
"name": "setTemplateHeadTStylingKey"
|
||||
},
|
||||
{
|
||||
"name": "setUpAttributes"
|
||||
},
|
||||
|
@ -1217,6 +1199,9 @@
|
|||
{
|
||||
"name": "walkUpViews"
|
||||
},
|
||||
{
|
||||
"name": "wrapInStaticStylingKey"
|
||||
},
|
||||
{
|
||||
"name": "wrapListener"
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ import {TNodeType} from '@angular/core/src/render3/interfaces/node';
|
|||
import {LView, TView, TViewType} from '@angular/core/src/render3/interfaces/view';
|
||||
import {enterView, leaveView} from '@angular/core/src/render3/state';
|
||||
import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list';
|
||||
import {ArrayMap} from '@angular/core/src/util/array_utils';
|
||||
|
||||
|
||||
describe('lView_debug', () => {
|
||||
|
@ -35,19 +36,19 @@ describe('lView_debug', () => {
|
|||
});
|
||||
|
||||
it('should decode static styling', () => {
|
||||
tNode.styles = 'color: blue';
|
||||
tNode.classes = 'STATIC';
|
||||
expect(tNode.styleBindings_).toEqual(['color: blue']);
|
||||
expect(tNode.classBindings_).toEqual(['STATIC']);
|
||||
tNode.residualStyles = ['color', 'blue'] as ArrayMap<any>;
|
||||
tNode.residualClasses = ['STATIC', true] as ArrayMap<any>;
|
||||
expect(tNode.styleBindings_).toEqual([['color', 'blue'] as ArrayMap<any>]);
|
||||
expect(tNode.classBindings_).toEqual([['STATIC', true] as ArrayMap<any>]);
|
||||
});
|
||||
|
||||
it('should decode no-template property binding', () => {
|
||||
tNode.classes = 'STATIC';
|
||||
tNode.residualClasses = ['STATIC', true] as ArrayMap<any>;
|
||||
insertTStylingBinding(tView.data, tNode, 'CLASS', 2, true, true);
|
||||
insertTStylingBinding(tView.data, tNode, 'color', 4, true, false);
|
||||
|
||||
expect(tNode.styleBindings_).toEqual([
|
||||
null, {
|
||||
{
|
||||
index: 4,
|
||||
key: 'color',
|
||||
isTemplate: false,
|
||||
|
@ -55,10 +56,11 @@ describe('lView_debug', () => {
|
|||
nextDuplicate: false,
|
||||
prevIndex: 0,
|
||||
nextIndex: 0,
|
||||
}
|
||||
},
|
||||
null
|
||||
]);
|
||||
expect(tNode.classBindings_).toEqual([
|
||||
'STATIC', {
|
||||
{
|
||||
index: 2,
|
||||
key: 'CLASS',
|
||||
isTemplate: false,
|
||||
|
@ -66,17 +68,18 @@ describe('lView_debug', () => {
|
|||
nextDuplicate: false,
|
||||
prevIndex: 0,
|
||||
nextIndex: 0,
|
||||
}
|
||||
},
|
||||
['STATIC', true] as ArrayMap<any>
|
||||
]);
|
||||
});
|
||||
|
||||
it('should decode template and directive property binding', () => {
|
||||
tNode.classes = 'STATIC';
|
||||
tNode.residualClasses = ['STATIC', true] as ArrayMap<any>;
|
||||
insertTStylingBinding(tView.data, tNode, 'CLASS', 2, false, true);
|
||||
insertTStylingBinding(tView.data, tNode, 'color', 4, false, false);
|
||||
|
||||
expect(tNode.styleBindings_).toEqual([
|
||||
null, {
|
||||
{
|
||||
index: 4,
|
||||
key: 'color',
|
||||
isTemplate: true,
|
||||
|
@ -84,10 +87,11 @@ describe('lView_debug', () => {
|
|||
nextDuplicate: false,
|
||||
prevIndex: 0,
|
||||
nextIndex: 0,
|
||||
}
|
||||
},
|
||||
null
|
||||
]);
|
||||
expect(tNode.classBindings_).toEqual([
|
||||
'STATIC', {
|
||||
{
|
||||
index: 2,
|
||||
key: 'CLASS',
|
||||
isTemplate: true,
|
||||
|
@ -95,14 +99,15 @@ describe('lView_debug', () => {
|
|||
nextDuplicate: false,
|
||||
prevIndex: 0,
|
||||
nextIndex: 0,
|
||||
}
|
||||
},
|
||||
['STATIC', true] as ArrayMap<any>
|
||||
]);
|
||||
|
||||
insertTStylingBinding(tView.data, tNode, null, 6, true, true);
|
||||
insertTStylingBinding(tView.data, tNode, null, 8, true, false);
|
||||
|
||||
expect(tNode.styleBindings_).toEqual([
|
||||
null, {
|
||||
{
|
||||
index: 8,
|
||||
key: null,
|
||||
isTemplate: false,
|
||||
|
@ -119,14 +124,15 @@ describe('lView_debug', () => {
|
|||
nextDuplicate: false,
|
||||
prevIndex: 8,
|
||||
nextIndex: 0,
|
||||
}
|
||||
},
|
||||
null
|
||||
]);
|
||||
expect(tNode.classBindings_).toEqual([
|
||||
'STATIC', {
|
||||
{
|
||||
index: 6,
|
||||
key: null,
|
||||
isTemplate: false,
|
||||
prevDuplicate: true,
|
||||
prevDuplicate: false,
|
||||
nextDuplicate: true,
|
||||
prevIndex: 0,
|
||||
nextIndex: 2,
|
||||
|
@ -139,7 +145,8 @@ describe('lView_debug', () => {
|
|||
nextDuplicate: false,
|
||||
prevIndex: 6,
|
||||
nextIndex: 0,
|
||||
}
|
||||
},
|
||||
['STATIC', true] as ArrayMap<any>
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,8 +32,13 @@ import {enterView, getBindingRoot, getLView, setBindingIndex} from '@angular/cor
|
|||
export function enterViewWithOneDiv() {
|
||||
const renderer = domRendererFactory3.createRenderer(null, null);
|
||||
const div = renderer.createElement('div');
|
||||
const tView =
|
||||
createTView(TViewType.Component, -1, emptyTemplate, 1, 10, null, null, null, null, null);
|
||||
const consts = 1;
|
||||
const vars = 60; // Space for directive expando, template, component + 3 directives if we assume
|
||||
// that each consume 10 slots.
|
||||
const tView = createTView(
|
||||
TViewType.Component, -1, emptyTemplate, consts, vars, null, null, null, null, null);
|
||||
// Just assume that the expando starts after 10 initial bindings.
|
||||
tView.expandoStartIndex = HEADER_OFFSET + 10;
|
||||
const tNode = tView.firstChild = createTNode(tView, null !, TNodeType.Element, 0, 'div', null);
|
||||
const lView = createLView(
|
||||
null, tView, null, LViewFlags.CheckAlways, null, null, domRendererFactory3, renderer, null,
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DirectiveDef} from '@angular/core/src/render3';
|
||||
import {ɵɵdefineDirective} from '@angular/core/src/render3/definition';
|
||||
import {classStringParser, initializeStylingStaticArrayMap, styleStringParser, toStylingArrayMap, ɵɵclassProp, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer} from '@angular/core/src/render3/instructions/styling';
|
||||
import {AttributeMarker} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TVIEW} from '@angular/core/src/render3/interfaces/view';
|
||||
import {getLView, leaveView} from '@angular/core/src/render3/state';
|
||||
import {AttributeMarker, TAttributes, TDirectiveDefs} from '@angular/core/src/render3/interfaces/node';
|
||||
import {StylingRange, TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, setTStylingRangeNext, setTStylingRangePrev, toTStylingRange} from '@angular/core/src/render3/interfaces/styling';
|
||||
import {HEADER_OFFSET, TVIEW} from '@angular/core/src/render3/interfaces/view';
|
||||
import {getLView, leaveView, setBindingRootForHostBindings} from '@angular/core/src/render3/state';
|
||||
import {getNativeByIndex} from '@angular/core/src/render3/util/view_utils';
|
||||
import {bypassSanitizationTrustStyle} from '@angular/core/src/sanitization/bypass';
|
||||
import {ɵɵsanitizeStyle} from '@angular/core/src/sanitization/sanitization';
|
||||
|
@ -269,25 +272,155 @@ describe('styling', () => {
|
|||
describe('populateStylingStaticArrayMap', () => {
|
||||
it('should initialize to null if no mergedAttrs', () => {
|
||||
const tNode = getLView()[TVIEW].firstChild !;
|
||||
expect(tNode.stylesMap).toEqual(undefined);
|
||||
expect(tNode.classesMap).toEqual(undefined);
|
||||
expect(tNode.residualStyles).toEqual(undefined);
|
||||
expect(tNode.residualClasses).toEqual(undefined);
|
||||
initializeStylingStaticArrayMap(tNode);
|
||||
expect(tNode.stylesMap).toEqual(null);
|
||||
expect(tNode.classesMap).toEqual(null);
|
||||
expect(tNode.residualStyles).toEqual(null);
|
||||
expect(tNode.residualClasses).toEqual(null);
|
||||
});
|
||||
|
||||
it('should initialize from mergeAttrs', () => {
|
||||
const tNode = getLView()[TVIEW].firstChild !;
|
||||
expect(tNode.stylesMap).toEqual(undefined);
|
||||
expect(tNode.classesMap).toEqual(undefined);
|
||||
expect(tNode.residualStyles).toEqual(undefined);
|
||||
expect(tNode.residualClasses).toEqual(undefined);
|
||||
tNode.mergedAttrs = [
|
||||
'ignore', 'value', //
|
||||
AttributeMarker.Classes, 'foo', 'bar', //
|
||||
AttributeMarker.Styles, 'width', '0', 'color', 'red', //
|
||||
];
|
||||
initializeStylingStaticArrayMap(tNode);
|
||||
expect(tNode.classesMap).toEqual(['bar', true, 'foo', true] as any);
|
||||
expect(tNode.stylesMap).toEqual(['color', 'red', 'width', '0'] as any);
|
||||
expect(tNode.residualClasses).toEqual(['bar', true, 'foo', true] as any);
|
||||
expect(tNode.residualStyles).toEqual(['color', 'red', 'width', '0'] as any);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('static', () => {
|
||||
describe('template only', () => {
|
||||
it('should capture static values in TStylingKey', () => {
|
||||
givenTemplateAttrs([AttributeMarker.Styles, 'content', '"TEMPLATE"']);
|
||||
|
||||
ɵɵstyleProp('content', '"dynamic"');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
['', 'content', 'content', '"TEMPLATE"'], 'prev', // contains statics
|
||||
null // residual
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"dynamic"'});
|
||||
|
||||
ɵɵstyleProp('content', undefined);
|
||||
expectTStylingKeys('style').toEqual([
|
||||
['', 'content', 'content', '"TEMPLATE"'], 'both', // contains statics
|
||||
'content', 'prev', // Making sure that second instruction does not have statics again.
|
||||
null // residual
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"dynamic"'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('directives only', () => {
|
||||
it('should update residual on second directive', () => {
|
||||
givenDirectiveAttrs([
|
||||
[AttributeMarker.Styles, 'content', '"lowest"'], // 0
|
||||
[AttributeMarker.Styles, 'content', '"middle"'], // 1
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"middle"'});
|
||||
|
||||
activateHostBindings(0);
|
||||
ɵɵstyleProp('content', '"dynamic"');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
['', 'content', 'content', '"lowest"'], 'both', // 1: contains statics
|
||||
['content', '"middle"'], // residual
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"middle"'});
|
||||
|
||||
// second binding should not get statics
|
||||
ɵɵstyleProp('content', '"dynamic2"');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
['', 'content', 'content', '"lowest"'], 'both', // 1: contains statics
|
||||
'content', 'both', // 1: Should not get statics
|
||||
['content', '"middle"'] // residual
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"middle"'});
|
||||
|
||||
activateHostBindings(1);
|
||||
ɵɵstyleProp('content', '"dynamic3"');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
['', 'content', 'content', '"lowest"'], 'both', // 1: contains statics
|
||||
'content', 'both', // 1: Should not get statics
|
||||
['', 'content', 'content', '"middle"'], 'prev', // 0: contains statics
|
||||
null // residual
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"dynamic3"'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template and directives', () => {
|
||||
it('should combine property and map', () => {
|
||||
givenDirectiveAttrs([
|
||||
[AttributeMarker.Styles, 'content', '"lowest"', 'color', 'blue'], // 0
|
||||
[AttributeMarker.Styles, 'content', '"middle"', 'width', '100px'], // 1
|
||||
]);
|
||||
givenTemplateAttrs([AttributeMarker.Styles, 'content', '"TEMPLATE"', 'color', 'red']);
|
||||
|
||||
// TEMPLATE
|
||||
ɵɵstyleProp('content', undefined);
|
||||
expectTStylingKeys('style').toEqual([
|
||||
// TEMPLATE
|
||||
['', 'content', 'color', 'red', 'content', '"TEMPLATE"', 'width', '100px'], 'prev',
|
||||
// RESIDUAL
|
||||
null
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"TEMPLATE"', color: 'red', width: '100px'});
|
||||
|
||||
// Directive 0
|
||||
activateHostBindings(0);
|
||||
ɵɵstyleMap('color: red; width: 0px; height: 50px');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
// Host Binding 0
|
||||
['', null, 'color', 'blue', 'content', '"lowest"'], 'both',
|
||||
// TEMPLATE
|
||||
['', 'content', 'color', 'red', 'content', '"TEMPLATE"', 'width', '100px'], 'prev',
|
||||
// RESIDUAL
|
||||
null
|
||||
]);
|
||||
expectStyle(div).toEqual(
|
||||
{content: '"TEMPLATE"', color: 'red', width: '100px', height: '50px'});
|
||||
|
||||
// Directive 1
|
||||
activateHostBindings(1);
|
||||
ɵɵstyleMap('color: red; width: 0px; height: 50px');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
// Host Binding 0
|
||||
['', null, 'color', 'blue', 'content', '"lowest"'], 'both',
|
||||
// Host Binding 1
|
||||
['', null, 'content', '"middle"', 'width', '100px'], 'both',
|
||||
// TEMPLATE
|
||||
['', 'content', 'color', 'red', 'content', '"TEMPLATE"'], 'prev',
|
||||
// RESIDUAL
|
||||
null
|
||||
]);
|
||||
expectStyle(div).toEqual(
|
||||
{content: '"TEMPLATE"', color: 'red', width: '0px', height: '50px'});
|
||||
});
|
||||
|
||||
it('should read value from residual', () => {
|
||||
givenDirectiveAttrs([
|
||||
[AttributeMarker.Styles, 'content', '"lowest"', 'color', 'blue'], // 0
|
||||
[AttributeMarker.Styles, 'content', '"middle"', 'width', '100px'], // 1
|
||||
]);
|
||||
givenTemplateAttrs([AttributeMarker.Styles, 'content', '"TEMPLATE"', 'color', 'red']);
|
||||
|
||||
// Directive 1
|
||||
activateHostBindings(1);
|
||||
ɵɵstyleProp('color', 'white');
|
||||
expectTStylingKeys('style').toEqual([
|
||||
// Host Binding 0 + 1
|
||||
['', 'color', 'color', 'blue', 'content', '"middle"', 'width', '100px'], 'both',
|
||||
// RESIDUAL
|
||||
['color', 'red', 'content', '"TEMPLATE"']
|
||||
]);
|
||||
expectStyle(div).toEqual({content: '"TEMPLATE"', color: 'red', width: '100px'});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -346,6 +479,41 @@ describe('styling', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('TStylingRange', () => {
|
||||
const MAX_VALUE = StylingRange.UNSIGNED_MASK;
|
||||
|
||||
it('should throw on negative values', () => {
|
||||
expect(() => toTStylingRange(0, -1)).toThrow();
|
||||
expect(() => toTStylingRange(-1, 0)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw on overflow', () => {
|
||||
expect(() => toTStylingRange(0, MAX_VALUE + 1)).toThrow();
|
||||
expect(() => toTStylingRange(MAX_VALUE + 1, 0)).toThrow();
|
||||
});
|
||||
|
||||
it('should retrieve the same value which went in just below overflow', () => {
|
||||
const range = toTStylingRange(MAX_VALUE, MAX_VALUE);
|
||||
expect(getTStylingRangePrev(range)).toEqual(MAX_VALUE);
|
||||
expect(getTStylingRangeNext(range)).toEqual(MAX_VALUE);
|
||||
});
|
||||
|
||||
it('should correctly increment', () => {
|
||||
let range = toTStylingRange(0, 0);
|
||||
for (let i = 0; i <= MAX_VALUE; i++) {
|
||||
range = setTStylingRangeNext(range, i);
|
||||
range = setTStylingRangePrev(range, i);
|
||||
expect(getTStylingRangeNext(range)).toEqual(i);
|
||||
expect(getTStylingRangePrev(range)).toEqual(i);
|
||||
if (i == 10) {
|
||||
// Skip the boring stuff in the middle.
|
||||
i = MAX_VALUE - 10;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
@ -355,4 +523,97 @@ function expectStyle(element: HTMLElement) {
|
|||
|
||||
function expectClass(element: HTMLElement) {
|
||||
return expect(getElementClasses(element));
|
||||
}
|
||||
|
||||
function givenTemplateAttrs(tAttrs: TAttributes) {
|
||||
const tNode = getTNode();
|
||||
tNode.attrs = tAttrs;
|
||||
applyTAttributes(tAttrs);
|
||||
}
|
||||
|
||||
function getTNode() {
|
||||
return getLView()[TVIEW].firstChild !;
|
||||
}
|
||||
|
||||
function getTData() {
|
||||
return getLView()[TVIEW].data;
|
||||
}
|
||||
|
||||
class MockDir {}
|
||||
|
||||
function givenDirectiveAttrs(tAttrs: TAttributes[]) {
|
||||
const tNode = getTNode();
|
||||
const tData = getTData();
|
||||
const directives: TDirectiveDefs = tNode.directives = [0];
|
||||
for (let i = 0; i < tAttrs.length; i++) {
|
||||
const tAttr = tAttrs[i];
|
||||
const directiveDef = ɵɵdefineDirective({type: MockDir, hostAttrs: tAttr}) as DirectiveDef<any>;
|
||||
applyTAttributes(directiveDef.hostAttrs);
|
||||
tData[getTDataIndexFromDirectiveIndex(i)] = directiveDef;
|
||||
directives.push(directiveDef);
|
||||
}
|
||||
}
|
||||
|
||||
function applyTAttributes(attrs: TAttributes | null) {
|
||||
if (attrs === null) return;
|
||||
const div: HTMLElement = getLView()[HEADER_OFFSET];
|
||||
let mode: AttributeMarker = AttributeMarker.ImplicitAttributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const item = attrs[i];
|
||||
if (typeof item === 'number') {
|
||||
mode = item;
|
||||
} else if (typeof item === 'string') {
|
||||
if (mode == AttributeMarker.ImplicitAttributes) {
|
||||
div.setAttribute(item, attrs[++i] as string);
|
||||
} else if (mode == AttributeMarker.Classes) {
|
||||
div.classList.add(item);
|
||||
} else if (mode == AttributeMarker.Styles) {
|
||||
div.style.setProperty(item, attrs[++i] as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function activateHostBindings(directiveIndex: number) {
|
||||
const bindingRootIndex = getBindingRootIndexFromDirectiveIndex(directiveIndex);
|
||||
const currentDirectiveIndex = getTDataIndexFromDirectiveIndex(directiveIndex);
|
||||
setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex);
|
||||
}
|
||||
|
||||
function getBindingRootIndexFromDirectiveIndex(index: number) {
|
||||
// For simplicity assume that each directive has 10 vars.
|
||||
// We need to offset 1 for template, and 1 for expando.
|
||||
return HEADER_OFFSET + (index + 2) * 10;
|
||||
}
|
||||
|
||||
function getTDataIndexFromDirectiveIndex(index: number) {
|
||||
return HEADER_OFFSET + index + 10; // offset to give template bindings space.
|
||||
}
|
||||
|
||||
function expectTStylingKeys(styling: 'style' | 'class') {
|
||||
const tNode = getTNode();
|
||||
const tData = getTData();
|
||||
const isClassBased = styling === 'class';
|
||||
const headIndex = getTStylingRangePrev(isClassBased ? tNode.classBindings : tNode.styleBindings);
|
||||
const tStylingKeys: (string | (null | string)[] | null)[] = [];
|
||||
let index = headIndex;
|
||||
let prevIndex = index;
|
||||
// rewind to beginning of list.
|
||||
while ((prevIndex = getTStylingRangePrev(tData[index + 1] as TStylingRange)) !== 0) {
|
||||
index = prevIndex;
|
||||
}
|
||||
|
||||
// insert into array.
|
||||
while (index !== 0) {
|
||||
const tStylingKey = tData[index] as TStylingKey;
|
||||
const prevDup = getTStylingRangePrevDuplicate(tData[index + 1] as TStylingRange);
|
||||
const nextDup = getTStylingRangeNextDuplicate(tData[index + 1] as TStylingRange);
|
||||
tStylingKeys.push(tStylingKey as string[] | string | null);
|
||||
tStylingKeys.push(prevDup ? (nextDup ? 'both' : 'prev') : (nextDup ? 'next' : ''));
|
||||
index = getTStylingRangeNext(tData[index + 1] as TStylingRange);
|
||||
}
|
||||
tStylingKeys.push(
|
||||
(isClassBased ? tNode.residualClasses : tNode.residualStyles) as null | string[]);
|
||||
|
||||
return expect(tStylingKeys);
|
||||
}
|
|
@ -407,7 +407,7 @@ describe('TNode styling linked list', () => {
|
|||
|
||||
it('should mark duplicate on static fields', () => {
|
||||
const tNode = createTNode(null !, null !, TNodeType.Element, 0, '', null);
|
||||
tNode.styles = 'color: blue;';
|
||||
tNode.residualStyles = ['color', 'blue'] as any;
|
||||
const tData: TData = [null, null];
|
||||
insertTStylingBinding(tData, tNode, 'width', 2, false, false);
|
||||
expectPriorityOrder(tData, tNode, false).toEqual([
|
||||
|
@ -419,14 +419,14 @@ describe('TNode styling linked list', () => {
|
|||
expectPriorityOrder(tData, tNode, false).toEqual([
|
||||
// PREV, NEXT
|
||||
[2, 'width', false, false],
|
||||
[4, 'color', true, false],
|
||||
[4, 'color', false, true],
|
||||
]);
|
||||
|
||||
insertTStylingBinding(tData, tNode, null, 6, false, false);
|
||||
expectPriorityOrder(tData, tNode, false).toEqual([
|
||||
// PREV, NEXT
|
||||
[2, 'width', false, true],
|
||||
[4, 'color', true, true],
|
||||
[4, 'color', false, true],
|
||||
[6, null, true, false],
|
||||
]);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue