Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ivy): properly apply style="", [style], [style.foo] and [attr.style] bindings #24602

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 6 additions & 4 deletions modules/benchmarks/src/largetable/render3/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +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, ɵ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 {TableCell, buildTable, emptyTable} from '../util';

const c0 = ['background-color'];
export class LargeTableComponent {
data: TableCell[][] = emptyTable;

Expand Down Expand Up @@ -47,12 +48,13 @@ export class LargeTableComponent {
{
if (rf2 & RenderFlags.Create) {
E(0, 'td');
{ T(1); }
s(1, c0);
{ T(2); }
e();
}
if (rf2 & RenderFlags.Update) {
sn(0, 'background-color', b(cell.row % 2 ? '' : 'grey'));
t(1, b(cell.value));
sp(1, 0, cell.row % 2 ? '' : 'grey');
t(2, b(cell.value));
}
}
v();
Expand Down
30 changes: 17 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 {TreeNode, buildTree, emptyTree} from '../util';

Expand All @@ -30,6 +30,7 @@ export function detectChanges(component: TreeComponent) {
numberOfChecksEl.textContent = `${detectChangesRuns}`;
}

const c0 = ['background-color'];
export class TreeComponent {
data: TreeNode = emptyTree;

Expand All @@ -40,15 +41,16 @@ export class TreeComponent {
template: function(rf: RenderFlags, ctx: TreeComponent) {
if (rf & RenderFlags.Create) {
E(0, 'span');
{ T(1); }
s(1, c0);
{ 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, 0, ctx.data.depth % 2 ? '' : 'grey');
t(2, i1(' ', ctx.data.value, ' '));
cR(3);
{
if (ctx.data.left != null) {
let rf0 = V(0);
Expand All @@ -65,7 +67,7 @@ export class TreeComponent {
}
}
cr();
cR(3);
cR(4);
{
if (ctx.data.right != null) {
let rf0 = V(0);
Expand Down Expand Up @@ -106,22 +108,24 @@ export class TreeFunction {
});
}

const c1 = ['background-color'];
export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
if (rf & RenderFlags.Create) {
E(0, 'tree');
{
E(1, 'span');
{ T(2); }
s(2, c1);
{ 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, 0, ctx.depth % 2 ? '' : 'grey');
t(3, i1(' ', ctx.value, ' '));
cR(4);
{
if (ctx.left != null) {
let rf0 = V(0);
Expand All @@ -130,7 +134,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: 6 additions & 0 deletions packages/compiler/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,9 @@ export const enum RenderFlags {
/* Whether to run the update block (e.g. refresh bindings) */
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,
}
8 changes: 6 additions & 2 deletions packages/compiler/src/render3/r3_identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ export class Identifiers {

static elementClassNamed: o.ExternalReference = {name: 'ɵkn', moduleName: CORE};

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

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

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

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

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

Expand Down
111 changes: 111 additions & 0 deletions packages/compiler/src/render3/view/styling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @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 Char {
OpenParen = 40,
CloseParen = 41,
Colon = 58,
Semicolon = 59,
BackSlash = 92,
QuoteNone = 0, // indicating we are not inside a quote
QuoteDouble = 34,
QuoteSingle = 39,
}


/**
* Parses string representation of a style and converts it into object literal.
*
* @param value string representation of style as used in the `style` attribute in HTML.
* Example: `color: red; height: auto`.
* @returns an object literal. `{ color: 'red', height: 'auto'}`.
*/
export function parseStyle(value: string): {[key: string]: any} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This util could possibly benefit #19645 as well, should it be ivy-only?

const styles: {[key: string]: any} = {};

let i = 0;
let parenDepth = 0;
let quote: Char = Char.QuoteNone;
let valueStart = 0;
let propStart = 0;
let currentProp: string|null = null;
let valueHasQuotes = false;
while (i < value.length) {
const token = value.charCodeAt(i++) as Char;
switch (token) {
case Char.OpenParen:
parenDepth++;
break;
case Char.CloseParen:
parenDepth--;
break;
case Char.QuoteSingle:
// valueStart needs to be there since prop values don't
// have quotes in CSS
valueHasQuotes = valueHasQuotes || valueStart > 0;
if (quote === Char.QuoteNone) {
quote = Char.QuoteSingle;
} else if (quote === Char.QuoteSingle && value.charCodeAt(i - 1) !== Char.BackSlash) {
quote = Char.QuoteNone;
}
break;
case Char.QuoteDouble:
// same logic as above
valueHasQuotes = valueHasQuotes || valueStart > 0;
if (quote === Char.QuoteNone) {
quote = Char.QuoteDouble;
} else if (quote === Char.QuoteDouble && value.charCodeAt(i - 1) !== Char.BackSlash) {
quote = Char.QuoteNone;
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can merge these 2 cases for smaller code generation:

      case Char.QuoteSingle:
      case Char.QuoteDouble:
        // valueStart needs to be there since prop values don't
        // have quotes in CSS
        valueHasQuotes = valueHasQuotes || valueStart > 0;
        if (quote === Char.QuoteNone) {
          quote = token;
        } else if (quote === token && value.charCodeAt(i - 1) !== Char.BackSlash) {
          quote = Char.QuoteNone;
        }
        break;

case Char.Colon:
if (!currentProp && parenDepth === 0 && quote === Char.QuoteNone) {
currentProp = hyphenate(value.substring(propStart, i - 1).trim());
valueStart = i;
}
break;
case Char.Semicolon:
if (currentProp && valueStart > 0 && parenDepth === 0 && quote === Char.QuoteNone) {
const styleVal = value.substring(valueStart, i - 1).trim();
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal;
propStart = i;
valueStart = 0;
currentProp = null;
valueHasQuotes = false;
}
break;
}
}

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

return styles;
}

export function stripUnnecessaryQuotes(value: string): string {
const qS = value.charCodeAt(0);
const qE = value.charCodeAt(value.length - 1);
if (qS == qE && (qS == Char.QuoteSingle || qS == Char.QuoteDouble)) {
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();
}