Skip to content

Commit

Permalink
feat(ivy): properly apply style="", [style], [style.foo] and [attr.st…
Browse files Browse the repository at this point in the history
…yle] bindings
  • Loading branch information
matsko committed Jun 21, 2018
1 parent cb31381 commit 578c392
Show file tree
Hide file tree
Showing 18 changed files with 789 additions and 127 deletions.
11 changes: 5 additions & 6 deletions modules/benchmarks/src/largetable/render3/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵs as s, ɵsa as sa, ɵsm as sm, ɵsp as sp, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';

import {TableCell, buildTable, emptyTable} from '../util';

export class LargeTableComponent {
data: TableCell[][] = emptyTable;

/** @nocollapse */
static ngComponentDef: ComponentDefInternal<LargeTableComponent> = defineComponent({
type: LargeTableComponent,
Expand Down Expand Up @@ -47,12 +45,13 @@ export class LargeTableComponent {
{
if (rf2 & RenderFlags.Create) {
E(0, 'td');
{ T(1); }
s(1);
{ T(2); }
e();
}
if (rf2 & RenderFlags.Update) {
sn(0, 'background-color', b(cell.row % 2 ? '' : 'grey'));
t(1, b(cell.value));
sp(1, 'background-color', cell.row % 2 ? '' : 'grey');
t(2, b(cell.value));
}
}
v();
Expand Down
28 changes: 15 additions & 13 deletions modules/benchmarks/src/tree/render3/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵs as s, ɵsa as sa, ɵsm as sm, ɵsp as sp, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';

import {TreeNode, buildTree, emptyTree} from '../util';
Expand Down Expand Up @@ -41,15 +41,16 @@ export class TreeComponent {
template: function(rf: RenderFlags, ctx: TreeComponent) {
if (rf & RenderFlags.Create) {
E(0, 'span');
{ T(1); }
s(1);
{ T(2); }
e();
C(2);
C(3);
C(4);
}
if (rf & RenderFlags.Update) {
sn(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
t(1, i1(' ', ctx.data.value, ' '));
cR(2);
sp(1, 'background-color', ctx.data.depth % 2 ? '' : 'grey');
t(2, i1(' ', ctx.data.value, ' '));
cR(3);
{
if (ctx.data.left != null) {
let rf0 = V(0);
Expand All @@ -66,7 +67,7 @@ export class TreeComponent {
}
}
cr();
cR(3);
cR(4);
{
if (ctx.data.right != null) {
let rf0 = V(0);
Expand Down Expand Up @@ -112,17 +113,18 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
E(0, 'tree');
{
E(1, 'span');
{ T(2); }
s(2);
{ T(3); }
e();
C(3);
C(4);
C(5);
}
e();
}
if (rf & RenderFlags.Update) {
sn(1, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
t(2, i1(' ', ctx.value, ' '));
cR(3);
sp(2, 'background-color', ctx.depth % 2 ? '' : 'grey');
t(3, i1(' ', ctx.value, ' '));
cR(4);
{
if (ctx.left != null) {
let rf0 = V(0);
Expand All @@ -131,7 +133,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
}
}
cr();
cR(4);
cR(5);
{
if (ctx.right != null) {
let rf0 = V(0);
Expand Down
6 changes: 5 additions & 1 deletion packages/compiler/src/render3/r3_identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ export class Identifiers {

static elementStyle: o.ExternalReference = {name: 'ɵs', moduleName: CORE};

static elementStyleNamed: o.ExternalReference = {name: 'ɵsn', moduleName: CORE};
static elementStyleMap: o.ExternalReference = {name: 'ɵsm', moduleName: CORE};

static elementStyleProp: o.ExternalReference = {name: 'ɵsp', moduleName: CORE};

static elementStyleApply: o.ExternalReference = {name: 'ɵsa', moduleName: CORE};

static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};

Expand Down
108 changes: 108 additions & 0 deletions packages/compiler/src/render3/view/styling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

const enum CharCode {
OpenParen = 40,
CloseParen = 41,
SingleQuote = 39,
DoubleQuote = 34,
Colon = 58,
Semicolon = 59,
BackSlash = 92,
}

const enum QuoteMode {
None = 0,
Single = 1,
Double = 2,
}

export function parseStyle(value: string): {[key: string]: any} {
const styles: {[key: string]: any} = {};

let i = 0;
let parenDepth = 0;
let quote: QuoteMode = 0;
let valueStart = 0;
let propStart = 0;
let currentProp: string|null = null;
let valueHasQuotes = false;
while (i < value.length) {
const token = value.charCodeAt(i++) as CharCode;
switch (token) {
case CharCode.OpenParen:
parenDepth++;
break;
case CharCode.CloseParen:
parenDepth--;
break;
case CharCode.SingleQuote:
// valueStart needs to be there since prop values don't
// have quotes in CSS
valueHasQuotes = valueHasQuotes || valueStart > 0;
if (quote === QuoteMode.None) {
quote = QuoteMode.Single;
} else if (quote === QuoteMode.Single && value.charCodeAt(i - 1) !== CharCode.BackSlash) {
quote = QuoteMode.None;
}
break;
case CharCode.DoubleQuote:
// same logic as above
valueHasQuotes = valueHasQuotes || valueStart > 0;
if (quote === QuoteMode.None) {
quote = QuoteMode.Double;
} else if (quote === QuoteMode.Double && value.charCodeAt(i - 1) !== CharCode.BackSlash) {
quote = QuoteMode.None;
}
break;
case CharCode.Colon:
if (!currentProp && parenDepth === 0 && quote === QuoteMode.None) {
currentProp = hyphenate(value.substring(propStart, i - 1).trim());
valueStart = i;
}
break;
case CharCode.Semicolon:
if (currentProp && valueStart > 0 && parenDepth === 0 && quote === QuoteMode.None) {
const styleVal = value.substring(valueStart, i - 1).trim();
styles[currentProp] = valueHasQuotes ? chompStartAndEndQuotes(styleVal) : styleVal;
propStart = i;
valueStart = 0;
currentProp = null;
valueHasQuotes = false;
}
break;
}
}

if (currentProp && valueStart) {
const styleVal = value.substr(valueStart).trim();
styles[currentProp] = valueHasQuotes ? chompStartAndEndQuotes(styleVal) : styleVal;
}

return styles;
}

export function chompStartAndEndQuotes(value: string): string {
const qS = value.charCodeAt(0);
const qE = value.charCodeAt(value.length - 1);
if (qS == qE && (qS == CharCode.SingleQuote || qS == CharCode.DoubleQuote)) {
const tempValue = value.substring(1, value.length - 1);
// special case to avoid using a multi-quoted string that was just chomped
// (e.g. `font-family: "Verdana", "sans-serif"`)
if (tempValue.indexOf('\'') == -1 && tempValue.indexOf('"') == -1) {
value = tempValue;
}
}
return value;
}

export function hyphenate(value: string): string {
return value.replace(/[a-z][A-Z]/g, v => {
return v.charAt(0) + '-' + v.charAt(1);
}).toLowerCase();
}
91 changes: 76 additions & 15 deletions packages/compiler/src/render3/view/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,20 @@ import {Identifiers as R3} from '../r3_identifiers';
import {htmlAstToRender3Ast} from '../r3_template_transform';

import {R3QueryMetadata} from './api';
import {parseStyle} from './styling';
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';

const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
[BindingType.Property]: R3.elementProperty,
[BindingType.Attribute]: R3.elementAttribute,
[BindingType.Class]: R3.elementClassNamed,
[BindingType.Style]: R3.elementStyleNamed,
[BindingType.Class]: R3.elementClassNamed
};

// `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,
'style': R3.elementStyle
'className': R3.elementClass
};

export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
Expand Down Expand Up @@ -308,16 +307,24 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Add the attributes
const i18nMessages: o.Statement[] = [];
const attributes: o.Expression[] = [];
const initialStyles: {key: string, quoted: boolean, value: o.Expression}[] = [];

Object.getOwnPropertyNames(outputAttrs).forEach(name => {
const value = outputAttrs[name];
attributes.push(o.literal(name));
if (attrI18nMetas.hasOwnProperty(name)) {
const meta = parseI18nMeta(attrI18nMetas[name]);
const variable = this.constantPool.getTranslation(value, meta);
attributes.push(variable);
if (name == 'style') {
const styles = parseStyle(value);
Object.keys(styles).forEach(key => {
initialStyles.push({key, value: o.literal(styles[key]), quoted: key.indexOf('-') >= 0});
});
} else {
attributes.push(o.literal(value));
attributes.push(o.literal(name));
if (attrI18nMetas.hasOwnProperty(name)) {
const meta = parseI18nMeta(attrI18nMetas[name]);
const variable = this.constantPool.getTranslation(value, meta);
attributes.push(variable);
} else {
attributes.push(o.literal(value));
}
}
});

Expand Down Expand Up @@ -361,7 +368,29 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver

const implicit = o.variable(CONTEXT_NAME);

if (isEmptyElement) {
const styleInputs: 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]="" asignments
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);
}
});

const elementStyleIndex =
(initialStyles.length || styleInputs.length) ? this.allocateDataSlot() : 0;
const createSelfClosingInstruction =
styleInputs.length == 0 && initialStyles.length == 0 && isEmptyElement;

if (createSelfClosingInstruction) {
this.instruction(
this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
} else {
Expand All @@ -373,6 +402,15 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this._creationCode, element.sourceSpan, R3.elementStart,
...trimTrailingNulls(parameters));

// initial styling for static style="..." attributes
if (elementStyleIndex) {
let paramsList: (o.LiteralExpr | o.LiteralMapExpr)[] = [o.literal(elementStyleIndex)];
if (Object.keys(initialStyles).length) {
paramsList.push(o.literalMap(initialStyles));
}
this._creationCode.push(o.importExpr(R3.elementStyle).callFn(paramsList).toStmt());
}

// Generate Listeners (outputs)
element.outputs.forEach((outputAst: t.BoundEvent) => {
const elName = sanitizeIdentifier(element.name);
Expand All @@ -396,11 +434,32 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
});
}

if (styleInputs.length && elementStyleIndex) {
const indexLiteral = o.literal(elementStyleIndex);
styleInputs.forEach((input, i) => {
const isMulti = i == 0 && input.name == 'style';
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
if (isMulti) {
this.instruction(
this._bindingCode, input.sourceSpan, R3.elementStyleMap, indexLiteral,
convertedBinding);
} else {
this.instruction(
this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral,
o.literal(input.name), convertedBinding);
}
});

const spanEnd = styleInputs[styleInputs.length - 1].sourceSpan;
this.instruction(this._bindingCode, spanEnd, R3.elementStyleApply, indexLiteral);
}

// Generate element input bindings
element.inputs.forEach((input: t.BoundAttribute) => {
allOtherInputs.forEach((input: t.BoundAttribute) => {
if (input.type === BindingType.Animation) {
this._unsupported('animations');
}

const convertedBinding = this.convertPropertyBinding(implicit, input.value);
const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP[input.name];
if (specialInstruction) {
Expand Down Expand Up @@ -434,7 +493,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
t.visitAll(this, element.children);
}

if (!isEmptyElement) {
if (!createSelfClosingInstruction) {
// Finish element construction mode.
this.instruction(
this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd);
Expand Down Expand Up @@ -560,7 +619,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
}

private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
o.Expression {
const pipesConvertedValue = value.visit(this._valueConverter);
if (pipesConvertedValue instanceof Interpolation) {
const convertedPropertyBinding = convertPropertyBinding(
Expand All @@ -573,7 +633,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
() => error('Unexpected interpolation'));
this._bindingCode.push(...convertedPropertyBinding.stmts);
return o.importExpr(R3.bind).callFn([convertedPropertyBinding.currValExpr]);
const valExpr = convertedPropertyBinding.currValExpr;
return skipBindFn ? valExpr : o.importExpr(R3.bind).callFn([valExpr]);
}
}
}
Expand Down

0 comments on commit 578c392

Please sign in to comment.