fix(ivy): merge static style rendering across elements, directives and components (#27661)
PR Close #27661
This commit is contained in:
parent
0b3ae3d70c
commit
13eb57a59f
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AttributeMarker, InitialStylingFlags} from '@angular/compiler/src/core';
|
||||
import {AttributeMarker} from '@angular/compiler/src/core';
|
||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
|
@ -48,17 +48,15 @@ describe('compiler compliance', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ["title", "Hello"];
|
||||
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
|
||||
const $c3$ = ["cx", "20", "cy", "30", "r", "50"];
|
||||
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
|
||||
const $c2$ = ["cx", "20", "cy", "30", "r", "50"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $c1$);
|
||||
$r3$.ɵelementStyling($c2$);
|
||||
$r3$.ɵnamespaceSVG();
|
||||
$r3$.ɵelementStart(1, "svg");
|
||||
$r3$.ɵelement(2, "circle", $c3$);
|
||||
$r3$.ɵelement(2, "circle", $c2$);
|
||||
$r3$.ɵelementEnd();
|
||||
$r3$.ɵnamespaceHTML();
|
||||
$r3$.ɵelementStart(3, "p");
|
||||
|
@ -100,13 +98,11 @@ describe('compiler compliance', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ["title", "Hello"];
|
||||
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
|
||||
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $c1$);
|
||||
$r3$.ɵelementStyling($c2$);
|
||||
$r3$.ɵnamespaceMathML();
|
||||
$r3$.ɵelementStart(1, "math");
|
||||
$r3$.ɵelement(2, "infinity");
|
||||
|
@ -150,13 +146,11 @@ describe('compiler compliance', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ["title", "Hello"];
|
||||
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
|
||||
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $c1$);
|
||||
$r3$.ɵelementStyling($c2$);
|
||||
$r3$.ɵtext(1, "Hello ");
|
||||
$r3$.ɵelementStart(2, "b");
|
||||
$r3$.ɵtext(3, "World");
|
||||
|
@ -486,8 +480,8 @@ describe('compiler compliance', () => {
|
|||
const factory =
|
||||
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
|
||||
const template = `
|
||||
const _c0 = ["error"];
|
||||
const _c1 = ["background-color"];
|
||||
const $e0_classBindings$ = ["error"];
|
||||
const $e0_styleBindings$ = ["background-color"];
|
||||
…
|
||||
MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
|
||||
factory: function MyComponent_Factory(t){
|
||||
|
@ -498,7 +492,7 @@ describe('compiler compliance', () => {
|
|||
template: function MyComponent_Template(rf,ctx){
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div");
|
||||
$r3$.ɵelementStyling(_c0, _c1);
|
||||
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
|
@ -1204,7 +1198,7 @@ describe('compiler compliance', () => {
|
|||
}
|
||||
};
|
||||
const output = `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $_c1$ = ["id", "second"];
|
||||
function Cmp_div_Template_0(rf, ctx) { if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $_c1$);
|
||||
|
@ -1310,7 +1304,6 @@ describe('compiler compliance', () => {
|
|||
const {source} = compile(files, angularFiles);
|
||||
expectEmit(source, output, 'Invalid content projection instructions generated');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('queries', () => {
|
||||
|
|
|
@ -236,7 +236,7 @@ describe('compiler compliance: directives', () => {
|
|||
|
||||
const MyComponentDefinition = `
|
||||
…
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $_c1$ = ["directiveA", ""];
|
||||
function MyComponent_ng_container_Template_0(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AttributeMarker} from '@angular/compiler/src/core';
|
||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler';
|
||||
|
@ -393,7 +394,7 @@ describe('i18n support in the view compiler', () => {
|
|||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["ngFor", "", 1, "ngForOf"];
|
||||
const $_c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"];
|
||||
/**
|
||||
* @desc d
|
||||
* @meaning m
|
||||
|
@ -522,7 +523,7 @@ describe('i18n support in the view compiler', () => {
|
|||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["ngFor", "", 1, "ngForOf"];
|
||||
const $_c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"];
|
||||
/**
|
||||
* @desc d
|
||||
* @meaning m
|
||||
|
@ -922,7 +923,7 @@ describe('i18n support in the view compiler', () => {
|
|||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$ = goog.getMsg(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", {
|
||||
"interpolation": "\uFFFD0\uFFFD",
|
||||
"startTagDiv": "\uFFFD#3\uFFFD",
|
||||
|
@ -976,7 +977,7 @@ describe('i18n support in the view compiler', () => {
|
|||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["src", "logo.png"];
|
||||
const $_c1$ = [1, "ngIf"];
|
||||
const $_c1$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
function MyComponent_img_Template_1(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelement(0, "img", $_c0$);
|
||||
|
@ -1043,7 +1044,7 @@ describe('i18n support in the view compiler', () => {
|
|||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
function MyComponent_div_div_Template_4(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵi18nStart(0, $I18N_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$, 2);
|
||||
|
@ -1136,7 +1137,7 @@ describe('i18n support in the view compiler', () => {
|
|||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$ = goog.getMsg("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", {
|
||||
"startTagSpan": "\uFFFD#2\uFFFD",
|
||||
"interpolation": "\uFFFD0\uFFFD",
|
||||
|
@ -1259,23 +1260,21 @@ describe('i18n support in the view compiler', () => {
|
|||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["myClass", 1, "myClass", true];
|
||||
const $_c0$ = [${AttributeMarker.Classes}, "myClass"];
|
||||
const $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$ = goog.getMsg("Text #1");
|
||||
const $_c1$ = ["padding", 1, "padding", "10px"];
|
||||
const $_c1$ = [${AttributeMarker.Styles}, "padding", "10px"];
|
||||
const $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$ = goog.getMsg("Text #2");
|
||||
…
|
||||
consts: 4,
|
||||
vars: 0,
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "span");
|
||||
$r3$.ɵelementStart(0, "span", $_c0$);
|
||||
$r3$.ɵi18nStart(1, $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$);
|
||||
$r3$.ɵelementStyling($_c0$);
|
||||
$r3$.ɵi18nEnd();
|
||||
$r3$.ɵelementEnd();
|
||||
$r3$.ɵelementStart(2, "span");
|
||||
$r3$.ɵelementStart(2, "span", $_c1$);
|
||||
$r3$.ɵi18nStart(3, $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$);
|
||||
$r3$.ɵelementStyling(null, $_c1$);
|
||||
$r3$.ɵi18nEnd();
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
|
@ -1701,7 +1700,7 @@ describe('i18n support in the view compiler', () => {
|
|||
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD"
|
||||
});
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $_c1$ = ["title", "icu only"];
|
||||
const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}");
|
||||
const $I18N_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$, {
|
||||
|
@ -1942,7 +1941,7 @@ describe('i18n support in the view compiler', () => {
|
|||
const $I18N_APP_SPEC_TS_2$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS_2$, {
|
||||
"VAR_SELECT": "\uFFFD1\uFFFD"
|
||||
});
|
||||
const $_c3$ = [1, "ngIf"];
|
||||
const $_c3$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}");
|
||||
const $I18N_APP_SPEC_TS__4$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS__4$, {
|
||||
"VAR_SELECT": "\uFFFD0:1\uFFFD"
|
||||
|
@ -2050,7 +2049,7 @@ describe('i18n support in the view compiler', () => {
|
|||
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD"
|
||||
});
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}");
|
||||
const $I18N_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$, {
|
||||
"VAR_SELECT": "\uFFFD0:1\uFFFD"
|
||||
|
@ -2113,7 +2112,7 @@ describe('i18n support in the view compiler', () => {
|
|||
const $I18N_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD"
|
||||
});
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", {
|
||||
"interpolation": "\uFFFD1:1\uFFFD"
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AttributeMarker, InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import {AttributeMarker, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
|
@ -366,8 +366,8 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "style"];
|
||||
const $e0_styling$ = ["opacity","width","height",${InitialStylingFlags.VALUES_MODE},"opacity","1"];
|
||||
const $_c0$ = [${AttributeMarker.Styles}, "opacity", "1", ${AttributeMarker.SelectOnly}, "style"];
|
||||
const $_c1$ = ["width", "height"];
|
||||
…
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
|
@ -379,14 +379,14 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 1,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelementStyling(null, $e0_styling$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵelementStart(0, "div", $_c0$);
|
||||
$r3$.ɵelementStyling(null, $_c1$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStylingMap(0, null, $ctx$.myStyleExp);
|
||||
$r3$.ɵelementStyleProp(0, 1, $ctx$.myWidth);
|
||||
$r3$.ɵelementStyleProp(0, 2, $ctx$.myHeight);
|
||||
$r3$.ɵelementStyleProp(0, 0, $ctx$.myWidth);
|
||||
$r3$.ɵelementStyleProp(0, 1, $ctx$.myHeight);
|
||||
$r3$.ɵelementStylingApply(0);
|
||||
$r3$.ɵelementAttribute(0, "style", $r3$.ɵbind("border-width: 10px"), $r3$.ɵsanitizeStyle);
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["background-image"];
|
||||
const $_c0$ = ["background-image"];
|
||||
export class MyComponent {
|
||||
constructor() {
|
||||
this.myImage = 'url(foo.jpg)';
|
||||
|
@ -456,7 +456,6 @@ describe('compiler compliance: styling', () => {
|
|||
});
|
||||
|
||||
it('should support [style.foo.suffix] style bindings with a suffix', () => {
|
||||
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
|
@ -476,7 +475,7 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $e0_styles$= ["font-size"];
|
||||
const $e0_styles$ = ["font-size"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
|
@ -564,8 +563,8 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class"];
|
||||
const $e0_cd$ = ["grape","apple","orange",${InitialStylingFlags.VALUES_MODE},"grape",true];
|
||||
const $e0_attrs$ = [${AttributeMarker.Classes}, "grape", ${AttributeMarker.SelectOnly}, "class"];
|
||||
const $e0_bindings$ = ["apple", "orange"];
|
||||
…
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
|
@ -578,13 +577,13 @@ describe('compiler compliance: styling', () => {
|
|||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelementStyling($e0_cd$);
|
||||
$r3$.ɵelementStyling($e0_bindings$);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStylingMap(0, $ctx$.myClassExp);
|
||||
$r3$.ɵelementClassProp(0, 1, $ctx$.yesToApple);
|
||||
$r3$.ɵelementClassProp(0, 2, $ctx$.yesToOrange);
|
||||
$r3$.ɵelementClassProp(0, 0, $ctx$.yesToApple);
|
||||
$r3$.ɵelementClassProp(0, 1, $ctx$.yesToOrange);
|
||||
$r3$.ɵelementStylingApply(0);
|
||||
$r3$.ɵelementAttribute(0, "class", $r3$.ɵbind("banana"));
|
||||
}
|
||||
|
@ -606,7 +605,7 @@ describe('compiler compliance: styling', () => {
|
|||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="foo"
|
||||
template: \`<div class=" foo "
|
||||
style="width:100px"
|
||||
[attr.class]="'round'"
|
||||
[attr.style]="'height:100px'"></div>\`
|
||||
|
@ -620,9 +619,7 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class", "style"];
|
||||
const $e0_cd$ = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
|
||||
const $e0_sd$ = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
|
||||
const $e0_attrs$ = [${AttributeMarker.Classes}, "foo", ${AttributeMarker.Styles}, "width", "100px", ${AttributeMarker.SelectOnly}, "class", "style"];
|
||||
…
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
|
@ -635,7 +632,6 @@ describe('compiler compliance: styling', () => {
|
|||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelementStyling($e0_cd$, $e0_sd$);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
|
@ -765,10 +761,13 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $e0_classBindings$ = ["foo"];
|
||||
const $e0_styleBindings$ = ["bar", "baz"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div");
|
||||
$r3$.ɵelementStyling($e0_styling$, $e1_styling$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵpipe(1, "pipe");
|
||||
$r3$.ɵpipe(2, "pipe");
|
||||
$r3$.ɵpipe(3, "pipe");
|
||||
|
@ -828,16 +827,18 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true];
|
||||
const _c1 = ["width", "height", "color", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"];
|
||||
const $e0_attrs$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
|
||||
const $e0_classBindings$ = ["foo"];
|
||||
const $e0_styleBindings$ = ["color"];
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
$r3$.ɵelementHostAttrs(ctx, $e0_attrs$);
|
||||
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStylingMap(elIndex, ctx.myClass, ctx.myStyle, ctx);
|
||||
$r3$.ɵelementStyleProp(elIndex, 2, ctx.myColorProp, null, ctx);
|
||||
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myColorProp, null, ctx);
|
||||
$r3$.ɵelementClassProp(elIndex, 0, ctx.myFooClass, ctx);
|
||||
$r3$.ɵelementStylingApply(elIndex, ctx);
|
||||
}
|
||||
|
@ -959,10 +960,10 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["foo"];
|
||||
const _c1 = ["width"];
|
||||
const _c2 = ["bar"];
|
||||
const _c3 = ["height"];
|
||||
const $widthDir_classes$ = ["foo"];
|
||||
const $widthDir_styles$ = ["width"];
|
||||
const $heightDir_classes$ = ["bar"];
|
||||
const $heightDir_styles$ = ["height"];
|
||||
…
|
||||
function ClassDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
|
@ -976,7 +977,7 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
function WidthDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStyling(_c0, _c1, null, ctx);
|
||||
$r3$.ɵelementStyling($widthDir_classes$, $widthDir_styles$, null, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myWidth, null, ctx);
|
||||
|
@ -987,7 +988,7 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
function HeightDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStyling(_c2, _c3, null, ctx);
|
||||
$r3$.ɵelementStyling($heightDir_classes$, $heightDir_styles$, null, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myHeight, null, ctx);
|
||||
|
@ -1014,7 +1015,8 @@ describe('compiler compliance: styling', () => {
|
|||
template: '',
|
||||
host: {
|
||||
'style': 'width:200px; height:500px',
|
||||
'class': 'foo baz'
|
||||
'class': 'foo baz',
|
||||
'title': 'foo title'
|
||||
}
|
||||
})
|
||||
export class MyComponent {
|
||||
|
@ -1029,6 +1031,9 @@ describe('compiler compliance: styling', () => {
|
|||
|
||||
@HostBinding('title')
|
||||
title = 'some title';
|
||||
|
||||
@Input('name')
|
||||
name = '';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
|
@ -1038,13 +1043,13 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $_c0$ = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true];
|
||||
const $_c1$ = ["width", "height", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"];
|
||||
const $_c0$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵallocHostVars(2);
|
||||
$r3$.ɵelementStyling($_c0$, $_c1$, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
$r3$.ɵelementHostAttrs(ctx, $_c0$);
|
||||
$r3$.ɵelementStyling(null, null, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.id), null, true);
|
||||
|
|
|
@ -379,13 +379,9 @@ export const enum RenderFlags {
|
|||
Update = 0b10
|
||||
}
|
||||
|
||||
export const enum InitialStylingFlags {
|
||||
VALUES_MODE = 0b1,
|
||||
}
|
||||
|
||||
// Pasted from render3/interfaces/node.ts
|
||||
/**
|
||||
* A set of marker values to be used in the attributes arrays. Those markers indicate that some
|
||||
* A set of marker values to be used in the attributes arrays. These markers indicate that some
|
||||
* items are not regular attributes and the processing should be adapted accordingly.
|
||||
*/
|
||||
export const enum AttributeMarker {
|
||||
|
@ -396,11 +392,48 @@ export const enum AttributeMarker {
|
|||
*/
|
||||
NamespaceURI = 0,
|
||||
|
||||
/**
|
||||
* Signals class declaration.
|
||||
*
|
||||
* Each value following `Classes` designates a class name to include on the element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div class="foo bar baz">...<d/vi>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Classes, 'foo', 'bar', 'baz'];
|
||||
* ```
|
||||
*/
|
||||
Classes = 1,
|
||||
|
||||
/**
|
||||
* Signals style declaration.
|
||||
*
|
||||
* Each pair of values following `Styles` designates a style name and value to include on the
|
||||
* element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div style="width:100px; height:200px; color:red">...</div>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Styles, 'width', '100px', 'height'. '200px', 'color', 'red'];
|
||||
* ```
|
||||
*/
|
||||
Styles = 2,
|
||||
|
||||
/**
|
||||
* This marker indicates that the following attribute names were extracted from bindings (ex.:
|
||||
* [foo]="exp") and / or event handlers (ex. (bar)="doSth()").
|
||||
* Taking the above bindings and outputs as an example an attributes array could look as follows:
|
||||
* ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar']
|
||||
*/
|
||||
SelectOnly = 1
|
||||
}
|
||||
SelectOnly = 3,
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ export class Identifiers {
|
|||
|
||||
static elementStyling: o.ExternalReference = {name: 'ɵelementStyling', moduleName: CORE};
|
||||
|
||||
static elementHostAttrs: o.ExternalReference = {name: 'ɵelementHostAttrs', moduleName: CORE};
|
||||
|
||||
static elementStylingMap: o.ExternalReference = {name: 'ɵelementStylingMap', moduleName: CORE};
|
||||
|
||||
static elementStyleProp: o.ExternalReference = {name: 'ɵelementStyleProp', moduleName: CORE};
|
||||
|
|
|
@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform';
|
|||
import {typeWithParameters} from '../util';
|
||||
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||
import {StylingBuilder, StylingInstruction} from './styling';
|
||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
|
||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||
|
||||
|
@ -709,16 +709,35 @@ function createHostBindingsFunction(
|
|||
}
|
||||
}
|
||||
|
||||
if (styleBuilder.hasBindingsOrInitialValues) {
|
||||
const createInstruction = styleBuilder.buildCreateLevelInstruction(null, constantPool);
|
||||
if (createInstruction) {
|
||||
const createStmt = createStylingStmt(createInstruction, bindingContext, bindingFn);
|
||||
createStatements.push(createStmt);
|
||||
if (styleBuilder.hasBindingsOrInitialValues()) {
|
||||
// since we're dealing with directives here and directives have a hostBinding
|
||||
// function, we need to generate special instructions that deal with styling
|
||||
// (both bindings and initial values). The instruction below will instruct
|
||||
// all initial styling (styling that is inside of a host binding within a
|
||||
// directive) to be attached to the host element of the directive.
|
||||
const hostAttrsInstruction =
|
||||
styleBuilder.buildDirectiveHostAttrsInstruction(null, constantPool);
|
||||
if (hostAttrsInstruction) {
|
||||
createStatements.push(createStylingStmt(hostAttrsInstruction, bindingContext, bindingFn));
|
||||
}
|
||||
|
||||
// singular style/class bindings (things like `[style.prop]` and `[class.name]`)
|
||||
// MUST be registered on a given element within the component/directive
|
||||
// templateFn/hostBindingsFn functions. The instruction below will figure out
|
||||
// what all the bindings are and then generate the statements required to register
|
||||
// those bindings to the element via `elementStyling`.
|
||||
const elementStylingInstruction =
|
||||
styleBuilder.buildElementStylingInstruction(null, constantPool);
|
||||
if (elementStylingInstruction) {
|
||||
createStatements.push(
|
||||
createStylingStmt(elementStylingInstruction, bindingContext, bindingFn));
|
||||
}
|
||||
|
||||
// finally each binding that was registered in the statement above will need to be added to
|
||||
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
||||
// are evaluated and updated for the element.
|
||||
styleBuilder.buildUpdateLevelInstructions(valueConverter).forEach(instruction => {
|
||||
const updateStmt = createStylingStmt(instruction, bindingContext, bindingFn);
|
||||
updateStatements.push(updateStmt);
|
||||
updateStatements.push(createStylingStmt(instruction, bindingContext, bindingFn));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,15 @@ const enum Char {
|
|||
*
|
||||
* @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'}`.
|
||||
* @returns An array of style property name and value pairs, e.g. `['color', 'red', 'height',
|
||||
* 'auto']`
|
||||
*/
|
||||
export function parse(value: string): {[key: string]: any} {
|
||||
const styles: {[key: string]: any} = {};
|
||||
export function parse(value: string): string[] {
|
||||
// we use a string array here instead of a string map
|
||||
// because a string-map is not guaranteed to retain the
|
||||
// order of the entries whereas a string array can be
|
||||
// construted in a [key, value, key, value] format.
|
||||
const styles: string[] = [];
|
||||
|
||||
let i = 0;
|
||||
let parenDepth = 0;
|
||||
|
@ -72,7 +77,7 @@ export function parse(value: string): {[key: string]: any} {
|
|||
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;
|
||||
styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
|
||||
propStart = i;
|
||||
valueStart = 0;
|
||||
currentProp = null;
|
||||
|
@ -84,7 +89,7 @@ export function parse(value: string): {[key: string]: any} {
|
|||
|
||||
if (currentProp && valueStart) {
|
||||
const styleVal = value.substr(valueStart).trim();
|
||||
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal;
|
||||
styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
|
||||
}
|
||||
|
||||
return styles;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import {InitialStylingFlags} from '../../core';
|
||||
import {AST, BindingType, ParseSpan} from '../../expression_parser/ast';
|
||||
import {AttributeMarker} from '../../core';
|
||||
import {AST, BindingType} from '../../expression_parser/ast';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import * as t from '../r3_ast';
|
||||
|
@ -40,6 +40,10 @@ interface BoundStylingEntry {
|
|||
/**
|
||||
* Produces creation/update instructions for all styling bindings (class and style)
|
||||
*
|
||||
* It also produces the creation instruction to register all initial styling values
|
||||
* (which are all the static class="..." and style="..." attribute values that exist
|
||||
* on an element within a template).
|
||||
*
|
||||
* The builder class below handles producing instructions for the following cases:
|
||||
*
|
||||
* - Static style/class attributes (style="..." and class="...")
|
||||
|
@ -63,25 +67,57 @@ interface BoundStylingEntry {
|
|||
* The creation/update methods within the builder class produce these instructions.
|
||||
*/
|
||||
export class StylingBuilder {
|
||||
public readonly hasBindingsOrInitialValues = false;
|
||||
/** Whether or not there are any static styling values present */
|
||||
private _hasInitialValues = false;
|
||||
/**
|
||||
* Whether or not there are any styling bindings present
|
||||
* (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`)
|
||||
*/
|
||||
private _hasBindings = false;
|
||||
|
||||
/** the input for [class] (if it exists) */
|
||||
private _classMapInput: BoundStylingEntry|null = null;
|
||||
/** the input for [style] (if it exists) */
|
||||
private _styleMapInput: BoundStylingEntry|null = null;
|
||||
/** an array of each [style.prop] input */
|
||||
private _singleStyleInputs: BoundStylingEntry[]|null = null;
|
||||
/** an array of each [class.name] input */
|
||||
private _singleClassInputs: BoundStylingEntry[]|null = null;
|
||||
private _lastStylingInput: BoundStylingEntry|null = null;
|
||||
|
||||
// maps are used instead of hash maps because a Map will
|
||||
// retain the ordering of the keys
|
||||
|
||||
/**
|
||||
* Represents the location of each style binding in the template
|
||||
* (e.g. `<div [style.width]="w" [style.height]="h">` implies
|
||||
* that `width=0` and `height=1`)
|
||||
*/
|
||||
private _stylesIndex = new Map<string, number>();
|
||||
|
||||
/**
|
||||
* Represents the location of each class binding in the template
|
||||
* (e.g. `<div [class.big]="b" [class.hidden]="h">` implies
|
||||
* that `big=0` and `hidden=1`)
|
||||
*/
|
||||
private _classesIndex = new Map<string, number>();
|
||||
private _initialStyleValues: {[propName: string]: string} = {};
|
||||
private _initialClassValues: {[className: string]: boolean} = {};
|
||||
private _initialStyleValues: string[] = [];
|
||||
private _initialClassValues: string[] = [];
|
||||
|
||||
// certain style properties ALWAYS need sanitization
|
||||
// this is checked each time new styles are encountered
|
||||
private _useDefaultSanitizer = false;
|
||||
private _applyFnRequired = false;
|
||||
|
||||
constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {}
|
||||
|
||||
hasBindingsOrInitialValues() { return this._hasBindings || this._hasInitialValues; }
|
||||
|
||||
/**
|
||||
* Registers a given input to the styling builder to be later used when producing AOT code.
|
||||
*
|
||||
* The code below will only accept the input if it is somehow tied to styling (whether it be
|
||||
* style/class bindings or static style/class attributes).
|
||||
*/
|
||||
registerBoundInput(input: t.BoundAttribute): boolean {
|
||||
// [attr.style] or [attr.class] are skipped in the code below,
|
||||
// they should not be treated as styling-based bindings since
|
||||
|
@ -117,14 +153,12 @@ export class StylingBuilder {
|
|||
(this._singleStyleInputs = this._singleStyleInputs || []).push(entry);
|
||||
this._useDefaultSanitizer = this._useDefaultSanitizer || isStyleSanitizable(propertyName);
|
||||
registerIntoMap(this._stylesIndex, propertyName);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
} else {
|
||||
this._useDefaultSanitizer = true;
|
||||
this._styleMapInput = entry;
|
||||
}
|
||||
this._lastStylingInput = entry;
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
this._applyFnRequired = true;
|
||||
this._hasBindings = true;
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
@ -133,107 +167,152 @@ export class StylingBuilder {
|
|||
const entry = { name: className, value, sourceSpan } as BoundStylingEntry;
|
||||
if (className) {
|
||||
(this._singleClassInputs = this._singleClassInputs || []).push(entry);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
registerIntoMap(this._classesIndex, className);
|
||||
} else {
|
||||
this._classMapInput = entry;
|
||||
}
|
||||
this._lastStylingInput = entry;
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
this._applyFnRequired = true;
|
||||
this._hasBindings = true;
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the element's static style string value to the builder.
|
||||
*
|
||||
* @param value the style string (e.g. `width:100px; height:200px;`)
|
||||
*/
|
||||
registerStyleAttr(value: string) {
|
||||
this._initialStyleValues = parseStyle(value);
|
||||
Object.keys(this._initialStyleValues).forEach(prop => {
|
||||
registerIntoMap(this._stylesIndex, prop);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
});
|
||||
this._hasInitialValues = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the element's static class string value to the builder.
|
||||
*
|
||||
* @param value the className string (e.g. `disabled gold zoom`)
|
||||
*/
|
||||
registerClassAttr(value: string) {
|
||||
this._initialClassValues = {};
|
||||
value.split(/\s+/g).forEach(className => {
|
||||
this._initialClassValues[className] = true;
|
||||
registerIntoMap(this._classesIndex, className);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
});
|
||||
this._initialClassValues = value.trim().split(/\s+/g);
|
||||
this._hasInitialValues = true;
|
||||
}
|
||||
|
||||
private _buildInitExpr(registry: Map<string, number>, initialValues: {[key: string]: any}):
|
||||
o.Expression|null {
|
||||
const exprs: o.Expression[] = [];
|
||||
const nameAndValueExprs: o.Expression[] = [];
|
||||
|
||||
// _c0 = [prop, prop2, prop3, ...]
|
||||
registry.forEach((value, key) => {
|
||||
const keyLiteral = o.literal(key);
|
||||
exprs.push(keyLiteral);
|
||||
const initialValue = initialValues[key];
|
||||
if (initialValue) {
|
||||
nameAndValueExprs.push(keyLiteral, o.literal(initialValue));
|
||||
/**
|
||||
* Appends all styling-related expressions to the provided attrs array.
|
||||
*
|
||||
* @param attrs an existing array where each of the styling expressions
|
||||
* will be inserted into.
|
||||
*/
|
||||
populateInitialStylingAttrs(attrs: o.Expression[]): void {
|
||||
// [CLASS_MARKER, 'foo', 'bar', 'baz' ...]
|
||||
if (this._initialClassValues.length) {
|
||||
attrs.push(o.literal(AttributeMarker.Classes));
|
||||
for (let i = 0; i < this._initialClassValues.length; i++) {
|
||||
attrs.push(o.literal(this._initialClassValues[i]));
|
||||
}
|
||||
});
|
||||
|
||||
if (nameAndValueExprs.length) {
|
||||
// _c0 = [... MARKER ...]
|
||||
exprs.push(o.literal(InitialStylingFlags.VALUES_MODE));
|
||||
// _c0 = [prop, VALUE, prop2, VALUE2, ...]
|
||||
exprs.push(...nameAndValueExprs);
|
||||
}
|
||||
|
||||
return exprs.length ? o.literalArr(exprs) : null;
|
||||
// [STYLE_MARKER, 'width', '200px', 'height', '100px', ...]
|
||||
if (this._initialStyleValues.length) {
|
||||
attrs.push(o.literal(AttributeMarker.Styles));
|
||||
for (let i = 0; i < this._initialStyleValues.length; i += 2) {
|
||||
attrs.push(
|
||||
o.literal(this._initialStyleValues[i]), o.literal(this._initialStyleValues[i + 1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildCreateLevelInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `elementHostAttrs`.
|
||||
*
|
||||
* The instruction generation code below is used for producing the AOT statement code which is
|
||||
* responsible for registering initial styles (within a directive hostBindings' creation block)
|
||||
* to the directive host element.
|
||||
*/
|
||||
buildDirectiveHostAttrsInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
if (this.hasBindingsOrInitialValues) {
|
||||
const initialClasses = this._buildInitExpr(this._classesIndex, this._initialClassValues);
|
||||
const initialStyles = this._buildInitExpr(this._stylesIndex, this._initialStyleValues);
|
||||
|
||||
// in the event that a [style] binding is used then sanitization will
|
||||
// always be imported because it is not possible to know ahead of time
|
||||
// whether style bindings will use or not use any sanitizable properties
|
||||
// that isStyleSanitizable() will detect
|
||||
const useSanitizer = this._useDefaultSanitizer;
|
||||
const params: (o.Expression)[] = [];
|
||||
|
||||
if (initialClasses) {
|
||||
// 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).
|
||||
params.push(constantPool.getConstLiteral(initialClasses, true));
|
||||
} else if (initialStyles || useSanitizer || this._directiveExpr) {
|
||||
// no point in having an extra `null` value unless there are follow-up params
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
|
||||
if (initialStyles) {
|
||||
// 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).
|
||||
params.push(constantPool.getConstLiteral(initialStyles, true));
|
||||
} else if (useSanitizer || this._directiveExpr) {
|
||||
// no point in having an extra `null` value unless there are follow-up params
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
|
||||
if (useSanitizer || this._directiveExpr) {
|
||||
params.push(useSanitizer ? o.importExpr(R3.defaultStyleSanitizer) : o.NULL_EXPR);
|
||||
if (this._directiveExpr) {
|
||||
params.push(this._directiveExpr);
|
||||
if (this._hasInitialValues && this._directiveExpr) {
|
||||
return {
|
||||
sourceSpan,
|
||||
reference: R3.elementHostAttrs,
|
||||
buildParams: () => {
|
||||
const attrs: o.Expression[] = [];
|
||||
this.populateInitialStylingAttrs(attrs);
|
||||
return [this._directiveExpr !, getConstantLiteralFromArray(constantPool, attrs)];
|
||||
}
|
||||
}
|
||||
|
||||
return {sourceSpan, reference: R3.elementStyling, buildParams: () => params};
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _buildStylingMap(valueConverter: ValueConverter): StylingInstruction|null {
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `elementStyling`.
|
||||
*
|
||||
* The instruction generation code below is used for producing the AOT statement code which is
|
||||
* responsible for registering style/class bindings to an element.
|
||||
*/
|
||||
buildElementStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
if (this._hasBindings) {
|
||||
return {
|
||||
sourceSpan,
|
||||
reference: R3.elementStyling,
|
||||
buildParams: () => {
|
||||
// a string array of every style-based binding
|
||||
const styleBindingProps =
|
||||
this._singleStyleInputs ? this._singleStyleInputs.map(i => o.literal(i.name)) : [];
|
||||
// a string array of every class-based binding
|
||||
const classBindingNames =
|
||||
this._singleClassInputs ? this._singleClassInputs.map(i => o.literal(i.name)) : [];
|
||||
|
||||
// to salvage space in the AOT generated code, there is no point in passing
|
||||
// in `null` into a param if any follow-up params are not used. Therefore,
|
||||
// only when a trailing param is used then it will be filled with nulls in between
|
||||
// (otherwise a shorter amount of params will be filled). The code below helps
|
||||
// determine how many params are required in the expression code.
|
||||
//
|
||||
// min params => elementStyling()
|
||||
// max params => elementStyling(classBindings, styleBindings, sanitizer, directive)
|
||||
let expectedNumberOfArgs = 0;
|
||||
if (this._directiveExpr) {
|
||||
expectedNumberOfArgs = 4;
|
||||
} else if (this._useDefaultSanitizer) {
|
||||
expectedNumberOfArgs = 3;
|
||||
} else if (styleBindingProps.length) {
|
||||
expectedNumberOfArgs = 2;
|
||||
} else if (classBindingNames.length) {
|
||||
expectedNumberOfArgs = 1;
|
||||
}
|
||||
|
||||
const params: o.Expression[] = [];
|
||||
addParam(
|
||||
params, classBindingNames.length > 0,
|
||||
getConstantLiteralFromArray(constantPool, classBindingNames), 1,
|
||||
expectedNumberOfArgs);
|
||||
addParam(
|
||||
params, styleBindingProps.length > 0,
|
||||
getConstantLiteralFromArray(constantPool, styleBindingProps), 2,
|
||||
expectedNumberOfArgs);
|
||||
addParam(
|
||||
params, this._useDefaultSanitizer, o.importExpr(R3.defaultStyleSanitizer), 3,
|
||||
expectedNumberOfArgs);
|
||||
if (this._directiveExpr) {
|
||||
params.push(this._directiveExpr);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `elementStylingMap`.
|
||||
*
|
||||
* The instruction data will contain all expressions for `elementStylingMap` to function
|
||||
* which include the `[style]` and `[class]` expression params (if they exist) as well as
|
||||
* the sanitizer and directive reference expression.
|
||||
*/
|
||||
buildElementStylingMapInstruction(valueConverter: ValueConverter): StylingInstruction|null {
|
||||
if (this._classMapInput || this._styleMapInput) {
|
||||
const stylingInput = this._classMapInput ! || this._styleMapInput !;
|
||||
|
||||
|
@ -332,18 +411,20 @@ export class StylingBuilder {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs all instructions which contain the expressions that will be placed
|
||||
* into the update block of a template function or a directive hostBindings function.
|
||||
*/
|
||||
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
|
||||
const instructions: StylingInstruction[] = [];
|
||||
if (this.hasBindingsOrInitialValues) {
|
||||
const mapInstruction = this._buildStylingMap(valueConverter);
|
||||
if (this._hasBindings) {
|
||||
const mapInstruction = this.buildElementStylingMapInstruction(valueConverter);
|
||||
if (mapInstruction) {
|
||||
instructions.push(mapInstruction);
|
||||
}
|
||||
instructions.push(...this._buildStyleInputs(valueConverter));
|
||||
instructions.push(...this._buildClassInputs(valueConverter));
|
||||
if (this._applyFnRequired) {
|
||||
instructions.push(this._buildApplyFn());
|
||||
}
|
||||
instructions.push(this._buildApplyFn());
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
@ -363,3 +444,26 @@ function isStyleSanitizable(prop: string): boolean {
|
|||
return prop === 'background-image' || prop === 'background' || prop === 'border-image' ||
|
||||
prop === 'filter' || prop === 'list-style' || prop === 'list-style-image';
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper function to either provide the constant literal that will house the value
|
||||
* here or a null value if the provided values are empty.
|
||||
*/
|
||||
function getConstantLiteralFromArray(
|
||||
constantPool: ConstantPool, values: o.Expression[]): o.Expression {
|
||||
return values.length ? constantPool.getConstLiteral(o.literalArr(values), true) : o.NULL_EXPR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper function that adds a parameter or does nothing at all depending on the provided
|
||||
* predicate and totalExpectedArgs values
|
||||
*/
|
||||
function addParam(
|
||||
params: o.Expression[], predicate: boolean, value: o.Expression, argNumber: number,
|
||||
totalExpectedArgs: number) {
|
||||
if (predicate) {
|
||||
params.push(value);
|
||||
} else if (argNumber < totalExpectedArgs) {
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import {I18nContext} from './i18n/context';
|
|||
import {I18nMetaVisitor} from './i18n/meta';
|
||||
import {getSerializedI18nContent} from './i18n/serializer';
|
||||
import {I18N_ICU_MAPPING_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util';
|
||||
import {StylingBuilder, StylingInstruction} from './styling';
|
||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||
import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util';
|
||||
|
||||
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
||||
|
@ -532,7 +532,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
|
||||
// this will build the instructions so that they fall into the following syntax
|
||||
// add attributes for directive matching purposes
|
||||
attributes.push(...this.prepareSyntheticAndSelectOnlyAttrs(allOtherInputs, element.outputs));
|
||||
attributes.push(...this.prepareSyntheticAndSelectOnlyAttrs(
|
||||
allOtherInputs, element.outputs, stylingBuilder));
|
||||
parameters.push(this.toAttrsParam(attributes));
|
||||
|
||||
// local refs (ex.: <div #foo #bar="baz">)
|
||||
|
@ -562,11 +563,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
return element.children.length > 0;
|
||||
};
|
||||
|
||||
const createSelfClosingInstruction = !stylingBuilder.hasBindingsOrInitialValues &&
|
||||
const createSelfClosingInstruction = !stylingBuilder.hasBindingsOrInitialValues() &&
|
||||
!isNgContainer && element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren();
|
||||
|
||||
const createSelfClosingI18nInstruction = !createSelfClosingInstruction &&
|
||||
!stylingBuilder.hasBindingsOrInitialValues && hasTextChildrenOnly(element.children);
|
||||
!stylingBuilder.hasBindingsOrInitialValues() && hasTextChildrenOnly(element.children);
|
||||
|
||||
if (createSelfClosingInstruction) {
|
||||
this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters));
|
||||
|
@ -616,10 +617,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
}
|
||||
}
|
||||
|
||||
// initial styling for static style="..." and class="..." attributes
|
||||
// The style bindings code is placed into two distinct blocks within the template function AOT
|
||||
// code: creation and update. The creation code contains the `elementStyling` instructions
|
||||
// which will apply the collected binding values to the element. `elementStyling` is
|
||||
// designed to run inside of `elementStart` and `elementEnd`. The update instructions
|
||||
// (things like `elementStyleProp`, `elementClassProp`, etc..) are applied later on in this
|
||||
// file
|
||||
this.processStylingInstruction(
|
||||
implicit,
|
||||
stylingBuilder.buildCreateLevelInstruction(element.sourceSpan, this.constantPool), true);
|
||||
stylingBuilder.buildElementStylingInstruction(element.sourceSpan, this.constantPool),
|
||||
true);
|
||||
|
||||
// Generate Listeners (outputs)
|
||||
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
||||
|
@ -629,6 +636,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
});
|
||||
}
|
||||
|
||||
// the code here will collect all update-level styling instructions and add them to the
|
||||
// update block of the template function AOT code. Instructions like `elementStyleProp`,
|
||||
// `elementStylingMap`, `elementClassProp` and `elementStylingApply` are all generated
|
||||
// and assign in the code below.
|
||||
stylingBuilder.buildUpdateLevelInstructions(this._valueConverter).forEach(instruction => {
|
||||
this.processStylingInstruction(implicit, instruction, false);
|
||||
});
|
||||
|
@ -934,8 +945,26 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
}
|
||||
}
|
||||
|
||||
private prepareSyntheticAndSelectOnlyAttrs(inputs: t.BoundAttribute[], outputs: t.BoundEvent[]):
|
||||
o.Expression[] {
|
||||
/**
|
||||
* Prepares all attribute expression values for the `TAttributes` array.
|
||||
*
|
||||
* The purpose of this function is to properly construct an attributes array that
|
||||
* is passed into the `elementStart` (or just `element`) functions. Because there
|
||||
* are many different types of attributes, the array needs to be constructed in a
|
||||
* special way so that `elementStart` can properly evaluate them.
|
||||
*
|
||||
* The format looks like this:
|
||||
*
|
||||
* ```
|
||||
* attrs = [prop, value, prop2, value2,
|
||||
* CLASSES, class1, class2,
|
||||
* STYLES, style1, value1, style2, value2,
|
||||
* SELECT_ONLY, name1, name2, name2, ...]
|
||||
* ```
|
||||
*/
|
||||
private prepareSyntheticAndSelectOnlyAttrs(
|
||||
inputs: t.BoundAttribute[], outputs: t.BoundEvent[],
|
||||
styles?: StylingBuilder): o.Expression[] {
|
||||
const attrExprs: o.Expression[] = [];
|
||||
const nonSyntheticInputs: t.BoundAttribute[] = [];
|
||||
|
||||
|
@ -954,6 +983,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
});
|
||||
}
|
||||
|
||||
// it's important that this occurs before SelectOnly because once `elementStart`
|
||||
// comes across the SelectOnly marker then it will continue reading each value as
|
||||
// as single property value cell by cell.
|
||||
if (styles) {
|
||||
styles.populateInitialStylingAttrs(attrExprs);
|
||||
}
|
||||
|
||||
if (nonSyntheticInputs.length || outputs.length) {
|
||||
attrExprs.push(o.literal(core.AttributeMarker.SelectOnly));
|
||||
nonSyntheticInputs.forEach((i: t.BoundAttribute) => attrExprs.push(asLiteral(i.name)));
|
||||
|
|
|
@ -10,55 +10,53 @@ import {hyphenate, parse as parseStyle, stripUnnecessaryQuotes} from '../../src/
|
|||
describe('style parsing', () => {
|
||||
it('should parse empty or blank strings', () => {
|
||||
const result1 = parseStyle('');
|
||||
expect(result1).toEqual({});
|
||||
expect(result1).toEqual([]);
|
||||
|
||||
const result2 = parseStyle(' ');
|
||||
expect(result2).toEqual({});
|
||||
expect(result2).toEqual([]);
|
||||
});
|
||||
|
||||
it('should parse a string into a key/value map', () => {
|
||||
const result = parseStyle('width:100px;height:200px;opacity:0');
|
||||
expect(result).toEqual({width: '100px', height: '200px', opacity: '0'});
|
||||
expect(result).toEqual(['width', '100px', 'height', '200px', 'opacity', '0']);
|
||||
});
|
||||
|
||||
it('should trim values and properties', () => {
|
||||
const result = parseStyle('width :333px ; height:666px ; opacity: 0.5;');
|
||||
expect(result).toEqual({width: '333px', height: '666px', opacity: '0.5'});
|
||||
expect(result).toEqual(['width', '333px', 'height', '666px', 'opacity', '0.5']);
|
||||
});
|
||||
|
||||
it('should chomp out start/end quotes', () => {
|
||||
const result = parseStyle(
|
||||
'content: "foo"; opacity: \'0.5\'; font-family: "Verdana", Helvetica, "sans-serif"');
|
||||
expect(result).toEqual(
|
||||
{content: 'foo', opacity: '0.5', 'font-family': '"Verdana", Helvetica, "sans-serif"'});
|
||||
['content', 'foo', 'opacity', '0.5', 'font-family', '"Verdana", Helvetica, "sans-serif"']);
|
||||
});
|
||||
|
||||
it('should not mess up with quoted strings that contain [:;] values', () => {
|
||||
const result = parseStyle('content: "foo; man: guy"; width: 100px');
|
||||
expect(result).toEqual({content: 'foo; man: guy', width: '100px'});
|
||||
expect(result).toEqual(['content', 'foo; man: guy', 'width', '100px']);
|
||||
});
|
||||
|
||||
it('should not mess up with quoted strings that contain inner quote values', () => {
|
||||
const quoteStr = '"one \'two\' three \"four\" five"';
|
||||
const result = parseStyle(`content: ${quoteStr}; width: 123px`);
|
||||
expect(result).toEqual({content: quoteStr, width: '123px'});
|
||||
expect(result).toEqual(['content', quoteStr, 'width', '123px']);
|
||||
});
|
||||
|
||||
it('should respect parenthesis that are placed within a style', () => {
|
||||
const result = parseStyle('background-image: url("foo.jpg")');
|
||||
expect(result).toEqual({'background-image': 'url("foo.jpg")'});
|
||||
expect(result).toEqual(['background-image', 'url("foo.jpg")']);
|
||||
});
|
||||
|
||||
it('should respect multi-level parenthesis that contain special [:;] characters', () => {
|
||||
const result = parseStyle('color: rgba(calc(50 * 4), var(--cool), :5;); height: 100px;');
|
||||
expect(result).toEqual({color: 'rgba(calc(50 * 4), var(--cool), :5;)', height: '100px'});
|
||||
expect(result).toEqual(['color', 'rgba(calc(50 * 4), var(--cool), :5;)', 'height', '100px']);
|
||||
});
|
||||
|
||||
it('should hyphenate style properties from camel case', () => {
|
||||
const result = parseStyle('borderWidth: 200px');
|
||||
expect(result).toEqual({
|
||||
'border-width': '200px',
|
||||
});
|
||||
expect(result).toEqual(['border-width', '200px']);
|
||||
});
|
||||
|
||||
describe('quote chomping', () => {
|
||||
|
|
|
@ -92,6 +92,7 @@ export {
|
|||
elementContainerStart as ɵelementContainerStart,
|
||||
elementContainerEnd as ɵelementContainerEnd,
|
||||
elementStyling as ɵelementStyling,
|
||||
elementHostAttrs as ɵelementHostAttrs,
|
||||
elementStylingMap as ɵelementStylingMap,
|
||||
elementStyleProp as ɵelementStyleProp,
|
||||
elementStylingApply as ɵelementStylingApply,
|
||||
|
|
|
@ -12,7 +12,7 @@ import {getComponent, getContext, getInjectionTokens, getInjector, getListeners,
|
|||
import {TNode} from '../render3/interfaces/node';
|
||||
import {StylingIndex} from '../render3/interfaces/styling';
|
||||
import {TVIEW} from '../render3/interfaces/view';
|
||||
import {getProp, getValue, isClassBased} from '../render3/styling/class_and_style_bindings';
|
||||
import {getProp, getValue, isClassBasedValue} from '../render3/styling/class_and_style_bindings';
|
||||
import {getStylingContext} from '../render3/styling/util';
|
||||
import {DebugContext} from '../view/index';
|
||||
|
||||
|
@ -273,7 +273,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
|||
if (stylingContext) {
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
|
||||
i += StylingIndex.Size) {
|
||||
if (isClassBased(lNode, i)) {
|
||||
if (isClassBasedValue(lNode, i)) {
|
||||
const className = getProp(lNode, i);
|
||||
const value = getValue(lNode, i);
|
||||
if (typeof value == 'boolean') {
|
||||
|
@ -303,7 +303,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
|||
if (stylingContext) {
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
|
||||
i += StylingIndex.Size) {
|
||||
if (!isClassBased(lNode, i)) {
|
||||
if (!isClassBasedValue(lNode, i)) {
|
||||
const styleName = getProp(lNode, i);
|
||||
const value = getValue(lNode, i) as string | null;
|
||||
if (value !== null) {
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
*/
|
||||
import './ng_dev_mode';
|
||||
import {assertDomNode} from './assert';
|
||||
import {EMPTY_ARRAY} from './definition';
|
||||
import {EMPTY_ARRAY} from './empty';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||
import {TNode, TNodeFlags} from './interfaces/node';
|
||||
import {RElement} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
|
||||
import {getComponentViewByIndex, getNativeByTNode, getTNode, readElementValue, readPatchedData} from './util';
|
||||
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
|
||||
|
||||
|
||||
|
||||
/** Returns the matching `LContext` data for a given DOM node, directive or component instance.
|
||||
|
|
|
@ -9,22 +9,15 @@
|
|||
import './ng_dev_mode';
|
||||
|
||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||
import {Provider} from '../di/provider';
|
||||
import {NgModuleDef} from '../metadata/ng_module';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {Mutable, Type} from '../type';
|
||||
import {noSideEffects, stringify} from '../util';
|
||||
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
|
||||
import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||
import {CssSelectorList} from './interfaces/projection';
|
||||
|
||||
export const EMPTY: {} = {};
|
||||
export const EMPTY_ARRAY: any[] = [];
|
||||
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
||||
Object.freeze(EMPTY);
|
||||
Object.freeze(EMPTY_ARRAY);
|
||||
}
|
||||
let _renderCompCount = 0;
|
||||
|
||||
/**
|
||||
|
@ -389,7 +382,7 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
|
|||
|
||||
*/
|
||||
function invertObject(obj: any, secondary?: any): any {
|
||||
if (obj == null) return EMPTY;
|
||||
if (obj == null) return EMPTY_OBJ;
|
||||
const newLookup: any = {};
|
||||
for (const minifiedKey in obj) {
|
||||
if (obj.hasOwnProperty(minifiedKey)) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
|
||||
import {assertDefined} from './assert';
|
||||
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery';
|
||||
import {NodeInjector} from './di';
|
||||
|
@ -14,7 +15,8 @@ import {LContext} from './interfaces/context';
|
|||
import {DirectiveDef} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node';
|
||||
import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
|
||||
import {readPatchedLView, stringify} from './util';
|
||||
import {readElementValue, readPatchedLView, stringify} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -327,7 +329,7 @@ export function getListeners(element: Element): Listener[] {
|
|||
const secondParam = tCleanup[i++];
|
||||
if (typeof firstParam === 'string') {
|
||||
const name: string = firstParam;
|
||||
const listenerElement: Element = lView[secondParam];
|
||||
const listenerElement = readElementValue(lView[secondParam]) as any as Element;
|
||||
const callback: (value: any) => any = lCleanup[tCleanup[i++]];
|
||||
const useCaptureOrIndx = tCleanup[i++];
|
||||
// if useCaptureOrIndx is boolean then report it as is.
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import './ng_dev_mode';
|
||||
|
||||
/**
|
||||
* This file contains reuseable "empty" symbols that can be used as default return values
|
||||
* in different parts of the rendering code. Because the same symbols are returned, this
|
||||
* allows for identity checks against these values to be consistently used by the framework
|
||||
* code.
|
||||
*/
|
||||
|
||||
export const EMPTY_OBJ: {} = {};
|
||||
export const EMPTY_ARRAY: any[] = [];
|
||||
|
||||
// freezing the values prevents any code from accidentally inserting new values in
|
||||
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
||||
Object.freeze(EMPTY_OBJ);
|
||||
Object.freeze(EMPTY_ARRAY);
|
||||
}
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import {Type} from '../../type';
|
||||
import {fillProperties} from '../../util/property';
|
||||
import {EMPTY, EMPTY_ARRAY} from '../definition';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||
import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
|
||||
|
||||
|
||||
|
||||
|
@ -178,7 +178,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
|||
function maybeUnwrapEmpty<T>(value: T[]): T[];
|
||||
function maybeUnwrapEmpty<T>(value: T): T;
|
||||
function maybeUnwrapEmpty(value: any): any {
|
||||
if (value === EMPTY) {
|
||||
if (value === EMPTY_OBJ) {
|
||||
return {};
|
||||
} else if (value === EMPTY_ARRAY) {
|
||||
return [];
|
||||
|
|
|
@ -48,8 +48,8 @@ export {
|
|||
|
||||
elementContainerStart,
|
||||
elementContainerEnd,
|
||||
|
||||
elementStyling,
|
||||
elementHostAttrs,
|
||||
elementStylingMap,
|
||||
elementStyleProp,
|
||||
elementStylingApply,
|
||||
|
@ -79,7 +79,7 @@ export {
|
|||
|
||||
directiveInject,
|
||||
injectAttribute,
|
||||
|
||||
|
||||
getCurrentView
|
||||
} from './instructions';
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import {Sanitizer} from '../sanitization/security';
|
|||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
import {Type} from '../type';
|
||||
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
|
||||
|
||||
import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert';
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings';
|
||||
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
||||
|
@ -22,7 +23,7 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreat
|
|||
import {throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
|
||||
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
|
||||
import {PlayerFactory} from './interfaces/player';
|
||||
|
@ -30,19 +31,19 @@ import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'
|
|||
import {LQueries} from './interfaces/query';
|
||||
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {SanitizerFn} from './interfaces/sanitization';
|
||||
import {StylingIndex} from './interfaces/styling';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
|
||||
import {createStylingContextTemplate, renderStyleAndClassBindings, setStyle, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||
import {BoundPlayerFactory} from './styling/player_factory';
|
||||
import {getStylingContext, isAnimationProp} from './styling/util';
|
||||
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, loadInternal, readElementValue, readPatchedLView, stringify} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A permanent marker promise which signifies that the current CD tree is
|
||||
* clean.
|
||||
|
@ -456,7 +457,8 @@ export function namespaceHTML() {
|
|||
*
|
||||
* @param index Index of the element in the data array
|
||||
* @param name Name of the DOM Node
|
||||
* @param attrs Statically bound set of attributes to be written into the DOM element on creation.
|
||||
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
|
||||
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
*/
|
||||
export function element(
|
||||
|
@ -526,7 +528,8 @@ export function elementContainerEnd(): void {
|
|||
*
|
||||
* @param index Index of the element in the LView array
|
||||
* @param name Name of the DOM Node
|
||||
* @param attrs Statically bound set of attributes to be written into the DOM element on creation.
|
||||
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
|
||||
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
*
|
||||
* Attributes and localRefs are passed as an array of strings where elements with an even index
|
||||
|
@ -550,6 +553,14 @@ export function elementStart(
|
|||
const tNode = createNodeAtIndex(index, TNodeType.Element, native !, name, attrs || null);
|
||||
|
||||
if (attrs) {
|
||||
// it's important to only prepare styling-related datastructures once for a given
|
||||
// tNode and not each time an element is created. Also, the styling code is designed
|
||||
// to be patched and constructed at various points, but only up until the first element
|
||||
// is created. Then the styling context is locked and can only be instantiated for each
|
||||
// successive element that is created.
|
||||
if (tView.firstTemplatePass && !tNode.stylingTemplate && hasStyling(attrs)) {
|
||||
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
|
||||
}
|
||||
setUpAttributes(native, attrs);
|
||||
}
|
||||
|
||||
|
@ -563,6 +574,23 @@ export function elementStart(
|
|||
attachPatchData(native, lView);
|
||||
}
|
||||
increaseElementDepthCount();
|
||||
|
||||
// if a directive contains a host binding for "class" then all class-based data will
|
||||
// flow through that (except for `[class.prop]` bindings). This also includes initial
|
||||
// static class values as well. (Note that this will be fixed once map-based `[style]`
|
||||
// and `[class]` bindings work for multiple directives.)
|
||||
if (tView.firstTemplatePass) {
|
||||
const inputData = initializeTNodeInputs(tNode);
|
||||
if (inputData && inputData.hasOwnProperty('class')) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
}
|
||||
}
|
||||
|
||||
// There is no point in rendering styles when a class directive is present since
|
||||
// it will take that over for us (this will be removed once #FW-882 is in).
|
||||
if (tNode.stylingTemplate && (tNode.flags & TNodeFlags.hasClassInput) === 0) {
|
||||
renderInitialStylesAndClasses(native, tNode.stylingTemplate, lView[RENDERER]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -721,25 +749,28 @@ function setUpAttributes(native: RElement, attrs: TAttributes): void {
|
|||
let i = 0;
|
||||
|
||||
while (i < attrs.length) {
|
||||
const attrName = attrs[i];
|
||||
if (attrName === AttributeMarker.SelectOnly) break;
|
||||
if (attrName === NG_PROJECT_AS_ATTR_NAME) {
|
||||
i += 2;
|
||||
} else {
|
||||
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||
const attrName = attrs[i++];
|
||||
if (typeof attrName == 'number') {
|
||||
if (attrName === AttributeMarker.NamespaceURI) {
|
||||
// Namespaced attributes
|
||||
const namespaceURI = attrs[i + 1] as string;
|
||||
const attrName = attrs[i + 2] as string;
|
||||
const attrVal = attrs[i + 3] as string;
|
||||
const namespaceURI = attrs[i++] as string;
|
||||
const attrName = attrs[i++] as string;
|
||||
const attrVal = attrs[i++] as string;
|
||||
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||
isProc ?
|
||||
(renderer as ProceduralRenderer3)
|
||||
.setAttribute(native, attrName, attrVal, namespaceURI) :
|
||||
native.setAttributeNS(namespaceURI, attrName, attrVal);
|
||||
i += 4;
|
||||
} else {
|
||||
// All other `AttributeMarker`s are ignored here.
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/// attrName is string;
|
||||
const attrVal = attrs[i++];
|
||||
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
|
||||
// Standard attributes
|
||||
const attrVal = attrs[i + 1];
|
||||
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||
if (isAnimationProp(attrName)) {
|
||||
if (isProc) {
|
||||
(renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
|
||||
|
@ -750,7 +781,6 @@ function setUpAttributes(native: RElement, attrs: TAttributes): void {
|
|||
.setAttribute(native, attrName as string, attrVal as string) :
|
||||
native.setAttribute(attrName as string, attrVal as string);
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -902,6 +932,15 @@ export function elementEnd(): void {
|
|||
|
||||
queueLifecycleHooks(getLView()[TVIEW], previousOrParentTNode);
|
||||
decreaseElementDepthCount();
|
||||
|
||||
// this is fired at the end of elementEnd because ALL of the stylingBindings code
|
||||
// (for directives and the template) have now executed which means the styling
|
||||
// context can be instantiated properly.
|
||||
if (hasClassInput(previousOrParentTNode)) {
|
||||
const stylingContext = getStylingContext(previousOrParentTNode.index, lView);
|
||||
setInputsForProperty(
|
||||
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1096,119 +1135,84 @@ function generatePropertyAliases(tNode: TNode, direction: BindingDirection): Pro
|
|||
return propStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or remove a class in a `classList` on a DOM element.
|
||||
*
|
||||
* This instruction is meant to handle the [class.foo]="exp" case
|
||||
*
|
||||
* @param index The index of the element to update in the data array
|
||||
* @param classIndex Index of class to toggle. Because it is going to DOM, this is not subject to
|
||||
* renaming as part of minification.
|
||||
* @param value A value indicating if a given class should be added or removed.
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
*/
|
||||
export function elementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory, directive?: {}): void {
|
||||
if (directive != undefined) {
|
||||
return hackImplementationOfElementClassProp(
|
||||
index, classIndex, value, directive); // proper supported in next PR
|
||||
}
|
||||
const val =
|
||||
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
|
||||
updateElementClassProp(getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign any inline style values to the element during creation mode.
|
||||
*
|
||||
* This instruction is meant to be called during creation mode to apply all styling
|
||||
* (e.g. `style="..."`) values to the element. This is also where the provided index
|
||||
* value is allocated for the styling details for its corresponding element (the element
|
||||
* index is the previous index value from this one).
|
||||
* This instruction is meant to be called during creation mode to register all
|
||||
* dynamic style and class bindings on the element. Note for static values (no binding)
|
||||
* see `elementStart` and `elementHostAttrs`.
|
||||
*
|
||||
* (Note this function calls `elementStylingApply` immediately when called.)
|
||||
* @param classBindingNames An array containing bindable class names.
|
||||
* The `elementClassProp` refers to the class name by index in this array.
|
||||
* (i.e. `['foo', 'bar']` means `foo=0` and `bar=1`).
|
||||
* @param styleBindingNames An array containing bindable style properties.
|
||||
* The `elementStyleProp` refers to the class name by index in this array.
|
||||
* (i.e. `['width', 'height']` means `width=0` and `height=1`).
|
||||
* @param styleSanitizer An optional sanitizer function that will be used to sanitize any CSS
|
||||
* property values that are applied to the element (during rendering).
|
||||
* Note that the sanitizer instance itself is tied to the `directive` (if provided).
|
||||
* @param directive A directive instance the styling is associated with. If not provided
|
||||
* current view's controller instance is assumed.
|
||||
*
|
||||
*
|
||||
* @param index Index value which will be allocated to store styling data for the element.
|
||||
* (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 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).
|
||||
* @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 styleSanitizer An optional sanitizer function that will be used (if provided)
|
||||
* to sanitize the any CSS property values that are applied to the element (during rendering).
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStyling(
|
||||
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
classBindingNames?: string[] | null, styleBindingNames?: string[] | null,
|
||||
styleSanitizer?: StyleSanitizeFn | null, directive?: {}): void {
|
||||
if (directive != undefined) {
|
||||
isCreationMode() &&
|
||||
hackImplementationOfElementStyling(
|
||||
classDeclarations || null, styleDeclarations || null, styleSanitizer || null,
|
||||
directive); // supported in next PR
|
||||
return;
|
||||
}
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
const inputData = initializeTNodeInputs(tNode);
|
||||
|
||||
if (!tNode.stylingTemplate) {
|
||||
const hasClassInput = inputData && inputData.hasOwnProperty('class') ? true : false;
|
||||
if (hasClassInput) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
}
|
||||
|
||||
// initialize the styling template.
|
||||
tNode.stylingTemplate = createStylingContextTemplate(
|
||||
classDeclarations, styleDeclarations, styleSanitizer, hasClassInput);
|
||||
}
|
||||
|
||||
if (styleDeclarations && styleDeclarations.length ||
|
||||
classDeclarations && classDeclarations.length) {
|
||||
const index = tNode.index;
|
||||
if (delegateToClassInput(tNode)) {
|
||||
const lView = getLView();
|
||||
const stylingContext = getStylingContext(index, lView);
|
||||
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
|
||||
setInputsForProperty(lView, tNode.inputs !['class'] !, initialClasses);
|
||||
}
|
||||
elementStylingApply(index - HEADER_OFFSET);
|
||||
tNode.stylingTemplate = createEmptyStylingContext();
|
||||
}
|
||||
updateContextWithBindings(
|
||||
tNode.stylingTemplate !, directive || null, classBindingNames, styleBindingNames,
|
||||
styleSanitizer, hasClassInput(tNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign static styling values to a host element.
|
||||
*
|
||||
* NOTE: This instruction is meant to used from `hostBindings` function only.
|
||||
*
|
||||
* @param directive A directive instance the styling is associated with.
|
||||
* @param attrs An array containing class and styling information. The values must be marked with
|
||||
* `AttributeMarker`.
|
||||
*
|
||||
* ```
|
||||
* var attrs = [AttributeMarker.Classes, 'foo', 'bar',
|
||||
* AttributeMarker.Styles, 'width', '100px', 'height, '200px']
|
||||
* elementHostAttrs(directive, attrs);
|
||||
* ```
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementHostAttrs(directive: any, attrs: TAttributes) {
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
if (!tNode.stylingTemplate) {
|
||||
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
|
||||
}
|
||||
patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all styling values to the element which have been queued by any styling instructions.
|
||||
* Apply styling binding to the element.
|
||||
*
|
||||
* This instruction is meant to be run once one or more `elementStyle` and/or `elementStyleProp`
|
||||
* have been issued against the element. This function will also determine if any styles have
|
||||
* changed and will then skip the operation if there is nothing new to render.
|
||||
* This instruction is meant to be run after `elementStyle` and/or `elementStyleProp`.
|
||||
* if any styling bindings have changed then the changes are flushed to the element.
|
||||
*
|
||||
* Once called then all queued styles will be flushed.
|
||||
*
|
||||
* @param index Index of the element's styling storage that will be rendered.
|
||||
* (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 directive the ref to the directive that is attempting to change styling.
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
components
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStylingApply(index: number, directive?: {}): void {
|
||||
if (directive != undefined) {
|
||||
return hackImplementationOfElementStylingApply(index, directive); // supported in next PR
|
||||
}
|
||||
export function elementStylingApply(index: number, directive?: any): void {
|
||||
const lView = getLView();
|
||||
const isFirstRender = (lView[FLAGS] & LViewFlags.FirstLViewPass) !== 0;
|
||||
const totalPlayersQueued = renderStyleAndClassBindings(
|
||||
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender);
|
||||
const totalPlayersQueued = renderStyling(
|
||||
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender, null,
|
||||
null, directive);
|
||||
if (totalPlayersQueued > 0) {
|
||||
const rootContext = getRootContext(lView);
|
||||
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
|
||||
|
@ -1216,29 +1220,34 @@ export function elementStylingApply(index: number, directive?: {}): void {
|
|||
}
|
||||
|
||||
/**
|
||||
* Queue a given style to be rendered on an Element.
|
||||
* Update a style bindings value on an element.
|
||||
*
|
||||
* If the style value is `null` then it will be removed from the element
|
||||
* (or assigned a different value depending if there are any styles placed
|
||||
* on the element with `elementStyle` or any styles that are present
|
||||
* from when the element was created (with `elementStyling`).
|
||||
*
|
||||
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
||||
* (Note that the styling element is updated as part of `elementStylingApply`.)
|
||||
*
|
||||
* @param index Index of the element's styling storage to change in the data array.
|
||||
* (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 styleIndex Index of the style property on this element. (Monotonically increasing.)
|
||||
* @param value New value to write (null to remove).
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param styleIndex Index of style to update. This index value refers to the
|
||||
* index of the style in the style bindings array that was passed into
|
||||
* `elementStlyingBindings`.
|
||||
* @param value New value to write (null to remove). Note that if a directive also
|
||||
* attempts to write to the same binding value then it will only be able to
|
||||
* do so if the template binding value is `null` (or doesn't exist at all).
|
||||
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
|
||||
* Note that when a suffix is provided then the underlying sanitizer will
|
||||
* be ignored.
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
components
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStyleProp(
|
||||
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
|
||||
suffix?: string, directive?: {}): void {
|
||||
suffix?: string | null, directive?: {}): void {
|
||||
let valueToAdd: string|null = null;
|
||||
if (value !== null) {
|
||||
if (suffix) {
|
||||
|
@ -1253,35 +1262,59 @@ export function elementStyleProp(
|
|||
valueToAdd = value as any as string;
|
||||
}
|
||||
}
|
||||
if (directive != undefined) {
|
||||
hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive);
|
||||
} else {
|
||||
updateElementStyleProp(
|
||||
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd);
|
||||
}
|
||||
updateElementStyleProp(
|
||||
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd, directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a key/value map of styles to be rendered on an Element.
|
||||
* Add or remove a class via a class binding on a DOM element.
|
||||
*
|
||||
* This instruction is meant to handle the `[style]="exp"` usage. When styles are applied to
|
||||
* the Element they will then be placed with respect to any styles set with `elementStyleProp`.
|
||||
* If any styles are set to `null` then they will be removed from the element (unless the same
|
||||
* style properties have been assigned to the element during creation using `elementStyling`).
|
||||
* This instruction is meant to handle the [class.foo]="exp" case and, therefore,
|
||||
* the class itself must already be applied using `elementStyling` within
|
||||
* the creation block.
|
||||
*
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param classIndex Index of class to toggle. This index value refers to the
|
||||
* index of the class in the class bindings array that was passed into
|
||||
* `elementStlyingBindings` (which is meant to be called before this
|
||||
* function is).
|
||||
* @param value A true/false value which will turn the class on or off.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
components
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory, directive?: {}): void {
|
||||
const onOrOffClassValue =
|
||||
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
|
||||
updateElementClassProp(
|
||||
getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, onOrOffClassValue,
|
||||
directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update style and/or class bindings using object literal.
|
||||
*
|
||||
* This instruction is meant apply styling via the `[style]="exp"` and `[class]="exp"` template
|
||||
* bindings. When styles are applied to the Element they will then be placed with respect to
|
||||
* any styles set with `elementStyleProp`. If any styles are set to `null` then they will be
|
||||
* removed from the element.
|
||||
*
|
||||
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
||||
*
|
||||
* @param index Index of the element's styling storage to change in the data array.
|
||||
* (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 index Index of the element's with which styling is associated.
|
||||
* @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.
|
||||
* @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 directive the ref to the directive that is attempting to change styling.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStylingMap<T>(
|
||||
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
||||
|
@ -1292,113 +1325,24 @@ export function elementStylingMap<T>(
|
|||
const lView = getLView();
|
||||
const tNode = getTNode(index, lView);
|
||||
const stylingContext = getStylingContext(index + HEADER_OFFSET, lView);
|
||||
if (delegateToClassInput(tNode) && classes !== NO_CHANGE) {
|
||||
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
|
||||
if (hasClassInput(tNode) && classes !== NO_CHANGE) {
|
||||
const initialClasses = getInitialClassNameValue(stylingContext);
|
||||
const classInputVal =
|
||||
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
|
||||
setInputsForProperty(lView, tNode.inputs !['class'] !, classInputVal);
|
||||
} else {
|
||||
updateStylingMap(stylingContext, classes, styles);
|
||||
}
|
||||
updateStylingMap(stylingContext, classes, styles);
|
||||
}
|
||||
|
||||
/* START OF HACK BLOCK */
|
||||
/*
|
||||
* HACK
|
||||
* The code below is a quick and dirty implementation of the host style binding so that we can make
|
||||
* progress on TestBed. Once the correct implementation is created this code should be removed.
|
||||
*/
|
||||
interface HostStylingHack {
|
||||
classDeclarations: string[];
|
||||
styleDeclarations: string[];
|
||||
styleSanitizer: StyleSanitizeFn|null;
|
||||
}
|
||||
type HostStylingHackMap = Map<{}, HostStylingHack>;
|
||||
|
||||
function hackImplementationOfElementStyling(
|
||||
classDeclarations: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleDeclarations: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleSanitizer: StyleSanitizeFn | null, directive: {}): void {
|
||||
const node = getNativeByTNode(getPreviousOrParentTNode(), getLView()) as RElement;
|
||||
ngDevMode && assertDefined(node, 'expecting parent DOM node');
|
||||
const hostStylingHackMap: HostStylingHackMap =
|
||||
((node as any).hostStylingHack || ((node as any).hostStylingHack = new Map()));
|
||||
const squashedClassDeclarations = hackSquashDeclaration(classDeclarations);
|
||||
hostStylingHackMap.set(directive, {
|
||||
classDeclarations: squashedClassDeclarations,
|
||||
styleDeclarations: hackSquashDeclaration(styleDeclarations), styleSanitizer
|
||||
});
|
||||
hackSetStaticClasses(node, squashedClassDeclarations);
|
||||
}
|
||||
|
||||
function hackSetStaticClasses(node: RElement, classDeclarations: (string | boolean)[]) {
|
||||
// Static classes need to be set here because static classes don't generate
|
||||
// elementClassProp instructions.
|
||||
const lView = getLView();
|
||||
const staticClassStartIndex =
|
||||
classDeclarations.indexOf(InitialStylingFlags.VALUES_MODE as any) + 1;
|
||||
const renderer = lView[RENDERER];
|
||||
|
||||
for (let i = staticClassStartIndex; i < classDeclarations.length; i += 2) {
|
||||
const className = classDeclarations[i] as string;
|
||||
const value = classDeclarations[i + 1];
|
||||
// if value is true, then this is a static class and we should set it now.
|
||||
// class bindings are set separately in elementClassProp.
|
||||
if (value === true) {
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
renderer.addClass(node, className);
|
||||
} else {
|
||||
const classList = (node as HTMLElement).classList;
|
||||
classList.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hackSquashDeclaration(declarations: (string | boolean | InitialStylingFlags)[] | null):
|
||||
string[] {
|
||||
// assume the array is correct. This should be fine for View Engine compatibility.
|
||||
return declarations || [] as any;
|
||||
}
|
||||
|
||||
function hackImplementationOfElementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory, directive: {}): void {
|
||||
const lView = getLView();
|
||||
const node = getNativeByIndex(index, lView);
|
||||
ngDevMode && assertDefined(node, 'could not locate node');
|
||||
const hostStylingHack: HostStylingHack = (node as any).hostStylingHack.get(directive);
|
||||
const className = hostStylingHack.classDeclarations[classIndex];
|
||||
const renderer = lView[RENDERER];
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
value ? renderer.addClass(node, className) : renderer.removeClass(node, className);
|
||||
} else {
|
||||
const classList = (node as HTMLElement).classList;
|
||||
value ? classList.add(className) : classList.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStylingApply(index: number, directive?: {}): void {
|
||||
// Do nothing because the hack implementation is eager.
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStyleProp(
|
||||
index: number, styleIndex: number, value: string | null, suffix?: string,
|
||||
directive?: {}): void {
|
||||
const lView = getLView();
|
||||
const node = getNativeByIndex(index, lView);
|
||||
ngDevMode && assertDefined(node, 'could not locate node');
|
||||
const hostStylingHack: HostStylingHack = (node as any).hostStylingHack.get(directive);
|
||||
const styleName = hostStylingHack.styleDeclarations[styleIndex];
|
||||
const renderer = lView[RENDERER];
|
||||
setStyle(node, styleName, value as string, renderer, null);
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStylingMap<T>(
|
||||
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
||||
styles?: {[styleName: string]: any} | NO_CHANGE | null, directive?: {}): void {
|
||||
throw new Error('unimplemented. Should not be needed by ViewEngine compatibility');
|
||||
}
|
||||
|
||||
/* END OF HACK BLOCK */
|
||||
|
||||
//////////////////////////
|
||||
//// Text
|
||||
//////////////////////////
|
||||
|
@ -2885,10 +2829,6 @@ function initializeTNodeInputs(tNode: TNode | null) {
|
|||
return null;
|
||||
}
|
||||
|
||||
export function delegateToClassInput(tNode: TNode) {
|
||||
return tNode.flags & TNodeFlags.hasClassInput;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current OpaqueViewState instance.
|
||||
|
|
|
@ -23,6 +23,14 @@ export const VIEWS = 1;
|
|||
// As we already have these constants in LView, we don't need to re-create them.
|
||||
export const NATIVE = 6;
|
||||
export const RENDER_PARENT = 7;
|
||||
// Because interfaces in TS/JS cannot be instanceof-checked this means that we
|
||||
// need to rely on predictable characteristics of data-structures to check if they
|
||||
// are what we expect for them to be. The `LContainer` interface code below has a
|
||||
// fixed length and the constant value below references that. Using the length value
|
||||
// below we can predictably gaurantee that we are dealing with an `LContainer` array.
|
||||
// This value MUST be kept up to date with the length of the `LContainer` array
|
||||
// interface below so that runtime type checking can work.
|
||||
export const LCONTAINER_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* The state associated with a container.
|
||||
|
|
|
@ -344,8 +344,4 @@ export type PipeTypeList =
|
|||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
||||
export const enum InitialStylingFlags {
|
||||
VALUES_MODE = 0b1,
|
||||
}
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
|
@ -54,7 +54,7 @@ export const enum TNodeProviderIndexes {
|
|||
CptViewProvidersCountShifter = 0b00000000000000010000000000000000,
|
||||
}
|
||||
/**
|
||||
* A set of marker values to be used in the attributes arrays. Those markers indicate that some
|
||||
* A set of marker values to be used in the attributes arrays. These markers indicate that some
|
||||
* items are not regular attributes and the processing should be adapted accordingly.
|
||||
*/
|
||||
export const enum AttributeMarker {
|
||||
|
@ -65,13 +65,50 @@ export const enum AttributeMarker {
|
|||
*/
|
||||
NamespaceURI = 0,
|
||||
|
||||
/**
|
||||
* Signals class declaration.
|
||||
*
|
||||
* Each value following `Classes` designates a class name to include on the element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div class="foo bar baz">...<d/vi>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Classes, 'foo', 'bar', 'baz'];
|
||||
* ```
|
||||
*/
|
||||
Classes = 1,
|
||||
|
||||
/**
|
||||
* Signals style declaration.
|
||||
*
|
||||
* Each pair of values following `Styles` designates a style name and value to include on the
|
||||
* element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div style="width:100px; height:200px; color:red">...</div>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Styles, 'width', '100px', 'height'. '200px', 'color', 'red'];
|
||||
* ```
|
||||
*/
|
||||
Styles = 2,
|
||||
|
||||
/**
|
||||
* This marker indicates that the following attribute names were extracted from bindings (ex.:
|
||||
* [foo]="exp") and / or event handlers (ex. (bar)="doSth()").
|
||||
* Taking the above bindings and outputs as an example an attributes array could look as follows:
|
||||
* ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar']
|
||||
*/
|
||||
SelectOnly = 1
|
||||
SelectOnly = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,130 +9,199 @@ import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
|||
import {RElement} from '../interfaces/renderer';
|
||||
import {PlayerContext} from './player';
|
||||
|
||||
|
||||
/**
|
||||
* The styling context acts as a styling manifest (shaped as an array) for determining which
|
||||
* 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.
|
||||
* and `updateClassProp` functions. It also stores the static style/class values that were
|
||||
* extracted from the template by the compiler.
|
||||
*
|
||||
* 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.
|
||||
* A context is created by Angular when:
|
||||
* 1. An element contains static styling values (like style="..." or class="...")
|
||||
* 2. An element contains single property binding values (like [style.prop]="x" or
|
||||
* [class.prop]="y")
|
||||
* 3. An element contains multi property binding values (like [style]="x" or [class]="y")
|
||||
* 4. A directive contains host bindings for static, single or multi styling properties/bindings.
|
||||
* 5. An animation player is added to an element via `addPlayer`
|
||||
*
|
||||
* each value from here onwards is mapped as so:
|
||||
* [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 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` or `updateClassProp` (fixed set)
|
||||
*
|
||||
* multi: any styles that are updated using `updateStylingMap` (dynamic set)
|
||||
*
|
||||
* 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 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:
|
||||
* Note that even if an element contains static styling then this context will be created and
|
||||
* attached to it. The reason why this happens (instead of treating styles/classes as regular
|
||||
* HTML attributes) is because the style/class bindings must be able to default themselves back
|
||||
* to their respective static values when they are set to null.
|
||||
*
|
||||
* Say for example we have this:
|
||||
* ```
|
||||
* <!-- when myWidthExp=null then a width of "100px"
|
||||
* will be used a default value for width -->
|
||||
* <div style="width:100px" [style.width]="myWidthExp"></div>
|
||||
* ```
|
||||
*
|
||||
* Even in the situation where there are no bindings, the static styling is still placed into the
|
||||
* context because there may be another directive on the same element that has styling.
|
||||
*
|
||||
* When Angular initializes styling data for an element then it will first register the static
|
||||
* styling values on the element using one of these two instructions:
|
||||
*
|
||||
* 1. elementStart or element (within the template function of a component)
|
||||
* 2. elementHostAttrs (for directive host bindings)
|
||||
*
|
||||
* In either case, a styling context will be created and stored within an element's LViewData. Once
|
||||
* the styling context is created then single and multi properties can stored within it. For this to
|
||||
* happen, the following function needs to be called:
|
||||
*
|
||||
* `elementStyling` (called with style properties, class properties and a sanitizer + a directive
|
||||
* instance).
|
||||
*
|
||||
* When this instruction is called it will populate the styling context with the provided style
|
||||
* and class names into the context.
|
||||
*
|
||||
* The context itself looks like this:
|
||||
*
|
||||
* context = [
|
||||
* element,
|
||||
* playerContext | null,
|
||||
* styleSanitizer | null,
|
||||
* [null, '100px', '200px', true], // property names are not needed since they have already been
|
||||
* written to DOM.
|
||||
*
|
||||
* configMasterVal,
|
||||
* 1, // this instructs how many `style` values there are so that class index values can be
|
||||
* offsetted
|
||||
* { classOne: true, classTwo: false } | 'classOne classTwo' | null // last class value provided
|
||||
* into updateStylingMap
|
||||
* { styleOne: '100px', styleTwo: 0 } | null // last style value provided into updateStylingMap
|
||||
*
|
||||
* // 8
|
||||
* 'width',
|
||||
* pointers(1, 15); // Point to static `width`: `100px` and multi `width`.
|
||||
* null,
|
||||
*
|
||||
* // 11
|
||||
* 'height',
|
||||
* pointers(2, 18); // Point to static `height`: `200px` and multi `height`.
|
||||
* null,
|
||||
*
|
||||
* // 14
|
||||
* 'foo',
|
||||
* pointers(1, 21); // Point to static `foo`: `true` and multi `foo`.
|
||||
* null,
|
||||
*
|
||||
* // 17
|
||||
* 'width',
|
||||
* pointers(1, 6); // Point to static `width`: `100px` and single `width`.
|
||||
* null,
|
||||
*
|
||||
* // 21
|
||||
* 'height',
|
||||
* pointers(2, 9); // Point to static `height`: `200px` and single `height`.
|
||||
* null,
|
||||
*
|
||||
* // 24
|
||||
* 'foo',
|
||||
* pointers(3, 12); // Point to static `foo`: `true` and single `foo`.
|
||||
* null,
|
||||
* // 0-8: header values (about 8 entries of configuration data)
|
||||
* // 9+: this is where each entry is stored:
|
||||
* ]
|
||||
*
|
||||
* function pointers(staticIndex: number, dynamicIndex: number) {
|
||||
* // combine the two indices into a single word.
|
||||
* return (staticIndex << StylingFlags.BitCountSize) |
|
||||
* (dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize));
|
||||
* Let's say we have the following template code:
|
||||
*
|
||||
* ```
|
||||
* <div class="foo bar"
|
||||
* style="width:200px; color:red"
|
||||
* [style.width]="myWidthExp"
|
||||
* [style.height]="myHeightExp"
|
||||
* [class.baz]="myBazExp">
|
||||
* ```
|
||||
*
|
||||
* The context generated from these values will look like this (note that
|
||||
* for each binding name (the class and style bindings) the values will
|
||||
* be inserted twice into the array (once for single property entries) and
|
||||
* another for multi property entries).
|
||||
*
|
||||
* context = [
|
||||
* // 0-8: header values (about 8 entries of configuration data)
|
||||
* // 9+: this is where each entry is stored:
|
||||
*
|
||||
* // SINGLE PROPERTIES
|
||||
* configForWidth,
|
||||
* 'width'
|
||||
* myWidthExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForHeight,
|
||||
* 'height'
|
||||
* myHeightExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForBazClass,
|
||||
* 'baz
|
||||
* myBazClassExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* // MULTI PROPERTIES
|
||||
* configForWidth,
|
||||
* 'width'
|
||||
* myWidthExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForHeight,
|
||||
* 'height'
|
||||
* myHeightExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForBazClass,
|
||||
* 'baz
|
||||
* myBazClassExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
* ]
|
||||
*
|
||||
* The configuration values are left out of the example above because
|
||||
* the ordering of them could change between code patches. Please read the
|
||||
* documentation below to get a better understand of what the configuration
|
||||
* values are and how they work.
|
||||
*
|
||||
* Each time a binding property is updated (whether it be through a single
|
||||
* property instruction like `elementStyleProp`, `elementClassProp` or
|
||||
* `elementStylingMap`) then the values in the context will be updated as
|
||||
* well.
|
||||
*
|
||||
* If for example `[style.width]` updates to `555px` then its value will be reflected
|
||||
* in the context as so:
|
||||
*
|
||||
* context = [
|
||||
* // ...
|
||||
* configForWidth, // this will be marked DIRTY
|
||||
* 'width'
|
||||
* '555px',
|
||||
* 0,
|
||||
* //..
|
||||
* ]
|
||||
*
|
||||
* The context and directive data will also be marked dirty.
|
||||
*
|
||||
* Despite the context being updated, nothing has been rendered on screen (not styles or
|
||||
* classes have been set on the element). To kick off rendering for an element the following
|
||||
* function needs to be run `elementStylingApply`.
|
||||
*
|
||||
* `elementStylingApply` will run through the context and find each dirty value and render them onto
|
||||
* the element. Once complete, all styles/classes will be set to clean. Because of this, the render
|
||||
* function will now know not to rerun itself again if called again unless new style/class values
|
||||
* have changed.
|
||||
*
|
||||
* ## Directives
|
||||
* Directives style values (which are provided through host bindings) are also supported and
|
||||
* housed within the same styling context as are template-level style/class properties/bindings.
|
||||
* Both directive-level and template-level styling bindings share the same context.
|
||||
*
|
||||
* Each of the following instructions supports accepting a directive instance as an input parameter:
|
||||
*
|
||||
* - `elementHostAttrs`
|
||||
* - `elementStyling`
|
||||
* - `elementStyleProp`
|
||||
* - `elementClassProp`
|
||||
* - `elementStylingMap`
|
||||
* - `elementStylingApply`
|
||||
*
|
||||
* Each time a directiveRef is passed in, it will be converted into an index by examining the
|
||||
* directive registry (which lives in the context configuration area). The index is then used
|
||||
* to help single style properties figure out where a value is located in the context.
|
||||
*
|
||||
* If two directives or a directive + a template binding both write to the same style/class
|
||||
* binding then the styling context code will decide which one wins based on the following
|
||||
* rule:
|
||||
*
|
||||
* 1. If the template binding has a value then it always wins
|
||||
* 2. If not then whichever first-registered directive that has that value first will win
|
||||
*
|
||||
* The code example helps make this clear:
|
||||
*
|
||||
* ```
|
||||
* <div [style.width]="myWidth" [my-width-directive]="'600px">
|
||||
* @Directive({ selector: '[my-width-directive' ]})
|
||||
* class MyWidthDirective {
|
||||
* @Input('my-width-directive')
|
||||
* @HostBinding('style.width')
|
||||
* public width = null;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 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).
|
||||
* Since there is a style binding for width present on the element (`[style.width]`) then
|
||||
* it will always win over the width binding that is present as a host binding within
|
||||
* the `MyWidthDirective`. However, if `[style.width]` renders as `null` (so `myWidth=null`)
|
||||
* then the `MyWidthDirective` will be able to write to the `width` style within the context.
|
||||
* Simply put, whichever directive writes to a value ends up having ownership of it.
|
||||
*
|
||||
* 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).
|
||||
* The way in which the ownership is facilitated is through index value. The earliest directives
|
||||
* get the smallest index values (with 0 being reserved for the template element bindings). Each
|
||||
* time a value is written from a directive or the template bindings, the value itself gets
|
||||
* assigned the directive index value in its data. If another directive writes a value again then
|
||||
* its directive index gets compared against the directive index that exists on the element. Only
|
||||
* when the new value's directive index is less than the existing directive index then the new
|
||||
* value will be written to the context.
|
||||
*
|
||||
* Each directive also has its own sanitizer and dirty flags. These values are consumed within the
|
||||
* rendering function.
|
||||
*/
|
||||
export interface StylingContext extends Array<InitialStyles|{[key: string]: any}|number|string|
|
||||
boolean|RElement|StyleSanitizeFn|PlayerContext|null> {
|
||||
/**
|
||||
* Location of animation context (which contains the active players) for this element styling
|
||||
* context.
|
||||
*/
|
||||
[StylingIndex.PlayerContext]: PlayerContext|null;
|
||||
|
||||
/**
|
||||
* The style sanitizer that is used within this context
|
||||
*/
|
||||
[StylingIndex.StyleSanitizerPosition]: StyleSanitizeFn|null;
|
||||
|
||||
/**
|
||||
* Location of initial data shared by all instances of this style.
|
||||
*/
|
||||
[StylingIndex.InitialStylesPosition]: InitialStyles;
|
||||
|
||||
export interface StylingContext extends
|
||||
Array<{[key: string]: any}|number|string|boolean|RElement|StyleSanitizeFn|PlayerContext|null> {
|
||||
/**
|
||||
* 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
|
||||
|
@ -140,12 +209,27 @@ export interface StylingContext extends Array<InitialStyles|{[key: string]: any}
|
|||
*/
|
||||
[StylingIndex.MasterFlagPosition]: number;
|
||||
|
||||
/**
|
||||
* Location of the collection of directives for this context
|
||||
*/
|
||||
[StylingIndex.DirectiveRegistryPosition]: DirectiveRegistryValues;
|
||||
|
||||
/**
|
||||
* Location of all static styles values
|
||||
*/
|
||||
[StylingIndex.InitialStyleValuesPosition]: InitialStylingValues;
|
||||
|
||||
/**
|
||||
* Location of all static class values
|
||||
*/
|
||||
[StylingIndex.InitialClassValuesPosition]: InitialStylingValues;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
[StylingIndex.ClassOffsetPosition]: number;
|
||||
[StylingIndex.SinglePropOffsetPositions]: SinglePropOffsetValues;
|
||||
|
||||
/**
|
||||
* Location of element that is used as a target for this context.
|
||||
|
@ -156,24 +240,206 @@ export interface StylingContext extends Array<InitialStyles|{[key: string]: any}
|
|||
* The last class value that was interpreted by elementStylingMap. This is cached
|
||||
* So that the algorithm can exit early incase the value has not changed.
|
||||
*/
|
||||
[StylingIndex.PreviousOrCachedMultiClassValue]: {[key: string]: any}|string|null;
|
||||
[StylingIndex.CachedClassValueOrInitialClassString]: {[key: string]: any}|string|(string)[]|null;
|
||||
|
||||
/**
|
||||
* The last style value that was interpreted by elementStylingMap. This is cached
|
||||
* So that the algorithm can exit early incase the value has not changed.
|
||||
*/
|
||||
[StylingIndex.PreviousMultiStyleValue]: {[key: string]: any}|null;
|
||||
[StylingIndex.CachedStyleValue]: {[key: string]: any}|(string)[]|null;
|
||||
|
||||
/**
|
||||
* Location of animation context (which contains the active players) for this element styling
|
||||
* context.
|
||||
*/
|
||||
[StylingIndex.PlayerContext]: PlayerContext|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The initial styles is populated whether or not there are any initial styles passed into
|
||||
* the context during allocation. The 0th value must be null so that index values of `0` within
|
||||
* the context flags can always point to a null value safely when nothing is set.
|
||||
* Used as a styling array to house static class and style values that were extracted
|
||||
* by the compiler and placed in the animation context via `elementStart` and
|
||||
* `elementHostAttrs`.
|
||||
*
|
||||
* 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.
|
||||
* See [InitialStylingValuesIndex] for a breakdown of how all this works.
|
||||
*/
|
||||
export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
|
||||
export interface InitialStylingValues extends Array<string|boolean|null> { [0]: null; }
|
||||
|
||||
/**
|
||||
* Used as an offset/position index to figure out where initial styling
|
||||
* values are located.
|
||||
*
|
||||
* Used as a reference point to provide markers to all static styling
|
||||
* values (the initial style and class values on an element) within an
|
||||
* array within the StylingContext. This array contains key/value pairs
|
||||
* where the key is the style property name or className and the value is
|
||||
* the style value or whether or not a class is present on the elment.
|
||||
*
|
||||
* The first value is also always null so that a initial index value of
|
||||
* `0` will always point to a null value.
|
||||
*
|
||||
* If a <div> elements contains a list of static styling values like so:
|
||||
*
|
||||
* <div class="foo bar baz" style="width:100px; height:200px;">
|
||||
*
|
||||
* Then the initial styles for that will look like so:
|
||||
*
|
||||
* Styles:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'width', '100px', height, '200px'
|
||||
* ]
|
||||
*
|
||||
* Classes:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'foo', true, 'bar', true, 'baz', true
|
||||
* ]
|
||||
*
|
||||
* Initial style and class entries have their own arrays. This is because
|
||||
* it's easier to add to the end of one array and not then have to update
|
||||
* every context entries' pointer index to the newly offseted values.
|
||||
*
|
||||
* When property bindinds are added to a context then initial style/class
|
||||
* values will also be inserted into the array. This is to create a space
|
||||
* in the situation when a follow-up directive inserts static styling into
|
||||
* the array. By default style values are `null` and class values are
|
||||
* `false` when inserted by property bindings.
|
||||
*
|
||||
* For example:
|
||||
* <div class="foo bar baz"
|
||||
* [class.car]="myCarExp"
|
||||
* style="width:100px; height:200px;"
|
||||
* [style.opacity]="myOpacityExp">
|
||||
*
|
||||
* Will construct initial styling values that look like:
|
||||
*
|
||||
* Styles:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'width', '100px', height, '200px', 'opacity', null
|
||||
* ]
|
||||
*
|
||||
* Classes:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'foo', true, 'bar', true, 'baz', true, 'car', false
|
||||
* ]
|
||||
*
|
||||
* Now if a directive comes along and introduces `car` as a static
|
||||
* class value or `opacity` then those values will be filled into
|
||||
* the initial styles array.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* @Directive({
|
||||
* selector: 'opacity-car-directive',
|
||||
* host: {
|
||||
* 'style': 'opacity:0.5',
|
||||
* 'class': 'car'
|
||||
* }
|
||||
* })
|
||||
* class OpacityCarDirective {}
|
||||
*
|
||||
* This will render itself as:
|
||||
*
|
||||
* Styles:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'width', '100px', height, '200px', 'opacity', null
|
||||
* ]
|
||||
*
|
||||
* Classes:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'foo', true, 'bar', true, 'baz', true, 'car', false
|
||||
* ]
|
||||
*/
|
||||
export const enum InitialStylingValuesIndex {
|
||||
KeyValueStartPosition = 1,
|
||||
PropOffset = 0,
|
||||
ValueOffset = 1,
|
||||
Size = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* An array located in the StylingContext that houses all directive instances and additional
|
||||
* data about them.
|
||||
*
|
||||
* Each entry in this array represents a source of where style/class binding values could
|
||||
* come from. By default, there is always at least one directive here with a null value and
|
||||
* that represents bindings that live directly on an element (not host bindings).
|
||||
*
|
||||
* Each successive entry in the array is an actual instance of an array as well as some
|
||||
* additional info.
|
||||
*
|
||||
* An entry within this array has the following values:
|
||||
* [0] = The instance of the directive (or null when it is not a directive, but a template binding
|
||||
* source)
|
||||
* [1] = The pointer that tells where the single styling (stuff like [class.foo] and [style.prop])
|
||||
* offset values are located. This value will allow for a binding instruction to find exactly
|
||||
* where a style is located.
|
||||
* [2] = Whether or not the directive has any styling values that are dirty. This is used as
|
||||
* reference within the renderClassAndStyleBindings function to decide whether to skip
|
||||
* iterating through the context when rendering is executed.
|
||||
* [3] = The styleSanitizer instance that is assigned to the directive. Although it's unlikely,
|
||||
* a directive could introduce its own special style sanitizer and for this reach each
|
||||
* directive will get its own space for it (if null then the very first sanitizer is used).
|
||||
*
|
||||
* Each time a new directive is added it will insert these four values at the end of the array.
|
||||
* When this array is examined (using indexOf) then the resulting directiveIndex will be resolved
|
||||
* by dividing the index value by the size of the array entries (so if DirA is at spot 8 then its
|
||||
* index will be 2).
|
||||
*/
|
||||
export interface DirectiveRegistryValues extends Array<null|{}|boolean|number|StyleSanitizeFn> {
|
||||
[DirectiveRegistryValuesIndex.DirectiveValueOffset]: null;
|
||||
[DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset]: number;
|
||||
[DirectiveRegistryValuesIndex.DirtyFlagOffset]: boolean;
|
||||
[DirectiveRegistryValuesIndex.StyleSanitizerOffset]: StyleSanitizeFn|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum that outlines the offset/position values for each directive entry and its data
|
||||
* that are housed inside of [DirectiveRegistryValues].
|
||||
*/
|
||||
export const enum DirectiveRegistryValuesIndex {
|
||||
DirectiveValueOffset = 0,
|
||||
SinglePropValuesIndexOffset = 1,
|
||||
DirtyFlagOffset = 2,
|
||||
StyleSanitizerOffset = 3,
|
||||
Size = 4
|
||||
}
|
||||
|
||||
/**
|
||||
* An array that contains the index pointer values for every single styling property
|
||||
* that exists in the context and for every directive. It also contains the total
|
||||
* single styles and single classes that exists in the context as the first two values.
|
||||
*
|
||||
* Let's say we have the following template code:
|
||||
*
|
||||
* <div [style.width]="myWidth"
|
||||
* [style.height]="myHeight"
|
||||
* [class.flipped]="flipClass"
|
||||
* directive-with-opacity>
|
||||
* directive-with-foo-bar-classes>
|
||||
*
|
||||
* We have two directive and template-binding sources,
|
||||
* 2 + 1 styles and 1 + 1 classes. When the bindings are
|
||||
* registered the SinglePropOffsets array will look like so:
|
||||
*
|
||||
* s_0/c_0 = template directive value
|
||||
* s_1/c_1 = directive one (directive-with-opacity)
|
||||
* s_2/c_2 = directive two (directive-with-foo-bar-classes)
|
||||
*
|
||||
* [3, 2, 2, 1, s_00, s01, c_01, 1, 0, s_10, 0, 1, c_20
|
||||
*/
|
||||
export interface SinglePropOffsetValues extends Array<number> {
|
||||
[SinglePropOffsetValuesIndex.StylesCountPosition]: number;
|
||||
[SinglePropOffsetValuesIndex.ClassesCountPosition]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum that outlines the offset/position values for each single prop/class entry
|
||||
* that are housed inside of [SinglePropOffsetValues].
|
||||
*/
|
||||
export const enum SinglePropOffsetValuesIndex {
|
||||
StylesCountPosition = 0,
|
||||
ClassesCountPosition = 1,
|
||||
ValueStartPosition = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to set the context to be dirty or not both on the master flag (position 1)
|
||||
|
@ -181,47 +447,49 @@ export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
|
|||
*/
|
||||
export const enum StylingFlags {
|
||||
// Implies no configurations
|
||||
None = 0b00000,
|
||||
None = 0b000000,
|
||||
// Whether or not the entry or context itself is dirty
|
||||
Dirty = 0b00001,
|
||||
Dirty = 0b000001,
|
||||
// Whether or not this is a class-based assignment
|
||||
Class = 0b00010,
|
||||
Class = 0b000010,
|
||||
// Whether or not a sanitizer was applied to this property
|
||||
Sanitize = 0b00100,
|
||||
Sanitize = 0b000100,
|
||||
// Whether or not any player builders within need to produce new players
|
||||
PlayerBuildersDirty = 0b01000,
|
||||
PlayerBuildersDirty = 0b001000,
|
||||
// If NgClass is present (or some other class handler) then it will handle the map expressions and
|
||||
// initial classes
|
||||
OnlyProcessSingleClasses = 0b10000,
|
||||
OnlyProcessSingleClasses = 0b010000,
|
||||
// The max amount of bits used to represent these configuration values
|
||||
BitCountSize = 5,
|
||||
// There are only five bits here
|
||||
BitMask = 0b11111
|
||||
BindingAllocationLocked = 0b100000,
|
||||
BitCountSize = 6,
|
||||
// There are only six bits here
|
||||
BitMask = 0b111111
|
||||
}
|
||||
|
||||
/** 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
|
||||
PlayerContext = 0,
|
||||
// Position of where the style sanitizer is stored within the styling context
|
||||
StyleSanitizerPosition = 1,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
InitialStylesPosition = 2,
|
||||
// Index of location where the start of single properties are stored. (`updateStyleProp`)
|
||||
MasterFlagPosition = 3,
|
||||
MasterFlagPosition = 0,
|
||||
// Position of where the registered directives exist for this styling context
|
||||
DirectiveRegistryPosition = 1,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
InitialStyleValuesPosition = 2,
|
||||
InitialClassValuesPosition = 3,
|
||||
// Index of location where the class index offset value is located
|
||||
ClassOffsetPosition = 4,
|
||||
SinglePropOffsetPositions = 4,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
// This index must align with HOST, see interfaces/view.ts
|
||||
ElementPosition = 5,
|
||||
// Position of where the last string-based CSS class value was stored (or a cached version of the
|
||||
// initial styles when a [class] directive is present)
|
||||
PreviousOrCachedMultiClassValue = 6,
|
||||
CachedClassValueOrInitialClassString = 6,
|
||||
// Position of where the last string-based CSS class value was stored
|
||||
PreviousMultiStyleValue = 7,
|
||||
// Location of single (prop) value entries are stored within the context
|
||||
SingleStylesStartPosition = 8,
|
||||
CachedStyleValue = 7,
|
||||
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
PlayerContext = 8,
|
||||
// Location of single (prop) value entries are stored within the context
|
||||
SingleStylesStartPosition = 9,
|
||||
FlagsOffset = 0,
|
||||
PropertyOffset = 1,
|
||||
ValueOffset = 2,
|
||||
|
@ -233,3 +501,16 @@ export const enum StylingIndex {
|
|||
// The binary digit value as a mask
|
||||
BitMask = 0b11111111111111, // 14 bits
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum that outlines the bit flag data for directive owner and player index
|
||||
* values that exist within en entry that lives in the StylingContext.
|
||||
*
|
||||
* The values here split a number value into two sets of bits:
|
||||
* - The first 16 bits are used to store the directiveIndex that owns this style value
|
||||
* - The other 16 bits are used to store the playerBuilderIndex that is attached to this style
|
||||
*/
|
||||
export const enum DirectiveOwnerAndPlayerBuilderIndex {
|
||||
BitCountSize = 16,
|
||||
BitMask = 0b1111111111111111
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ import {Component, Directive} from '../../metadata/directives';
|
|||
import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading';
|
||||
import {ViewEncapsulation} from '../../metadata/view';
|
||||
import {Type} from '../../type';
|
||||
import {stringify} from '../../util';
|
||||
import {EMPTY_ARRAY} from '../definition';
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from '../fields';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {R3DirectiveMetadataFacade, getCompilerFacade} from './compiler_facade';
|
||||
import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from './compiler_facade_interface';
|
||||
|
@ -154,8 +154,6 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
|||
};
|
||||
}
|
||||
|
||||
const EMPTY_OBJ = {};
|
||||
|
||||
function convertToR3QueryPredicate(selector: any): any|string[] {
|
||||
return typeof selector === 'string' ? splitByComma(selector) : selector;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
|||
'ɵregisterContentQuery': r3.registerContentQuery,
|
||||
'ɵreference': r3.reference,
|
||||
'ɵelementStyling': r3.elementStyling,
|
||||
'ɵelementHostAttrs': r3.elementHostAttrs,
|
||||
'ɵelementStylingMap': r3.elementStylingMap,
|
||||
'ɵelementStyleProp': r3.elementStyleProp,
|
||||
'ɵelementStylingApply': r3.elementStylingApply,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,12 +9,13 @@ import '../ng_dev_mode';
|
|||
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {getLContext} from '../context_discovery';
|
||||
import {ACTIVE_INDEX, LContainer} from '../interfaces/container';
|
||||
import {LContainer} from '../interfaces/container';
|
||||
import {LContext} from '../interfaces/context';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node';
|
||||
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {InitialStyles, StylingContext, StylingIndex} from '../interfaces/styling';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
|
||||
import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
|
||||
import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
|
||||
import {getTNode} from '../util';
|
||||
|
||||
import {CorePlayerHandler} from './core_player_handler';
|
||||
|
@ -23,16 +24,18 @@ const ANIMATION_PROP_PREFIX = '@';
|
|||
|
||||
export function createEmptyStylingContext(
|
||||
element?: RElement | null, sanitizer?: StyleSanitizeFn | null,
|
||||
initialStylingValues?: InitialStyles): StylingContext {
|
||||
initialStyles?: InitialStylingValues | null,
|
||||
initialClasses?: InitialStylingValues | null): StylingContext {
|
||||
return [
|
||||
null, // PlayerContext
|
||||
sanitizer || null, // StyleSanitizer
|
||||
initialStylingValues || [null], // InitialStyles
|
||||
0, // MasterFlags
|
||||
0, // ClassOffset
|
||||
element || null, // Element
|
||||
null, // PreviousMultiClassValue
|
||||
null // PreviousMultiStyleValue
|
||||
0, // MasterFlags
|
||||
[null, -1, false, sanitizer || null], // DirectiveRefs
|
||||
initialStyles || [null], // InitialStyles
|
||||
initialClasses || [null], // InitialClasses
|
||||
[0, 0], // SinglePropOffsets
|
||||
element || null, // Element
|
||||
null, // PreviousMultiClassValue
|
||||
null, // PreviousMultiStyleValue
|
||||
null, // PlayerContext
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -47,6 +50,9 @@ export function allocStylingContext(
|
|||
// each instance gets a copy
|
||||
const context = templateStyleContext.slice() as any as StylingContext;
|
||||
context[StylingIndex.ElementPosition] = element;
|
||||
|
||||
// this will prevent any other directives from extending the context
|
||||
context[StylingIndex.MasterFlagPosition] |= StylingFlags.BindingAllocationLocked;
|
||||
return context;
|
||||
}
|
||||
|
||||
|
@ -89,8 +95,8 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
|
|||
|
||||
export function isStylingContext(value: any): value is StylingContext {
|
||||
// Not an LView or an LContainer
|
||||
return Array.isArray(value) && typeof value[FLAGS] !== 'number' &&
|
||||
typeof value[ACTIVE_INDEX] !== 'number';
|
||||
return Array.isArray(value) && typeof value[StylingIndex.MasterFlagPosition] === 'number' &&
|
||||
Array.isArray(value[StylingIndex.InitialStyleValuesPosition]);
|
||||
}
|
||||
|
||||
export function isAnimationProp(name: string): boolean {
|
||||
|
@ -182,3 +188,15 @@ export function allocPlayerContext(data: StylingContext): PlayerContext {
|
|||
export function throwInvalidRefError() {
|
||||
throw new Error('Only elements that exist in an Angular application can be used for animations');
|
||||
}
|
||||
|
||||
export function hasStyling(attrs: TAttributes): boolean {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
if (attr == AttributeMarker.Classes || attr == AttributeMarker.Styles) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasClassInput(tNode: TNode) {
|
||||
return tNode.flags & TNodeFlags.hasClassInput ? true : false;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {global} from '../util';
|
||||
|
||||
import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from './assert';
|
||||
import {ACTIVE_INDEX, LContainer} from './interfaces/container';
|
||||
import {ACTIVE_INDEX, LCONTAINER_LENGTH, LContainer} from './interfaces/container';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||
import {ComponentDef, DirectiveDef} from './interfaces/definition';
|
||||
import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector';
|
||||
|
@ -18,8 +18,6 @@ import {RComment, RElement, RText} from './interfaces/renderer';
|
|||
import {StylingContext} from './interfaces/styling';
|
||||
import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, TView} from './interfaces/view';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the values are different from a change detection stand point.
|
||||
*
|
||||
|
@ -127,7 +125,7 @@ export function isComponentDef<T>(def: DirectiveDef<T>): def is ComponentDef<T>
|
|||
|
||||
export function isLContainer(value: RElement | RComment | LContainer | StylingContext): boolean {
|
||||
// Styling contexts are also arrays, but their first index contains an element node
|
||||
return Array.isArray(value) && typeof value[ACTIVE_INDEX] === 'number';
|
||||
return Array.isArray(value) && value.length === LCONTAINER_LENGTH;
|
||||
}
|
||||
|
||||
export function isRootView(target: LView): boolean {
|
||||
|
|
|
@ -9,7 +9,29 @@
|
|||
import '@angular/core/test/bundling/util/src/reflect_metadata';
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵbindPlayerFactory as bindPlayerFactory, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
import {Component, Directive, ElementRef, HostBinding, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵbindPlayerFactory as bindPlayerFactory, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[make-color-grey]',
|
||||
exportAs: 'makeColorGrey',
|
||||
host: {'style': 'font-family: Times New Roman;'}
|
||||
})
|
||||
class MakeColorGreyDirective {
|
||||
@HostBinding('style.background-color') private _backgroundColor: string|null = null;
|
||||
@HostBinding('style.color') private _textColor: string|null = null;
|
||||
|
||||
on() {
|
||||
this._backgroundColor = 'grey';
|
||||
this._textColor = 'black';
|
||||
}
|
||||
|
||||
off() {
|
||||
this._backgroundColor = null;
|
||||
this._textColor = null;
|
||||
}
|
||||
|
||||
toggle() { this._backgroundColor ? this.off() : this.on(); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'animation-world',
|
||||
|
@ -20,21 +42,40 @@ import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as P
|
|||
</nav>
|
||||
<div class="list">
|
||||
<div
|
||||
*ngFor="let item of items" class="record" [class]="makeClass(item)" style="border-radius: 10px"
|
||||
[style]="styles">
|
||||
{{ item }}
|
||||
#makeColorGrey="makeColorGrey"
|
||||
make-color-grey
|
||||
*ngFor="let item of items"
|
||||
class="record"
|
||||
[style.transform]="item.active ? 'scale(1.5)' : 'none'"
|
||||
[class]="makeClass(item)"
|
||||
style="border-radius: 10px"
|
||||
[style]="styles"
|
||||
[style.color]="item.value == 4 ? 'red' : null"
|
||||
[style.background-color]="item.value == 4 ? 'white' : null"
|
||||
(click)="toggleActive(item, makeColorGrey)">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class AnimationWorldComponent {
|
||||
items: any[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
items: any[] = [
|
||||
{value: 1, active: false}, {value: 2, active: false}, {value: 3, active: false},
|
||||
{value: 4, active: false}, {value: 5, active: false}, {value: 6, active: false},
|
||||
{value: 7, active: false}, {value: 8, active: false}, {value: 9, active: false}
|
||||
];
|
||||
private _hostElement: HTMLElement;
|
||||
public styles: {[key: string]: any}|null = null;
|
||||
|
||||
constructor(element: ElementRef) { this._hostElement = element.nativeElement; }
|
||||
|
||||
makeClass(index: number) { return `record-${index}`; }
|
||||
makeClass(item: any) { return `record-${item.value}`; }
|
||||
|
||||
toggleActive(item: any, makeColorGrey: MakeColorGreyDirective) {
|
||||
item.active = !item.active;
|
||||
makeColorGrey.toggle();
|
||||
markDirty(this);
|
||||
}
|
||||
|
||||
animateWithStyles() {
|
||||
this.styles = animateStyleFactory([{opacity: 0}, {opacity: 1}], 300, 'ease-out');
|
||||
|
@ -52,7 +93,8 @@ class AnimationWorldComponent {
|
|||
}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [AnimationWorldComponent], imports: [CommonModule]})
|
||||
@NgModule(
|
||||
{declarations: [AnimationWorldComponent, MakeColorGreyDirective], imports: [CommonModule]})
|
||||
class AnimationWorldModule {
|
||||
}
|
||||
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
"name": "DECLARATION_VIEW"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY"
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
"name": "EMPTY_OBJ"
|
||||
},
|
||||
{
|
||||
"name": "EmptyErrorImpl"
|
||||
|
|
|
@ -45,10 +45,10 @@
|
|||
"name": "DefaultIterableDifferFactory"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY"
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
"name": "EMPTY_OBJ"
|
||||
},
|
||||
{
|
||||
"name": "ElementRef"
|
||||
|
@ -86,6 +86,9 @@
|
|||
{
|
||||
"name": "IterableDiffers"
|
||||
},
|
||||
{
|
||||
"name": "LCONTAINER_LENGTH"
|
||||
},
|
||||
{
|
||||
"name": "MONKEY_PATCH_KEY_NAME"
|
||||
},
|
||||
|
@ -320,21 +323,9 @@
|
|||
{
|
||||
"name": "_c18"
|
||||
},
|
||||
{
|
||||
"name": "_c19"
|
||||
},
|
||||
{
|
||||
"name": "_c2"
|
||||
},
|
||||
{
|
||||
"name": "_c20"
|
||||
},
|
||||
{
|
||||
"name": "_c21"
|
||||
},
|
||||
{
|
||||
"name": "_c22"
|
||||
},
|
||||
{
|
||||
"name": "_c3"
|
||||
},
|
||||
|
@ -371,6 +362,9 @@
|
|||
{
|
||||
"name": "_symbolIterator"
|
||||
},
|
||||
{
|
||||
"name": "_updateSingleStylingValue"
|
||||
},
|
||||
{
|
||||
"name": "addComponentLogic"
|
||||
},
|
||||
|
@ -389,6 +383,9 @@
|
|||
{
|
||||
"name": "allocStylingContext"
|
||||
},
|
||||
{
|
||||
"name": "allowValueChange"
|
||||
},
|
||||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
|
@ -491,9 +488,6 @@
|
|||
{
|
||||
"name": "createRootContext"
|
||||
},
|
||||
{
|
||||
"name": "createStylingContextTemplate"
|
||||
},
|
||||
{
|
||||
"name": "createTNode"
|
||||
},
|
||||
|
@ -530,9 +524,6 @@
|
|||
{
|
||||
"name": "defineInjectable"
|
||||
},
|
||||
{
|
||||
"name": "delegateToClassInput"
|
||||
},
|
||||
{
|
||||
"name": "destroyLView"
|
||||
},
|
||||
|
@ -554,6 +545,9 @@
|
|||
{
|
||||
"name": "directiveInject"
|
||||
},
|
||||
{
|
||||
"name": "directiveOwnerPointers"
|
||||
},
|
||||
{
|
||||
"name": "domRendererFactory3"
|
||||
},
|
||||
|
@ -614,6 +608,9 @@
|
|||
{
|
||||
"name": "findDirectiveMatches"
|
||||
},
|
||||
{
|
||||
"name": "findOrPatchDirectiveIntoRegistry"
|
||||
},
|
||||
{
|
||||
"name": "findViaComponent"
|
||||
},
|
||||
|
@ -668,6 +665,15 @@
|
|||
{
|
||||
"name": "getDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveIndexFromEntry"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveIndexFromRegistry"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveRegistryValuesIndexOf"
|
||||
},
|
||||
{
|
||||
"name": "getElementDepthCount"
|
||||
},
|
||||
|
@ -686,9 +692,15 @@
|
|||
{
|
||||
"name": "getHostTElementNode"
|
||||
},
|
||||
{
|
||||
"name": "getInitialClassNameValue"
|
||||
},
|
||||
{
|
||||
"name": "getInitialIndex"
|
||||
},
|
||||
{
|
||||
"name": "getInitialStylingValuesIndexOf"
|
||||
},
|
||||
{
|
||||
"name": "getInitialValue"
|
||||
},
|
||||
|
@ -710,6 +722,9 @@
|
|||
{
|
||||
"name": "getLViewChild"
|
||||
},
|
||||
{
|
||||
"name": "getMatchingBindingIndex"
|
||||
},
|
||||
{
|
||||
"name": "getMultiOrSingleIndex"
|
||||
},
|
||||
|
@ -791,6 +806,9 @@
|
|||
{
|
||||
"name": "getRootView"
|
||||
},
|
||||
{
|
||||
"name": "getSinglePropIndexValue"
|
||||
},
|
||||
{
|
||||
"name": "getStyleSanitizer"
|
||||
},
|
||||
|
@ -816,19 +834,7 @@
|
|||
"name": "getValue"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementClassProp"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStyling"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStylingApply"
|
||||
},
|
||||
{
|
||||
"name": "hackSetStaticClasses"
|
||||
},
|
||||
{
|
||||
"name": "hackSquashDeclaration"
|
||||
"name": "hasClassInput"
|
||||
},
|
||||
{
|
||||
"name": "hasParentInjector"
|
||||
|
@ -836,6 +842,9 @@
|
|||
{
|
||||
"name": "hasPlayerBuilderChanged"
|
||||
},
|
||||
{
|
||||
"name": "hasStyling"
|
||||
},
|
||||
{
|
||||
"name": "hasTagAndTypeMatch"
|
||||
},
|
||||
|
@ -851,6 +860,9 @@
|
|||
{
|
||||
"name": "initNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeStaticContext"
|
||||
},
|
||||
{
|
||||
"name": "initializeTNodeInputs"
|
||||
},
|
||||
|
@ -911,6 +923,9 @@
|
|||
{
|
||||
"name": "isDifferent"
|
||||
},
|
||||
{
|
||||
"name": "isDirectiveDirty"
|
||||
},
|
||||
{
|
||||
"name": "isDirty"
|
||||
},
|
||||
|
@ -1083,7 +1098,13 @@
|
|||
"name": "renderEmbeddedTemplate"
|
||||
},
|
||||
{
|
||||
"name": "renderStyleAndClassBindings"
|
||||
"name": "renderInitialStylesAndClasses"
|
||||
},
|
||||
{
|
||||
"name": "renderInitialStylingValues"
|
||||
},
|
||||
{
|
||||
"name": "renderStyling"
|
||||
},
|
||||
{
|
||||
"name": "resetComponentState"
|
||||
|
@ -1127,6 +1148,9 @@
|
|||
{
|
||||
"name": "setCurrentDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "setDirectiveDirty"
|
||||
},
|
||||
{
|
||||
"name": "setDirty"
|
||||
},
|
||||
|
@ -1166,6 +1190,9 @@
|
|||
{
|
||||
"name": "setProp"
|
||||
},
|
||||
{
|
||||
"name": "setSanitizeFlag"
|
||||
},
|
||||
{
|
||||
"name": "setStyle"
|
||||
},
|
||||
|
@ -1215,7 +1242,7 @@
|
|||
"name": "updateClassProp"
|
||||
},
|
||||
{
|
||||
"name": "updateStyleProp"
|
||||
"name": "updateContextWithBindings"
|
||||
},
|
||||
{
|
||||
"name": "updateViewQuery"
|
||||
|
|
|
@ -542,7 +542,6 @@ describe('discovery utils deprecated', () => {
|
|||
});
|
||||
|
||||
it('should return a map of local refs for an element with styling context', () => {
|
||||
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
|
@ -554,7 +553,6 @@ describe('discovery utils deprecated', () => {
|
|||
if (rf & RenderFlags.Create) {
|
||||
// <div #elRef class="fooClass">
|
||||
elementStart(0, 'div', null, ['elRef', '']);
|
||||
elementStyling(['fooClass']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation2, nextContext, reference, template, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {NgIf} from './common_with_def';
|
||||
import {ComponentFixture, createComponent, renderToHtml} from './render_util';
|
||||
|
@ -206,8 +206,8 @@ describe('exports', () => {
|
|||
/** <div [class.red]="myInput.checked"</div> <input type="checkbox" checked #myInput> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
elementStyling([InitialStylingFlags.VALUES_MODE, 'red', true]);
|
||||
elementStart(0, 'div', [AttributeMarker.Classes, 'red']);
|
||||
elementStyling(['red']);
|
||||
elementEnd();
|
||||
element(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
|
||||
}
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ElementRef, EventEmitter} from '@angular/core';
|
||||
import {ElementRef} from '@angular/core';
|
||||
|
||||
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index';
|
||||
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
|
||||
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery, elementHostAttrs} from '../../src/render3/instructions';
|
||||
import {query, queryRefresh} from '../../src/render3/query';
|
||||
import {RenderFlags, InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
|
||||
|
||||
import {ComponentFixture, TemplateFixture, createComponent, createDirective} from './render_util';
|
||||
|
@ -1141,9 +1141,8 @@ describe('host bindings', () => {
|
|||
vars: 0,
|
||||
hostBindings: (rf: RenderFlags, ctx: StaticHostClass, elIndex: number) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStyling(
|
||||
['mat-toolbar', InitialStylingFlags.VALUES_MODE, 'mat-toolbar', true], null, null,
|
||||
ctx);
|
||||
elementHostAttrs(ctx, [AttributeMarker.Classes, 'mat-toolbar']);
|
||||
elementStyling(['mat-toolbar'], null, null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStylingApply(0, ctx);
|
||||
|
@ -1164,6 +1163,5 @@ describe('host bindings', () => {
|
|||
const hostBindingEl = fixture.hostElement.querySelector('static-host-class') as HTMLElement;
|
||||
expect(hostBindingEl.className).toEqual('mat-toolbar');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,6 @@ import {NgForOfContext} from '@angular/common';
|
|||
import {RenderFlags} from '../../src/render3';
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {bind, element, elementAttribute, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap, interpolation1, renderTemplate, template, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {AttributeMarker} from '../../src/render3/interfaces/node';
|
||||
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass';
|
||||
import {defaultStyleSanitizer, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
|
@ -28,10 +27,19 @@ describe('instructions', () => {
|
|||
elementEnd();
|
||||
}
|
||||
|
||||
function createDiv(initialStyles?: (string | number)[], styleSanitizer?: StyleSanitizeFn) {
|
||||
elementStart(0, 'div');
|
||||
elementStyling(
|
||||
[], initialStyles && Array.isArray(initialStyles) ? initialStyles : null, styleSanitizer);
|
||||
function createDiv(
|
||||
initialClasses?: string[] | null, classBindingNames?: string[] | null,
|
||||
initialStyles?: string[] | null, styleBindingNames?: string[] | null,
|
||||
styleSanitizer?: StyleSanitizeFn) {
|
||||
const attrs: any[] = [];
|
||||
if (initialClasses) {
|
||||
attrs.push(AttributeMarker.Classes, ...initialClasses);
|
||||
}
|
||||
if (initialStyles) {
|
||||
attrs.push(AttributeMarker.Styles, ...initialStyles);
|
||||
}
|
||||
elementStart(0, 'div', attrs);
|
||||
elementStyling(classBindingNames || null, styleBindingNames || null, styleSanitizer);
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
|
@ -191,8 +199,9 @@ describe('instructions', () => {
|
|||
|
||||
describe('elementStyleProp', () => {
|
||||
it('should automatically sanitize unless a bypass operation is applied', () => {
|
||||
const t = new TemplateFixture(
|
||||
() => { return createDiv(['background-image'], defaultStyleSanitizer); }, () => {}, 1);
|
||||
const t = new TemplateFixture(() => {
|
||||
return createDiv(null, null, null, ['background-image'], defaultStyleSanitizer);
|
||||
}, () => {}, 1);
|
||||
t.update(() => {
|
||||
elementStyleProp(0, 0, 'url("http://server")');
|
||||
elementStylingApply(0);
|
||||
|
@ -211,8 +220,9 @@ describe('instructions', () => {
|
|||
it('should not re-apply the style value even if it is a newly bypassed again', () => {
|
||||
const sanitizerInterceptor = new MockSanitizerInterceptor();
|
||||
const t = createTemplateFixtureWithSanitizer(
|
||||
() => createDiv(['background-image'], sanitizerInterceptor.getStyleSanitizer()), 1,
|
||||
sanitizerInterceptor);
|
||||
() => createDiv(
|
||||
null, null, null, ['background-image'], sanitizerInterceptor.getStyleSanitizer()),
|
||||
1, sanitizerInterceptor);
|
||||
|
||||
t.update(() => {
|
||||
elementStyleProp(0, 0, bypassSanitizationTrustStyle('apple'));
|
||||
|
@ -232,8 +242,8 @@ describe('instructions', () => {
|
|||
|
||||
describe('elementStyleMap', () => {
|
||||
function createDivWithStyle() {
|
||||
elementStart(0, 'div');
|
||||
elementStyling([], ['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
|
||||
elementStart(0, 'div', [AttributeMarker.Styles, 'height', '10px']);
|
||||
elementStyling([], ['height']);
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
|
@ -251,7 +261,8 @@ describe('instructions', () => {
|
|||
const sanitizerInterceptor =
|
||||
new MockSanitizerInterceptor(value => { detectedValues.push(value); });
|
||||
const fixture = createTemplateFixtureWithSanitizer(
|
||||
() => createDiv([], sanitizerInterceptor.getStyleSanitizer()), 1, sanitizerInterceptor);
|
||||
() => createDiv(null, null, null, null, sanitizerInterceptor.getStyleSanitizer()), 1,
|
||||
sanitizerInterceptor);
|
||||
|
||||
fixture.update(() => {
|
||||
elementStylingMap(0, null, {
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
*/
|
||||
|
||||
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {RendererType2} from '../../src/render/api';
|
||||
import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index';
|
||||
|
||||
import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, elementStart, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, element, elementStyling, elementStylingApply, elementStyleProp, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject, elementHostAttrs} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
import {NO_CHANGE} from '../../src/render3/tokens';
|
||||
import {HEADER_OFFSET, CONTEXT} from '../../src/render3/interfaces/view';
|
||||
import {enableBindings, disableBindings} from '../../src/render3/state';
|
||||
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
|
@ -1412,7 +1412,6 @@ describe('render3 integration test', () => {
|
|||
});
|
||||
|
||||
describe('elementStyle', () => {
|
||||
|
||||
it('should support binding to styles', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
|
@ -1472,8 +1471,7 @@ describe('render3 integration test', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('elementClass', () => {
|
||||
|
||||
describe('class-based styling', () => {
|
||||
it('should support CSS class toggle', () => {
|
||||
/** <span [class.active]="class"></span> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
|
@ -1519,9 +1517,8 @@ describe('render3 integration test', () => {
|
|||
it('should work correctly with existing static classes', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
elementStyling(
|
||||
['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
|
||||
elementStart(0, 'span', [AttributeMarker.Classes, 'existing']);
|
||||
elementStyling(['existing', 'active']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
|
@ -1553,7 +1550,7 @@ describe('render3 integration test', () => {
|
|||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'my-comp');
|
||||
{ elementStyling(['active']); }
|
||||
elementStyling(['active']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
|
@ -1572,7 +1569,6 @@ describe('render3 integration test', () => {
|
|||
expect(fixture.html).toEqual('<my-comp class="">Comp Content</my-comp>');
|
||||
});
|
||||
|
||||
|
||||
it('should apply classes properly when nodes have LContainers', () => {
|
||||
let structuralComp !: StructuralComp;
|
||||
|
||||
|
@ -1656,17 +1652,17 @@ describe('render3 integration test', () => {
|
|||
set klass(value: string) { this.classesVal = value; }
|
||||
}
|
||||
|
||||
it('should delegate all initial classes to a [class] input binding if present on a directive on the same element',
|
||||
it('should delegate initial classes to a [class] input binding if present on a directive on the same element',
|
||||
() => {
|
||||
/**
|
||||
* <my-comp class="apple orange banana" DirWithClass></my-comp>
|
||||
* <div class="apple orange banana" DirWithClass></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['DirWithClass']);
|
||||
elementStyling([
|
||||
InitialStylingFlags.VALUES_MODE, 'apple', true, 'orange', true, 'banana', true
|
||||
]);
|
||||
elementStart(
|
||||
0, 'div',
|
||||
['DirWithClass', AttributeMarker.Classes, 'apple', 'orange', 'banana']);
|
||||
elementStyling();
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
|
@ -1681,7 +1677,7 @@ describe('render3 integration test', () => {
|
|||
it('should update `[class]` and bindings in the provided directive if the input is matched',
|
||||
() => {
|
||||
/**
|
||||
* <my-comp DirWithClass></my-comp>
|
||||
* <div DirWithClass></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
|
@ -1698,6 +1694,223 @@ describe('render3 integration test', () => {
|
|||
const fixture = new ComponentFixture(App);
|
||||
expect(mockClassDirective !.classesVal).toEqual('cucumber grape');
|
||||
});
|
||||
|
||||
it('should apply initial styling to the element that contains the directive with host styling',
|
||||
() => {
|
||||
class DirWithInitialStyling {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirWithInitialStyling,
|
||||
selectors: [['', 'DirWithInitialStyling', '']],
|
||||
factory: () => new DirWithInitialStyling(),
|
||||
hostBindings: function(
|
||||
rf: RenderFlags, ctx: DirWithInitialStyling, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementHostAttrs(ctx, [
|
||||
AttributeMarker.Classes, 'heavy', 'golden', AttributeMarker.Styles, 'color',
|
||||
'purple', 'font-weight', 'bold'
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public classesVal: string = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* <div DirWithInitialStyling
|
||||
* class="big"
|
||||
* style="color:black; * font-size:200px"></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', [
|
||||
'DirWithInitialStyling', '', AttributeMarker.Classes, 'big',
|
||||
AttributeMarker.Styles, 'color', 'black', 'font-size', '200px'
|
||||
]);
|
||||
}
|
||||
}, 1, 0, [DirWithInitialStyling]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
const target = fixture.hostElement.querySelector('div') !;
|
||||
const classes = target.getAttribute('class') !.split(/\s+/).sort();
|
||||
expect(classes).toEqual(['big', 'golden', 'heavy']);
|
||||
|
||||
expect(target.style.getPropertyValue('color')).toEqual('black');
|
||||
expect(target.style.getPropertyValue('font-size')).toEqual('200px');
|
||||
expect(target.style.getPropertyValue('font-weight')).toEqual('bold');
|
||||
});
|
||||
|
||||
it('should apply single styling bindings present within a directive onto the same element and defer the element\'s initial styling values when missing',
|
||||
() => {
|
||||
let dirInstance: DirWithSingleStylingBindings;
|
||||
/**
|
||||
* <DirWithInitialStyling class="def" [class.xyz] style="width:555px;" [style.width]
|
||||
* [style.height]></my-comp>
|
||||
*/
|
||||
class DirWithSingleStylingBindings {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirWithSingleStylingBindings,
|
||||
selectors: [['', 'DirWithSingleStylingBindings', '']],
|
||||
factory: () => dirInstance = new DirWithSingleStylingBindings(),
|
||||
hostBindings: function(
|
||||
rf: RenderFlags, ctx: DirWithSingleStylingBindings, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementHostAttrs(
|
||||
ctx,
|
||||
[AttributeMarker.Classes, 'def', AttributeMarker.Styles, 'width', '555px']);
|
||||
elementStyling(['xyz'], ['width', 'height'], null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(elementIndex, 0, ctx.width, null, ctx);
|
||||
elementStyleProp(elementIndex, 1, ctx.height, null, ctx);
|
||||
elementClassProp(elementIndex, 0, ctx.activateXYZClass, ctx);
|
||||
elementStylingApply(elementIndex, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
width: null|string = null;
|
||||
height: null|string = null;
|
||||
activateXYZClass: boolean = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <div DirWithInitialStyling class="abc" style="width:100px;
|
||||
* height:200px"></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', [
|
||||
'DirWithSingleStylingBindings', '', AttributeMarker.Classes, 'abc',
|
||||
AttributeMarker.Styles, 'width', '100px', 'height', '200px'
|
||||
]);
|
||||
}
|
||||
}, 1, 0, [DirWithSingleStylingBindings]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
const target = fixture.hostElement.querySelector('div') !;
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('200px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeFalsy();
|
||||
|
||||
dirInstance !.width = '444px';
|
||||
dirInstance !.height = '999px';
|
||||
dirInstance !.activateXYZClass = true;
|
||||
fixture.update();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('444px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('999px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeTruthy();
|
||||
|
||||
dirInstance !.width = null;
|
||||
dirInstance !.height = null;
|
||||
fixture.update();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('200px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should properly prioritize style binding collision when they exist on multiple directives',
|
||||
() => {
|
||||
let dir1Instance: Dir1WithStyle;
|
||||
/**
|
||||
* Directive with host props:
|
||||
* [style.width]
|
||||
*/
|
||||
class Dir1WithStyle {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: Dir1WithStyle,
|
||||
selectors: [['', 'Dir1WithStyle', '']],
|
||||
factory: () => dir1Instance = new Dir1WithStyle(),
|
||||
hostBindings: function(rf: RenderFlags, ctx: Dir1WithStyle, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStyling(null, ['width'], null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(elementIndex, 0, ctx.width, null, ctx);
|
||||
elementStylingApply(elementIndex, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
width: null|string = null;
|
||||
}
|
||||
|
||||
let dir2Instance: Dir2WithStyle;
|
||||
/**
|
||||
* Directive with host props:
|
||||
* [style.width]
|
||||
* style="width:111px"
|
||||
*/
|
||||
class Dir2WithStyle {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: Dir2WithStyle,
|
||||
selectors: [['', 'Dir2WithStyle', '']],
|
||||
factory: () => dir2Instance = new Dir2WithStyle(),
|
||||
hostBindings: function(rf: RenderFlags, ctx: Dir2WithStyle, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementHostAttrs(ctx, [AttributeMarker.Styles, 'width', '111px']);
|
||||
elementStyling(null, ['width'], null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(elementIndex, 0, ctx.width, null, ctx);
|
||||
elementStylingApply(elementIndex, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
width: null|string = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <div Dir1WithStyle Dir2WithStyle [style.width]></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['Dir1WithStyle', '', 'Dir2WithStyle', '']);
|
||||
elementStyling(null, ['width']);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(0, 0, ctx.width);
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}, 1, 0, [Dir1WithStyle, Dir2WithStyle]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
const target = fixture.hostElement.querySelector('div') !;
|
||||
expect(target.style.getPropertyValue('width')).toEqual('111px');
|
||||
|
||||
fixture.component.width = '999px';
|
||||
dir1Instance !.width = '222px';
|
||||
dir2Instance !.width = '333px';
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('999px');
|
||||
|
||||
fixture.component.width = null;
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('222px');
|
||||
|
||||
dir1Instance !.width = null;
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('333px');
|
||||
|
||||
dir2Instance !.width = null;
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('111px');
|
||||
|
||||
dir1Instance !.width = '666px';
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('666px');
|
||||
|
||||
fixture.component.width = '777px';
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('777px');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2655,4 +2868,4 @@ class ProxyRenderer3Factory implements RendererFactory3 {
|
|||
this.lastCapturedType = rendererType;
|
||||
return domRendererFactory3.createRenderer(hostElement, rendererType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,7 @@ import {PlayState, Player} from '../../../src/render3/interfaces/player';
|
|||
export class MockPlayer implements Player {
|
||||
parent: Player|null = null;
|
||||
|
||||
data: any;
|
||||
log: string[] = [];
|
||||
state: PlayState = PlayState.Pending;
|
||||
private _listeners: {[state: string]: (() => any)[]} = {};
|
||||
|
|
Loading…
Reference in New Issue