refactor(ivy): Implement `computeStaticStyling` (#34418)
The `computeStaticStyling` will be used for computing static styling value during `firstCreatePass`. The function takes into account static styling from the template as well as from the host bindings. The host bindings need to be merged in front of the template so that they have the correct priority. PR Closes #34418
This commit is contained in:
parent
54af220107
commit
b7ff38b1ef
|
@ -12,7 +12,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 18271,
|
||||
"main-es2015": 18214,
|
||||
"polyfills-es2015": 36808
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 137141,
|
||||
"main-es2015": 139487,
|
||||
"polyfills-es2015": 37494
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 267182,
|
||||
"main-es2015": 268796,
|
||||
"polyfills-es2015": 36808,
|
||||
"5-es2015": 751
|
||||
}
|
||||
|
@ -49,7 +49,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 226288,
|
||||
"main-es2015": 228770,
|
||||
"polyfills-es2015": 36808,
|
||||
"5-es2015": 779
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ export function ɵɵelement(
|
|||
}
|
||||
|
||||
function setDirectiveStylingInput(
|
||||
context: TStylingContext | StylingMapArray | null, lView: LView,
|
||||
context: TStylingContext | StylingMapArray | string | null, lView: LView,
|
||||
stylingInputs: (string | number)[], propName: string) {
|
||||
// older versions of Angular treat the input as `null` in the
|
||||
// event that the value does not exist at all. For this reason
|
||||
|
|
|
@ -553,7 +553,8 @@ export function registerInitialStylingOnTNode(
|
|||
return hasAdditionalInitialStyling;
|
||||
}
|
||||
|
||||
function updateRawValueOnContext(context: TStylingContext | StylingMapArray, value: string) {
|
||||
function updateRawValueOnContext(
|
||||
context: TStylingContext | StylingMapArray | string, value: string) {
|
||||
const stylingMapArr = getStylingMapArray(context) !;
|
||||
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value;
|
||||
}
|
||||
|
|
|
@ -600,7 +600,8 @@ export interface TNode {
|
|||
* are encountered. If and when this happens then the existing `StylingMapArray` value
|
||||
* will be placed into the initial styling slot in the newly created `TStylingContext`.
|
||||
*/
|
||||
styles: StylingMapArray|TStylingContext|null;
|
||||
// TODO(misko): `Remove StylingMapArray|TStylingContext|null` in follow up PR.
|
||||
styles: StylingMapArray|TStylingContext|string|null;
|
||||
|
||||
/**
|
||||
* A collection of all class bindings and/or static class values for an element.
|
||||
|
@ -620,7 +621,8 @@ export interface TNode {
|
|||
* are encountered. If and when this happens then the existing `StylingMapArray` value
|
||||
* will be placed into the initial styling slot in the newly created `TStylingContext`.
|
||||
*/
|
||||
classes: StylingMapArray|TStylingContext|null;
|
||||
// TODO(misko): `Remove StylingMapArray|TStylingContext|null` in follow up PR.
|
||||
classes: StylingMapArray|TStylingContext|string|null;
|
||||
|
||||
/**
|
||||
* Stores the head/tail index of the class bindings.
|
||||
|
|
|
@ -1030,8 +1030,8 @@ export const setStyleAttr = (renderer: Renderer3 | null, native: RElement, value
|
|||
* initial styling values on an element.
|
||||
*/
|
||||
export function renderStylingMap(
|
||||
renderer: Renderer3, element: RElement, stylingValues: TStylingContext | StylingMapArray | null,
|
||||
isClassBased: boolean): void {
|
||||
renderer: Renderer3, element: RElement,
|
||||
stylingValues: TStylingContext | StylingMapArray | string | null, isClassBased: boolean): void {
|
||||
const stylingMapArr = getStylingMapArray(stylingValues);
|
||||
if (stylingMapArr) {
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < stylingMapArr.length;
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {concatStringsWithSpace} from '../../util/stringify';
|
||||
import {assertFirstCreatePass} from '../assert';
|
||||
import {AttributeMarker, TAttributes, TNode} from '../interfaces/node';
|
||||
import {TVIEW} from '../interfaces/view';
|
||||
import {getLView} from '../state';
|
||||
|
||||
/**
|
||||
* Compute the static styling (class/style) from `TAttributes`.
|
||||
*
|
||||
* This function should be called during `firstCreatePass` only.
|
||||
*
|
||||
* @param tNode The `TNode` into which the styling information should be loaded.
|
||||
* @param attrs `TAttributes` containing the styling information.
|
||||
*/
|
||||
export function computeStaticStyling(tNode: TNode, attrs: TAttributes): void {
|
||||
ngDevMode && assertFirstCreatePass(
|
||||
getLView()[TVIEW], 'Expecting to be called in first template pass only');
|
||||
let styles: string|null = tNode.styles as string | null;
|
||||
let classes: string|null = tNode.classes as string | null;
|
||||
let mode: AttributeMarker|0 = 0;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const value = attrs[i];
|
||||
if (typeof value === 'number') {
|
||||
mode = value;
|
||||
} else if (mode == AttributeMarker.Classes) {
|
||||
classes = concatStringsWithSpace(classes, value as string);
|
||||
} else if (mode == AttributeMarker.Styles) {
|
||||
const style = value as string;
|
||||
const styleValue = attrs[++i] as string;
|
||||
styles = concatStringsWithSpace(styles, style + ': ' + styleValue + ';');
|
||||
}
|
||||
}
|
||||
styles !== null && (tNode.styles = styles);
|
||||
classes !== null && (tNode.classes = classes);
|
||||
}
|
|
@ -10,12 +10,12 @@ import {stylePropNeedsSanitization, ɵɵsanitizeStyle} from '../../sanitization/
|
|||
import {assertEqual, throwError} from '../../util/assert';
|
||||
import {TNode} from '../interfaces/node';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {StylingMapArray, TStylingKey, TStylingMapKey, TStylingRange, getTStylingRangeNext, getTStylingRangePrev, getTStylingRangePrevDuplicate, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange} from '../interfaces/styling';
|
||||
import {TStylingKey, TStylingMapKey, TStylingRange, getTStylingRangeNext, getTStylingRangePrev, getTStylingRangePrevDuplicate, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange} from '../interfaces/styling';
|
||||
import {LView, TData, TVIEW} from '../interfaces/view';
|
||||
import {getLView} from '../state';
|
||||
|
||||
import {splitClassList, toggleClass} from './class_differ';
|
||||
import {StyleChangesMap, parseKeyValue, removeStyle} from './style_differ';
|
||||
import {getLastParsedKey, parseClassName, parseClassNameNext, parseStyle, parseStyleNext} from './styling_parser';
|
||||
|
||||
|
||||
|
||||
|
@ -254,8 +254,10 @@ 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);
|
||||
markDuplicates(tData, tStylingKey, index, null, false);
|
||||
markDuplicates(
|
||||
tData, tStylingKey, index, (isClassBinding ? tNode.classes : tNode.styles) as string, true,
|
||||
isClassBinding);
|
||||
markDuplicates(tData, tStylingKey, index, '', false, isClassBinding);
|
||||
|
||||
tBindings = toTStylingRange(tmplHead, tmplTail);
|
||||
if (isClassBinding) {
|
||||
|
@ -320,8 +322,8 @@ export function insertTStylingBinding(
|
|||
* @param isPrevDir
|
||||
*/
|
||||
function markDuplicates(
|
||||
tData: TData, tStylingKey: TStylingKey, index: number, staticValues: StylingMapArray | null,
|
||||
isPrevDir: boolean) {
|
||||
tData: TData, tStylingKey: TStylingKey, index: number, staticValues: string, isPrevDir: boolean,
|
||||
isClassBinding: boolean) {
|
||||
const tStylingAtIndex = tData[index + 1] as TStylingRange;
|
||||
const key: string|null = typeof tStylingKey === 'object' ? tStylingKey.key : tStylingKey;
|
||||
const isMap = key === null;
|
||||
|
@ -345,16 +347,19 @@ function markDuplicates(
|
|||
cursor = isPrevDir ? getTStylingRangePrev(tStyleRangeAtCursor) :
|
||||
getTStylingRangeNext(tStyleRangeAtCursor);
|
||||
}
|
||||
if (staticValues !== null && // If we have static values to search
|
||||
!foundDuplicate // If we have duplicate don't bother since we are already marked as
|
||||
// duplicate
|
||||
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 {
|
||||
for (let i = 1; foundDuplicate === false && i < staticValues.length; i = i + 2) {
|
||||
if (staticValues[i] === key) {
|
||||
} else if (staticValues != null) {
|
||||
for (let i = isClassBinding ? parseClassName(staticValues) : parseStyle(staticValues); //
|
||||
i >= 0; //
|
||||
i = isClassBinding ? parseClassNameNext(staticValues, i) :
|
||||
parseStyleNext(staticValues, i)) {
|
||||
if (getLastParsedKey(staticValues) === key) {
|
||||
foundDuplicate = true;
|
||||
break;
|
||||
}
|
||||
|
@ -386,8 +391,9 @@ export function flushStyleBinding(
|
|||
// When styling changes we don't have to start at the begging. Instead we start at the change
|
||||
// value and look up the previous concatenation as a starting point going forward.
|
||||
const lastUnchangedValueIndex = getTStylingRangePrev(tStylingRangeAtIndex);
|
||||
let text = lastUnchangedValueIndex === 0 ? getStaticStylingValue(tNode, isClassBinding) :
|
||||
lView[lastUnchangedValueIndex + 1] as string;
|
||||
let text = lastUnchangedValueIndex === 0 ?
|
||||
((isClassBinding ? tNode.classes : tNode.styles) as string) :
|
||||
lView[lastUnchangedValueIndex + 1] as string;
|
||||
let cursor = index;
|
||||
while (cursor !== 0) {
|
||||
const value = lView[cursor];
|
||||
|
@ -400,16 +406,6 @@ export function flushStyleBinding(
|
|||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the static value for styling.
|
||||
*
|
||||
* @param tNode
|
||||
* @param isClassBinding
|
||||
*/
|
||||
function getStaticStylingValue(tNode: TNode, isClassBinding: Boolean) {
|
||||
// TODO(misko): implement once we have more code integrated.
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Append new styling to the currently concatenated styling text.
|
||||
|
|
|
@ -219,8 +219,10 @@ export function hyphenate(value: string): string {
|
|||
* will copy over an initial styling values from the tNode (which are stored as a
|
||||
* `StylingMapArray` on the `tNode.classes` or `tNode.styles` values).
|
||||
*/
|
||||
export function getStylingMapArray(value: TStylingContext | StylingMapArray | null):
|
||||
export function getStylingMapArray(value: TStylingContext | StylingMapArray | string | null):
|
||||
StylingMapArray|null {
|
||||
// TODO(misko): remove after TNode.classes/styles becomes `string` only
|
||||
if (typeof value === 'string') return null;
|
||||
return isStylingContext(value) ?
|
||||
(value as TStylingContext)[TStylingContextIndex.InitialStylingValuePosition] :
|
||||
value as StylingMapArray;
|
||||
|
@ -240,7 +242,10 @@ export function isStylingMapArray(value: any): boolean {
|
|||
(typeof(value as StylingMapArray)[StylingMapArrayIndex.ValuesStartPosition] === 'string');
|
||||
}
|
||||
|
||||
export function getInitialStylingValue(context: TStylingContext | StylingMapArray | null): string {
|
||||
export function getInitialStylingValue(context: TStylingContext | StylingMapArray | string | null):
|
||||
string {
|
||||
// TODO(misko): remove after TNode.classes/styles becomes `string` only
|
||||
if (typeof context === 'string') return context;
|
||||
const map = getStylingMapArray(context);
|
||||
return map && (map[StylingMapArrayIndex.RawValuePosition] as string | null) || '';
|
||||
}
|
||||
|
|
|
@ -36,3 +36,18 @@ export function stringify(token: any): string {
|
|||
const newLineIndex = res.indexOf('\n');
|
||||
return newLineIndex === -1 ? res : res.substring(0, newLineIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates two strings with separator, allocating new strings only when necessary.
|
||||
*
|
||||
* @param before before string.
|
||||
* @param separator separator string.
|
||||
* @param after after string.
|
||||
* @returns concatenated string.
|
||||
*/
|
||||
export function concatStringsWithSpace(before: string | null, after: string | null): string {
|
||||
return (before == null || before === '') ?
|
||||
(after === null ? '' : after) :
|
||||
((after == null || after === '') ? before : before + ' ' + after);
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {createTNode} from '@angular/core/src/render3/instructions/shared';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeType} from '@angular/core/src/render3/interfaces/node';
|
||||
import {LView} from '@angular/core/src/render3/interfaces/view';
|
||||
import {enterView} from '@angular/core/src/render3/state';
|
||||
import {computeStaticStyling} from '@angular/core/src/render3/styling/static_styling';
|
||||
|
||||
describe('static styling', () => {
|
||||
const mockFirstCreatePassLView: LView = [null, {firstCreatePass: true}] as any;
|
||||
let tNode !: TNode;
|
||||
beforeEach(() => {
|
||||
enterView(mockFirstCreatePassLView, null);
|
||||
tNode = createTNode(null !, null !, TNodeType.Element, 0, '', null);
|
||||
});
|
||||
it('should initialize when no attrs', () => {
|
||||
computeStaticStyling(tNode, []);
|
||||
expect(tNode.classes).toEqual(null);
|
||||
expect(tNode.styles).toEqual(null);
|
||||
});
|
||||
|
||||
it('should initialize from attrs', () => {
|
||||
const tAttrs: TAttributes = [
|
||||
'ignore', //
|
||||
AttributeMarker.Classes, 'my-class', //
|
||||
AttributeMarker.Styles, 'color', 'red' //
|
||||
];
|
||||
computeStaticStyling(tNode, tAttrs);
|
||||
expect(tNode.classes).toEqual('my-class');
|
||||
expect(tNode.styles).toEqual('color: red;');
|
||||
});
|
||||
|
||||
it('should initialize from attrs when multiple', () => {
|
||||
const tAttrs: TAttributes = [
|
||||
'ignore', //
|
||||
AttributeMarker.Classes, 'my-class', 'other', //
|
||||
AttributeMarker.Styles, 'color', 'red', 'width', '100px' //
|
||||
];
|
||||
computeStaticStyling(tNode, tAttrs);
|
||||
expect(tNode.classes).toEqual('my-class other');
|
||||
expect(tNode.styles).toEqual('color: red; width: 100px;');
|
||||
});
|
||||
});
|
|
@ -115,6 +115,7 @@ describe('TNode styling linked list', () => {
|
|||
// ɵɵstyleProp('color', '#008'); // Binding index: 30
|
||||
|
||||
const tNode = createTNode(null !, null !, TNodeType.Element, 0, '', null);
|
||||
tNode.styles = '';
|
||||
const tData: TData = newArray(32, null);
|
||||
const STYLE = STYLE_MAP_STYLING_KEY;
|
||||
|
||||
|
@ -408,7 +409,7 @@ describe('TNode styling linked list', () => {
|
|||
|
||||
it('should mark duplicate on static fields', () => {
|
||||
const tNode = createTNode(null !, null !, TNodeType.Element, 0, '', null);
|
||||
tNode.styles = [null, 'color', 'blue'];
|
||||
tNode.styles = 'color: blue;';
|
||||
const tData: TData = [null, null];
|
||||
insertTStylingBinding(tData, tNode, 'width', 2, false, false);
|
||||
expectPriorityOrder(tData, tNode, false).toEqual([
|
||||
|
@ -636,6 +637,8 @@ class StylingFixture {
|
|||
lView: LView = [null, null !] as any;
|
||||
tNode: TNode = createTNode(null !, null !, TNodeType.Element, 0, '', null);
|
||||
constructor(bindingSources: TStylingKey[][], public isClassBinding: boolean) {
|
||||
this.tNode.classes = '';
|
||||
this.tNode.styles = '';
|
||||
let bindingIndex = this.tData.length;
|
||||
for (let i = 0; i < bindingSources.length; i++) {
|
||||
const bindings = bindingSources[i];
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {concatStringsWithSpace} from '@angular/core/src/util/stringify';
|
||||
|
||||
describe('stringify', () => {
|
||||
describe('concatStringsWithSpace', () => {
|
||||
it('should concat with null', () => {
|
||||
expect(concatStringsWithSpace(null, null)).toEqual('');
|
||||
expect(concatStringsWithSpace('a', null)).toEqual('a');
|
||||
expect(concatStringsWithSpace(null, 'b')).toEqual('b');
|
||||
});
|
||||
|
||||
it('should concat when empty', () => {
|
||||
expect(concatStringsWithSpace('', '')).toEqual('');
|
||||
expect(concatStringsWithSpace('a', '')).toEqual('a');
|
||||
expect(concatStringsWithSpace('', 'b')).toEqual('b');
|
||||
});
|
||||
|
||||
it('should concat when not empty',
|
||||
() => { expect(concatStringsWithSpace('before', 'after')).toEqual('before after'); });
|
||||
});
|
||||
});
|
|
@ -786,7 +786,7 @@ export declare const ɵɵdefineDirective: <T>(directiveDefinition: {
|
|||
features?: DirectiveDefFeature[] | undefined;
|
||||
hostBindings?: HostBindingsFunction<T> | undefined;
|
||||
hostVars?: number | undefined;
|
||||
hostAttrs?: (string | (string | SelectorFlags)[] | AttributeMarker)[] | undefined;
|
||||
hostAttrs?: TAttributes | undefined;
|
||||
contentQueries?: ContentQueriesFunction<T> | undefined;
|
||||
viewQuery?: ViewQueriesFunction<T> | null | undefined;
|
||||
exportAs?: string[] | undefined;
|
||||
|
|
Loading…
Reference in New Issue