Skip to content

Commit 3980640

Browse files
matskomhevery
authored andcommitted
feat(ivy): properly apply style="", [style], [style.foo] and [attr.style] bindings (angular#24602)
PR Close angular#24602
1 parent 52d43a9 commit 3980640

22 files changed

Lines changed: 1902 additions & 141 deletions

modules/benchmarks/src/largetable/render3/table.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
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';
9+
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';
1010
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
1111

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

14+
const c0 = ['background-color'];
1415
export class LargeTableComponent {
1516
data: TableCell[][] = emptyTable;
1617

@@ -47,12 +48,13 @@ export class LargeTableComponent {
4748
{
4849
if (rf2 & RenderFlags.Create) {
4950
E(0, 'td');
50-
{ T(1); }
51+
s(1, c0);
52+
{ T(2); }
5153
e();
5254
}
5355
if (rf2 & RenderFlags.Update) {
54-
sn(0, 'background-color', b(cell.row % 2 ? '' : 'grey'));
55-
t(1, b(cell.value));
56+
sp(1, 0, cell.row % 2 ? '' : 'grey');
57+
t(2, b(cell.value));
5658
}
5759
}
5860
v();

modules/benchmarks/src/tree/render3/tree.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
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';
9+
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';
1010

1111
import {TreeNode, buildTree, emptyTree} from '../util';
1212

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

33+
const c0 = ['background-color'];
3334
export class TreeComponent {
3435
data: TreeNode = emptyTree;
3536

@@ -40,15 +41,16 @@ export class TreeComponent {
4041
template: function(rf: RenderFlags, ctx: TreeComponent) {
4142
if (rf & RenderFlags.Create) {
4243
E(0, 'span');
43-
{ T(1); }
44+
s(1, c0);
45+
{ T(2); }
4446
e();
45-
C(2);
4647
C(3);
48+
C(4);
4749
}
4850
if (rf & RenderFlags.Update) {
49-
sn(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
50-
t(1, i1(' ', ctx.data.value, ' '));
51-
cR(2);
51+
sp(1, 0, ctx.data.depth % 2 ? '' : 'grey');
52+
t(2, i1(' ', ctx.data.value, ' '));
53+
cR(3);
5254
{
5355
if (ctx.data.left != null) {
5456
let rf0 = V(0);
@@ -65,7 +67,7 @@ export class TreeComponent {
6567
}
6668
}
6769
cr();
68-
cR(3);
70+
cR(4);
6971
{
7072
if (ctx.data.right != null) {
7173
let rf0 = V(0);
@@ -106,22 +108,24 @@ export class TreeFunction {
106108
});
107109
}
108110

111+
const c1 = ['background-color'];
109112
export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
110113
if (rf & RenderFlags.Create) {
111114
E(0, 'tree');
112115
{
113116
E(1, 'span');
114-
{ T(2); }
117+
s(2, c1);
118+
{ T(3); }
115119
e();
116-
C(3);
117120
C(4);
121+
C(5);
118122
}
119123
e();
120124
}
121125
if (rf & RenderFlags.Update) {
122-
sn(1, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
123-
t(2, i1(' ', ctx.value, ' '));
124-
cR(3);
126+
sp(2, 0, ctx.depth % 2 ? '' : 'grey');
127+
t(3, i1(' ', ctx.value, ' '));
128+
cR(4);
125129
{
126130
if (ctx.left != null) {
127131
let rf0 = V(0);
@@ -130,7 +134,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
130134
}
131135
}
132136
cr();
133-
cR(4);
137+
cR(5);
134138
{
135139
if (ctx.right != null) {
136140
let rf0 = V(0);

packages/compiler/src/core.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,9 @@ export const enum RenderFlags {
379379
/* Whether to run the update block (e.g. refresh bindings) */
380380
Update = 0b10
381381
}
382+
383+
// Note this will expand once `class` is introduced to styling
384+
export const enum InitialStylingFlags {
385+
/** Mode for matching initial style values */
386+
INITIAL_STYLES = 0b00,
387+
}

packages/compiler/src/render3/r3_identifiers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ export class Identifiers {
3737

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

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

42-
static elementStyleNamed: o.ExternalReference = {name: 'ɵsn', moduleName: CORE};
42+
static elementStyle: o.ExternalReference = {name: 'ɵsm', moduleName: CORE};
43+
44+
static elementStyleProp: o.ExternalReference = {name: 'ɵsp', moduleName: CORE};
45+
46+
static elementStylingApply: o.ExternalReference = {name: 'ɵsa', moduleName: CORE};
4347

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
const enum Char {
10+
OpenParen = 40,
11+
CloseParen = 41,
12+
Colon = 58,
13+
Semicolon = 59,
14+
BackSlash = 92,
15+
QuoteNone = 0, // indicating we are not inside a quote
16+
QuoteDouble = 34,
17+
QuoteSingle = 39,
18+
}
19+
20+
21+
/**
22+
* Parses string representation of a style and converts it into object literal.
23+
*
24+
* @param value string representation of style as used in the `style` attribute in HTML.
25+
* Example: `color: red; height: auto`.
26+
* @returns an object literal. `{ color: 'red', height: 'auto'}`.
27+
*/
28+
export function parseStyle(value: string): {[key: string]: any} {
29+
const styles: {[key: string]: any} = {};
30+
31+
let i = 0;
32+
let parenDepth = 0;
33+
let quote: Char = Char.QuoteNone;
34+
let valueStart = 0;
35+
let propStart = 0;
36+
let currentProp: string|null = null;
37+
let valueHasQuotes = false;
38+
while (i < value.length) {
39+
const token = value.charCodeAt(i++) as Char;
40+
switch (token) {
41+
case Char.OpenParen:
42+
parenDepth++;
43+
break;
44+
case Char.CloseParen:
45+
parenDepth--;
46+
break;
47+
case Char.QuoteSingle:
48+
// valueStart needs to be there since prop values don't
49+
// have quotes in CSS
50+
valueHasQuotes = valueHasQuotes || valueStart > 0;
51+
if (quote === Char.QuoteNone) {
52+
quote = Char.QuoteSingle;
53+
} else if (quote === Char.QuoteSingle && value.charCodeAt(i - 1) !== Char.BackSlash) {
54+
quote = Char.QuoteNone;
55+
}
56+
break;
57+
case Char.QuoteDouble:
58+
// same logic as above
59+
valueHasQuotes = valueHasQuotes || valueStart > 0;
60+
if (quote === Char.QuoteNone) {
61+
quote = Char.QuoteDouble;
62+
} else if (quote === Char.QuoteDouble && value.charCodeAt(i - 1) !== Char.BackSlash) {
63+
quote = Char.QuoteNone;
64+
}
65+
break;
66+
case Char.Colon:
67+
if (!currentProp && parenDepth === 0 && quote === Char.QuoteNone) {
68+
currentProp = hyphenate(value.substring(propStart, i - 1).trim());
69+
valueStart = i;
70+
}
71+
break;
72+
case Char.Semicolon:
73+
if (currentProp && valueStart > 0 && parenDepth === 0 && quote === Char.QuoteNone) {
74+
const styleVal = value.substring(valueStart, i - 1).trim();
75+
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal;
76+
propStart = i;
77+
valueStart = 0;
78+
currentProp = null;
79+
valueHasQuotes = false;
80+
}
81+
break;
82+
}
83+
}
84+
85+
if (currentProp && valueStart) {
86+
const styleVal = value.substr(valueStart).trim();
87+
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal;
88+
}
89+
90+
return styles;
91+
}
92+
93+
export function stripUnnecessaryQuotes(value: string): string {
94+
const qS = value.charCodeAt(0);
95+
const qE = value.charCodeAt(value.length - 1);
96+
if (qS == qE && (qS == Char.QuoteSingle || qS == Char.QuoteDouble)) {
97+
const tempValue = value.substring(1, value.length - 1);
98+
// special case to avoid using a multi-quoted string that was just chomped
99+
// (e.g. `font-family: "Verdana", "sans-serif"`)
100+
if (tempValue.indexOf('\'') == -1 && tempValue.indexOf('"') == -1) {
101+
value = tempValue;
102+
}
103+
}
104+
return value;
105+
}
106+
107+
export function hyphenate(value: string): string {
108+
return value.replace(/[a-z][A-Z]/g, v => {
109+
return v.charAt(0) + '-' + v.charAt(1);
110+
}).toLowerCase();
111+
}

0 commit comments

Comments
 (0)