feat(ivy): properly apply class="", [class], [class.foo] and [attr.class] bindings (#24822)
PR Close #24822
This commit is contained in:
parent
c8ad9657c9
commit
ba3eb8b654
|
@ -48,13 +48,13 @@ export class LargeTableComponent {
|
|||
{
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
E(0, 'td');
|
||||
s(1, c0);
|
||||
{ T(2); }
|
||||
s(c0);
|
||||
{ T(1); }
|
||||
e();
|
||||
}
|
||||
if (rf2 & RenderFlags.Update) {
|
||||
sp(1, 0, cell.row % 2 ? '' : 'grey');
|
||||
t(2, b(cell.value));
|
||||
sp(0, 0, cell.row % 2 ? '' : 'grey');
|
||||
t(1, b(cell.value));
|
||||
}
|
||||
}
|
||||
v();
|
||||
|
|
|
@ -41,16 +41,16 @@ export class TreeComponent {
|
|||
template: function(rf: RenderFlags, ctx: TreeComponent) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
E(0, 'span');
|
||||
s(1, c0);
|
||||
{ T(2); }
|
||||
s(c0);
|
||||
{ T(1); }
|
||||
e();
|
||||
C(2);
|
||||
C(3);
|
||||
C(4);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
sp(1, 0, ctx.data.depth % 2 ? '' : 'grey');
|
||||
t(2, i1(' ', ctx.data.value, ' '));
|
||||
cR(3);
|
||||
sp(0, 0, ctx.data.depth % 2 ? '' : 'grey');
|
||||
t(1, i1(' ', ctx.data.value, ' '));
|
||||
cR(2);
|
||||
{
|
||||
if (ctx.data.left != null) {
|
||||
let rf0 = V(0);
|
||||
|
@ -67,7 +67,7 @@ export class TreeComponent {
|
|||
}
|
||||
}
|
||||
cr();
|
||||
cR(4);
|
||||
cR(3);
|
||||
{
|
||||
if (ctx.data.right != null) {
|
||||
let rf0 = V(0);
|
||||
|
@ -114,18 +114,18 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
|
|||
E(0, 'tree');
|
||||
{
|
||||
E(1, 'span');
|
||||
s(2, c1);
|
||||
{ T(3); }
|
||||
s(c1);
|
||||
{ T(2); }
|
||||
e();
|
||||
C(3);
|
||||
C(4);
|
||||
C(5);
|
||||
}
|
||||
e();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
sp(2, 0, ctx.depth % 2 ? '' : 'grey');
|
||||
t(3, i1(' ', ctx.value, ' '));
|
||||
cR(4);
|
||||
sp(1, 0, ctx.depth % 2 ? '' : 'grey');
|
||||
t(2, i1(' ', ctx.value, ' '));
|
||||
cR(3);
|
||||
{
|
||||
if (ctx.left != null) {
|
||||
let rf0 = V(0);
|
||||
|
@ -134,7 +134,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
|
|||
}
|
||||
}
|
||||
cr();
|
||||
cR(5);
|
||||
cR(4);
|
||||
{
|
||||
if (ctx.right != null) {
|
||||
let rf0 = V(0);
|
||||
|
|
|
@ -380,8 +380,6 @@ export const enum RenderFlags {
|
|||
Update = 0b10
|
||||
}
|
||||
|
||||
// Note this will expand once `class` is introduced to styling
|
||||
export const enum InitialStylingFlags {
|
||||
/** Mode for matching initial style values */
|
||||
INITIAL_STYLES = 0b00,
|
||||
VALUES_MODE = 0b1,
|
||||
}
|
||||
|
|
|
@ -33,13 +33,11 @@ export class Identifiers {
|
|||
|
||||
static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE};
|
||||
|
||||
static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE};
|
||||
|
||||
static elementClassNamed: o.ExternalReference = {name: 'ɵkn', moduleName: CORE};
|
||||
static elementClassProp: o.ExternalReference = {name: 'ɵcp', moduleName: CORE};
|
||||
|
||||
static elementStyling: o.ExternalReference = {name: 'ɵs', moduleName: CORE};
|
||||
|
||||
static elementStyle: o.ExternalReference = {name: 'ɵsm', moduleName: CORE};
|
||||
static elementStylingMap: o.ExternalReference = {name: 'ɵsm', moduleName: CORE};
|
||||
|
||||
static elementStyleProp: o.ExternalReference = {name: 'ɵsp', moduleName: CORE};
|
||||
|
||||
|
|
|
@ -40,19 +40,12 @@ function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefin
|
|||
case BindingType.Attribute:
|
||||
return R3.elementAttribute;
|
||||
case BindingType.Class:
|
||||
return R3.elementClassNamed;
|
||||
return R3.elementClassProp;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// `className` is used below instead of `class` because the interception
|
||||
// code (where this map is used) deals with DOM element property values
|
||||
// (like elm.propName) and not component bindining properties (like [propName]).
|
||||
const SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP: {[index: string]: o.ExternalReference} = {
|
||||
'className': R3.elementClass
|
||||
};
|
||||
|
||||
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||
private _dataIndex = 0;
|
||||
private _bindingContext = 0;
|
||||
|
@ -310,33 +303,59 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
const i18nMessages: o.Statement[] = [];
|
||||
const attributes: o.Expression[] = [];
|
||||
const initialStyleDeclarations: o.Expression[] = [];
|
||||
const initialClassDeclarations: o.Expression[] = [];
|
||||
|
||||
const styleInputs: t.BoundAttribute[] = [];
|
||||
const classInputs: t.BoundAttribute[] = [];
|
||||
const allOtherInputs: t.BoundAttribute[] = [];
|
||||
|
||||
element.inputs.forEach((input: t.BoundAttribute) => {
|
||||
// [attr.style] should not be treated as a styling-based
|
||||
// binding since it is intended to write directly to the attr
|
||||
// and therefore will skip all style resolution that is present
|
||||
// with style="", [style]="" and [style.prop]="" assignments
|
||||
if (input.name == 'style' && input.type == BindingType.Property) {
|
||||
// this should always go first in the compilation (for [style])
|
||||
styleInputs.splice(0, 0, input);
|
||||
} else if (input.type == BindingType.Style) {
|
||||
styleInputs.push(input);
|
||||
} else {
|
||||
allOtherInputs.push(input);
|
||||
switch (input.type) {
|
||||
// [attr.style] or [attr.class] should not be treated as styling-based
|
||||
// bindings since they are intended to be written directly to the attr
|
||||
// and therefore will skip all style/class resolution that is present
|
||||
// with style="", [style]="" and [style.prop]="", class="",
|
||||
// [class.prop]="". [class]="" assignments
|
||||
case BindingType.Property:
|
||||
if (input.name == 'style') {
|
||||
// this should always go first in the compilation (for [style])
|
||||
styleInputs.splice(0, 0, input);
|
||||
} else if (isClassBinding(input)) {
|
||||
// this should always go first in the compilation (for [class])
|
||||
classInputs.splice(0, 0, input);
|
||||
} else {
|
||||
allOtherInputs.push(input);
|
||||
}
|
||||
break;
|
||||
case BindingType.Style:
|
||||
styleInputs.push(input);
|
||||
break;
|
||||
case BindingType.Class:
|
||||
classInputs.push(input);
|
||||
break;
|
||||
default:
|
||||
allOtherInputs.push(input);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let currStyleIndex = 0;
|
||||
let currClassIndex = 0;
|
||||
let staticStylesMap: {[key: string]: any}|null = null;
|
||||
let staticClassesMap: {[key: string]: boolean}|null = null;
|
||||
const stylesIndexMap: {[key: string]: number} = {};
|
||||
const classesIndexMap: {[key: string]: number} = {};
|
||||
Object.getOwnPropertyNames(outputAttrs).forEach(name => {
|
||||
const value = outputAttrs[name];
|
||||
if (name == 'style') {
|
||||
staticStylesMap = parseStyle(value);
|
||||
Object.keys(staticStylesMap).forEach(prop => { stylesIndexMap[prop] = currStyleIndex++; });
|
||||
} else if (name == 'class') {
|
||||
staticClassesMap = {};
|
||||
value.split(/\s+/g).forEach(className => {
|
||||
classesIndexMap[className] = currClassIndex++;
|
||||
staticClassesMap ![className] = true;
|
||||
});
|
||||
} else {
|
||||
attributes.push(o.literal(name));
|
||||
if (attrI18nMetas.hasOwnProperty(name)) {
|
||||
|
@ -357,6 +376,14 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < classInputs.length; i++) {
|
||||
const input = classInputs[i];
|
||||
const isMapBasedClassBinding = i === 0 && isClassBinding(input);
|
||||
if (!isMapBasedClassBinding && !stylesIndexMap.hasOwnProperty(input.name)) {
|
||||
classesIndexMap[input.name] = currClassIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// this will build the instructions so that they fall into the following syntax
|
||||
// => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2]
|
||||
Object.keys(stylesIndexMap).forEach(prop => {
|
||||
|
@ -364,7 +391,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
});
|
||||
|
||||
if (staticStylesMap) {
|
||||
initialStyleDeclarations.push(o.literal(core.InitialStylingFlags.INITIAL_STYLES));
|
||||
initialStyleDeclarations.push(o.literal(core.InitialStylingFlags.VALUES_MODE));
|
||||
|
||||
Object.keys(staticStylesMap).forEach(prop => {
|
||||
initialStyleDeclarations.push(o.literal(prop));
|
||||
|
@ -373,6 +400,22 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
});
|
||||
}
|
||||
|
||||
Object.keys(classesIndexMap).forEach(prop => {
|
||||
initialClassDeclarations.push(o.literal(prop));
|
||||
});
|
||||
|
||||
if (staticClassesMap) {
|
||||
initialClassDeclarations.push(o.literal(core.InitialStylingFlags.VALUES_MODE));
|
||||
|
||||
Object.keys(staticClassesMap).forEach(className => {
|
||||
initialClassDeclarations.push(o.literal(className));
|
||||
initialClassDeclarations.push(o.literal(true));
|
||||
});
|
||||
}
|
||||
|
||||
const hasStylingInstructions = initialStyleDeclarations.length || styleInputs.length ||
|
||||
initialClassDeclarations.length || classInputs.length;
|
||||
|
||||
const attrArg: o.Expression = attributes.length > 0 ?
|
||||
this.constantPool.getConstLiteral(o.literalArr(attributes), true) :
|
||||
o.TYPED_NULL_EXPR;
|
||||
|
@ -411,10 +454,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
|
||||
const implicit = o.variable(CONTEXT_NAME);
|
||||
|
||||
const elementStyleIndex =
|
||||
(initialStyleDeclarations.length || styleInputs.length) ? this.allocateDataSlot() : 0;
|
||||
const createSelfClosingInstruction =
|
||||
elementStyleIndex === 0 && element.children.length === 0 && element.outputs.length === 0;
|
||||
!hasStylingInstructions && element.children.length === 0 && element.outputs.length === 0;
|
||||
|
||||
if (createSelfClosingInstruction) {
|
||||
this.instruction(
|
||||
|
@ -429,16 +470,30 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
...trimTrailingNulls(parameters));
|
||||
|
||||
// initial styling for static style="..." attributes
|
||||
if (elementStyleIndex > 0) {
|
||||
let paramsList: (o.Expression)[] = [o.literal(elementStyleIndex)];
|
||||
if (hasStylingInstructions) {
|
||||
const paramsList: (o.Expression)[] = [];
|
||||
|
||||
if (initialStyleDeclarations.length) {
|
||||
// the template compiler handles initial styling (e.g. style="foo") values
|
||||
// the template compiler handles initial style (e.g. style="foo") values
|
||||
// in a special command called `elementStyle` so that the initial styles
|
||||
// can be processed during runtime. These initial styles values are bound to
|
||||
// a constant because the inital style values do not change (since they're static).
|
||||
paramsList.push(
|
||||
this.constantPool.getConstLiteral(o.literalArr(initialStyleDeclarations), true));
|
||||
} else if (initialClassDeclarations.length) {
|
||||
// no point in having an extra `null` value unless there are follow-up params
|
||||
paramsList.push(o.NULL_EXPR);
|
||||
}
|
||||
|
||||
if (initialClassDeclarations.length) {
|
||||
// the template compiler handles initial class styling (e.g. class="foo") values
|
||||
// in a special command called `elementClass` so that the initial class
|
||||
// can be processed during runtime. These initial class values are bound to
|
||||
// a constant because the inital class values do not change (since they're static).
|
||||
paramsList.push(
|
||||
this.constantPool.getConstLiteral(o.literalArr(initialClassDeclarations), true));
|
||||
}
|
||||
|
||||
this._creationCode.push(o.importExpr(R3.elementStyling).callFn(paramsList).toStmt());
|
||||
}
|
||||
|
||||
|
@ -465,25 +520,64 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
});
|
||||
}
|
||||
|
||||
if (styleInputs.length && elementStyleIndex > 0) {
|
||||
const indexLiteral = o.literal(elementStyleIndex);
|
||||
styleInputs.forEach((input, i) => {
|
||||
const isMapBasedStyleBinding = i == 0 && input.name == 'style';
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
|
||||
if (isMapBasedStyleBinding) {
|
||||
this.instruction(
|
||||
this._bindingCode, input.sourceSpan, R3.elementStyle, indexLiteral, convertedBinding);
|
||||
} else {
|
||||
if ((styleInputs.length || classInputs.length) && hasStylingInstructions) {
|
||||
const indexLiteral = o.literal(elementIndex);
|
||||
|
||||
const firstStyle = styleInputs[0];
|
||||
const mapBasedStyleInput = firstStyle && firstStyle.name == 'style' ? firstStyle : null;
|
||||
|
||||
const firstClass = classInputs[0];
|
||||
const mapBasedClassInput = firstClass && isClassBinding(firstClass) ? firstClass : null;
|
||||
|
||||
const stylingInput = mapBasedStyleInput || mapBasedClassInput;
|
||||
if (stylingInput) {
|
||||
const params: o.Expression[] = [];
|
||||
if (mapBasedStyleInput) {
|
||||
params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true));
|
||||
} else if (mapBasedClassInput) {
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
if (mapBasedClassInput) {
|
||||
params.push(this.convertPropertyBinding(implicit, mapBasedClassInput.value, true));
|
||||
}
|
||||
this.instruction(
|
||||
this._bindingCode, stylingInput.sourceSpan, R3.elementStylingMap, indexLiteral,
|
||||
...params);
|
||||
}
|
||||
|
||||
let lastInputCommand: t.BoundAttribute|null = null;
|
||||
if (styleInputs.length) {
|
||||
let i = mapBasedStyleInput ? 1 : 0;
|
||||
for (i; i < styleInputs.length; i++) {
|
||||
const input = styleInputs[i];
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
|
||||
const key = input.name;
|
||||
let styleIndex: number = stylesIndexMap[key] !;
|
||||
const styleIndex: number = stylesIndexMap[key] !;
|
||||
this.instruction(
|
||||
this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral,
|
||||
o.literal(styleIndex), convertedBinding);
|
||||
}
|
||||
});
|
||||
|
||||
const spanEnd = styleInputs[styleInputs.length - 1].sourceSpan;
|
||||
this.instruction(this._bindingCode, spanEnd, R3.elementStylingApply, indexLiteral);
|
||||
lastInputCommand = styleInputs[styleInputs.length - 1];
|
||||
}
|
||||
|
||||
if (classInputs.length) {
|
||||
let i = mapBasedClassInput ? 1 : 0;
|
||||
for (i; i < classInputs.length; i++) {
|
||||
const input = classInputs[i];
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
|
||||
const key = input.name;
|
||||
const classIndex: number = classesIndexMap[key] !;
|
||||
this.instruction(
|
||||
this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral,
|
||||
o.literal(classIndex), convertedBinding);
|
||||
}
|
||||
|
||||
lastInputCommand = classInputs[classInputs.length - 1];
|
||||
}
|
||||
|
||||
this.instruction(
|
||||
this._bindingCode, lastInputCommand !.sourceSpan, R3.elementStylingApply, indexLiteral);
|
||||
}
|
||||
|
||||
// Generate element input bindings
|
||||
|
@ -494,18 +588,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
}
|
||||
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP[input.name];
|
||||
if (specialInstruction) {
|
||||
// special case for [style] and [class] bindings since they are not handled as
|
||||
// standard properties within this implementation. Instead they are
|
||||
// handed off to special cased instruction handlers which will then
|
||||
// delegate them as animation sequences (or input bindings for dirs/cmps)
|
||||
this.instruction(
|
||||
this._bindingCode, input.sourceSpan, specialInstruction, o.literal(elementIndex),
|
||||
convertedBinding);
|
||||
return;
|
||||
}
|
||||
|
||||
const instruction = mapBindingToInstruction(input.type);
|
||||
if (instruction) {
|
||||
// TODO(chuckj): runtime: security context?
|
||||
|
@ -975,3 +1057,7 @@ export function makeBindingParser(): BindingParser {
|
|||
new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), null,
|
||||
[]);
|
||||
}
|
||||
|
||||
function isClassBinding(input: t.BoundAttribute): boolean {
|
||||
return input.name == 'className' || input.name == 'class';
|
||||
}
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InitialStylingFlags} from '../../src/core';
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
|
||||
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
|
||||
* test in compiler_canonical_spec.ts should have a corresponding test here.
|
||||
*/
|
||||
|
@ -44,15 +47,17 @@ describe('compiler compliance', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||
const $c2$ = ['cx', '20', 'cy', '30', 'r', '50'];
|
||||
const $c1$ = ['title', 'Hello'];
|
||||
const $c2$ = ['my-app', ${InitialStylingFlags.VALUES_MODE}, 'my-app', true];
|
||||
const $c3$ = ['cx', '20', 'cy', '30', 'r', '50'];
|
||||
…
|
||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵs((null as any), $c2$);
|
||||
$r3$.ɵNS();
|
||||
$r3$.ɵE(1, 'svg');
|
||||
$r3$.ɵEe(2, 'circle', $c2$);
|
||||
$r3$.ɵEe(2, 'circle', $c3$);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵNH();
|
||||
$r3$.ɵE(3, 'p');
|
||||
|
@ -93,11 +98,13 @@ describe('compiler compliance', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||
const $c1$ = ['title', 'Hello'];
|
||||
const $c2$ = ['my-app', ${InitialStylingFlags.VALUES_MODE}, 'my-app', true];
|
||||
…
|
||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵs((null as any), $c2$);
|
||||
$r3$.ɵNM();
|
||||
$r3$.ɵE(1, 'math');
|
||||
$r3$.ɵEe(2, 'infinity');
|
||||
|
@ -141,11 +148,13 @@ describe('compiler compliance', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||
const $c1$ = ['title', 'Hello'];
|
||||
const $c2$ = ['my-app', ${InitialStylingFlags.VALUES_MODE}, 'my-app', true];
|
||||
…
|
||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵs((null as any), $c2$);
|
||||
$r3$.ɵT(1, 'Hello ');
|
||||
$r3$.ɵE(2, 'b');
|
||||
$r3$.ɵT(3, 'World');
|
||||
|
@ -322,6 +331,7 @@ describe('compiler compliance', () => {
|
|||
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
||||
const template = `
|
||||
const _c0 = ['background-color'];
|
||||
const _c1 = ['error'];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[['my-component']],
|
||||
factory:function MyComponent_Factory(){
|
||||
|
@ -329,13 +339,13 @@ describe('compiler compliance', () => {
|
|||
},template:function MyComponent_Template(rf:number,ctx:any){
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1, _c0);
|
||||
$r3$.ɵs(_c0, _c1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsp(1, 0, ctx.color);
|
||||
$r3$.ɵsa(1);
|
||||
$r3$.ɵkn(0, 'error', $r3$.ɵb(ctx.error));
|
||||
$r3$.ɵsp(0, 0, ctx.color);
|
||||
$r3$.ɵcp(0, 0, ctx.error);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -43,12 +43,12 @@ describe('compiler compliance: styling', () => {
|
|||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1);
|
||||
$r3$.ɵs();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(1, $ctx$.myStyleExp);
|
||||
$r3$.ɵsa(1);
|
||||
$r3$.ɵsm(0, $ctx$.myStyleExp);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -57,7 +57,7 @@ describe('compiler compliance: styling', () => {
|
|||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should place initial, multi, singular and application followed by attribute styling instructions in the template code in that order',
|
||||
it('should place initial, multi, singular and application followed by attribute style instructions in the template code in that order',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
|
@ -85,7 +85,7 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ['opacity','width','height',${InitialStylingFlags.INITIAL_STYLES},'opacity','1'];
|
||||
const _c0 = ['opacity','width','height',${InitialStylingFlags.VALUES_MODE},'opacity','1'];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
|
@ -96,14 +96,14 @@ describe('compiler compliance: styling', () => {
|
|||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1, _c0);
|
||||
$r3$.ɵs(_c0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(1, $ctx$.myStyleExp);
|
||||
$r3$.ɵsp(1, 1, $ctx$.myWidth);
|
||||
$r3$.ɵsp(1, 2, $ctx$.myHeight);
|
||||
$r3$.ɵsa(1);
|
||||
$r3$.ɵsm(0, $ctx$.myStyleExp);
|
||||
$r3$.ɵsp(0, 1, $ctx$.myWidth);
|
||||
$r3$.ɵsp(0, 2, $ctx$.myHeight);
|
||||
$r3$.ɵsa(0);
|
||||
$r3$.ɵa(0, 'style', $r3$.ɵb('border-width: 10px'));
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ describe('compiler compliance: styling', () => {
|
|||
template: \`<div [class]="myClassExp"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
myClassExp = [{color:'orange'}, {color:'green', duration:1000}]
|
||||
myClassExp = {'foo':true}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
|
@ -139,10 +139,13 @@ describe('compiler compliance: styling', () => {
|
|||
const template = `
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵEe(0, 'div');
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵk(0,$r3$.ɵb($ctx$.myClassExp));
|
||||
$r3$.ɵsm(0,(null as any),$ctx$.myClassExp);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -150,5 +153,112 @@ describe('compiler compliance: styling', () => {
|
|||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should place initial, multi, singular and application followed by attribute class instructions in the template code in that order',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="grape"
|
||||
[attr.class]="'banana'"
|
||||
[class.apple]="yesToApple"
|
||||
[class]="myClassExp"
|
||||
[class.orange]="yesToOrange"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
myClassExp = {a:true, b:true};
|
||||
yesToApple = true;
|
||||
yesToOrange = true;
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ['grape','apple','orange',${InitialStylingFlags.VALUES_MODE},'grape',true];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[['my-component']],
|
||||
factory:function MyComponent_Factory(){
|
||||
return new MyComponent();
|
||||
},
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs((null as any), _c0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(0, (null as any), $ctx$.myClassExp);
|
||||
$r3$.ɵcp(0, 1, $ctx$.yesToApple);
|
||||
$r3$.ɵcp(0, 2, $ctx$.yesToOrange);
|
||||
$r3$.ɵsa(0);
|
||||
$r3$.ɵa(0, 'class', $r3$.ɵb('banana'));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should not generate the styling apply instruction if there are only static style/class attributes',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="foo"
|
||||
style="width:100px"
|
||||
[attr.class]="'round'"
|
||||
[attr.style]="'height:100px'"></div>\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ['width',${InitialStylingFlags.VALUES_MODE},'width','100px'];
|
||||
const _c1 = ['foo',${InitialStylingFlags.VALUES_MODE},'foo',true];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[['my-component']],
|
||||
factory:function MyComponent_Factory(){
|
||||
return new MyComponent();
|
||||
},
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(_c0, _c1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵa(0, 'class', $r3$.ɵb('round'));
|
||||
$r3$.ɵa(0, 'style', $r3$.ɵb('height:100px'));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -80,8 +80,7 @@ export {
|
|||
sm as ɵsm,
|
||||
sp as ɵsp,
|
||||
sa as ɵsa,
|
||||
k as ɵk,
|
||||
kn as ɵkn,
|
||||
cp as ɵcp,
|
||||
t as ɵt,
|
||||
v as ɵv,
|
||||
st as ɵst,
|
||||
|
|
|
@ -63,6 +63,7 @@ export function assertComponentType(
|
|||
msg: string =
|
||||
'Type passed in is not ComponentType, it does not have \'ngComponentDef\' property.') {
|
||||
if (!actual.ngComponentDef) {
|
||||
debugger;
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
@ -70,4 +71,4 @@ export function assertComponentType(
|
|||
function throwError(msg: string): never {
|
||||
debugger; // Left intentionally for better debugger experience.
|
||||
throw new Error(`ASSERTION ERROR: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,14 +51,13 @@ export {
|
|||
|
||||
element as Ee,
|
||||
elementAttribute as a,
|
||||
elementClass as k,
|
||||
elementClassNamed as kn,
|
||||
elementClassProp as cp,
|
||||
elementEnd as e,
|
||||
elementProperty as p,
|
||||
elementStart as E,
|
||||
|
||||
elementStyling as s,
|
||||
elementStyle as sm,
|
||||
elementStylingMap as sm,
|
||||
elementStyleProp as sp,
|
||||
elementStylingApply as sa,
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE
|
|||
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
|
||||
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {LInjector} from './interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||
|
@ -25,8 +25,8 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, Curre
|
|||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyles as renderElementStyles, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp} from './styling';
|
||||
import {isDifferent, stringify} from './util';
|
||||
import {StylingContext, StylingIndex, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling';
|
||||
import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
||||
|
@ -96,6 +96,7 @@ export const CIRCULAR = '__CIRCULAR__';
|
|||
*/
|
||||
let renderer: Renderer3;
|
||||
let rendererFactory: RendererFactory3;
|
||||
let currentElementNode: LElementNode|null = null;
|
||||
|
||||
export function getRenderer(): Renderer3 {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
|
@ -668,6 +669,7 @@ export function elementStart(
|
|||
|
||||
const node: LElementNode =
|
||||
createLNode(index, TNodeType.Element, native !, name, attrs || null, null);
|
||||
currentElementNode = node;
|
||||
|
||||
if (attrs) {
|
||||
setUpAttributes(native, attrs);
|
||||
|
@ -1104,6 +1106,7 @@ export function elementEnd() {
|
|||
const queries = previousOrParentNode.queries;
|
||||
queries && queries.addNode(previousOrParentNode);
|
||||
queueLifecycleHooks(previousOrParentNode.tNode.flags, tView);
|
||||
currentElementNode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1118,7 +1121,7 @@ export function elementEnd() {
|
|||
export function elementAttribute(
|
||||
index: number, name: string, value: any, sanitizer?: SanitizerFn): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
const element: LElementNode = load(index);
|
||||
const element = loadElement(index);
|
||||
if (value == null) {
|
||||
ngDevMode && ngDevMode.rendererRemoveAttribute++;
|
||||
isProceduralRenderer(renderer) ? renderer.removeAttribute(element.native, name) :
|
||||
|
@ -1149,7 +1152,7 @@ export function elementAttribute(
|
|||
export function elementProperty<T>(
|
||||
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
|
||||
if (value === NO_CHANGE) return;
|
||||
const node = load(index) as LElementNode;
|
||||
const node = loadElement(index) as LElementNode;
|
||||
const tNode = node.tNode;
|
||||
// if tNode.inputs is undefined, a listener has created outputs, but inputs haven't
|
||||
// yet been checked
|
||||
|
@ -1268,44 +1271,9 @@ function generatePropertyAliases(
|
|||
* renaming as part of minification.
|
||||
* @param value A value indicating if a given class should be added or removed.
|
||||
*/
|
||||
export function elementClassNamed<T>(index: number, className: string, value: T | NO_CHANGE): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
const lElement = load(index) as LElementNode;
|
||||
if (value) {
|
||||
ngDevMode && ngDevMode.rendererAddClass++;
|
||||
isProceduralRenderer(renderer) ? renderer.addClass(lElement.native, className) :
|
||||
lElement.native.classList.add(className);
|
||||
|
||||
} else {
|
||||
ngDevMode && ngDevMode.rendererRemoveClass++;
|
||||
isProceduralRenderer(renderer) ? renderer.removeClass(lElement.native, className) :
|
||||
lElement.native.classList.remove(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the `className` property on a DOM element.
|
||||
*
|
||||
* This instruction is meant to handle the `[class]="exp"` usage.
|
||||
*
|
||||
* `elementClass` instruction writes the value to the "element's" `className` property.
|
||||
*
|
||||
* @param index The index of the element to update in the data array
|
||||
* @param value A value indicating a set of classes which should be applied. The method overrides
|
||||
* any existing classes. The value is stringified (`toString`) before it is applied to the
|
||||
* element.
|
||||
*/
|
||||
export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
// TODO: This is a naive implementation which simply writes value to the `className`. In the
|
||||
// future
|
||||
// we will add logic here which would work with the animation code.
|
||||
const lElement: LElementNode = load(index);
|
||||
ngDevMode && ngDevMode.rendererSetClassName++;
|
||||
isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) :
|
||||
lElement.native['className'] = stringify(value);
|
||||
}
|
||||
export function elementClassProp<T>(
|
||||
index: number, stylingIndex: number, value: T | NO_CHANGE): void {
|
||||
updateElementClassProp(getStylingContext(index), stylingIndex, value ? true : false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1323,22 +1291,29 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
|||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param styles A key/value map of CSS styles that will be registered on the element.
|
||||
* @param styleDeclarations A key/value array of CSS styles that will be registered on the element.
|
||||
* Each individual style will be used on the element as long as it is not overridden
|
||||
* by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`)
|
||||
* bindings. If a style binding changes its value to null then the initial styling
|
||||
* values that are passed in here will be applied to the element (if matched).
|
||||
* @param classDeclarations A key/value array of CSS classes that will be registered on the element.
|
||||
* Each individual style will be used on the element as long as it is not overridden
|
||||
* by any classes placed on the element by multiple (`[class]`) or singular (`[class.named]`)
|
||||
* bindings. If a class binding changes its value to a falsy value then the matching initial
|
||||
* class value that are passed in here will be applied to the element (if matched).
|
||||
*/
|
||||
export function elementStyling<T>(index: number, styles?: (string | number)[] | null): void {
|
||||
const tNode = load<LElementNode>(index - 1).tNode;
|
||||
export function elementStyling<T>(
|
||||
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null): void {
|
||||
const lElement = currentElementNode !;
|
||||
const tNode = lElement.tNode;
|
||||
if (!tNode.stylingTemplate) {
|
||||
// initialize the styling template.
|
||||
tNode.stylingTemplate = createStylingContextTemplate(styles);
|
||||
tNode.stylingTemplate = createStylingContextTemplate(styleDeclarations, classDeclarations);
|
||||
}
|
||||
// Allocate space but leave null for lazy creation.
|
||||
viewData[index + HEADER_OFFSET] = null;
|
||||
if (styles && styles.length) {
|
||||
elementStylingApply(index);
|
||||
if (styleDeclarations && styleDeclarations.length ||
|
||||
classDeclarations && classDeclarations.length) {
|
||||
elementStylingApply(tNode.index - HEADER_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1354,12 +1329,13 @@ export function elementStyling<T>(index: number, styles?: (string | number)[] |
|
|||
*/
|
||||
function getStylingContext(index: number): StylingContext {
|
||||
let stylingContext = load<StylingContext>(index);
|
||||
if (!stylingContext) {
|
||||
const lElement: LElementNode = load(index - 1);
|
||||
if (!Array.isArray(stylingContext)) {
|
||||
const lElement = stylingContext as any as LElementNode;
|
||||
const tNode = lElement.tNode;
|
||||
ngDevMode &&
|
||||
assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()');
|
||||
stylingContext = viewData[index + HEADER_OFFSET] = allocStylingContext(tNode.stylingTemplate !);
|
||||
stylingContext = viewData[index + HEADER_OFFSET] =
|
||||
allocStylingContext(lElement, tNode.stylingTemplate !);
|
||||
}
|
||||
return stylingContext;
|
||||
}
|
||||
|
@ -1379,7 +1355,7 @@ function getStylingContext(index: number): StylingContext {
|
|||
* index.)
|
||||
*/
|
||||
export function elementStylingApply<T>(index: number): void {
|
||||
renderElementStyles(load<LElementNode>(index - 1), getStylingContext(index), renderer);
|
||||
renderElementStyles(getStylingContext(index), renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1436,12 +1412,17 @@ export function elementStyleProp<T>(
|
|||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param value A value indicating if a given style should be added or removed.
|
||||
* The expected shape of `value` is an object where keys are style names and the values
|
||||
* are their corresponding values to set. If value is null, then the style is removed.
|
||||
* @param styles A key/value style map of the styles that will be applied to the given element.
|
||||
* Any missing styles (that have already been applied to the element beforehand) will be
|
||||
* removed (unset) from the element's styling.
|
||||
* @param classes A key/value style map of CSS classes that will be added to the given element.
|
||||
* Any missing classes (that have already been applied to the element beforehand) will be
|
||||
* removed (unset) from the element's list of CSS classes.
|
||||
*/
|
||||
export function elementStyle<T>(index: number, value: {[styleName: string]: any} | null): void {
|
||||
updateElementStyleMap(getStylingContext(index), value);
|
||||
export function elementStylingMap<T>(
|
||||
index: number, styles: {[styleName: string]: any} | null,
|
||||
classes?: {[key: string]: any} | string | null): void {
|
||||
updateStylingMap(getStylingContext(index), styles, classes);
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
|
@ -1476,7 +1457,7 @@ export function text(index: number, value?: any): void {
|
|||
export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
ngDevMode && assertDataInRange(index + HEADER_OFFSET);
|
||||
const existingNode = load(index) as LTextNode;
|
||||
const existingNode = loadElement(index) as any as LTextNode;
|
||||
ngDevMode && assertDefined(existingNode, 'LNode should exist');
|
||||
ngDevMode && assertDefined(existingNode.native, 'native element should exist');
|
||||
ngDevMode && ngDevMode.rendererSetText++;
|
||||
|
@ -1758,7 +1739,7 @@ export function container(
|
|||
* @param index The index of the container in the data array
|
||||
*/
|
||||
export function containerRefreshStart(index: number): void {
|
||||
previousOrParentNode = load(index) as LNode;
|
||||
previousOrParentNode = loadElement(index) as LNode;
|
||||
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
||||
isParent = true;
|
||||
(previousOrParentNode as LContainerNode).data[ACTIVE_INDEX] = 0;
|
||||
|
@ -2541,17 +2522,6 @@ export function store<T>(index: number, value: T): void {
|
|||
viewData[adjustedIndex] = value;
|
||||
}
|
||||
|
||||
/** Retrieves a value from current `viewData`. */
|
||||
export function load<T>(index: number): T {
|
||||
return loadInternal<T>(index, viewData);
|
||||
}
|
||||
|
||||
/** Retrieves a value from any `LViewData`. */
|
||||
export function loadInternal<T>(index: number, arr: LViewData): T {
|
||||
ngDevMode && assertDataInRange(index + HEADER_OFFSET, arr);
|
||||
return arr[index + HEADER_OFFSET];
|
||||
}
|
||||
|
||||
/** Retrieves a value from the `directives` array. */
|
||||
export function loadDirective<T>(index: number): T {
|
||||
ngDevMode && assertDefined(directives, 'Directives array should be defined if reading a dir.');
|
||||
|
@ -2568,6 +2538,15 @@ export function loadQueryList<T>(queryListIdx: number): QueryList<T> {
|
|||
return viewData[CONTENT_QUERIES] ![queryListIdx];
|
||||
}
|
||||
|
||||
/** Retrieves a value from current `viewData`. */
|
||||
export function load<T>(index: number): T {
|
||||
return loadInternal<T>(index, viewData);
|
||||
}
|
||||
|
||||
export function loadElement(index: number): LElementNode {
|
||||
return loadElementInternal(index, viewData);
|
||||
}
|
||||
|
||||
/** Gets the current binding value and increments the binding index. */
|
||||
export function consumeBinding(): any {
|
||||
ngDevMode && assertDataInRange(viewData[BINDING_INDEX]);
|
||||
|
@ -2645,7 +2624,7 @@ function assertHasParent() {
|
|||
|
||||
function assertDataInRange(index: number, arr?: any[]) {
|
||||
if (arr == null) arr = viewData;
|
||||
assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index');
|
||||
assertDataInRangeInternal(index, arr || viewData);
|
||||
}
|
||||
|
||||
function assertDataNext(index: number, arr?: any[]) {
|
||||
|
|
|
@ -299,8 +299,6 @@ export type PipeTypeList =
|
|||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
||||
// Note this will expand once `class` is introduced to styling
|
||||
export const enum InitialStylingFlags {
|
||||
/** Mode for matching initial style values */
|
||||
INITIAL_STYLES = 0b00,
|
||||
VALUES_MODE = 0b1,
|
||||
}
|
||||
|
|
|
@ -63,8 +63,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
|||
'ɵi7': r3.i7,
|
||||
'ɵi8': r3.i8,
|
||||
'ɵiV': r3.iV,
|
||||
'ɵk': r3.k,
|
||||
'ɵkn': r3.kn,
|
||||
'ɵcp': r3.cp,
|
||||
'ɵL': r3.L,
|
||||
'ɵld': r3.ld,
|
||||
'ɵP': r3.P,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'
|
|||
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||
import {CLEANUP, CONTAINER_INDEX, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {stringify} from './util';
|
||||
import {readElementValue, stringify} from './util';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
||||
|
||||
|
@ -32,7 +32,7 @@ export function getNextLNode(node: LNode): LNode|null {
|
|||
export function getChildLNode(node: LNode): LNode|null {
|
||||
if (node.tNode.child) {
|
||||
const viewData = node.tNode.type === TNodeType.View ? node.data as LViewData : node.view;
|
||||
return viewData[node.tNode.child.index];
|
||||
return readElementValue(viewData[node.tNode.child.index]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNo
|
|||
return containerHostIndex === -1 ? null : node.view[containerHostIndex].dynamicLContainerNode;
|
||||
}
|
||||
const parent = node.tNode.parent;
|
||||
return parent ? node.view[parent.index] : node.view[HOST_NODE];
|
||||
return readElementValue(parent ? node.view[parent.index] : node.view[HOST_NODE]);
|
||||
}
|
||||
|
||||
const enum WalkLNodeTreeAction {
|
||||
|
@ -463,7 +463,7 @@ function removeListeners(viewData: LViewData): void {
|
|||
for (let i = 0; i < cleanup.length - 1; i += 2) {
|
||||
if (typeof cleanup[i] === 'string') {
|
||||
// This is a listener with the native renderer
|
||||
const native = viewData[cleanup[i + 1]].native;
|
||||
const native = readElementValue(viewData[cleanup[i + 1]]).native;
|
||||
const listener = viewData[CLEANUP] ![cleanup[i + 2]];
|
||||
native.removeEventListener(cleanup[i], listener, cleanup[i + 3]);
|
||||
i += 2;
|
||||
|
|
|
@ -12,66 +12,81 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
|
|||
|
||||
/**
|
||||
* The styling context acts as a styling manifest (shaped as an array) for determining which
|
||||
* styling properties have been assigned via the provided `updateStyleMap` and `updateStyleProp`
|
||||
* functions. There are also two initialization functions `allocStylingContext` and
|
||||
* `createStylingContextTemplate` which are used to initialize and/or clone the context.
|
||||
* styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp`
|
||||
* and `updateClassProp` functions. There are also two initialization functions
|
||||
* `allocStylingContext` and `createStylingContextTemplate` which are used to initialize
|
||||
* and/or clone the context.
|
||||
*
|
||||
* The context is an array where the first two cells are used for static data (initial styling)
|
||||
* and dirty flags / index offsets). The remaining set of cells is used for multi (map) and single
|
||||
* (prop) style values.
|
||||
*
|
||||
* each value from here onwards is mapped as so:
|
||||
* [i] = mutation/type flag for the style value
|
||||
* [i] = mutation/type flag for the style/class value
|
||||
* [i + 1] = prop string (or null incase it has been removed)
|
||||
* [i + 2] = value string (or null incase it has been removed)
|
||||
*
|
||||
* There are three types of styling types stored in this context:
|
||||
* initial: any styles that are passed in once the context is created
|
||||
* (these are stored in the first cell of the array and the first
|
||||
* value of this array is always `null` even if no initial styles exist.
|
||||
* value of this array is always `null` even if no initial styling exists.
|
||||
* the `null` value is there so that any new styles have a parent to point
|
||||
* to. This way we can always assume that there is a parent.)
|
||||
*
|
||||
* single: any styles that are updated using `updateStyleProp` (fixed set)
|
||||
* single: any styles that are updated using `updateStyleProp` or `updateClassProp` (fixed set)
|
||||
*
|
||||
* multi: any styles that are updated using `updateStyleMap` (dynamic set)
|
||||
* multi: any styles that are updated using `updateStylingMap` (dynamic set)
|
||||
*
|
||||
* Note that context is only used to collect style information. Only when `renderStyles`
|
||||
* Note that context is only used to collect style information. Only when `renderStyling`
|
||||
* is called is when the styling payload will be rendered (or built as a key/value map).
|
||||
*
|
||||
* When the context is created, depending on what initial styles are passed in, the context itself
|
||||
* will be pre-filled with slots based on the initial style properties. Say for example we have a
|
||||
* series of initial styles that look like so:
|
||||
* When the context is created, depending on what initial styling values are passed in, the
|
||||
* context itself will be pre-filled with slots based on the initial style properties. Say
|
||||
* for example we have a series of initial styles that look like so:
|
||||
*
|
||||
* style="width:100px; height:200px;"
|
||||
* class="foo"
|
||||
*
|
||||
* Then the initial state of the context (once initialized) will look like so:
|
||||
*
|
||||
* ```
|
||||
* context = [
|
||||
* [null, '100px', '200px'], // property names are not needed since they have already been
|
||||
* [null, '100px', '200px', true], // property names are not needed since they have already been
|
||||
* written to DOM.
|
||||
*
|
||||
* 1, // this instructs how many `style` values there are so that class index values can be
|
||||
* offsetted
|
||||
*
|
||||
* configMasterVal,
|
||||
*
|
||||
* // 2
|
||||
* // 3
|
||||
* 'width',
|
||||
* pointers(1, 8); // Point to static `width`: `100px` and multi `width`.
|
||||
* pointers(1, 12); // Point to static `width`: `100px` and multi `width`.
|
||||
* null,
|
||||
*
|
||||
* // 5
|
||||
* // 6
|
||||
* 'height',
|
||||
* pointers(2, 11); // Point to static `height`: `200px` and multi `height`.
|
||||
* pointers(2, 15); // Point to static `height`: `200px` and multi `height`.
|
||||
* null,
|
||||
*
|
||||
* // 8
|
||||
* // 9
|
||||
* 'foo',
|
||||
* pointers(1, 18); // Point to static `foo`: `true` and multi `foo`.
|
||||
* null,
|
||||
*
|
||||
* // 12
|
||||
* 'width',
|
||||
* pointers(1, 2); // Point to static `width`: `100px` and single `width`.
|
||||
* pointers(1, 3); // Point to static `width`: `100px` and single `width`.
|
||||
* null,
|
||||
*
|
||||
* // 11
|
||||
* // 15
|
||||
* 'height',
|
||||
* pointers(2, 5); // Point to static `height`: `200px` and single `height`.
|
||||
* pointers(2, 6); // Point to static `height`: `200px` and single `height`.
|
||||
* null,
|
||||
*
|
||||
* // 18
|
||||
* 'foo',
|
||||
* pointers(3, 9); // Point to static `foo`: `true` and single `foo`.
|
||||
* null,
|
||||
* ]
|
||||
*
|
||||
|
@ -82,30 +97,50 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
|
|||
* }
|
||||
* ```
|
||||
*
|
||||
* The values are duplicated so that space is set aside for both multi ([style])
|
||||
* and single ([style.prop]) values. The respective config values (configValA, configValB, etc...)
|
||||
* are a combination of the StylingFlags with two index values: the `initialIndex` (which points to
|
||||
* the index location of the style value in the initial styles array in slot 0) and the
|
||||
* `dynamicIndex` (which points to the matching single/multi index position in the context array
|
||||
* for the same prop).
|
||||
* The values are duplicated so that space is set aside for both multi ([style] and [class])
|
||||
* and single ([style.prop] or [class.named]) values. The respective config values
|
||||
* (configValA, configValB, etc...) are a combination of the StylingFlags with two index
|
||||
* values: the `initialIndex` (which points to the index location of the style value in
|
||||
* the initial styles array in slot 0) and the `dynamicIndex` (which points to the
|
||||
* matching single/multi index position in the context array for the same prop).
|
||||
*
|
||||
* This means that every time `updateStyleProp` is called it must be called using an index value
|
||||
* (not a property string) which references the index value of the initial style when the context
|
||||
* was created. This also means that `updateStyleProp` cannot be called with a new property
|
||||
* (only `updateStyleMap` can include new CSS properties that will be added to the context).
|
||||
* This means that every time `updateStyleProp` or `updateClassProp` are called then they
|
||||
* must be called using an index value (not a property string) which references the index
|
||||
* value of the initial style prop/class when the context was created. This also means that
|
||||
* `updateStyleProp` or `updateClassProp` cannot be called with a new property (only
|
||||
* `updateStylingMap` can include new CSS properties that will be added to the context).
|
||||
*/
|
||||
export interface StylingContext extends Array<InitialStyles|number|string|null> {
|
||||
export interface StylingContext extends
|
||||
Array<InitialStyles|number|string|boolean|LElementNode|null> {
|
||||
/**
|
||||
* Location of element that is used as a target for this context.
|
||||
*/
|
||||
[0]: LElementNode|null;
|
||||
|
||||
/**
|
||||
* Location of initial data shared by all instances of this style.
|
||||
*/
|
||||
[0]: InitialStyles;
|
||||
[1]: InitialStyles;
|
||||
|
||||
/**
|
||||
* A numeric value representing the configuration status (whether the context is dirty or not)
|
||||
* mixed together (using bit shifting) with a index value which tells the starting index value
|
||||
* of where the multi style entries begin.
|
||||
*/
|
||||
[1]: number;
|
||||
[2]: number;
|
||||
|
||||
/**
|
||||
* A numeric value representing the class index offset value. Whenever a single class is
|
||||
* applied (using `elementClassProp`) it should have an styling index value that doesn't
|
||||
* need to take into account any style values that exist in the context.
|
||||
*/
|
||||
[3]: number;
|
||||
|
||||
/**
|
||||
* The last CLASS STRING VALUE that was interpreted by elementStylingMap. This is cached
|
||||
* So that the algorithm can exit early incase the string has not changed.
|
||||
*/
|
||||
[4]: string|null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,7 +151,7 @@ export interface StylingContext extends Array<InitialStyles|number|string|null>
|
|||
* All other entries in this array are of `string` value and correspond to the values that
|
||||
* were extracted from the `style=""` attribute in the HTML code for the provided template.
|
||||
*/
|
||||
export interface InitialStyles extends Array<string|null> { [0]: null; }
|
||||
export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
|
||||
|
||||
/**
|
||||
* Used to set the context to be dirty or not both on the master flag (position 1)
|
||||
|
@ -124,21 +159,31 @@ export interface InitialStyles extends Array<string|null> { [0]: null; }
|
|||
*/
|
||||
export const enum StylingFlags {
|
||||
// Implies no configurations
|
||||
None = 0b0,
|
||||
None = 0b00,
|
||||
// Whether or not the entry or context itself is dirty
|
||||
Dirty = 0b1,
|
||||
Dirty = 0b01,
|
||||
// Whether or not this is a class-based assignment
|
||||
Class = 0b10,
|
||||
// The max amount of bits used to represent these configuration values
|
||||
BitCountSize = 1,
|
||||
BitCountSize = 2,
|
||||
// There are only two bits here
|
||||
BitMask = 0b11
|
||||
}
|
||||
|
||||
/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
|
||||
export const enum StylingIndex {
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
InitialStylesPosition = 0,
|
||||
ElementPosition = 0,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
InitialStylesPosition = 1,
|
||||
// Index of location where the start of single properties are stored. (`updateStyleProp`)
|
||||
MasterFlagPosition = 1,
|
||||
MasterFlagPosition = 2,
|
||||
// Index of location where the class index offset value is located
|
||||
ClassOffsetPosition = 3,
|
||||
// Position of where the last string-based CSS class value was stored
|
||||
CachedCssClassString = 4,
|
||||
// Location of single (prop) value entries are stored within the context
|
||||
SingleStylesStartPosition = 2,
|
||||
SingleStylesStartPosition = 5,
|
||||
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
|
||||
FlagsOffset = 0,
|
||||
PropertyOffset = 1,
|
||||
|
@ -157,9 +202,12 @@ export const enum StylingIndex {
|
|||
* A pre-computed template is designed to be computed once for a given element
|
||||
* (instructions.ts has logic for caching this).
|
||||
*/
|
||||
export function allocStylingContext(templateStyleContext: StylingContext): StylingContext {
|
||||
export function allocStylingContext(
|
||||
lElement: LElementNode | null, templateStyleContext: StylingContext): StylingContext {
|
||||
// each instance gets a copy
|
||||
return templateStyleContext.slice() as any as StylingContext;
|
||||
const context = templateStyleContext.slice() as any as StylingContext;
|
||||
context[StylingIndex.ElementPosition] = lElement;
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,38 +224,74 @@ export function allocStylingContext(templateStyleContext: StylingContext): Styli
|
|||
* -> ['width', 'height', SPECIAL_ENUM_VAL, 'width', '100px']
|
||||
* This implies that `width` and `height` will be later styled and that the `width`
|
||||
* property has an initial value of `100px`.
|
||||
*
|
||||
* @param initialClassDeclarations a list of class declarations and initial class values
|
||||
* that are used later within the styling context.
|
||||
*
|
||||
* -> ['foo', 'bar', SPECIAL_ENUM_VAL, 'foo', true]
|
||||
* This implies that `foo` and `bar` will be later styled and that the `foo`
|
||||
* class will be applied to the element as an initial class since it's true
|
||||
*/
|
||||
export function createStylingContextTemplate(
|
||||
initialStyleDeclarations?: (string | InitialStylingFlags)[] | null): StylingContext {
|
||||
const initialStyles: InitialStyles = [null];
|
||||
const context: StylingContext = [initialStyles, 0];
|
||||
initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null): StylingContext {
|
||||
const initialStylingValues: InitialStyles = [null];
|
||||
const context: StylingContext = [null, initialStylingValues, 0, 0, null];
|
||||
|
||||
const indexLookup: {[key: string]: number} = {};
|
||||
// we use two maps since a class name might collide with a CSS style prop
|
||||
const stylesLookup: {[key: string]: number} = {};
|
||||
const classesLookup: {[key: string]: number} = {};
|
||||
|
||||
let totalStyleDeclarations = 0;
|
||||
if (initialStyleDeclarations) {
|
||||
let hasPassedDeclarations = false;
|
||||
for (let i = 0; i < initialStyleDeclarations.length; i++) {
|
||||
const v = initialStyleDeclarations[i] as string | InitialStylingFlags;
|
||||
|
||||
// this flag value marks where the declarations end the initial values begin
|
||||
if (v === InitialStylingFlags.INITIAL_STYLES) {
|
||||
if (v === InitialStylingFlags.VALUES_MODE) {
|
||||
hasPassedDeclarations = true;
|
||||
} else {
|
||||
const prop = v as string;
|
||||
if (hasPassedDeclarations) {
|
||||
const value = initialStyleDeclarations[++i] as string;
|
||||
initialStyles.push(value);
|
||||
indexLookup[prop] = initialStyles.length - 1;
|
||||
initialStylingValues.push(value);
|
||||
stylesLookup[prop] = initialStylingValues.length - 1;
|
||||
} else {
|
||||
// it's safe to use `0` since the default initial value for
|
||||
// each property will always be null (which is at position 0)
|
||||
indexLookup[prop] = 0;
|
||||
totalStyleDeclarations++;
|
||||
stylesLookup[prop] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allProps = Object.keys(indexLookup);
|
||||
const totalProps = allProps.length;
|
||||
// make where the class offsets begin
|
||||
context[StylingIndex.ClassOffsetPosition] = totalStyleDeclarations;
|
||||
|
||||
if (initialClassDeclarations) {
|
||||
let hasPassedDeclarations = false;
|
||||
for (let i = 0; i < initialClassDeclarations.length; i++) {
|
||||
const v = initialClassDeclarations[i] as string | boolean | InitialStylingFlags;
|
||||
// this flag value marks where the declarations end the initial values begin
|
||||
if (v === InitialStylingFlags.VALUES_MODE) {
|
||||
hasPassedDeclarations = true;
|
||||
} else {
|
||||
const className = v as string;
|
||||
if (hasPassedDeclarations) {
|
||||
const value = initialClassDeclarations[++i] as boolean;
|
||||
initialStylingValues.push(value);
|
||||
classesLookup[className] = initialStylingValues.length - 1;
|
||||
} else {
|
||||
classesLookup[className] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styleProps = Object.keys(stylesLookup);
|
||||
const classNames = Object.keys(classesLookup);
|
||||
const classNamesIndexStart = styleProps.length;
|
||||
const totalProps = styleProps.length + classNames.length;
|
||||
|
||||
// *2 because we are filling for both single and multi style spaces
|
||||
const maxLength = totalProps * StylingIndex.Size * 2 + StylingIndex.SingleStylesStartPosition;
|
||||
|
@ -222,86 +306,140 @@ export function createStylingContextTemplate(
|
|||
const multiStart = totalProps * StylingIndex.Size + StylingIndex.SingleStylesStartPosition;
|
||||
|
||||
// fill single and multi-level styles
|
||||
for (let i = 0; i < allProps.length; i++) {
|
||||
const prop = allProps[i];
|
||||
for (let i = 0; i < totalProps; i++) {
|
||||
const isClassBased = i >= classNamesIndexStart;
|
||||
const prop = isClassBased ? classNames[i - classNamesIndexStart] : styleProps[i];
|
||||
const indexForInitial = isClassBased ? classesLookup[prop] : stylesLookup[prop];
|
||||
const initialValue = initialStylingValues[indexForInitial];
|
||||
|
||||
const indexForInitial = indexLookup[prop];
|
||||
const indexForMulti = i * StylingIndex.Size + multiStart;
|
||||
const indexForSingle = i * StylingIndex.Size + singleStart;
|
||||
const initialFlag = isClassBased ? StylingFlags.Class : StylingFlags.None;
|
||||
|
||||
setFlag(context, indexForSingle, pointers(StylingFlags.None, indexForInitial, indexForMulti));
|
||||
setFlag(context, indexForSingle, pointers(initialFlag, indexForInitial, indexForMulti));
|
||||
setProp(context, indexForSingle, prop);
|
||||
setValue(context, indexForSingle, null);
|
||||
|
||||
setFlag(context, indexForMulti, pointers(StylingFlags.Dirty, indexForInitial, indexForSingle));
|
||||
const flagForMulti =
|
||||
initialFlag | (initialValue !== null ? StylingFlags.Dirty : StylingFlags.None);
|
||||
setFlag(context, indexForMulti, pointers(flagForMulti, indexForInitial, indexForSingle));
|
||||
setProp(context, indexForMulti, prop);
|
||||
setValue(context, indexForMulti, null);
|
||||
}
|
||||
|
||||
// there is no initial value flag for the master index since it doesn't reference an initial style
|
||||
// value
|
||||
// there is no initial value flag for the master index since it doesn't
|
||||
// reference an initial style value
|
||||
setFlag(context, StylingIndex.MasterFlagPosition, pointers(0, 0, multiStart));
|
||||
setContextDirty(context, initialStyles.length > 1);
|
||||
setContextDirty(context, initialStylingValues.length > 1);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
const EMPTY_ARR: any[] = [];
|
||||
const EMPTY_OBJ: {[key: string]: any} = {};
|
||||
/**
|
||||
* Sets and resolves all `multi` styles on an `StylingContext` so that they can be
|
||||
* applied to the element once `renderStyles` is called.
|
||||
* Sets and resolves all `multi` styling on an `StylingContext` so that they can be
|
||||
* applied to the element once `renderStyling` is called.
|
||||
*
|
||||
* All missing styles (any values that are not provided in the new `styles` param)
|
||||
* will resolve to `null` within their respective positions in the context.
|
||||
* All missing styles/class (any values that are not provided in the new `styles`
|
||||
* or `classes` params) will resolve to `null` within their respective positions
|
||||
* in the context.
|
||||
*
|
||||
* @param context The styling context that will be updated with the
|
||||
* newly provided style values.
|
||||
* @param styles The key/value map of CSS styles that will be used for the update.
|
||||
* @param classes The key/value map of CSS class names that will be used for the update.
|
||||
*/
|
||||
export function updateStyleMap(context: StylingContext, styles: {[key: string]: any} | null): void {
|
||||
const propsToApply = styles ? Object.keys(styles) : EMPTY_ARR;
|
||||
export function updateStylingMap(
|
||||
context: StylingContext, styles: {[key: string]: any} | null,
|
||||
classes?: {[key: string]: any} | string | null): void {
|
||||
let classNames: string[] = EMPTY_ARR;
|
||||
let applyAllClasses = false;
|
||||
let ignoreAllClassUpdates = false;
|
||||
|
||||
// each time a string-based value pops up then it shouldn't require a deep
|
||||
// check of what's changed.
|
||||
if (typeof classes == 'string') {
|
||||
const cachedClassString = context[StylingIndex.CachedCssClassString] as string | null;
|
||||
if (cachedClassString && cachedClassString === classes) {
|
||||
ignoreAllClassUpdates = true;
|
||||
} else {
|
||||
context[StylingIndex.CachedCssClassString] = classes;
|
||||
classNames = classes.split(/\s+/);
|
||||
// this boolean is used to avoid having to create a key/value map of `true` values
|
||||
// since a classname string implies that all those classes are added
|
||||
applyAllClasses = true;
|
||||
}
|
||||
} else {
|
||||
classNames = classes ? Object.keys(classes) : EMPTY_ARR;
|
||||
context[StylingIndex.CachedCssClassString] = null;
|
||||
}
|
||||
|
||||
classes = (classes || EMPTY_OBJ) as{[key: string]: any};
|
||||
|
||||
const styleProps = styles ? Object.keys(styles) : EMPTY_ARR;
|
||||
styles = styles || EMPTY_OBJ;
|
||||
|
||||
const classesStartIndex = styleProps.length;
|
||||
const multiStartIndex = getMultiStartIndex(context);
|
||||
|
||||
let dirty = false;
|
||||
let ctxIndex = multiStartIndex;
|
||||
|
||||
let propIndex = 0;
|
||||
const propLimit = styleProps.length + classNames.length;
|
||||
|
||||
// the main loop here will try and figure out how the shape of the provided
|
||||
// styles differ with respect to the context. Later if the context/styles are
|
||||
// off-balance then they will be dealt in another loop after this one
|
||||
while (ctxIndex < context.length && propIndex < propsToApply.length) {
|
||||
const flag = getPointers(context, ctxIndex);
|
||||
const prop = getProp(context, ctxIndex);
|
||||
const value = getValue(context, ctxIndex);
|
||||
// styles differ with respect to the context. Later if the context/styles/classes
|
||||
// are off-balance then they will be dealt in another loop after this one
|
||||
while (ctxIndex < context.length && propIndex < propLimit) {
|
||||
const isClassBased = propIndex >= classesStartIndex;
|
||||
|
||||
const newProp = propsToApply[propIndex];
|
||||
const newValue = styles ![newProp];
|
||||
if (prop === newProp) {
|
||||
if (value !== newValue) {
|
||||
setValue(context, ctxIndex, newValue);
|
||||
const initialValue = getInitialValue(context, flag);
|
||||
// when there is a cache-hit for a string-based class then we should
|
||||
// avoid doing any work diffing any of the changes
|
||||
if (!ignoreAllClassUpdates || !isClassBased) {
|
||||
const adjustedPropIndex = isClassBased ? propIndex - classesStartIndex : propIndex;
|
||||
const newProp: string =
|
||||
isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex];
|
||||
const newValue: string|boolean =
|
||||
isClassBased ? (applyAllClasses ? true : classes[newProp]) : styles[newProp];
|
||||
|
||||
// there is no point in setting this to dirty if the previously
|
||||
// rendered value was being referenced by the initial style (or null)
|
||||
if (initialValue !== newValue) {
|
||||
setDirty(context, ctxIndex, true);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const indexOfEntry = findEntryPositionByProp(context, newProp, ctxIndex);
|
||||
if (indexOfEntry > 0) {
|
||||
// it was found at a later point ... just swap the values
|
||||
swapMultiContextEntries(context, ctxIndex, indexOfEntry);
|
||||
const prop = getProp(context, ctxIndex);
|
||||
if (prop === newProp) {
|
||||
const value = getValue(context, ctxIndex);
|
||||
if (value !== newValue) {
|
||||
setValue(context, ctxIndex, newValue);
|
||||
dirty = true;
|
||||
|
||||
const flag = getPointers(context, ctxIndex);
|
||||
const initialValue = getInitialValue(context, flag);
|
||||
|
||||
// there is no point in setting this to dirty if the previously
|
||||
// rendered value was being referenced by the initial style (or null)
|
||||
if (initialValue !== newValue) {
|
||||
setDirty(context, ctxIndex, true);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we only care to do this if the insertion is in the middle
|
||||
const doShift = ctxIndex < context.length;
|
||||
insertNewMultiProperty(context, ctxIndex, newProp, newValue);
|
||||
dirty = true;
|
||||
const indexOfEntry = findEntryPositionByProp(context, newProp, ctxIndex);
|
||||
if (indexOfEntry > 0) {
|
||||
// it was found at a later point ... just swap the values
|
||||
const valueToCompare = getValue(context, indexOfEntry);
|
||||
const flagToCompare = getPointers(context, indexOfEntry);
|
||||
swapMultiContextEntries(context, ctxIndex, indexOfEntry);
|
||||
if (valueToCompare !== newValue) {
|
||||
const initialValue = getInitialValue(context, flagToCompare);
|
||||
setValue(context, ctxIndex, newValue);
|
||||
if (initialValue !== newValue) {
|
||||
setDirty(context, ctxIndex, true);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we only care to do this if the insertion is in the middle
|
||||
insertNewMultiProperty(context, ctxIndex, isClassBased, newProp, newValue);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,11 +448,16 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]:
|
|||
}
|
||||
|
||||
// this means that there are left-over values in the context that
|
||||
// were not included in the provided styles and in this case the
|
||||
// goal is to "remove" them from the context (by nullifying)
|
||||
// were not included in the provided styles/classes and in this
|
||||
// case the goal is to "remove" them from the context (by nullifying)
|
||||
while (ctxIndex < context.length) {
|
||||
const value = context[ctxIndex + StylingIndex.ValueOffset];
|
||||
if (value !== null) {
|
||||
const flag = getPointers(context, ctxIndex);
|
||||
const isClassBased = (flag & StylingFlags.Class) === StylingFlags.Class;
|
||||
if (ignoreAllClassUpdates && isClassBased) break;
|
||||
|
||||
const value = getValue(context, ctxIndex);
|
||||
const doRemoveValue = valueExists(value, isClassBased);
|
||||
if (doRemoveValue) {
|
||||
setDirty(context, ctxIndex, true);
|
||||
setValue(context, ctxIndex, null);
|
||||
dirty = true;
|
||||
|
@ -322,13 +465,19 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]:
|
|||
ctxIndex += StylingIndex.Size;
|
||||
}
|
||||
|
||||
// this means that there are left-over property in the context that
|
||||
// this means that there are left-over properties in the context that
|
||||
// were not detected in the context during the loop above. In that
|
||||
// case we want to add the new entries into the list
|
||||
while (propIndex < propsToApply.length) {
|
||||
const prop = propsToApply[propIndex];
|
||||
const value = styles ![prop];
|
||||
context.push(StylingFlags.Dirty, prop, value);
|
||||
while (propIndex < propLimit) {
|
||||
const isClassBased = propIndex >= classesStartIndex;
|
||||
if (ignoreAllClassUpdates && isClassBased) break;
|
||||
|
||||
const adjustedPropIndex = isClassBased ? propIndex - classesStartIndex : propIndex;
|
||||
const prop = isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex];
|
||||
const value: string|boolean =
|
||||
isClassBased ? (applyAllClasses ? true : classes[prop]) : styles[prop];
|
||||
const flag = StylingFlags.Dirty | (isClassBased ? StylingFlags.Class : StylingFlags.None);
|
||||
context.push(flag, prop, value);
|
||||
propIndex++;
|
||||
dirty = true;
|
||||
}
|
||||
|
@ -339,13 +488,13 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]:
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets and resolves a single CSS style on a property on an `StylingContext` so that they
|
||||
* can be applied to the element once `renderElementStyles` is called.
|
||||
* Sets and resolves a single styling property/value on the provided `StylingContext` so
|
||||
* that they can be applied to the element once `renderStyling` is called.
|
||||
*
|
||||
* Note that prop-level styles are considered higher priority than styles that are applied
|
||||
* using `updateStyleMap`, therefore, when styles are rendered then any styles that
|
||||
* have been applied using this function will be considered first (then multi values second
|
||||
* and then initial values as a backup).
|
||||
* Note that prop-level styling values are considered higher priority than any styling that
|
||||
* has been applied using `updateStylingMap`, therefore, when styling values are rendered
|
||||
* then any styles/classes that have been applied using this function will be considered first
|
||||
* (then multi values second and then initial values as a backup).
|
||||
*
|
||||
* @param context The styling context that will be updated with the
|
||||
* newly provided style value.
|
||||
|
@ -353,7 +502,7 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]:
|
|||
* @param value The CSS style value that will be assigned
|
||||
*/
|
||||
export function updateStyleProp(
|
||||
context: StylingContext, index: number, value: string | null): void {
|
||||
context: StylingContext, index: number, value: string | boolean | null): void {
|
||||
const singleIndex = StylingIndex.SingleStylesStartPosition + index * StylingIndex.Size;
|
||||
const currValue = getValue(context, singleIndex);
|
||||
const currFlag = getPointers(context, singleIndex);
|
||||
|
@ -370,8 +519,10 @@ export function updateStyleProp(
|
|||
let multiDirty = false;
|
||||
let singleDirty = true;
|
||||
|
||||
const isClassBased = (currFlag & StylingFlags.Class) === StylingFlags.Class;
|
||||
|
||||
// only when the value is set to `null` should the multi-value get flagged
|
||||
if (value == null && valueForMulti) {
|
||||
if (!valueExists(value, isClassBased) && valueExists(valueForMulti, isClassBased)) {
|
||||
multiDirty = true;
|
||||
singleDirty = false;
|
||||
}
|
||||
|
@ -384,12 +535,28 @@ export function updateStyleProp(
|
|||
}
|
||||
|
||||
/**
|
||||
* Renders all queued styles using a renderer onto the given element.
|
||||
* This method will toggle the referenced CSS class (by the provided index)
|
||||
* within the given context.
|
||||
*
|
||||
* @param context The styling context that will be updated with the
|
||||
* newly provided class value.
|
||||
* @param index The index of the CSS class which is being updated.
|
||||
* @param addOrRemove Whether or not to add or remove the CSS class
|
||||
*/
|
||||
export function updateClassProp(
|
||||
context: StylingContext, index: number, addOrRemove: boolean): void {
|
||||
const adjustedIndex = index + context[StylingIndex.ClassOffsetPosition];
|
||||
updateStyleProp(context, adjustedIndex, addOrRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all queued styling using a renderer onto the given element.
|
||||
*
|
||||
* This function works by rendering any styles (that have been applied
|
||||
* using `updateStyleMap` and `updateStyleProp`) onto the
|
||||
* provided element using the provided renderer. Just before the styles
|
||||
* are rendered a final key/value style map will be assembled.
|
||||
* using `updateStylingMap`) and any classes (that have been applied using
|
||||
* `updateStyleProp`) onto the provided element using the provided renderer.
|
||||
* Just before the styles/classes are rendered a final key/value style map
|
||||
* will be assembled (if `styleStore` or `classStore` are provided).
|
||||
*
|
||||
* @param lElement the element that the styles will be rendered on
|
||||
* @param context The styling context that will be used to determine
|
||||
|
@ -397,13 +564,14 @@ export function updateStyleProp(
|
|||
* @param renderer the renderer that will be used to apply the styling
|
||||
* @param styleStore if provided, the updated style values will be applied
|
||||
* to this key/value map instead of being renderered via the renderer.
|
||||
* @returns an object literal. `{ color: 'red', height: 'auto'}`.
|
||||
* @param classStore if provided, the updated class values will be applied
|
||||
* to this key/value map instead of being renderered via the renderer.
|
||||
*/
|
||||
export function renderStyles(
|
||||
lElement: LElementNode, context: StylingContext, renderer: Renderer3,
|
||||
styleStore?: {[key: string]: any}) {
|
||||
export function renderStyling(
|
||||
context: StylingContext, renderer: Renderer3, styleStore?: {[key: string]: any},
|
||||
classStore?: {[key: string]: boolean}) {
|
||||
if (isContextDirty(context)) {
|
||||
const native = lElement.native;
|
||||
const native = context[StylingIndex.ElementPosition] !.native;
|
||||
const multiStartIndex = getMultiStartIndex(context);
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < context.length;
|
||||
i += StylingIndex.Size) {
|
||||
|
@ -412,27 +580,35 @@ export function renderStyles(
|
|||
const prop = getProp(context, i);
|
||||
const value = getValue(context, i);
|
||||
const flag = getPointers(context, i);
|
||||
const isClassBased = flag & StylingFlags.Class ? true : false;
|
||||
const isInSingleRegion = i < multiStartIndex;
|
||||
|
||||
let styleToApply: string|null = value;
|
||||
let valueToApply: string|boolean|null = value;
|
||||
|
||||
// STYLE DEFER CASE 1: Use a multi value instead of a null single value
|
||||
// VALUE DEFER CASE 1: Use a multi value instead of a null single value
|
||||
// this check implies that a single value was removed and we
|
||||
// should now defer to a multi value and use that (if set).
|
||||
if (isInSingleRegion && styleToApply == null) {
|
||||
if (isInSingleRegion && !valueExists(valueToApply, isClassBased)) {
|
||||
// single values ALWAYS have a reference to a multi index
|
||||
const multiIndex = getMultiOrSingleIndex(flag);
|
||||
styleToApply = getValue(context, multiIndex);
|
||||
valueToApply = getValue(context, multiIndex);
|
||||
}
|
||||
|
||||
// STYLE DEFER CASE 2: Use the initial value if all else fails (is null)
|
||||
// VALUE DEFER CASE 2: Use the initial value if all else fails (is falsy)
|
||||
// the initial value will always be a string or null,
|
||||
// therefore we can safely adopt it incase there's nothing else
|
||||
if (styleToApply == null) {
|
||||
styleToApply = getInitialValue(context, flag);
|
||||
// note that this should always be a falsy check since `false` is used
|
||||
// for both class and style comparisons (styles can't be false and false
|
||||
// classes are turned off and should therefore defer to their initial values)
|
||||
if (!valueExists(valueToApply, isClassBased)) {
|
||||
valueToApply = getInitialValue(context, flag);
|
||||
}
|
||||
|
||||
setStyle(native, prop, styleToApply, renderer, styleStore);
|
||||
if (isClassBased) {
|
||||
setClass(native, prop, valueToApply ? true : false, renderer, classStore);
|
||||
} else {
|
||||
setStyle(native, prop, valueToApply as string | null, renderer, styleStore);
|
||||
}
|
||||
setDirty(context, i, false);
|
||||
}
|
||||
}
|
||||
|
@ -443,7 +619,7 @@ export function renderStyles(
|
|||
|
||||
/**
|
||||
* This function renders a given CSS prop/value entry using the
|
||||
* provided renderer. If a `styleStore` value is provided then
|
||||
* provided renderer. If a `store` value is provided then
|
||||
* that will be used a render context instead of the provided
|
||||
* renderer.
|
||||
*
|
||||
|
@ -451,23 +627,51 @@ export function renderStyles(
|
|||
* @param prop the CSS style property that will be rendered
|
||||
* @param value the CSS style value that will be rendered
|
||||
* @param renderer
|
||||
* @param styleStore an optional key/value map that will be used as a context to render styles on
|
||||
* @param store an optional key/value map that will be used as a context to render styles on
|
||||
*/
|
||||
function setStyle(
|
||||
native: any, prop: string, value: string | null, renderer: Renderer3,
|
||||
styleStore?: {[key: string]: any}) {
|
||||
if (styleStore) {
|
||||
styleStore[prop] = value;
|
||||
} else if (value == null) {
|
||||
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
||||
isProceduralRenderer(renderer) ?
|
||||
renderer.removeStyle(native, prop, RendererStyleFlags3.DashCase) :
|
||||
native['style'].removeProperty(prop);
|
||||
} else {
|
||||
store?: {[key: string]: any}) {
|
||||
if (store) {
|
||||
store[prop] = value;
|
||||
} else if (value) {
|
||||
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||
isProceduralRenderer(renderer) ?
|
||||
renderer.setStyle(native, prop, value, RendererStyleFlags3.DashCase) :
|
||||
native['style'].setProperty(prop, value);
|
||||
} else {
|
||||
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
||||
isProceduralRenderer(renderer) ?
|
||||
renderer.removeStyle(native, prop, RendererStyleFlags3.DashCase) :
|
||||
native['style'].removeProperty(prop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function renders a given CSS class value using the provided
|
||||
* renderer (by adding or removing it from the provided element).
|
||||
* If a `store` value is provided then that will be used a render
|
||||
* context instead of the provided renderer.
|
||||
*
|
||||
* @param native the DOM Element
|
||||
* @param prop the CSS style property that will be rendered
|
||||
* @param value the CSS style value that will be rendered
|
||||
* @param renderer
|
||||
* @param store an optional key/value map that will be used as a context to render styles on
|
||||
*/
|
||||
function setClass(
|
||||
native: any, className: string, add: boolean, renderer: Renderer3,
|
||||
store?: {[key: string]: boolean}) {
|
||||
if (store) {
|
||||
store[className] = add;
|
||||
} else if (add) {
|
||||
ngDevMode && ngDevMode.rendererAddClass++;
|
||||
isProceduralRenderer(renderer) ? renderer.addClass(native, className) :
|
||||
native['classList'].add(className);
|
||||
} else {
|
||||
ngDevMode && ngDevMode.rendererRemoveClass++;
|
||||
isProceduralRenderer(renderer) ? renderer.removeClass(native, className) :
|
||||
native['classList'].remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -487,8 +691,14 @@ function isDirty(context: StylingContext, index: number): boolean {
|
|||
return ((context[adjustedIndex] as number) & StylingFlags.Dirty) == StylingFlags.Dirty;
|
||||
}
|
||||
|
||||
function isClassBased(context: StylingContext, index: number): boolean {
|
||||
const adjustedIndex =
|
||||
index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index;
|
||||
return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class;
|
||||
}
|
||||
|
||||
function pointers(configFlag: number, staticIndex: number, dynamicIndex: number) {
|
||||
return (configFlag & StylingFlags.Dirty) | (staticIndex << StylingFlags.BitCountSize) |
|
||||
return (configFlag & StylingFlags.BitMask) | (staticIndex << StylingFlags.BitCountSize) |
|
||||
(dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize));
|
||||
}
|
||||
|
||||
|
@ -515,7 +725,7 @@ function setProp(context: StylingContext, index: number, prop: string) {
|
|||
context[index + StylingIndex.PropertyOffset] = prop;
|
||||
}
|
||||
|
||||
function setValue(context: StylingContext, index: number, value: string | null) {
|
||||
function setValue(context: StylingContext, index: number, value: string | null | boolean) {
|
||||
context[index + StylingIndex.ValueOffset] = value;
|
||||
}
|
||||
|
||||
|
@ -531,8 +741,8 @@ function getPointers(context: StylingContext, index: number): number {
|
|||
return context[adjustedIndex] as number;
|
||||
}
|
||||
|
||||
function getValue(context: StylingContext, index: number): string|null {
|
||||
return context[index + StylingIndex.ValueOffset] as string | null;
|
||||
function getValue(context: StylingContext, index: number): string|boolean|null {
|
||||
return context[index + StylingIndex.ValueOffset] as string | boolean | null;
|
||||
}
|
||||
|
||||
function getProp(context: StylingContext, index: number): string {
|
||||
|
@ -597,20 +807,23 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition:
|
|||
if (singleIndex > 0) {
|
||||
const singleFlag = getPointers(context, singleIndex);
|
||||
const initialIndexForSingle = getInitialIndex(singleFlag);
|
||||
const updatedFlag = pointers(
|
||||
isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None,
|
||||
initialIndexForSingle, i);
|
||||
const flagValue = (isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None) |
|
||||
(isClassBased(context, singleIndex) ? StylingFlags.Class : StylingFlags.None);
|
||||
const updatedFlag = pointers(flagValue, initialIndexForSingle, i);
|
||||
setFlag(context, singleIndex, updatedFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertNewMultiProperty(
|
||||
context: StylingContext, index: number, name: string, value: string): void {
|
||||
context: StylingContext, index: number, classBased: boolean, name: string,
|
||||
value: string | boolean): void {
|
||||
const doShift = index < context.length;
|
||||
|
||||
// prop does not exist in the list, add it in
|
||||
context.splice(index, 0, StylingFlags.Dirty, name, value);
|
||||
context.splice(
|
||||
index, 0, StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), name,
|
||||
value);
|
||||
|
||||
if (doShift) {
|
||||
// because the value was inserted midway into the array then we
|
||||
|
@ -619,3 +832,10 @@ function insertNewMultiProperty(
|
|||
updateSinglePointerValues(context, index + StylingIndex.Size);
|
||||
}
|
||||
}
|
||||
|
||||
function valueExists(value: string | null | boolean, isClassBased?: boolean) {
|
||||
if (isClassBased) {
|
||||
return value ? true : false;
|
||||
}
|
||||
return value !== null;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* 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 {assertLessThan} from './assert';
|
||||
import {LElementNode} from './interfaces/node';
|
||||
import {HEADER_OFFSET, LViewData} from './interfaces/view';
|
||||
|
||||
|
||||
/**
|
||||
* Must use this method for CD (instead of === ) since NaN !== NaN
|
||||
|
@ -56,3 +60,27 @@ export function flatten(list: any[]): any[] {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Retrieves a value from any `LViewData`. */
|
||||
export function loadInternal<T>(index: number, arr: LViewData): T {
|
||||
ngDevMode && assertDataInRangeInternal(index + HEADER_OFFSET, arr);
|
||||
return arr[index + HEADER_OFFSET];
|
||||
}
|
||||
|
||||
export function assertDataInRangeInternal(index: number, arr: any[]) {
|
||||
assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index');
|
||||
}
|
||||
|
||||
/** Retrieves an element value from the provided `viewData`.
|
||||
*
|
||||
* Elements that are read may be wrapped in a style context,
|
||||
* therefore reading the value may involve unwrapping that.
|
||||
*/
|
||||
export function loadElementInternal(index: number, arr: LViewData): LElementNode {
|
||||
const value = loadInternal<LElementNode>(index, arr);
|
||||
return readElementValue(value);
|
||||
}
|
||||
|
||||
export function readElementValue(value: LElementNode | any[]): LElementNode {
|
||||
return (Array.isArray(value) ? (value as any as any[])[0] : value) as LElementNode;
|
||||
}
|
||||
|
|
|
@ -191,6 +191,9 @@
|
|||
{
|
||||
"name": "namespaceHTML"
|
||||
},
|
||||
{
|
||||
"name": "readElementValue"
|
||||
},
|
||||
{
|
||||
"name": "refreshChildComponents"
|
||||
},
|
||||
|
|
|
@ -119,6 +119,9 @@
|
|||
{
|
||||
"name": "RecordViewTuple"
|
||||
},
|
||||
{
|
||||
"name": "RendererStyleFlags3"
|
||||
},
|
||||
{
|
||||
"name": "SANITIZER"
|
||||
},
|
||||
|
@ -206,6 +209,15 @@
|
|||
{
|
||||
"name": "_c16"
|
||||
},
|
||||
{
|
||||
"name": "_c17"
|
||||
},
|
||||
{
|
||||
"name": "_c18"
|
||||
},
|
||||
{
|
||||
"name": "_c19"
|
||||
},
|
||||
{
|
||||
"name": "_c2"
|
||||
},
|
||||
|
@ -260,6 +272,9 @@
|
|||
{
|
||||
"name": "addToViewTree"
|
||||
},
|
||||
{
|
||||
"name": "allocStylingContext"
|
||||
},
|
||||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
|
@ -332,6 +347,9 @@
|
|||
{
|
||||
"name": "createRootContext"
|
||||
},
|
||||
{
|
||||
"name": "createStylingContextTemplate"
|
||||
},
|
||||
{
|
||||
"name": "createTNode"
|
||||
},
|
||||
|
@ -344,6 +362,9 @@
|
|||
{
|
||||
"name": "createViewQuery"
|
||||
},
|
||||
{
|
||||
"name": "currentElementNode"
|
||||
},
|
||||
{
|
||||
"name": "defineComponent"
|
||||
},
|
||||
|
@ -378,7 +399,7 @@
|
|||
"name": "domRendererFactory3"
|
||||
},
|
||||
{
|
||||
"name": "elementClassNamed"
|
||||
"name": "elementClassProp"
|
||||
},
|
||||
{
|
||||
"name": "elementEnd"
|
||||
|
@ -389,6 +410,12 @@
|
|||
{
|
||||
"name": "elementStart"
|
||||
},
|
||||
{
|
||||
"name": "elementStyling"
|
||||
},
|
||||
{
|
||||
"name": "elementStylingApply"
|
||||
},
|
||||
{
|
||||
"name": "enterView"
|
||||
},
|
||||
|
@ -446,9 +473,21 @@
|
|||
{
|
||||
"name": "getCurrentSanitizer"
|
||||
},
|
||||
{
|
||||
"name": "getInitialIndex"
|
||||
},
|
||||
{
|
||||
"name": "getInitialValue"
|
||||
},
|
||||
{
|
||||
"name": "getLViewChild"
|
||||
},
|
||||
{
|
||||
"name": "getMultiOrSingleIndex"
|
||||
},
|
||||
{
|
||||
"name": "getMultiStartIndex"
|
||||
},
|
||||
{
|
||||
"name": "getNextLNode"
|
||||
},
|
||||
|
@ -479,12 +518,18 @@
|
|||
{
|
||||
"name": "getParentState"
|
||||
},
|
||||
{
|
||||
"name": "getPointers"
|
||||
},
|
||||
{
|
||||
"name": "getPreviousIndex"
|
||||
},
|
||||
{
|
||||
"name": "getPreviousOrParentNode"
|
||||
},
|
||||
{
|
||||
"name": "getProp"
|
||||
},
|
||||
{
|
||||
"name": "getRenderFlags"
|
||||
},
|
||||
|
@ -494,6 +539,9 @@
|
|||
{
|
||||
"name": "getRootView"
|
||||
},
|
||||
{
|
||||
"name": "getStylingContext"
|
||||
},
|
||||
{
|
||||
"name": "getSymbolIterator"
|
||||
},
|
||||
|
@ -506,6 +554,9 @@
|
|||
{
|
||||
"name": "getTypeNameForDebugging$1"
|
||||
},
|
||||
{
|
||||
"name": "getValue"
|
||||
},
|
||||
{
|
||||
"name": "hostElement"
|
||||
},
|
||||
|
@ -536,6 +587,9 @@
|
|||
{
|
||||
"name": "invertObject"
|
||||
},
|
||||
{
|
||||
"name": "isContextDirty"
|
||||
},
|
||||
{
|
||||
"name": "isCssClassMatching"
|
||||
},
|
||||
|
@ -545,6 +599,9 @@
|
|||
{
|
||||
"name": "isDifferent"
|
||||
},
|
||||
{
|
||||
"name": "isDirty"
|
||||
},
|
||||
{
|
||||
"name": "isJsObject"
|
||||
},
|
||||
|
@ -575,6 +632,12 @@
|
|||
{
|
||||
"name": "load"
|
||||
},
|
||||
{
|
||||
"name": "loadElement"
|
||||
},
|
||||
{
|
||||
"name": "loadElementInternal"
|
||||
},
|
||||
{
|
||||
"name": "loadInternal"
|
||||
},
|
||||
|
@ -602,6 +665,9 @@
|
|||
{
|
||||
"name": "notImplemented"
|
||||
},
|
||||
{
|
||||
"name": "pointers"
|
||||
},
|
||||
{
|
||||
"name": "projectionNodeStack"
|
||||
},
|
||||
|
@ -626,6 +692,9 @@
|
|||
{
|
||||
"name": "queueViewHooks"
|
||||
},
|
||||
{
|
||||
"name": "readElementValue"
|
||||
},
|
||||
{
|
||||
"name": "refreshChildComponents"
|
||||
},
|
||||
|
@ -653,6 +722,9 @@
|
|||
{
|
||||
"name": "renderEmbeddedTemplate"
|
||||
},
|
||||
{
|
||||
"name": "renderStyling"
|
||||
},
|
||||
{
|
||||
"name": "resetApplicationState"
|
||||
},
|
||||
|
@ -677,9 +749,21 @@
|
|||
{
|
||||
"name": "searchMatchesQueuedForCreation"
|
||||
},
|
||||
{
|
||||
"name": "setClass"
|
||||
},
|
||||
{
|
||||
"name": "setContextDirty"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentInjector"
|
||||
},
|
||||
{
|
||||
"name": "setDirty"
|
||||
},
|
||||
{
|
||||
"name": "setFlag"
|
||||
},
|
||||
{
|
||||
"name": "setHostBindings"
|
||||
},
|
||||
|
@ -689,9 +773,18 @@
|
|||
{
|
||||
"name": "setInputsFromAttrs"
|
||||
},
|
||||
{
|
||||
"name": "setProp"
|
||||
},
|
||||
{
|
||||
"name": "setStyle"
|
||||
},
|
||||
{
|
||||
"name": "setUpAttributes"
|
||||
},
|
||||
{
|
||||
"name": "setValue"
|
||||
},
|
||||
{
|
||||
"name": "storeCleanupFn"
|
||||
},
|
||||
|
@ -725,9 +818,18 @@
|
|||
{
|
||||
"name": "trackByIdentity"
|
||||
},
|
||||
{
|
||||
"name": "updateClassProp"
|
||||
},
|
||||
{
|
||||
"name": "updateStyleProp"
|
||||
},
|
||||
{
|
||||
"name": "updateViewQuery"
|
||||
},
|
||||
{
|
||||
"name": "valueExists"
|
||||
},
|
||||
{
|
||||
"name": "viewAttached"
|
||||
},
|
||||
|
|
|
@ -15,7 +15,6 @@ import {ComponentDefInternal, InitialStylingFlags} from '../../../src/render3/in
|
|||
import {ComponentFixture, renderComponent, toHtml} from '../render_util';
|
||||
|
||||
|
||||
|
||||
/// See: `normative.md`
|
||||
describe('elements', () => {
|
||||
// Saving type as $any$, etc to simplify testing for compiler, as types aren't saved
|
||||
|
@ -271,6 +270,7 @@ describe('elements', () => {
|
|||
});
|
||||
|
||||
it('should bind to a specific class', () => {
|
||||
const c1: (string | InitialStylingFlags | boolean)[] = ['foo'];
|
||||
type $MyComponent$ = MyComponent;
|
||||
|
||||
@Component({selector: 'my-component', template: `<div [class.foo]="someFlag"></div>`})
|
||||
|
@ -283,10 +283,13 @@ describe('elements', () => {
|
|||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵEe(0, 'div');
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(null, c1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵkn(0, 'foo', $r3$.ɵb(ctx.someFlag));
|
||||
$r3$.ɵcp(0, 0, ctx.someFlag);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -320,13 +323,13 @@ describe('elements', () => {
|
|||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1, c0);
|
||||
$r3$.ɵs(c0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsp(1, 0, ctx.someColor);
|
||||
$r3$.ɵsp(1, 1, ctx.someWidth, 'px');
|
||||
$r3$.ɵsa(1);
|
||||
$r3$.ɵsp(0, 0, ctx.someColor);
|
||||
$r3$.ɵsp(0, 1, ctx.someWidth, 'px');
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -353,7 +356,9 @@ describe('elements', () => {
|
|||
it('should bind to many and keep order', () => {
|
||||
type $MyComponent$ = MyComponent;
|
||||
|
||||
const c0 = ['color', InitialStylingFlags.INITIAL_STYLES, 'color', 'red'];
|
||||
const c0 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red'];
|
||||
const c1 = ['foo'];
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template:
|
||||
|
@ -369,12 +374,13 @@ describe('elements', () => {
|
|||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1, c0);
|
||||
$r3$.ɵs(c0, c1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵp(0, 'id', $r3$.ɵb(ctx.someString + 1));
|
||||
$r3$.ɵkn(0, 'foo', $r3$.ɵb(ctx.someString == 'initial'));
|
||||
$r3$.ɵcp(0, 0, ctx.someString == 'initial');
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -406,13 +412,12 @@ describe('elements', () => {
|
|||
template: function StyleComponent_Template(rf: $RenderFlags$, ctx: $StyleComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1);
|
||||
$r3$.ɵs();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵk(0, $r3$.ɵb(ctx.classExp));
|
||||
$r3$.ɵsm(1, ctx.styleExp);
|
||||
$r3$.ɵsa(1);
|
||||
$r3$.ɵsm(0, ctx.styleExp, ctx.classExp);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -44,17 +44,17 @@ describe('compiler sanitization', () => {
|
|||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(1, ['background-image']);
|
||||
$r3$.ɵs(['background-image']);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵEe(2, 'img');
|
||||
$r3$.ɵEe(1, 'img');
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵsanitizeHtml);
|
||||
$r3$.ɵp(0, 'hidden', $r3$.ɵb(ctx.hidden));
|
||||
$r3$.ɵsp(1, 0, ctx.style, $r3$.ɵsanitizeStyle);
|
||||
$r3$.ɵsa(1);
|
||||
$r3$.ɵp(2, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
||||
$r3$.ɵa(2, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
||||
$r3$.ɵsp(0, 0, ctx.style, $r3$.ɵsanitizeStyle);
|
||||
$r3$.ɵsa(0);
|
||||
$r3$.ɵp(1, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
||||
$r3$.ɵa(1, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
*/
|
||||
|
||||
import {defineComponent, defineDirective} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {ComponentFixture, createComponent, renderToHtml} from './render_util';
|
||||
|
||||
describe('exports', () => {
|
||||
|
@ -212,13 +213,15 @@ describe('exports', () => {
|
|||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
elementStyling(null, [InitialStylingFlags.VALUES_MODE, 'red', true]);
|
||||
elementEnd();
|
||||
elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
|
||||
elementEnd();
|
||||
}
|
||||
const tmp = load(2) as any;
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementClassNamed(0, 'red', bind(tmp.checked));
|
||||
elementClassProp(0, 0, tmp.checked);
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common';
|
|||
|
||||
import {RenderFlags, directiveInject} from '../../src/render3';
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {bind, container, element, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleProp, elementStyling, elementStylingApply, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
|
||||
import {bind, container, element, elementAttribute, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node';
|
||||
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
|
@ -23,13 +23,13 @@ import {ComponentFixture, TemplateFixture} from './render_util';
|
|||
describe('instructions', () => {
|
||||
function createAnchor() {
|
||||
elementStart(0, 'a');
|
||||
elementStyling(1);
|
||||
elementStyling();
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
function createDiv(initialStyles?: (string | number)[]) {
|
||||
elementStart(0, 'div');
|
||||
elementStyling(1, initialStyles && Array.isArray(initialStyles) ? initialStyles : null);
|
||||
elementStyling(initialStyles && Array.isArray(initialStyles) ? initialStyles : null);
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
|
@ -193,15 +193,15 @@ describe('instructions', () => {
|
|||
it('should use sanitizer function', () => {
|
||||
const t = new TemplateFixture(() => { return createDiv(['background-image']); });
|
||||
t.update(() => {
|
||||
elementStyleProp(1, 0, 'url("http://server")', sanitizeStyle);
|
||||
elementStylingApply(1);
|
||||
elementStyleProp(0, 0, 'url("http://server")', sanitizeStyle);
|
||||
elementStylingApply(0);
|
||||
});
|
||||
// nothing is set because sanitizer suppresses it.
|
||||
expect(t.html).toEqual('<div></div>');
|
||||
|
||||
t.update(() => {
|
||||
elementStyleProp(1, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle);
|
||||
elementStylingApply(1);
|
||||
elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle);
|
||||
elementStylingApply(0);
|
||||
});
|
||||
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
|
||||
.toEqual('url("http://server")');
|
||||
|
@ -211,25 +211,33 @@ describe('instructions', () => {
|
|||
describe('elementStyleMap', () => {
|
||||
function createDivWithStyle() {
|
||||
elementStart(0, 'div');
|
||||
elementStyling(1, ['height', InitialStylingFlags.INITIAL_STYLES, 'height', '10px']);
|
||||
elementStyling(['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
it('should add style', () => {
|
||||
const fixture = new TemplateFixture(createDivWithStyle);
|
||||
fixture.update(() => {
|
||||
elementStyle(1, {'background-color': 'red'});
|
||||
elementStylingApply(1);
|
||||
elementStylingMap(0, {'background-color': 'red'});
|
||||
elementStylingApply(0);
|
||||
});
|
||||
expect(fixture.html).toEqual('<div style="height: 10px; background-color: red;"></div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('elementClass', () => {
|
||||
function createDivWithStyling() {
|
||||
elementStart(0, 'div');
|
||||
elementStyling();
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
it('should add class', () => {
|
||||
const fixture = new TemplateFixture(createDiv);
|
||||
fixture.update(() => elementClass(0, 'multiple classes'));
|
||||
const fixture = new TemplateFixture(createDivWithStyling);
|
||||
fixture.update(() => {
|
||||
elementStylingMap(0, null, 'multiple classes');
|
||||
elementStylingApply(0);
|
||||
});
|
||||
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
import {RenderFlags} from '@angular/core/src/render3';
|
||||
|
||||
import {defineComponent, defineDirective} from '../../src/render3/index';
|
||||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {HEADER_OFFSET} from '../../src/render3/interfaces/view';
|
||||
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
||||
|
@ -747,12 +748,12 @@ describe('render3 integration test', () => {
|
|||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
elementStyling(1, ['border-color']);
|
||||
elementStyling(['border-color']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(1, 0, ctx);
|
||||
elementStylingApply(1);
|
||||
elementStyleProp(0, 0, ctx);
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,12 +767,12 @@ describe('render3 integration test', () => {
|
|||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
elementStyling(1, ['font-size']);
|
||||
elementStyling(['font-size']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(1, 0, ctx, 'px');
|
||||
elementStylingApply(1);
|
||||
elementStyleProp(0, 0, ctx, 'px');
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -787,10 +788,12 @@ describe('render3 integration test', () => {
|
|||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
elementStyling(null, ['active']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementClassNamed(0, 'active', bind(ctx));
|
||||
elementClassProp(0, 0, ctx);
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -809,11 +812,14 @@ describe('render3 integration test', () => {
|
|||
it('should work correctly with existing static classes', () => {
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'span', ['class', 'existing']);
|
||||
elementStart(0, 'span');
|
||||
elementStyling(
|
||||
null, ['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementClassNamed(0, 'active', bind(ctx));
|
||||
elementClassProp(0, 1, ctx);
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue