refactor(ivy): remove styling state storage and introduce direct style writing (#32591)
This patch is a final major refactor in styling Angular. This PR includes three main fixes: All temporary state taht is persisted between template style/class application and style/class application in host bindings is now removed. Removes the styling() and stylingApply() instructions. Introduces a "direct apply" mode that is used apply prop-based style/class in the event that there are no map-based bindings as well as property collisions. PR Close #32259 PR Close #32591
This commit is contained in:
parent
e6ed4a21e4
commit
4f41473048
|
@ -16,8 +16,8 @@
|
|||
"uncompressed": {
|
||||
"runtime-es5": 3042,
|
||||
"runtime-es2015": 3048,
|
||||
"main-es5": 493330,
|
||||
"main-es2015": 435296,
|
||||
"main-es5": 492593,
|
||||
"main-es2015": 438296,
|
||||
"polyfills-es5": 131024,
|
||||
"polyfills-es2015": 52433
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
|||
"uncompressed": {
|
||||
"runtime-es5": 2932,
|
||||
"runtime-es2015": 2938,
|
||||
"main-es5": 550854,
|
||||
"main-es5": 552068,
|
||||
"main-es2015": 493320,
|
||||
"polyfills-es5": 131024,
|
||||
"polyfills-es2015": 52433
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1440,
|
||||
"main": 13264,
|
||||
"main": 13604,
|
||||
"polyfills": 45340
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export class TableComponent {
|
|||
@NgModule({imports: [BrowserModule], bootstrap: [TableComponent], declarations: [TableComponent]})
|
||||
export class AppModule {
|
||||
constructor(sanitizer: DomSanitizer) {
|
||||
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
|
||||
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('white');
|
||||
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ɵRenderFlags, ɵrenderComponent as renderComponent, ɵɵadvance, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineComponent, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply, ɵɵtext, ɵɵtextInterpolate1} from '@angular/core';
|
||||
import {ɵRenderFlags, ɵrenderComponent as renderComponent, ɵɵadvance, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineComponent, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵstyleProp, ɵɵtext, ɵɵtextInterpolate1} from '@angular/core';
|
||||
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {createDom, destroyDom, detectChanges} from '../render3/tree';
|
||||
|
@ -39,7 +39,6 @@ export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
|
|||
ɵɵelementStart(0, 'tree');
|
||||
{
|
||||
ɵɵelementStart(1, 'span');
|
||||
ɵɵstyling();
|
||||
{ ɵɵtext(2); }
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(3);
|
||||
|
@ -50,7 +49,6 @@ export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
|
|||
if (rf & ɵRenderFlags.Update) {
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', ctx.depth % 2 ? '' : 'grey');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵtextInterpolate1(' ', ctx.value, ' ');
|
||||
ɵɵcontainerRefreshStart(3);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 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 {Directive, DoCheck, Input, ɵRenderFlags, ɵɵallocHostVars, ɵɵclassMap, ɵɵdefineDirective, ɵɵstyling, ɵɵstylingApply} from '@angular/core';
|
||||
import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵallocHostVars, ɵɵclassMap, ɵɵdefineDirective} from '@angular/core';
|
||||
|
||||
import {NgClassImpl, NgClassImplProvider} from './ng_class_impl';
|
||||
|
||||
|
@ -35,11 +35,9 @@ export const ngClassDirectiveDef__POST_R3__ = ɵɵdefineDirective({
|
|||
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
|
||||
if (rf & ɵRenderFlags.Create) {
|
||||
ɵɵallocHostVars(1);
|
||||
ɵɵstyling();
|
||||
}
|
||||
if (rf & ɵRenderFlags.Update) {
|
||||
ɵɵclassMap(ctx.getValue());
|
||||
ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 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 {Directive, DoCheck, Input, ɵRenderFlags, ɵɵdefineDirective, ɵɵstyleMap, ɵɵstyling, ɵɵstylingApply} from '@angular/core';
|
||||
import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵallocHostVars, ɵɵdefineDirective, ɵɵstyleMap} from '@angular/core';
|
||||
|
||||
import {NgStyleImpl, NgStyleImplProvider} from './ng_style_impl';
|
||||
|
||||
|
@ -35,11 +35,10 @@ export const ngStyleDirectiveDef__POST_R3__ = ɵɵdefineDirective({
|
|||
selectors: null as any,
|
||||
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
|
||||
if (rf & ɵRenderFlags.Create) {
|
||||
ɵɵstyling();
|
||||
ɵɵallocHostVars(1);
|
||||
}
|
||||
if (rf & ɵRenderFlags.Update) {
|
||||
ɵɵstyleMap(ctx.getValue());
|
||||
ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -483,14 +483,11 @@ describe('compiler compliance', () => {
|
|||
vars: 2,
|
||||
template: function MyComponent_Template(rf,ctx){
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("background-color", ctx.color);
|
||||
$r3$.ɵɵclassProp("error", ctx.error);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
|
|
|
@ -376,14 +376,11 @@ describe('compiler compliance: styling', () => {
|
|||
const template = `
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -442,13 +439,10 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 2,
|
||||
template: function MyComponentWithInterpolation_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMapInterpolate1("foo foo-", $ctx$.fooId, "");
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
…
|
||||
|
@ -456,13 +450,10 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 3,
|
||||
template: function MyComponentWithMuchosInterpolation_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMapInterpolate2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, "");
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
…
|
||||
|
@ -470,13 +461,10 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 1,
|
||||
template: function MyComponentWithoutInterpolation_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMap($ctx$.exp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -522,16 +510,13 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 4,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", $_c0$);
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div", $_c0$);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
|
||||
$r3$.ɵɵstyleProp("width", $ctx$.myWidth);
|
||||
$r3$.ɵɵstyleProp("height", $ctx$.myHeight);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle);
|
||||
}
|
||||
},
|
||||
|
@ -572,14 +557,11 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 1,
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleProp("background-image", ctx.myImage);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
|
@ -612,13 +594,10 @@ describe('compiler compliance: styling', () => {
|
|||
const template = `
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("font-size", 12, "px");
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -676,13 +655,10 @@ describe('compiler compliance: styling', () => {
|
|||
const template = `
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMap($ctx$.myClassExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -728,15 +704,12 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 4,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div", $e0_attrs$);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMap($ctx$.myClassExp);
|
||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple);
|
||||
$r3$.ɵɵclassProp("orange", $ctx$.yesToOrange);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵattribute("class", "banana");
|
||||
}
|
||||
},
|
||||
|
@ -844,15 +817,12 @@ describe('compiler compliance: styling', () => {
|
|||
const template = `
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
|
||||
$r3$.ɵɵclassMap($ctx$.myClassExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -887,7 +857,6 @@ describe('compiler compliance: styling', () => {
|
|||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵpipe(1, "stylePipe");
|
||||
$r3$.ɵɵpipe(2, "classPipe");
|
||||
$r3$.ɵɵelementEnd();
|
||||
|
@ -896,7 +865,6 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 2, $ctx$.myStyleExp));
|
||||
$r3$.ɵɵclassMap($r3$.ɵɵpipeBind1(2, 4, $ctx$.myClassExp));
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -939,7 +907,6 @@ describe('compiler compliance: styling', () => {
|
|||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵpipe(1, "pipe");
|
||||
$r3$.ɵɵpipe(2, "pipe");
|
||||
$r3$.ɵɵpipe(3, "pipe");
|
||||
|
@ -954,9 +921,8 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 9, $ctx$.barExp, 3000));
|
||||
$r3$.ɵɵstyleProp("baz", $r3$.ɵɵpipeBind2(3, 12, $ctx$.bazExp, 4000));
|
||||
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 15, $ctx$.fooExp, 2000));
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(5);
|
||||
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
|
||||
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -999,16 +965,12 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("width", $ctx$.w1);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstyleProp("height", $ctx$.h1);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassProp("active", $ctx$.a1);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassProp("removed", $ctx$.r1);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1058,7 +1020,6 @@ describe('compiler compliance: styling', () => {
|
|||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(4);
|
||||
$r3$.ɵɵelementHostAttrs($e0_attrs$);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
|
@ -1066,7 +1027,6 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵclassMap(ctx.myClass);
|
||||
$r3$.ɵɵstyleProp("color", ctx.myColorProp);
|
||||
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
},
|
||||
consts: 0,
|
||||
|
@ -1118,7 +1078,6 @@ describe('compiler compliance: styling', () => {
|
|||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(6);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
|
@ -1128,7 +1087,6 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵstyleProp("width", ctx.myWidthProp);
|
||||
$r3$.ɵɵclassProp("bar", ctx.myBarClass);
|
||||
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
},
|
||||
consts: 0,
|
||||
|
@ -1179,9 +1137,7 @@ describe('compiler compliance: styling', () => {
|
|||
const template = `
|
||||
function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵstyling();
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelement(0, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
|
@ -1189,7 +1145,6 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵclassMap(ctx.myClassExp);
|
||||
$r3$.ɵɵstyleProp("height", ctx.myHeightExp);
|
||||
$r3$.ɵɵclassProp("bar", ctx.myBarClassExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
},
|
||||
`;
|
||||
|
@ -1198,7 +1153,6 @@ describe('compiler compliance: styling', () => {
|
|||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(4);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
|
@ -1206,7 +1160,6 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵclassMap(ctx.myClassExp);
|
||||
$r3$.ɵɵstyleProp("width", ctx.myWidthExp);
|
||||
$r3$.ɵɵclassProp("foo", ctx.myFooClassExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
},
|
||||
`;
|
||||
|
@ -1266,35 +1219,29 @@ describe('compiler compliance: styling', () => {
|
|||
function ClassDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(1);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMap(ctx.myClassMap);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
…
|
||||
function WidthDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(2);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("width", ctx.myWidth);
|
||||
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
…
|
||||
function HeightDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(2);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("height", ctx.myHeight);
|
||||
$r3$.ɵɵclassProp("bar", ctx.myBarClass);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
…
|
||||
|
@ -1336,34 +1283,24 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMapInterpolateV(["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate8("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate7("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate6("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate5("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate4("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate3("a", ctx.one, "b", ctx.two, "c", ctx.three, "d");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate2("a", ctx.one, "b", ctx.two, "c");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMapInterpolate1("a", ctx.one, "b");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵclassMap(ctx.one);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
`;
|
||||
|
@ -1438,34 +1375,24 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolateV("color", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]);
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate8("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate7("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate6("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate5("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate4("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate3("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate2("color", "a", ctx.one, "b", ctx.two, "c");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b");
|
||||
$r3$.ɵɵstylingApply();
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵstyleProp("color", ctx.one);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
`;
|
||||
|
@ -1496,7 +1423,6 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c", "px");
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
`;
|
||||
|
@ -1527,7 +1453,6 @@ describe('compiler compliance: styling', () => {
|
|||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c");
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
`;
|
||||
|
@ -1583,14 +1508,12 @@ describe('compiler compliance: styling', () => {
|
|||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(4);
|
||||
$r3$.ɵɵelementHostAttrs($_c0$);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap(ctx.myStyle);
|
||||
$r3$.ɵɵclassMap(ctx.myClass);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1627,13 +1550,11 @@ describe('compiler compliance: styling', () => {
|
|||
hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(4);
|
||||
$r3$.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
|
||||
$r3$.ɵɵstyleProp("width", ctx.myWidth);
|
||||
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1669,7 +1590,6 @@ describe('compiler compliance: styling', () => {
|
|||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleProp("background-image", ctx.bgExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
}
|
||||
|
@ -1705,7 +1625,6 @@ describe('compiler compliance: styling', () => {
|
|||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap(ctx.mapExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
}
|
||||
|
@ -1742,7 +1661,6 @@ describe('compiler compliance: styling', () => {
|
|||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMap(ctx.mapExp);
|
||||
$r3$.ɵɵclassProp("name", ctx.nameExp);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
…
|
||||
}
|
||||
|
@ -1805,7 +1723,6 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵpureFunction2(6, _c1, ctx._animValue,
|
||||
$r3$.ɵɵpureFunction2(3, _c0, ctx._animParam1, ctx._animParam2)));
|
||||
$r3$.ɵɵclassProp("foo", ctx.foo);
|
||||
$r3$.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1867,13 +1867,11 @@ runInEachFileSystem(os => {
|
|||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); });
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody);
|
||||
i0.ɵɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
|
||||
i0.ɵɵstyling();
|
||||
}
|
||||
if (rf & 2) {
|
||||
i0.ɵɵhostProperty("prop", ctx.bar);
|
||||
i0.ɵɵattribute("hello", ctx.foo);
|
||||
i0.ɵɵclassProp("someclass", ctx.someClass);
|
||||
i0.ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -69,8 +69,6 @@ export class Identifiers {
|
|||
|
||||
static elementContainer: o.ExternalReference = {name: 'ɵɵelementContainer', moduleName: CORE};
|
||||
|
||||
static styling: o.ExternalReference = {name: 'ɵɵstyling', moduleName: CORE};
|
||||
|
||||
static styleMap: o.ExternalReference = {name: 'ɵɵstyleMap', moduleName: CORE};
|
||||
|
||||
static classMap: o.ExternalReference = {name: 'ɵɵclassMap', moduleName: CORE};
|
||||
|
@ -115,8 +113,6 @@ export class Identifiers {
|
|||
static stylePropInterpolateV:
|
||||
o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE};
|
||||
|
||||
static stylingApply: o.ExternalReference = {name: 'ɵɵstylingApply', moduleName: CORE};
|
||||
|
||||
static styleSanitizer: o.ExternalReference = {name: 'ɵɵstyleSanitizer', moduleName: CORE};
|
||||
|
||||
static elementHostAttrs: o.ExternalReference = {name: 'ɵɵelementHostAttrs', moduleName: CORE};
|
||||
|
|
|
@ -713,16 +713,6 @@ function createHostBindingsFunction(
|
|||
}
|
||||
|
||||
if (styleBuilder.hasBindings) {
|
||||
// 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 `styling`.
|
||||
const stylingInstruction = styleBuilder.buildStylingInstruction(null, constantPool);
|
||||
if (stylingInstruction) {
|
||||
createStatements.push(createStylingStmt(stylingInstruction, 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.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import {AttributeMarker} from '../../core';
|
||||
import {AST, BindingType, Interpolation} from '../../expression_parser/ast';
|
||||
import {AST, ASTWithSource, BindingPipe, BindingType, Interpolation} from '../../expression_parser/ast';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import {isEmptyExpression} from '../../template_parser/template_parser';
|
||||
|
@ -68,7 +68,6 @@ interface BoundStylingEntry {
|
|||
* classMap(...)
|
||||
* styleProp(...)
|
||||
* classProp(...)
|
||||
* stylingApply(...)
|
||||
* }
|
||||
*
|
||||
* The creation/update methods within the builder class produce these instructions.
|
||||
|
@ -81,6 +80,7 @@ export class StylingBuilder {
|
|||
* (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`)
|
||||
*/
|
||||
public hasBindings = false;
|
||||
public hasBindingsWithPipes = false;
|
||||
|
||||
/** the input for [class] (if it exists) */
|
||||
private _classMapInput: BoundStylingEntry|null = null;
|
||||
|
@ -187,6 +187,7 @@ export class StylingBuilder {
|
|||
}
|
||||
this._lastStylingInput = entry;
|
||||
this._firstStylingInput = this._firstStylingInput || entry;
|
||||
this._checkForPipes(value);
|
||||
this.hasBindings = true;
|
||||
return entry;
|
||||
}
|
||||
|
@ -207,10 +208,17 @@ export class StylingBuilder {
|
|||
}
|
||||
this._lastStylingInput = entry;
|
||||
this._firstStylingInput = this._firstStylingInput || entry;
|
||||
this._checkForPipes(value);
|
||||
this.hasBindings = true;
|
||||
return entry;
|
||||
}
|
||||
|
||||
private _checkForPipes(value: AST) {
|
||||
if ((value instanceof ASTWithSource) && (value.ast instanceof BindingPipe)) {
|
||||
this.hasBindingsWithPipes = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the element's static style string value to the builder.
|
||||
*
|
||||
|
@ -284,25 +292,6 @@ export class StylingBuilder {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `styling`.
|
||||
*
|
||||
* The instruction generation code below is used for producing the AOT statement code which is
|
||||
* responsible for registering style/class bindings to an element.
|
||||
*/
|
||||
buildStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
if (this.hasBindings) {
|
||||
return {
|
||||
sourceSpan,
|
||||
allocateBindingSlots: 0,
|
||||
reference: R3.styling,
|
||||
params: () => [],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `classMap`.
|
||||
*
|
||||
|
@ -422,15 +411,6 @@ export class StylingBuilder {
|
|||
return [];
|
||||
}
|
||||
|
||||
private _buildApplyFn(): StylingInstruction {
|
||||
return {
|
||||
sourceSpan: this._lastStylingInput ? this._lastStylingInput.sourceSpan : null,
|
||||
reference: R3.stylingApply,
|
||||
allocateBindingSlots: 0,
|
||||
params: () => { return []; }
|
||||
};
|
||||
}
|
||||
|
||||
private _buildSanitizerFn(): StylingInstruction {
|
||||
return {
|
||||
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
|
||||
|
@ -460,7 +440,6 @@ export class StylingBuilder {
|
|||
}
|
||||
instructions.push(...this._buildStyleInputs(valueConverter));
|
||||
instructions.push(...this._buildClassInputs(valueConverter));
|
||||
instructions.push(this._buildApplyFn());
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
|
|
@ -623,11 +623,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
const hasChildren = (!isI18nRootElement && this.i18n) ? !hasTextChildrenOnly(element.children) :
|
||||
element.children.length > 0;
|
||||
|
||||
const createSelfClosingInstruction = !stylingBuilder.hasBindings &&
|
||||
const createSelfClosingInstruction = !stylingBuilder.hasBindingsWithPipes &&
|
||||
element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren;
|
||||
|
||||
const createSelfClosingI18nInstruction = !createSelfClosingInstruction &&
|
||||
!stylingBuilder.hasBindings && hasTextChildrenOnly(element.children);
|
||||
const createSelfClosingI18nInstruction =
|
||||
!createSelfClosingInstruction && hasTextChildrenOnly(element.children);
|
||||
|
||||
if (createSelfClosingInstruction) {
|
||||
this.creationInstruction(
|
||||
|
@ -681,16 +680,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
}
|
||||
}
|
||||
|
||||
// The style bindings code is placed into two distinct blocks within the template function AOT
|
||||
// code: creation and update. The creation code contains the `styling` instructions
|
||||
// which will apply the collected binding values to the element. `styling` is
|
||||
// designed to run inside of `elementStart` and `elementEnd`. The update instructions
|
||||
// (things like `styleProp`, `classProp`, etc..) are applied later on in this
|
||||
// file
|
||||
this.processStylingInstruction(
|
||||
elementIndex,
|
||||
stylingBuilder.buildStylingInstruction(element.sourceSpan, this.constantPool), true);
|
||||
|
||||
// Generate Listeners (outputs)
|
||||
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
||||
this.creationInstruction(
|
||||
|
|
|
@ -120,7 +120,6 @@ export {
|
|||
ɵɵelementContainerStart,
|
||||
ɵɵelementContainerEnd,
|
||||
ɵɵelementContainer,
|
||||
ɵɵstyling,
|
||||
ɵɵstyleMap,
|
||||
ɵɵstyleSanitizer,
|
||||
ɵɵclassMap,
|
||||
|
@ -143,7 +142,6 @@ export {
|
|||
ɵɵstylePropInterpolate7,
|
||||
ɵɵstylePropInterpolate8,
|
||||
ɵɵstylePropInterpolateV,
|
||||
ɵɵstylingApply,
|
||||
ɵɵclassProp,
|
||||
ɵɵelementHostAttrs,
|
||||
|
||||
|
|
|
@ -12,10 +12,9 @@ import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../render3/interfaces
|
|||
import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node';
|
||||
import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks';
|
||||
import {LView, PARENT, TData, TVIEW, T_HOST} from '../render3/interfaces/view';
|
||||
import {TStylingContext} from '../render3/styling_next/interfaces';
|
||||
import {stylingMapToStringMap} from '../render3/styling_next/map_based_bindings';
|
||||
import {StylingMapArray, TStylingContext} from '../render3/styling_next/interfaces';
|
||||
import {NodeStylingDebug} from '../render3/styling_next/styling_debug';
|
||||
import {isStylingContext} from '../render3/styling_next/util';
|
||||
import {isStylingContext, stylingMapToStringMap} from '../render3/styling_next/util';
|
||||
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext} from '../render3/util/discovery_utils';
|
||||
import {INTERPOLATION_DELIMITER, renderStringify} from '../render3/util/misc_utils';
|
||||
import {findComponentView} from '../render3/util/view_traversal_utils';
|
||||
|
@ -431,11 +430,11 @@ function _getStylingDebugInfo(element: any, isClassBased: boolean) {
|
|||
if (isClassBased) {
|
||||
return isStylingContext(tNode.classes) ?
|
||||
new NodeStylingDebug(tNode.classes as TStylingContext, lView, true).values :
|
||||
stylingMapToStringMap(tNode.classes);
|
||||
stylingMapToStringMap(tNode.classes as StylingMapArray | null);
|
||||
} else {
|
||||
return isStylingContext(tNode.styles) ?
|
||||
new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values :
|
||||
stylingMapToStringMap(tNode.styles);
|
||||
stylingMapToStringMap(tNode.styles as StylingMapArray | null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import {TElementNode, TNode, TNodeType} from './interfaces/node';
|
|||
import {PlayerHandler} from './interfaces/player';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
|
||||
import {getPreviousOrParentTNode, resetComponentState, selectView, setActiveHostElement} from './state';
|
||||
import {getPreviousOrParentTNode, incrementActiveDirectiveId, resetComponentState, selectView, setActiveHostElement} from './state';
|
||||
import {publishDefaultGlobalUtils} from './util/global_utils';
|
||||
import {defaultScheduler, stringifyForError} from './util/misc_utils';
|
||||
import {getRootContext} from './util/view_traversal_utils';
|
||||
|
@ -217,6 +217,7 @@ export function createRootComponent<T>(
|
|||
if (tView.firstTemplatePass && componentDef.hostBindings) {
|
||||
const elementIndex = rootTNode.index - HEADER_OFFSET;
|
||||
setActiveHostElement(elementIndex);
|
||||
incrementActiveDirectiveId();
|
||||
|
||||
const expando = tView.expandoInstructions !;
|
||||
invokeHostBindingsInCreationMode(
|
||||
|
|
|
@ -11,7 +11,6 @@ import {fillProperties} from '../../util/property';
|
|||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||
import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {isComponentDef} from '../interfaces/type_checks';
|
||||
import {adjustActiveDirectiveSuperClassDepthPosition} from '../state';
|
||||
|
||||
import {ɵɵNgOnChangesFeature} from './ng_onchanges_feature';
|
||||
|
||||
|
@ -177,24 +176,8 @@ function inheritHostBindings(
|
|||
// to ensure we don't inherit it twice.
|
||||
if (superHostBindings !== prevHostBindings) {
|
||||
if (prevHostBindings) {
|
||||
// because inheritance is unknown during compile time, the runtime code
|
||||
// needs to be informed of the super-class depth so that instruction code
|
||||
// can distinguish one host bindings function from another. The reason why
|
||||
// relying on the directive uniqueId exclusively is not enough is because the
|
||||
// uniqueId value and the directive instance stay the same between hostBindings
|
||||
// calls throughout the directive inheritance chain. This means that without
|
||||
// a super-class depth value, there is no way to know whether a parent or
|
||||
// sub-class host bindings function is currently being executed.
|
||||
definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => {
|
||||
// The reason why we increment first and then decrement is so that parent
|
||||
// hostBindings calls have a higher id value compared to sub-class hostBindings
|
||||
// calls (this way the leaf directive is always at a super-class depth of 0).
|
||||
adjustActiveDirectiveSuperClassDepthPosition(1);
|
||||
try {
|
||||
superHostBindings(rf, ctx, elementIndex);
|
||||
} finally {
|
||||
adjustActiveDirectiveSuperClassDepthPosition(-1);
|
||||
}
|
||||
superHostBindings(rf, ctx, elementIndex);
|
||||
prevHostBindings(rf, ctx, elementIndex);
|
||||
};
|
||||
} else {
|
||||
|
|
|
@ -114,8 +114,6 @@ export {
|
|||
ɵɵstylePropInterpolateV,
|
||||
|
||||
ɵɵstyleSanitizer,
|
||||
ɵɵstyling,
|
||||
ɵɵstylingApply,
|
||||
ɵɵtemplate,
|
||||
|
||||
ɵɵtext,
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
import {assertDataInRange, assertGreaterThan} from '../../util/assert';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
|
||||
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TVIEW} from '../interfaces/view';
|
||||
import {getCheckNoChangesMode, getLView, getSelectedIndex, setSelectedIndex} from '../state';
|
||||
import {ActiveElementFlags, executeElementExitFn, getCheckNoChangesMode, getLView, getSelectedIndex, hasActiveElementFlag, setSelectedIndex} from '../state';
|
||||
import {resetStylingState} from '../styling_next/state';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Advances to an element for later binding instructions.
|
||||
|
@ -51,6 +54,10 @@ export function selectIndexInternal(lView: LView, index: number, checkNoChangesM
|
|||
ngDevMode && assertGreaterThan(index, -1, 'Invalid index');
|
||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
||||
|
||||
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
|
||||
executeElementExitFn();
|
||||
}
|
||||
|
||||
// Flush the initial hooks for elements in the view that have been added up to this point.
|
||||
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
||||
if (!checkNoChangesMode) {
|
||||
|
@ -69,6 +76,10 @@ export function selectIndexInternal(lView: LView, index: number, checkNoChangesM
|
|||
}
|
||||
}
|
||||
|
||||
if (hasActiveElementFlag(ActiveElementFlags.ResetStylesOnExit)) {
|
||||
resetStylingState();
|
||||
}
|
||||
|
||||
// We must set the selected index *after* running the hooks, because hooks may have side-effects
|
||||
// that cause other template functions to run, thus updating the selected index, which is global
|
||||
// state. If we run `setSelectedIndex` *before* we run the hooks, in some cases the selected index
|
||||
|
|
|
@ -139,11 +139,11 @@ export function ɵɵelementEnd(): void {
|
|||
}
|
||||
}
|
||||
|
||||
if (hasClassInput(tNode) && tNode.classes) {
|
||||
if (hasClassInput(tNode)) {
|
||||
setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']);
|
||||
}
|
||||
|
||||
if (hasStyleInput(tNode) && tNode.styles) {
|
||||
if (hasStyleInput(tNode)) {
|
||||
setDirectiveStylingInput(tNode.styles, lView, tNode.inputs !['style']);
|
||||
}
|
||||
}
|
||||
|
@ -237,11 +237,12 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
|
|||
}
|
||||
|
||||
function setDirectiveStylingInput(
|
||||
context: TStylingContext | StylingMapArray, lView: LView, stylingInputs: (string | number)[]) {
|
||||
context: TStylingContext | StylingMapArray | null, lView: LView,
|
||||
stylingInputs: (string | number)[]) {
|
||||
// older versions of Angular treat the input as `null` in the
|
||||
// event that the value does not exist at all. For this reason
|
||||
// we can't have a styling value be an empty string.
|
||||
const value = getInitialStylingValue(context) || null;
|
||||
const value = (context && getInitialStylingValue(context)) || null;
|
||||
|
||||
// Ivy does an extra `[class]` write with a falsy value since the value
|
||||
// is applied during creation mode. This is a deviation from VE and should
|
||||
|
|
|
@ -30,8 +30,9 @@ import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRoo
|
|||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from '../node_assert';
|
||||
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, namespaceHTMLInternal, selectView, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {ActiveElementFlags, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, namespaceHTMLInternal, selectView, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {renderStylingMap} from '../styling_next/bindings';
|
||||
import {resetStylingState} from '../styling_next/state';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
|
||||
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
||||
|
@ -82,16 +83,18 @@ export function setHostBindings(tView: TView, viewData: LView): void {
|
|||
} else {
|
||||
// If it's not a number, it's a host binding function that needs to be executed.
|
||||
if (instruction !== null) {
|
||||
viewData[BINDING_INDEX] = bindingRootIndex;
|
||||
const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]);
|
||||
instruction(RenderFlags.Update, hostCtx, currentElementIndex);
|
||||
|
||||
// Each directive gets a uniqueId value that is the same for both
|
||||
// create and update calls when the hostBindings function is called. The
|
||||
// directive uniqueId is not set anywhere--it is just incremented between
|
||||
// each hostBindings call and is useful for helping instruction code
|
||||
// uniquely determine which directive is currently active when executed.
|
||||
// It is important that this be called first before the actual instructions
|
||||
// are run because this way the first directive ID value is not zero.
|
||||
incrementActiveDirectiveId();
|
||||
|
||||
viewData[BINDING_INDEX] = bindingRootIndex;
|
||||
const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]);
|
||||
instruction(RenderFlags.Update, hostCtx, currentElementIndex);
|
||||
}
|
||||
currentDirectiveIndex++;
|
||||
}
|
||||
|
@ -499,6 +502,12 @@ function executeTemplate<T>(
|
|||
}
|
||||
templateFn(rf, context);
|
||||
} finally {
|
||||
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
|
||||
executeElementExitFn();
|
||||
}
|
||||
if (hasActiveElementFlag(ActiveElementFlags.ResetStylesOnExit)) {
|
||||
resetStylingState();
|
||||
}
|
||||
setSelectedIndex(prevSelectedIndex);
|
||||
}
|
||||
}
|
||||
|
@ -1094,14 +1103,10 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
|
|||
const def = tView.data[i] as DirectiveDef<any>;
|
||||
const directive = viewData[i];
|
||||
if (def.hostBindings) {
|
||||
invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass);
|
||||
|
||||
// Each directive gets a uniqueId value that is the same for both
|
||||
// create and update calls when the hostBindings function is called. The
|
||||
// directive uniqueId is not set anywhere--it is just incremented between
|
||||
// each hostBindings call and is useful for helping instruction code
|
||||
// uniquely determine which directive is currently active when executed.
|
||||
// It is important that this be called first before the actual instructions
|
||||
// are run because this way the first directive ID value is not zero.
|
||||
incrementActiveDirectiveId();
|
||||
invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass);
|
||||
} else if (firstTemplatePass) {
|
||||
expando.push(null);
|
||||
}
|
||||
|
|
|
@ -116,7 +116,6 @@ export const angularCoreEnv: {[name: string]: Function} =
|
|||
'ɵɵclassMapInterpolate7': r3.ɵɵclassMapInterpolate7,
|
||||
'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8,
|
||||
'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV,
|
||||
'ɵɵstyling': r3.ɵɵstyling,
|
||||
'ɵɵstyleMap': r3.ɵɵstyleMap,
|
||||
'ɵɵstyleProp': r3.ɵɵstyleProp,
|
||||
'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1,
|
||||
|
@ -129,7 +128,6 @@ export const angularCoreEnv: {[name: string]: Function} =
|
|||
'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8,
|
||||
'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV,
|
||||
'ɵɵstyleSanitizer': r3.ɵɵstyleSanitizer,
|
||||
'ɵɵstylingApply': r3.ɵɵstylingApply,
|
||||
'ɵɵclassProp': r3.ɵɵclassProp,
|
||||
'ɵɵselect': r3.ɵɵselect,
|
||||
'ɵɵadvance': r3.ɵɵadvance,
|
||||
|
|
|
@ -13,7 +13,7 @@ import {assertLViewOrUndefined} from './assert';
|
|||
import {ComponentDef, DirectiveDef} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TViewNode} from './interfaces/node';
|
||||
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view';
|
||||
import {resetAllStylingState, resetStylingState} from './styling_next/state';
|
||||
import {resetStylingState} from './styling_next/state';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -129,31 +129,38 @@ export function getLView(): LView {
|
|||
* The reason why this value is `1` instead of `0` is because the `0`
|
||||
* value is reserved for the template.
|
||||
*/
|
||||
const MIN_DIRECTIVE_ID = 1;
|
||||
|
||||
let activeDirectiveId = MIN_DIRECTIVE_ID;
|
||||
let activeDirectiveId = 0;
|
||||
|
||||
/**
|
||||
* Position depth (with respect from leaf to root) in a directive sub-class inheritance chain.
|
||||
* Flags used for an active element during change detection.
|
||||
*
|
||||
* These flags are used within other instructions to inform cleanup or
|
||||
* exit operations to run when an element is being processed.
|
||||
*
|
||||
* Note that these flags are reset each time an element changes (whether it
|
||||
* happens when `advance()` is run or when change detection exits out of a template
|
||||
* function or when all host bindings are processed for an element).
|
||||
*/
|
||||
let activeDirectiveSuperClassDepthPosition = 0;
|
||||
export const enum ActiveElementFlags {
|
||||
Initial = 0b00,
|
||||
RunExitFn = 0b01,
|
||||
ResetStylesOnExit = 0b10,
|
||||
Size = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Total count of how many directives are a part of an inheritance chain.
|
||||
*
|
||||
* When directives are sub-classed (extended) from one to another, Angular
|
||||
* needs to keep track of exactly how many were encountered so it can accurately
|
||||
* generate the next directive id (once the next directive id is visited).
|
||||
* Normally the next directive id just a single incremented value from the
|
||||
* previous one, however, if the previous directive is a part of an inheritance
|
||||
* chain (a series of sub-classed directives) then the incremented value must
|
||||
* also take into account the total amount of sub-classed values.
|
||||
*
|
||||
* Note that this value resets back to zero once the next directive is
|
||||
* visited (when `incrementActiveDirectiveId` or `setActiveHostElement`
|
||||
* is called).
|
||||
* Determines whether or not a flag is currently set for the active element.
|
||||
*/
|
||||
let activeDirectiveSuperClassHeight = 0;
|
||||
export function hasActiveElementFlag(flag: ActiveElementFlags) {
|
||||
return (_selectedIndex & flag) === flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag is for the active element.
|
||||
*/
|
||||
export function setActiveElementFlag(flag: ActiveElementFlags) {
|
||||
_selectedIndex |= flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active directive host element and resets the directive id value
|
||||
|
@ -163,14 +170,44 @@ let activeDirectiveSuperClassHeight = 0;
|
|||
* the directive/component instance lives
|
||||
*/
|
||||
export function setActiveHostElement(elementIndex: number | null = null) {
|
||||
if (_selectedIndex !== elementIndex) {
|
||||
if (getSelectedIndex() !== elementIndex) {
|
||||
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
|
||||
executeElementExitFn();
|
||||
}
|
||||
if (hasActiveElementFlag(ActiveElementFlags.ResetStylesOnExit)) {
|
||||
resetStylingState();
|
||||
}
|
||||
setSelectedIndex(elementIndex === null ? -1 : elementIndex);
|
||||
activeDirectiveId = elementIndex === null ? 0 : MIN_DIRECTIVE_ID;
|
||||
activeDirectiveSuperClassDepthPosition = 0;
|
||||
activeDirectiveSuperClassHeight = 0;
|
||||
activeDirectiveId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let _elementExitFn: Function|null = null;
|
||||
export function executeElementExitFn() {
|
||||
_elementExitFn !();
|
||||
// TODO (matsko|misko): remove this unassignment once the state management of
|
||||
// global variables are better managed.
|
||||
_selectedIndex &= ~ActiveElementFlags.RunExitFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a function to be run once the element is "exited" in CD.
|
||||
*
|
||||
* Change detection will focus on an element either when the `advance()`
|
||||
* instruction is called or when the template or host bindings instruction
|
||||
* code is invoked. The element is then "exited" when the next element is
|
||||
* selected or when change detection for the template or host bindings is
|
||||
* complete. When this occurs (the element change operation) then an exit
|
||||
* function will be invoked if it has been set. This function can be used
|
||||
* to assign that exit function.
|
||||
*
|
||||
* @param fn
|
||||
*/
|
||||
export function setElementExitFn(fn: Function): void {
|
||||
setActiveElementFlag(ActiveElementFlags.RunExitFn);
|
||||
_elementExitFn = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current id value of the current directive.
|
||||
*
|
||||
|
@ -211,71 +248,12 @@ export function getActiveDirectiveId() {
|
|||
* different set of directives).
|
||||
*/
|
||||
export function incrementActiveDirectiveId() {
|
||||
activeDirectiveId += 1 + activeDirectiveSuperClassHeight;
|
||||
|
||||
// because we are dealing with a new directive this
|
||||
// means we have exited out of the inheritance chain
|
||||
activeDirectiveSuperClassDepthPosition = 0;
|
||||
activeDirectiveSuperClassHeight = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current super class (reverse inheritance) position depth for a directive.
|
||||
*
|
||||
* For example we have two directives: Child and Other (but Child is a sub-class of Parent)
|
||||
* <div child-dir other-dir></div>
|
||||
*
|
||||
* // increment
|
||||
* parentInstance->hostBindings() (depth = 1)
|
||||
* // decrement
|
||||
* childInstance->hostBindings() (depth = 0)
|
||||
* otherInstance->hostBindings() (depth = 0 b/c it's a different directive)
|
||||
*
|
||||
* Note that this is only active when `hostBinding` functions are being processed.
|
||||
*/
|
||||
export function adjustActiveDirectiveSuperClassDepthPosition(delta: number) {
|
||||
activeDirectiveSuperClassDepthPosition += delta;
|
||||
|
||||
// we keep track of the height value so that when the next directive is visited
|
||||
// then Angular knows to generate a new directive id value which has taken into
|
||||
// account how many sub-class directives were a part of the previous directive.
|
||||
activeDirectiveSuperClassHeight =
|
||||
Math.max(activeDirectiveSuperClassHeight, activeDirectiveSuperClassDepthPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns he current depth of the super/sub class inheritance chain.
|
||||
*
|
||||
* This will return how many inherited directive/component classes
|
||||
* exist in the current chain.
|
||||
*
|
||||
* ```typescript
|
||||
* @Directive({ selector: '[super-dir]' })
|
||||
* class SuperDir {}
|
||||
*
|
||||
* @Directive({ selector: '[sub-dir]' })
|
||||
* class SubDir extends SuperDir {}
|
||||
*
|
||||
* // if `<div sub-dir>` is used then the super class height is `1`
|
||||
* // if `<div super-dir>` is used then the super class height is `0`
|
||||
* ```
|
||||
*/
|
||||
export function getActiveDirectiveSuperClassHeight() {
|
||||
return activeDirectiveSuperClassHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current super class (reverse inheritance) depth for a directive.
|
||||
*
|
||||
* This is designed to help instruction code distinguish different hostBindings
|
||||
* calls from each other when a directive has extended from another directive.
|
||||
* Normally using the directive id value is enough, but with the case
|
||||
* of parent/sub-class directive inheritance more information is required.
|
||||
*
|
||||
* Note that this is only active when `hostBinding` functions are being processed.
|
||||
*/
|
||||
export function getActiveDirectiveSuperClassDepth() {
|
||||
return activeDirectiveSuperClassDepthPosition;
|
||||
// Each directive gets a uniqueId value that is the same for both
|
||||
// create and update calls when the hostBindings function is called. The
|
||||
// directive uniqueId is not set anywhere--it is just incremented between
|
||||
// each hostBindings call and is useful for helping instruction code
|
||||
// uniquely determine which directive is currently active when executed.
|
||||
activeDirectiveId += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -447,10 +425,10 @@ export function resetComponentState() {
|
|||
elementDepthCount = 0;
|
||||
bindingsEnabled = true;
|
||||
setCurrentStyleSanitizer(null);
|
||||
resetAllStylingState();
|
||||
}
|
||||
|
||||
let _selectedIndex = -1;
|
||||
/* tslint:disable */
|
||||
let _selectedIndex = -1 << ActiveElementFlags.Size;
|
||||
|
||||
/**
|
||||
* Gets the most recent index passed to {@link select}
|
||||
|
@ -459,7 +437,7 @@ let _selectedIndex = -1;
|
|||
* current `LView` to act on.
|
||||
*/
|
||||
export function getSelectedIndex() {
|
||||
return _selectedIndex;
|
||||
return _selectedIndex >> ActiveElementFlags.Size;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -467,13 +445,12 @@ export function getSelectedIndex() {
|
|||
*
|
||||
* Used with {@link property} instruction (and more in the future) to identify the index in the
|
||||
* current `LView` to act on.
|
||||
*
|
||||
* (Note that if an "exit function" was set earlier (via `setElementExitFn()`) then that will be
|
||||
* run if and when the provided `index` value is different from the current selected index value.)
|
||||
*/
|
||||
export function setSelectedIndex(index: number) {
|
||||
_selectedIndex = index;
|
||||
|
||||
// we have now jumped to another element
|
||||
// therefore the state is stale
|
||||
resetStylingState();
|
||||
_selectedIndex = index << ActiveElementFlags.Size;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 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 {SafeValue} from '../../sanitization/bypass';
|
||||
import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||
import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
|
||||
import {BIT_MASK_START_VALUE, deleteStylingStateFromStorage, getStylingState, resetStylingState, storeStylingState} from './state';
|
||||
import {allowStylingFlush, getBindingValue, getGuardMask, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getValuesCount, hasValueChanged, isContextLocked, isSanitizationRequired, isStylingValueDefined, lockContext, setGuardMask, stateIsPersisted} from './util';
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
|
||||
import {getStylingState, resetStylingState} from './state';
|
||||
import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, getBindingValue, getConfig, getDefaultValue, getGuardMask, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isContextLocked, isHostStylingActive, isSanitizationRequired, isStylingValueDefined, lockContext, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from './util';
|
||||
|
||||
|
||||
|
||||
|
@ -35,13 +35,6 @@ import {allowStylingFlush, getBindingValue, getGuardMask, getMapProp, getMapValu
|
|||
* --------
|
||||
*/
|
||||
|
||||
// The first bit value reflects a map-based binding value's bit.
|
||||
// The reason why it's always activated for every entry in the map
|
||||
// is so that if any map-binding values update then all other prop
|
||||
// based bindings will pass the guard check automatically without
|
||||
// any extra code or flags.
|
||||
export const DEFAULT_GUARD_MASK_VALUE = 0b1;
|
||||
|
||||
/**
|
||||
* The guard/update mask bit index location for map-based bindings.
|
||||
*
|
||||
|
@ -49,26 +42,6 @@ export const DEFAULT_GUARD_MASK_VALUE = 0b1;
|
|||
*/
|
||||
const STYLING_INDEX_FOR_MAP_BINDING = 0;
|
||||
|
||||
/**
|
||||
* Default fallback value for a styling binding.
|
||||
*
|
||||
* A value of `null` is used here which signals to the styling algorithm that
|
||||
* the styling value is not present. This way if there are no other values
|
||||
* detected then it will be removed once the style/class property is dirty and
|
||||
* diffed within the styling algorithm present in `flushStyling`.
|
||||
*/
|
||||
const DEFAULT_BINDING_VALUE = null;
|
||||
|
||||
/**
|
||||
* Default size count value for a new entry in a context.
|
||||
*
|
||||
* A value of `1` is used here because each entry in the context has a default
|
||||
* property.
|
||||
*/
|
||||
const DEFAULT_SIZE_VALUE = 1;
|
||||
|
||||
let deferredBindingQueue: (TStylingContext | number | string | null | boolean)[] = [];
|
||||
|
||||
/**
|
||||
* Visits a class-based binding and updates the new value (if changed).
|
||||
*
|
||||
|
@ -79,23 +52,25 @@ let deferredBindingQueue: (TStylingContext | number | string | null | boolean)[]
|
|||
* state each time it's called (which then allows the `TStylingContext`
|
||||
* and the bit mask values to be in sync).
|
||||
*/
|
||||
export function updateClassBinding(
|
||||
context: TStylingContext, data: LStylingData, element: RElement, prop: string | null,
|
||||
bindingIndex: number, value: boolean | string | null | undefined | StylingMapArray | NO_CHANGE,
|
||||
deferRegistration: boolean, forceUpdate: boolean): boolean {
|
||||
export function updateClassViaContext(
|
||||
context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number,
|
||||
prop: string | null, bindingIndex: number,
|
||||
value: boolean | string | null | undefined | StylingMapArray | NO_CHANGE,
|
||||
forceUpdate?: boolean): boolean {
|
||||
const isMapBased = !prop;
|
||||
const state = getStylingState(element, stateIsPersisted(context));
|
||||
const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++;
|
||||
const state = getStylingState(element, directiveIndex);
|
||||
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++;
|
||||
if (value !== NO_CHANGE) {
|
||||
const updated = updateBindingData(
|
||||
context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate, false);
|
||||
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate,
|
||||
false);
|
||||
if (updated || forceUpdate) {
|
||||
// We flip the bit in the bitMask to reflect that the binding
|
||||
// at the `index` slot has changed. This identifies to the flushing
|
||||
// phase that the bindings for this particular CSS class need to be
|
||||
// applied again because on or more of the bindings for the CSS
|
||||
// class have changed.
|
||||
state.classesBitMask |= 1 << index;
|
||||
state.classesBitMask |= 1 << countIndex;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -112,19 +87,20 @@ export function updateClassBinding(
|
|||
* state each time it's called (which then allows the `TStylingContext`
|
||||
* and the bit mask values to be in sync).
|
||||
*/
|
||||
export function updateStyleBinding(
|
||||
context: TStylingContext, data: LStylingData, element: RElement, prop: string | null,
|
||||
bindingIndex: number,
|
||||
export function updateStyleViaContext(
|
||||
context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number,
|
||||
prop: string | null, bindingIndex: number,
|
||||
value: string | number | SafeValue | null | undefined | StylingMapArray | NO_CHANGE,
|
||||
sanitizer: StyleSanitizeFn | null, deferRegistration: boolean, forceUpdate: boolean): boolean {
|
||||
sanitizer: StyleSanitizeFn | null, forceUpdate?: boolean): boolean {
|
||||
const isMapBased = !prop;
|
||||
const state = getStylingState(element, stateIsPersisted(context));
|
||||
const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++;
|
||||
const state = getStylingState(element, directiveIndex);
|
||||
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++;
|
||||
if (value !== NO_CHANGE) {
|
||||
const sanitizationRequired = isMapBased ||
|
||||
const sanitizationRequired = isMapBased ?
|
||||
true :
|
||||
(sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false);
|
||||
const updated = updateBindingData(
|
||||
context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate,
|
||||
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate,
|
||||
sanitizationRequired);
|
||||
if (updated || forceUpdate) {
|
||||
// We flip the bit in the bitMask to reflect that the binding
|
||||
|
@ -132,7 +108,7 @@ export function updateStyleBinding(
|
|||
// phase that the bindings for this particular property need to be
|
||||
// applied again because on or more of the bindings for the CSS
|
||||
// property have changed.
|
||||
state.stylesBitMask |= 1 << index;
|
||||
state.stylesBitMask |= 1 << countIndex;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -144,8 +120,6 @@ export function updateStyleBinding(
|
|||
*
|
||||
* This function is designed to be called from `updateStyleBinding` and `updateClassBinding`.
|
||||
* If called during the first update pass, the binding will be registered in the context.
|
||||
* If the binding does get registered and the `deferRegistration` flag is true then the
|
||||
* binding data will be queued up until the context is later flushed in `applyStyling`.
|
||||
*
|
||||
* This function will also update binding slot in the provided `LStylingData` with the
|
||||
* new binding entry (if it has changed).
|
||||
|
@ -153,74 +127,92 @@ export function updateStyleBinding(
|
|||
* @returns whether or not the binding value was updated in the `LStylingData`.
|
||||
*/
|
||||
function updateBindingData(
|
||||
context: TStylingContext, data: LStylingData, counterIndex: number, prop: string | null,
|
||||
bindingIndex: number,
|
||||
value: string | SafeValue | number | boolean | null | undefined | StylingMapArray | NO_CHANGE,
|
||||
deferRegistration: boolean, forceUpdate: boolean, sanitizationRequired: boolean): boolean {
|
||||
if (!isContextLocked(context)) {
|
||||
if (deferRegistration) {
|
||||
deferBindingRegistration(context, counterIndex, prop, bindingIndex, sanitizationRequired);
|
||||
} else {
|
||||
deferredBindingQueue.length && flushDeferredBindings();
|
||||
|
||||
// this will only happen during the first update pass of the
|
||||
// context. The reason why we can't use `tNode.firstTemplatePass`
|
||||
// here is because its not guaranteed to be true when the first
|
||||
// update pass is executed (remember that all styling instructions
|
||||
// are run in the update phase, and, as a result, are no more
|
||||
// styling instructions that are run in the creation phase).
|
||||
registerBinding(context, counterIndex, prop, bindingIndex, sanitizationRequired);
|
||||
}
|
||||
context: TStylingContext, data: LStylingData, counterIndex: number, sourceIndex: number,
|
||||
prop: string | null, bindingIndex: number,
|
||||
value: string | SafeValue | number | boolean | null | undefined | StylingMapArray,
|
||||
forceUpdate?: boolean, sanitizationRequired?: boolean): boolean {
|
||||
const hostBindingsMode = isHostStylingActive(sourceIndex);
|
||||
if (!isContextLocked(context, hostBindingsMode)) {
|
||||
// this will only happen during the first update pass of the
|
||||
// context. The reason why we can't use `tNode.firstTemplatePass`
|
||||
// here is because its not guaranteed to be true when the first
|
||||
// update pass is executed (remember that all styling instructions
|
||||
// are run in the update phase, and, as a result, are no more
|
||||
// styling instructions that are run in the creation phase).
|
||||
registerBinding(context, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired);
|
||||
patchConfig(
|
||||
context,
|
||||
hostBindingsMode ? TStylingConfig.HasHostBindings : TStylingConfig.HasTemplateBindings);
|
||||
patchConfig(context, prop ? TStylingConfig.HasPropBindings : TStylingConfig.HasMapBindings);
|
||||
}
|
||||
|
||||
const changed = forceUpdate || hasValueChanged(data[bindingIndex], value);
|
||||
if (changed) {
|
||||
data[bindingIndex] = value;
|
||||
setValue(data, bindingIndex, value);
|
||||
const doSetValuesAsStale = (getConfig(context) & TStylingConfig.HasHostBindings) &&
|
||||
!hostBindingsMode && (prop ? !value : true);
|
||||
if (doSetValuesAsStale) {
|
||||
renderHostBindingsAsStale(context, data, prop, !prop);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a binding registration to be run at a later point.
|
||||
* Iterates over all host-binding values for the given `prop` value in the context and sets their
|
||||
* corresponding binding values to `null`.
|
||||
*
|
||||
* The reasoning for this feature is to ensure that styling
|
||||
* bindings are registered in the correct order for when
|
||||
* directives/components have a super/sub class inheritance
|
||||
* chains. Each directive's styling bindings must be
|
||||
* registered into the context in reverse order. Therefore all
|
||||
* bindings will be buffered in reverse order and then applied
|
||||
* after the inheritance chain exits.
|
||||
* Whenever a template binding changes its value to `null`, all host-binding values should be
|
||||
* re-applied
|
||||
* to the element when the host bindings are evaluated. This may not always happen in the event
|
||||
* that none of the bindings changed within the host bindings code. For this reason this function
|
||||
* is expected to be called each time a template binding becomes falsy or when a map-based template
|
||||
* binding changes.
|
||||
*/
|
||||
function deferBindingRegistration(
|
||||
context: TStylingContext, counterIndex: number, prop: string | null, bindingIndex: number,
|
||||
sanitizationRequired: boolean) {
|
||||
deferredBindingQueue.unshift(context, counterIndex, prop, bindingIndex, sanitizationRequired);
|
||||
}
|
||||
function renderHostBindingsAsStale(
|
||||
context: TStylingContext, data: LStylingData, prop: string | null, isMapBased: boolean): void {
|
||||
const valuesCount = getValuesCount(context);
|
||||
|
||||
/**
|
||||
* Flushes the collection of deferred bindings and causes each entry
|
||||
* to be registered into the context.
|
||||
*/
|
||||
function flushDeferredBindings() {
|
||||
let i = 0;
|
||||
while (i < deferredBindingQueue.length) {
|
||||
const context = deferredBindingQueue[i++] as TStylingContext;
|
||||
const count = deferredBindingQueue[i++] as number;
|
||||
const prop = deferredBindingQueue[i++] as string;
|
||||
const bindingIndex = deferredBindingQueue[i++] as number | null;
|
||||
const sanitizationRequired = deferredBindingQueue[i++] as boolean;
|
||||
registerBinding(context, count, prop, bindingIndex, sanitizationRequired);
|
||||
if (hasConfig(context, TStylingConfig.HasPropBindings)) {
|
||||
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + valuesCount;
|
||||
|
||||
let i = TStylingContextIndex.ValuesStartPosition;
|
||||
while (i < context.length) {
|
||||
if (getProp(context, i) === prop) {
|
||||
break;
|
||||
}
|
||||
i += itemsPerRow;
|
||||
}
|
||||
|
||||
const bindingsStart = i + TStylingContextIndex.BindingsStartOffset;
|
||||
const valuesStart = bindingsStart + 1; // the first column is template bindings
|
||||
const valuesEnd = bindingsStart + valuesCount - 1;
|
||||
|
||||
for (let i = valuesStart; i < valuesEnd; i++) {
|
||||
const bindingIndex = context[i] as number;
|
||||
if (bindingIndex !== 0) {
|
||||
setValue(data, bindingIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
const bindingsStart =
|
||||
TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset;
|
||||
const valuesStart = bindingsStart + 1; // the first column is template bindings
|
||||
const valuesEnd = bindingsStart + valuesCount - 1;
|
||||
for (let i = valuesStart; i < valuesEnd; i++) {
|
||||
const stylingMap = getValue<StylingMapArray>(data, context[i] as number);
|
||||
if (stylingMap) {
|
||||
setMapAsDirty(stylingMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
deferredBindingQueue.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the provided binding (prop + bindingIndex) into the context.
|
||||
*
|
||||
* This function is shared between bindings that are assigned immediately
|
||||
* (via `updateBindingData`) and at a deferred stage. When called, it will
|
||||
* figure out exactly where to place the binding data in the context.
|
||||
*
|
||||
* It is needed because it will either update or insert a styling property
|
||||
* into the context at the correct spot.
|
||||
*
|
||||
|
@ -246,61 +238,80 @@ function flushDeferredBindings() {
|
|||
* value.
|
||||
*
|
||||
* Note that this function is also used for map-based styling bindings. They are treated
|
||||
* much the same as prop-based bindings, but, because they do not have a property value
|
||||
* (since it's a map), all map-based entries are stored in an already populated area of
|
||||
* the context at the top (which is reserved for map-based entries).
|
||||
* much the same as prop-based bindings, but, their property name value is set as `[MAP]`.
|
||||
*/
|
||||
export function registerBinding(
|
||||
context: TStylingContext, countId: number, prop: string | null,
|
||||
bindingValue: number | null | string | boolean, sanitizationRequired?: boolean): boolean {
|
||||
let registered = false;
|
||||
if (prop) {
|
||||
// prop-based bindings (e.g `<div [style.width]="w" [class.foo]="f">`)
|
||||
let found = false;
|
||||
let i = getPropValuesStartPosition(context);
|
||||
while (i < context.length) {
|
||||
const valuesCount = getValuesCount(context, i);
|
||||
const p = getProp(context, i);
|
||||
found = prop <= p;
|
||||
if (found) {
|
||||
// all style/class bindings are sorted by property name
|
||||
if (prop < p) {
|
||||
allocateNewContextEntry(context, i, prop, sanitizationRequired);
|
||||
}
|
||||
addBindingIntoContext(context, false, i, bindingValue, countId);
|
||||
break;
|
||||
}
|
||||
i += TStylingContextIndex.BindingsStartOffset + valuesCount;
|
||||
}
|
||||
context: TStylingContext, countId: number, sourceIndex: number, prop: string | null,
|
||||
bindingValue: number | null | string | boolean, sanitizationRequired?: boolean): void {
|
||||
let found = false;
|
||||
prop = prop || MAP_BASED_ENTRY_PROP_NAME;
|
||||
|
||||
if (!found) {
|
||||
allocateNewContextEntry(context, context.length, prop, sanitizationRequired);
|
||||
addBindingIntoContext(context, false, i, bindingValue, countId);
|
||||
registered = true;
|
||||
}
|
||||
} else {
|
||||
// map-based bindings (e.g `<div [style]="s" [class]="{className:true}">`)
|
||||
// there is no need to allocate the map-based binding region into the context
|
||||
// since it is already there when the context is first created.
|
||||
addBindingIntoContext(
|
||||
context, true, TStylingContextIndex.MapBindingsPosition, bindingValue, countId);
|
||||
registered = true;
|
||||
let totalSources = getTotalSources(context);
|
||||
|
||||
// if a new source is detected then a new column needs to be allocated into
|
||||
// the styling context. The column is basically a new allocation of binding
|
||||
// sources that will be available to each property.
|
||||
while (totalSources <= sourceIndex) {
|
||||
addNewSourceColumn(context);
|
||||
totalSources++;
|
||||
}
|
||||
|
||||
const isBindingIndexValue = typeof bindingValue === 'number';
|
||||
const entriesPerRow = TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
|
||||
let i = TStylingContextIndex.ValuesStartPosition;
|
||||
|
||||
// all style/class bindings are sorted by property name
|
||||
while (i < context.length) {
|
||||
const p = getProp(context, i);
|
||||
if (prop <= p) {
|
||||
if (prop < p) {
|
||||
allocateNewContextEntry(context, i, prop, sanitizationRequired);
|
||||
} else if (isBindingIndexValue) {
|
||||
patchConfig(context, TStylingConfig.HasCollisions);
|
||||
}
|
||||
addBindingIntoContext(context, i, bindingValue, countId, sourceIndex);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
i += entriesPerRow;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
allocateNewContextEntry(context, context.length, prop, sanitizationRequired);
|
||||
addBindingIntoContext(context, i, bindingValue, countId, sourceIndex);
|
||||
}
|
||||
return registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new row into the provided `TStylingContext` and assigns the provided `prop` value as
|
||||
* the property entry.
|
||||
*/
|
||||
function allocateNewContextEntry(
|
||||
context: TStylingContext, index: number, prop: string, sanitizationRequired?: boolean) {
|
||||
// 1,2: splice index locations
|
||||
// 3: each entry gets a config value (guard mask + flags)
|
||||
// 4. each entry gets a size value (which is always one because there is always a default binding
|
||||
// value)
|
||||
// 5. the property that is getting allocated into the context
|
||||
// 6. the default binding value (usually `null`)
|
||||
context: TStylingContext, index: number, prop: string, sanitizationRequired?: boolean): void {
|
||||
const config = sanitizationRequired ? TStylingContextPropConfigFlags.SanitizationRequired :
|
||||
TStylingContextPropConfigFlags.Default;
|
||||
context.splice(index, 0, config, DEFAULT_SIZE_VALUE, prop, DEFAULT_BINDING_VALUE);
|
||||
setGuardMask(context, index, DEFAULT_GUARD_MASK_VALUE);
|
||||
context.splice(
|
||||
index, 0,
|
||||
config, // 1) config value
|
||||
DEFAULT_GUARD_MASK_VALUE, // 2) template bit mask
|
||||
DEFAULT_GUARD_MASK_VALUE, // 3) host bindings bit mask
|
||||
prop, // 4) prop value (e.g. `width`, `myClass`, etc...)
|
||||
);
|
||||
|
||||
index += 4; // the 4 values above
|
||||
|
||||
// 5...) default binding index for the template value
|
||||
// depending on how many sources already exist in the context,
|
||||
// multiple default index entries may need to be inserted for
|
||||
// the new value in the context.
|
||||
const totalBindingsPerEntry = getTotalSources(context);
|
||||
for (let i = 0; i < totalBindingsPerEntry; i++) {
|
||||
context.splice(index, 0, DEFAULT_BINDING_INDEX);
|
||||
index++;
|
||||
}
|
||||
|
||||
// 6) default binding value for the new entry
|
||||
context.splice(index, 0, DEFAULT_BINDING_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -316,75 +327,70 @@ function allocateNewContextEntry(
|
|||
*
|
||||
* - Otherwise the binding value will update the default value for the property
|
||||
* and this will only happen if the default value is `null`.
|
||||
*
|
||||
* Note that this function also handles map-based bindings and will insert them
|
||||
* at the top of the context.
|
||||
*/
|
||||
function addBindingIntoContext(
|
||||
context: TStylingContext, isMapBased: boolean, index: number,
|
||||
bindingValue: number | string | boolean | null, countId: number) {
|
||||
const valuesCount = getValuesCount(context, index);
|
||||
|
||||
const firstValueIndex = index + TStylingContextIndex.BindingsStartOffset;
|
||||
let lastValueIndex = firstValueIndex + valuesCount;
|
||||
if (!isMapBased) {
|
||||
// prop-based values all have default values, but map-based entries do not.
|
||||
// we want to access the index for the default value in this case and not just
|
||||
// the bindings...
|
||||
lastValueIndex--;
|
||||
}
|
||||
|
||||
context: TStylingContext, index: number, bindingValue: number | string | boolean | null,
|
||||
bitIndex: number, sourceIndex: number) {
|
||||
if (typeof bindingValue === 'number') {
|
||||
// the loop here will check to see if the binding already exists
|
||||
// for the property in the context. Why? The reason for this is
|
||||
// because the styling context is not "locked" until the first
|
||||
// flush has occurred. This means that if a repeated element
|
||||
// registers its styling bindings then it will register each
|
||||
// binding more than once (since its duplicated). This check
|
||||
// will prevent that from happening. Note that this only happens
|
||||
// when a binding is first encountered and not each time it is
|
||||
// updated.
|
||||
for (let i = firstValueIndex; i <= lastValueIndex; i++) {
|
||||
const indexAtPosition = context[i];
|
||||
if (indexAtPosition === bindingValue) return;
|
||||
}
|
||||
|
||||
context.splice(lastValueIndex, 0, bindingValue);
|
||||
(context[index + TStylingContextIndex.ValuesCountOffset] as number)++;
|
||||
|
||||
// now that a new binding index has been added to the property
|
||||
// the guard mask bit value (at the `countId` position) needs
|
||||
// to be included into the existing mask value.
|
||||
const guardMask = getGuardMask(context, index) | (1 << countId);
|
||||
setGuardMask(context, index, guardMask);
|
||||
} else if (bindingValue !== null && context[lastValueIndex] == null) {
|
||||
context[lastValueIndex] = bindingValue;
|
||||
const hostBindingsMode = isHostStylingActive(sourceIndex);
|
||||
const cellIndex = index + TStylingContextIndex.BindingsStartOffset + sourceIndex;
|
||||
context[cellIndex] = bindingValue;
|
||||
const updatedBitMask = getGuardMask(context, index, hostBindingsMode) | (1 << bitIndex);
|
||||
setGuardMask(context, index, updatedBitMask, hostBindingsMode);
|
||||
} else if (bindingValue !== null && getDefaultValue(context, index) === null) {
|
||||
setDefaultValue(context, index, bindingValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new column into the provided `TStylingContext`.
|
||||
*
|
||||
* If and when a new source is detected then a new column needs to
|
||||
* be allocated into the styling context. The column is basically
|
||||
* a new allocation of binding sources that will be available to each
|
||||
* property.
|
||||
*
|
||||
* Each column that exists in the styling context resembles a styling
|
||||
* source. A styling source an either be the template or one or more
|
||||
* components or directives all containing styling host bindings.
|
||||
*/
|
||||
function addNewSourceColumn(context: TStylingContext): void {
|
||||
// we use -1 here because we want to insert right before the last value (the default value)
|
||||
const insertOffset = TStylingContextIndex.BindingsStartOffset + getValuesCount(context) - 1;
|
||||
|
||||
let index = TStylingContextIndex.ValuesStartPosition;
|
||||
while (index < context.length) {
|
||||
index += insertOffset;
|
||||
context.splice(index++, 0, DEFAULT_BINDING_INDEX);
|
||||
|
||||
// the value was inserted just before the default value, but the
|
||||
// next entry in the context starts just after it. Therefore++.
|
||||
index++;
|
||||
}
|
||||
context[TStylingContextIndex.TotalSourcesPosition]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all pending style and class bindings to the provided element.
|
||||
*
|
||||
* This function will attempt to flush styling via the provided `classesContext`
|
||||
* and `stylesContext` context values. This function is designed to be run from
|
||||
* the `stylingApply()` instruction (which is run at the very end of styling
|
||||
* change detection) and will rely on any state values that are set from when
|
||||
* any styling bindings update.
|
||||
* the internal `stylingApply` function (which is scheduled to run at the very
|
||||
* end of change detection for an element if one or more style/class bindings
|
||||
* were processed) and will rely on any state values that are set from when
|
||||
* any of the styling bindings executed.
|
||||
*
|
||||
* This function may be called multiple times on the same element because it can
|
||||
* be called from the template code as well as from host bindings. In order for
|
||||
* styling to be successfully flushed to the element (which will only happen once
|
||||
* despite this being called multiple times), the following criteria must be met:
|
||||
* This function is designed to be called twice: one when change detection has
|
||||
* processed an element within the template bindings (i.e. just as `advance()`
|
||||
* is called) and when host bindings have been processed. In both cases the
|
||||
* styles and classes in both contexts will be applied to the element, but the
|
||||
* algorithm will selectively decide which bindings to run depending on the
|
||||
* columns in the context. The provided `directiveIndex` value will help the
|
||||
* algorithm determine which bindings to apply: either the template bindings or
|
||||
* the host bindings (see `applyStylingToElement` for more information).
|
||||
*
|
||||
* - `flushStyling` is called from the very last directive that has styling for
|
||||
* the element (see `allowStylingFlush()`).
|
||||
* - one or more bindings for classes or styles has updated (this is checked by
|
||||
* examining the classes or styles bit mask).
|
||||
*
|
||||
* If the style and class values are successfully applied to the element then
|
||||
* the temporary state values for the element will be cleared. Otherwise, if
|
||||
* this did not occur then the styling state is persisted (see `state.ts` for
|
||||
* more information on how this works).
|
||||
* Note that once this function is called all temporary styling state data
|
||||
* (i.e. the `bitMask` and `counter` values for styles and classes will be cleared).
|
||||
*/
|
||||
export function flushStyling(
|
||||
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData,
|
||||
|
@ -392,53 +398,28 @@ export function flushStyling(
|
|||
element: RElement, directiveIndex: number, styleSanitizer: StyleSanitizeFn | null): void {
|
||||
ngDevMode && ngDevMode.flushStyling++;
|
||||
|
||||
const persistState = classesContext ? stateIsPersisted(classesContext) :
|
||||
(stylesContext ? stateIsPersisted(stylesContext) : false);
|
||||
const allowFlushClasses = allowStylingFlush(classesContext, directiveIndex);
|
||||
const allowFlushStyles = allowStylingFlush(stylesContext, directiveIndex);
|
||||
const state = getStylingState(element, directiveIndex);
|
||||
const hostBindingsMode = isHostStylingActive(state.sourceIndex);
|
||||
|
||||
// deferred bindings are bindings which are scheduled to register with
|
||||
// the context at a later point. These bindings can only registered when
|
||||
// the context will be 100% flushed to the element.
|
||||
if (deferredBindingQueue.length && (allowFlushClasses || allowFlushStyles)) {
|
||||
flushDeferredBindings();
|
||||
}
|
||||
|
||||
const state = getStylingState(element, persistState);
|
||||
const classesFlushed = maybeApplyStyling(
|
||||
renderer, element, data, classesContext, allowFlushClasses, state.classesBitMask, setClass,
|
||||
null);
|
||||
const stylesFlushed = maybeApplyStyling(
|
||||
renderer, element, data, stylesContext, allowFlushStyles, state.stylesBitMask, setStyle,
|
||||
styleSanitizer);
|
||||
|
||||
if (classesFlushed && stylesFlushed) {
|
||||
resetStylingState();
|
||||
if (persistState) {
|
||||
deleteStylingStateFromStorage(element);
|
||||
if (stylesContext) {
|
||||
if (!isContextLocked(stylesContext, hostBindingsMode)) {
|
||||
lockAndFinalizeContext(stylesContext, hostBindingsMode);
|
||||
}
|
||||
} else if (persistState) {
|
||||
storeStylingState(element, state);
|
||||
applyStylingViaContext(
|
||||
stylesContext, renderer, element, data, state.stylesBitMask, setStyle, styleSanitizer,
|
||||
hostBindingsMode);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeApplyStyling(
|
||||
renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, data: LStylingData,
|
||||
context: TStylingContext | null, allowFlush: boolean, bitMask: number,
|
||||
styleSetter: ApplyStylingFn, styleSanitizer: any | null): boolean {
|
||||
if (allowFlush && context) {
|
||||
lockAndFinalizeContext(context);
|
||||
if (contextHasUpdates(context, bitMask)) {
|
||||
ngDevMode && (styleSanitizer ? ngDevMode.stylesApplied++ : ngDevMode.classesApplied++);
|
||||
applyStyling(context !, renderer, element, data, bitMask, styleSetter, styleSanitizer);
|
||||
return true;
|
||||
if (classesContext) {
|
||||
if (!isContextLocked(classesContext, hostBindingsMode)) {
|
||||
lockAndFinalizeContext(classesContext, hostBindingsMode);
|
||||
}
|
||||
applyStylingViaContext(
|
||||
classesContext, renderer, element, data, state.classesBitMask, setClass, null,
|
||||
hostBindingsMode);
|
||||
}
|
||||
return allowFlush;
|
||||
}
|
||||
|
||||
function contextHasUpdates(context: TStylingContext | null, bitMask: number) {
|
||||
return context && bitMask > BIT_MASK_START_VALUE;
|
||||
resetStylingState();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -462,14 +443,50 @@ function contextHasUpdates(context: TStylingContext | null, bitMask: number) {
|
|||
* be updated each time a host binding applies its static styling values (via `elementHostAttrs`)
|
||||
* so these values are only read at this point because this is the very last point before the
|
||||
* first style/class values are flushed to the element.
|
||||
*
|
||||
* Note that the `TStylingContext` styling context contains two locks: one for template bindings
|
||||
* and another for host bindings. Either one of these locks will be set when styling is applied
|
||||
* during the template binding flush and/or during the host bindings flush.
|
||||
*/
|
||||
function lockAndFinalizeContext(context: TStylingContext): void {
|
||||
if (!isContextLocked(context)) {
|
||||
const initialValues = getStylingMapArray(context);
|
||||
if (initialValues) {
|
||||
updateInitialStylingOnContext(context, initialValues);
|
||||
function lockAndFinalizeContext(context: TStylingContext, hostBindingsMode: boolean): void {
|
||||
const initialValues = getStylingMapArray(context) !;
|
||||
updateInitialStylingOnContext(context, initialValues);
|
||||
lockContext(context, hostBindingsMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all initial styling entries into the provided context.
|
||||
*
|
||||
* This function will iterate over all entries in the provided `initialStyling` ar}ray and register
|
||||
* them as default (initial) values in the provided context. Initial styling values in a context are
|
||||
* the default values that are to be applied unless overwritten by a binding.
|
||||
*
|
||||
* The reason why this function exists and isn't a part of the context construction is because
|
||||
* host binding is evaluated at a later stage after the element is created. This means that
|
||||
* if a directive or component contains any initial styling code (i.e. `<div class="foo">`)
|
||||
* then that initial styling data can only be applied once the styling for that element
|
||||
* is first applied (at the end of the update phase). Once that happens then the context will
|
||||
* update itself with the complete initial styling for the element.
|
||||
*/
|
||||
function updateInitialStylingOnContext(
|
||||
context: TStylingContext, initialStyling: StylingMapArray): void {
|
||||
// `-1` is used here because all initial styling data is not a apart
|
||||
// of a binding (since it's static)
|
||||
const COUNT_ID_FOR_STYLING = -1;
|
||||
|
||||
let hasInitialStyling = false;
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < initialStyling.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const value = getMapValue(initialStyling, i);
|
||||
if (value) {
|
||||
const prop = getMapProp(initialStyling, i);
|
||||
registerBinding(context, COUNT_ID_FOR_STYLING, 0, prop, value, false);
|
||||
hasInitialStyling = true;
|
||||
}
|
||||
lockContext(context);
|
||||
}
|
||||
|
||||
if (hasInitialStyling) {
|
||||
patchConfig(context, TStylingConfig.HasInitialStyling);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,60 +514,74 @@ function lockAndFinalizeContext(context: TStylingContext): void {
|
|||
* algorithm works for map-based styling bindings.
|
||||
*
|
||||
* Note that this function is not designed to be called in isolation (use
|
||||
* `applyClasses` and `applyStyles` to actually apply styling values).
|
||||
* the `flushStyling` function so that it can call this function for both
|
||||
* the styles and classes contexts).
|
||||
*/
|
||||
export function applyStyling(
|
||||
export function applyStylingViaContext(
|
||||
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||
bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn,
|
||||
sanitizer: StyleSanitizeFn | null) {
|
||||
sanitizer: StyleSanitizeFn | null, hostBindingsMode: boolean): void {
|
||||
const bitMask = normalizeBitMaskValue(bitMaskValue);
|
||||
const stylingMapsSyncFn = getStylingMapsSyncFn();
|
||||
const mapsGuardMask = getGuardMask(context, TStylingContextIndex.MapBindingsPosition);
|
||||
const applyAllValues = (bitMask & mapsGuardMask) > 0;
|
||||
const mapsMode =
|
||||
|
||||
let stylingMapsSyncFn: SyncStylingMapsFn|null = null;
|
||||
let applyAllValues = false;
|
||||
if (hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
stylingMapsSyncFn = getStylingMapsSyncFn();
|
||||
const mapsGuardMask =
|
||||
getGuardMask(context, TStylingContextIndex.ValuesStartPosition, hostBindingsMode);
|
||||
applyAllValues = (bitMask & mapsGuardMask) !== 0;
|
||||
}
|
||||
|
||||
const valuesCount = getValuesCount(context);
|
||||
let totalBindingsToVisit = 1;
|
||||
let mapsMode =
|
||||
applyAllValues ? StylingMapsSyncMode.ApplyAllValues : StylingMapsSyncMode.TraverseValues;
|
||||
if (hostBindingsMode) {
|
||||
mapsMode |= StylingMapsSyncMode.RecurseInnerMaps;
|
||||
totalBindingsToVisit = valuesCount - 1;
|
||||
}
|
||||
|
||||
let i = getPropValuesStartPosition(context);
|
||||
while (i < context.length) {
|
||||
const valuesCount = getValuesCount(context, i);
|
||||
const guardMask = getGuardMask(context, i);
|
||||
const guardMask = getGuardMask(context, i, hostBindingsMode);
|
||||
if (bitMask & guardMask) {
|
||||
let valueApplied = false;
|
||||
const prop = getProp(context, i);
|
||||
const valuesCountUpToDefault = valuesCount - 1;
|
||||
const defaultValue = getBindingValue(context, i, valuesCountUpToDefault) as string | null;
|
||||
const defaultValue = getDefaultValue(context, i);
|
||||
|
||||
// case 1: apply prop-based values
|
||||
// try to apply the binding values and see if a non-null
|
||||
// value gets set for the styling binding
|
||||
for (let j = 0; j < valuesCountUpToDefault; j++) {
|
||||
// Part 1: Visit the `[styling.prop]` value
|
||||
for (let j = 0; j < totalBindingsToVisit; j++) {
|
||||
const bindingIndex = getBindingValue(context, i, j) as number;
|
||||
const value = bindingData[bindingIndex];
|
||||
if (isStylingValueDefined(value)) {
|
||||
const finalValue = sanitizer && isSanitizationRequired(context, i) ?
|
||||
sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) :
|
||||
value;
|
||||
applyStylingFn(renderer, element, prop, finalValue, bindingIndex);
|
||||
valueApplied = true;
|
||||
break;
|
||||
if (!valueApplied && bindingIndex !== 0) {
|
||||
const value = getValue(bindingData, bindingIndex);
|
||||
if (isStylingValueDefined(value)) {
|
||||
const checkValueOnly = hostBindingsMode && j === 0;
|
||||
if (!checkValueOnly) {
|
||||
const finalValue = sanitizer && isSanitizationRequired(context, i) ?
|
||||
sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) :
|
||||
unwrapSafeValue(value);
|
||||
applyStylingFn(renderer, element, prop, finalValue, bindingIndex);
|
||||
}
|
||||
valueApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Part 2: Visit the `[style]` or `[class]` map-based value
|
||||
if (stylingMapsSyncFn) {
|
||||
// determine whether or not to apply the target property or to skip it
|
||||
let mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp :
|
||||
StylingMapsSyncMode.ApplyTargetProp);
|
||||
if (hostBindingsMode && j === 0) {
|
||||
mode |= StylingMapsSyncMode.CheckValuesOnly;
|
||||
}
|
||||
const valueAppliedWithinMap = stylingMapsSyncFn(
|
||||
context, renderer, element, bindingData, j, applyStylingFn, sanitizer, mode, prop,
|
||||
defaultValue);
|
||||
valueApplied = valueApplied || valueAppliedWithinMap;
|
||||
}
|
||||
}
|
||||
|
||||
// case 2: apply map-based values
|
||||
// traverse through each map-based styling binding and update all values up to
|
||||
// the provided `prop` value. If the property was not applied in the loop above
|
||||
// then it will be attempted to be applied in the maps sync code below.
|
||||
if (stylingMapsSyncFn) {
|
||||
// determine whether or not to apply the target property or to skip it
|
||||
const mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp :
|
||||
StylingMapsSyncMode.ApplyTargetProp);
|
||||
const valueAppliedWithinMap = stylingMapsSyncFn(
|
||||
context, renderer, element, bindingData, applyStylingFn, sanitizer, mode, prop,
|
||||
defaultValue);
|
||||
valueApplied = valueApplied || valueAppliedWithinMap;
|
||||
}
|
||||
|
||||
// case 3: apply the default value
|
||||
// Part 3: apply the default value (e.g. `<div style="width:200">` => `200px` gets applied)
|
||||
// if the value has not yet been applied then a truthy value does not exist in the
|
||||
// prop-based or map-based bindings code. If and when this happens, just apply the
|
||||
// default value (even if the default value is `null`).
|
||||
|
@ -566,10 +597,92 @@ export function applyStyling(
|
|||
// values. For this reason, one more call to the sync function
|
||||
// needs to be issued at the end.
|
||||
if (stylingMapsSyncFn) {
|
||||
stylingMapsSyncFn(context, renderer, element, bindingData, applyStylingFn, sanitizer, mapsMode);
|
||||
if (hostBindingsMode) {
|
||||
mapsMode |= StylingMapsSyncMode.CheckValuesOnly;
|
||||
}
|
||||
stylingMapsSyncFn(
|
||||
context, renderer, element, bindingData, 0, applyStylingFn, sanitizer, mapsMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the provided styling map to the element directly (without context resolution).
|
||||
*
|
||||
* This function is designed to be run from the styling instructions and will be called
|
||||
* automatically. This function is intended to be used for performance reasons in the
|
||||
* event that there is no need to apply styling via context resolution.
|
||||
*
|
||||
* See `allowDirectStylingApply`.
|
||||
*
|
||||
* @returns whether or not the styling map was applied to the element.
|
||||
*/
|
||||
export function applyStylingMapDirectly(
|
||||
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
|
||||
bindingIndex: number, map: StylingMapArray, applyFn: ApplyStylingFn,
|
||||
sanitizer?: StyleSanitizeFn | null, forceUpdate?: boolean): boolean {
|
||||
if (forceUpdate || hasValueChanged(data[bindingIndex], map)) {
|
||||
setValue(data, bindingIndex, map);
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i);
|
||||
applyStylingValue(renderer, context, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the provided styling prop/value to the element directly (without context resolution).
|
||||
*
|
||||
* This function is designed to be run from the styling instructions and will be called
|
||||
* automatically. This function is intended to be used for performance reasons in the
|
||||
* event that there is no need to apply styling via context resolution.
|
||||
*
|
||||
* See `allowDirectStylingApply`.
|
||||
*
|
||||
* @returns whether or not the prop/value styling was applied to the element.
|
||||
*/
|
||||
export function applyStylingValueDirectly(
|
||||
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
|
||||
bindingIndex: number, prop: string, value: any, applyFn: ApplyStylingFn,
|
||||
sanitizer?: StyleSanitizeFn | null): boolean {
|
||||
if (hasValueChanged(data[bindingIndex], value)) {
|
||||
setValue(data, bindingIndex, value);
|
||||
applyStylingValue(renderer, context, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyStylingValue(
|
||||
renderer: any, context: TStylingContext, element: RElement, prop: string, value: any,
|
||||
applyFn: ApplyStylingFn, bindingIndex: number, sanitizer?: StyleSanitizeFn | null) {
|
||||
let valueToApply: string|null = unwrapSafeValue(value);
|
||||
if (isStylingValueDefined(valueToApply)) {
|
||||
valueToApply =
|
||||
sanitizer ? sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) : valueToApply;
|
||||
} else if (hasConfig(context, TStylingConfig.HasInitialStyling)) {
|
||||
const initialStyles = getStylingMapArray(context);
|
||||
if (initialStyles) {
|
||||
valueToApply = findInitialStylingValue(initialStyles, prop);
|
||||
}
|
||||
}
|
||||
applyFn(renderer, element, prop, valueToApply, bindingIndex);
|
||||
}
|
||||
|
||||
function findInitialStylingValue(map: StylingMapArray, prop: string): string|null {
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const p = getMapProp(map, i);
|
||||
if (p >= prop) {
|
||||
return p === prop ? getMapValue(map, i) : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeBitMaskValue(value: number | boolean): number {
|
||||
// if pass => apply all values (-1 implies that all bits are flipped to true)
|
||||
if (value === true) return -1;
|
||||
|
@ -593,7 +706,7 @@ export function setStylingMapsSyncFn(fn: SyncStylingMapsFn) {
|
|||
/**
|
||||
* Assigns a style value to a style property for the given element.
|
||||
*/
|
||||
const setStyle: ApplyStylingFn =
|
||||
export const setStyle: ApplyStylingFn =
|
||||
(renderer: Renderer3 | null, native: RElement, prop: string, value: string | null) => {
|
||||
// the reason why this may be `null` is either because
|
||||
// it's a container element or it's a part of a test
|
||||
|
@ -620,7 +733,7 @@ const setStyle: ApplyStylingFn =
|
|||
/**
|
||||
* Adds/removes the provided className value to the provided element.
|
||||
*/
|
||||
const setClass: ApplyStylingFn =
|
||||
export const setClass: ApplyStylingFn =
|
||||
(renderer: Renderer3 | null, native: RElement, className: string, value: any) => {
|
||||
if (className !== '') {
|
||||
// the reason why this may be `null` is either because
|
||||
|
@ -666,33 +779,3 @@ export function renderStylingMap(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all initial styling entries into the provided context.
|
||||
*
|
||||
* This function will iterate over all entries in the provided `initialStyling` ar}ray and register
|
||||
* them as default (initial) values in the provided context. Initial styling values in a context are
|
||||
* the default values that are to be applied unless overwritten by a binding.
|
||||
*
|
||||
* The reason why this function exists and isn't a part of the context construction is because
|
||||
* host binding is evaluated at a later stage after the element is created. This means that
|
||||
* if a directive or component contains any initial styling code (i.e. `<div class="foo">`)
|
||||
* then that initial styling data can only be applied once the styling for that element
|
||||
* is first applied (at the end of the update phase). Once that happens then the context will
|
||||
* update itself with the complete initial styling for the element.
|
||||
*/
|
||||
function updateInitialStylingOnContext(
|
||||
context: TStylingContext, initialStyling: StylingMapArray): void {
|
||||
// `-1` is used here because all initial styling data is not a spart
|
||||
// of a binding (since it's static)
|
||||
const INITIAL_STYLING_COUNT_ID = -1;
|
||||
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < initialStyling.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const value = getMapValue(initialStyling, i);
|
||||
if (value) {
|
||||
const prop = getMapProp(initialStyling, i);
|
||||
registerBinding(context, INITIAL_STYLING_COUNT_ID, prop, value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,17 +10,17 @@ import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
|||
import {setInputsForProperty} from '../instructions/shared';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeType} from '../interfaces/node';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {BINDING_INDEX, LView, RENDERER, TVIEW} from '../interfaces/view';
|
||||
import {getActiveDirectiveId, getActiveDirectiveSuperClassDepth, getActiveDirectiveSuperClassHeight, getCurrentStyleSanitizer, getLView, getPreviousOrParentTNode, getSelectedIndex, setCurrentStyleSanitizer} from '../state';
|
||||
import {BINDING_INDEX, LView, RENDERER} from '../interfaces/view';
|
||||
import {ActiveElementFlags, getActiveDirectiveId, getCurrentStyleSanitizer, getLView, getSelectedIndex, setActiveElementFlag, setCurrentStyleSanitizer, setElementExitFn} from '../state';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {renderStringify} from '../util/misc_utils';
|
||||
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
||||
|
||||
import {flushStyling, updateClassBinding, updateStyleBinding} from './bindings';
|
||||
import {applyStylingMapDirectly, applyStylingValueDirectly, flushStyling, setClass, setStyle, updateClassViaContext, updateStyleViaContext} from './bindings';
|
||||
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from './interfaces';
|
||||
import {activateStylingMapFeature, addItemToStylingMap, normalizeIntoStylingMap, stylingMapToString} from './map_based_bindings';
|
||||
import {activateStylingMapFeature} from './map_based_bindings';
|
||||
import {attachStylingDebugObject} from './styling_debug';
|
||||
import {allocTStylingContext, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, hasClassInput, hasStyleInput, hasValueChanged, isContextLocked, isStylingContext, updateLastDirectiveIndex as _updateLastDirectiveIndex} from './util';
|
||||
import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDirectStyling, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, hasClassInput, hasStyleInput, hasValueChanged, isContextLocked, isHostStylingActive, isStylingContext, normalizeIntoStylingMap, setValue, stylingMapToString} from './util';
|
||||
|
||||
|
||||
|
||||
|
@ -34,34 +34,13 @@ import {allocTStylingContext, concatString, forceClassesAsString, forceStylesAsS
|
|||
* --------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Temporary function to bridge styling functionality between this new
|
||||
* refactor (which is here inside of `styling_next/`) and the old
|
||||
* implementation (which lives inside of `styling/`).
|
||||
*
|
||||
* This function is executed during the creation block of an element.
|
||||
* Because the existing styling implementation issues a call to the
|
||||
* `styling()` instruction, this instruction will also get run. The
|
||||
* central idea here is that the directive index values are bound
|
||||
* into the context. The directive index is temporary and is only
|
||||
* required until the `select(n)` instruction is fully functional.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵstyling() {
|
||||
const tView = getLView()[TVIEW];
|
||||
if (tView.firstTemplatePass) {
|
||||
updateLastDirectiveIndex(getPreviousOrParentTNode(), getActiveDirectiveStylingIndex());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current style sanitizer function which will then be used
|
||||
* within all follow-up prop and map-based style binding instructions
|
||||
* for the given element.
|
||||
*
|
||||
* Note that once styling has been applied to the element (i.e. once
|
||||
* `select(n)` is executed or the hostBindings/template function exits)
|
||||
* `advance(n)` is executed or the hostBindings/template function exits)
|
||||
* then the active `sanitizerFn` will be set to `null`. This means that
|
||||
* once styling is applied to another element then a another call to
|
||||
* `styleSanitizer` will need to be made.
|
||||
|
@ -92,7 +71,7 @@ export function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void {
|
|||
* be ignored.
|
||||
*
|
||||
* Note that this will apply the provided style value to the host element if this function is called
|
||||
* within a host binding.
|
||||
* within a host binding function.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
|
@ -101,9 +80,15 @@ export function ɵɵstyleProp(
|
|||
stylePropInternal(getSelectedIndex(), prop, value, suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function for applying a single style to an element.
|
||||
*
|
||||
* The reason why this function has been separated from `ɵɵstyleProp` is because
|
||||
* it is also called from `ɵɵstylePropInterpolate`.
|
||||
*/
|
||||
export function stylePropInternal(
|
||||
elementIndex: number, prop: string, value: string | number | SafeValue | null,
|
||||
suffix?: string | null | undefined) {
|
||||
suffix?: string | null | undefined): void {
|
||||
const lView = getLView();
|
||||
|
||||
// if a value is interpolated then it may render a `NO_CHANGE` value.
|
||||
|
@ -112,9 +97,8 @@ export function stylePropInternal(
|
|||
// are stored inside of the lView.
|
||||
const bindingIndex = lView[BINDING_INDEX]++;
|
||||
|
||||
const updated = _stylingProp(
|
||||
elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false,
|
||||
deferStylingUpdate());
|
||||
const updated =
|
||||
stylingProp(elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false);
|
||||
if (ngDevMode) {
|
||||
ngDevMode.styleProp++;
|
||||
if (updated) {
|
||||
|
@ -134,7 +118,7 @@ export function stylePropInternal(
|
|||
* @param value A true/false value which will turn the class on or off.
|
||||
*
|
||||
* Note that this will apply the provided class value to the host element if this function
|
||||
* is called within a host binding.
|
||||
* is called within a host binding function.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
|
@ -147,8 +131,7 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
|
|||
// are stored inside of the lView.
|
||||
const bindingIndex = lView[BINDING_INDEX]++;
|
||||
|
||||
const updated =
|
||||
_stylingProp(getSelectedIndex(), bindingIndex, className, value, true, deferStylingUpdate());
|
||||
const updated = stylingProp(getSelectedIndex(), bindingIndex, className, value, true);
|
||||
if (ngDevMode) {
|
||||
ngDevMode.classProp++;
|
||||
if (updated) {
|
||||
|
@ -159,28 +142,57 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
|
|||
|
||||
/**
|
||||
* Shared function used to update a prop-based styling binding for an element.
|
||||
*
|
||||
* Depending on the state of the `tNode.styles` styles context, the style/prop
|
||||
* value may be applied directly to the element instead of being processed
|
||||
* through the context. The reason why this occurs is for performance and fully
|
||||
* depends on the state of the context (i.e. whether or not there are duplicate
|
||||
* bindings or whether or not there are map-based bindings and property bindings
|
||||
* present together).
|
||||
*/
|
||||
function _stylingProp(
|
||||
function stylingProp(
|
||||
elementIndex: number, bindingIndex: number, prop: string,
|
||||
value: boolean | number | SafeValue | string | null | undefined | NO_CHANGE,
|
||||
isClassBased: boolean, defer: boolean): boolean {
|
||||
isClassBased: boolean): boolean {
|
||||
let updated = false;
|
||||
|
||||
const lView = getLView();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
|
||||
let valueHasChanged = false;
|
||||
if (isClassBased) {
|
||||
valueHasChanged = updateClassBinding(
|
||||
getClassesContext(tNode), lView, native, prop, bindingIndex,
|
||||
value as string | boolean | null, defer, false);
|
||||
const hostBindingsMode = isHostStyling();
|
||||
const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode);
|
||||
const sanitizer = isClassBased ? null : getCurrentStyleSanitizer();
|
||||
|
||||
// Direct Apply Case: bypass context resolution and apply the
|
||||
// style/class value directly to the element
|
||||
if (allowDirectStyling(context, hostBindingsMode)) {
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
updated = applyStylingValueDirectly(
|
||||
renderer, context, native, lView, bindingIndex, prop, value,
|
||||
isClassBased ? setClass : setStyle, sanitizer);
|
||||
} else {
|
||||
const sanitizer = getCurrentStyleSanitizer();
|
||||
valueHasChanged = updateStyleBinding(
|
||||
getStylesContext(tNode), lView, native, prop, bindingIndex,
|
||||
value as string | SafeValue | null, sanitizer, defer, false);
|
||||
// Context Resolution (or first update) Case: save the value
|
||||
// and defer to the context to flush and apply the style/class binding
|
||||
// value to the element.
|
||||
const directiveIndex = getActiveDirectiveId();
|
||||
if (isClassBased) {
|
||||
updated = updateClassViaContext(
|
||||
context, lView, native, directiveIndex, prop, bindingIndex,
|
||||
value as string | boolean | null);
|
||||
} else {
|
||||
updated = updateStyleViaContext(
|
||||
context, lView, native, directiveIndex, prop, bindingIndex,
|
||||
value as string | SafeValue | null, sanitizer);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
setElementExitFn(applyStyling);
|
||||
}
|
||||
markStylingStateAsDirty();
|
||||
}
|
||||
|
||||
return valueHasChanged;
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,7 +219,6 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu
|
|||
const lView = getLView();
|
||||
const tNode = getTNode(index, lView);
|
||||
const context = getStylesContext(tNode);
|
||||
const directiveIndex = getActiveDirectiveStylingIndex();
|
||||
|
||||
// if a value is interpolated then it may render a `NO_CHANGE` value.
|
||||
// in this case we do not need to do anything, but the binding index
|
||||
|
@ -218,12 +229,12 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu
|
|||
// inputs are only evaluated from a template binding into a directive, therefore,
|
||||
// there should not be a situation where a directive host bindings function
|
||||
// evaluates the inputs (this should only happen in the template function)
|
||||
if (!directiveIndex && hasStyleInput(tNode) && styles !== NO_CHANGE) {
|
||||
if (!isHostStyling() && hasStyleInput(tNode) && styles !== NO_CHANGE) {
|
||||
updateDirectiveInputValue(context, lView, tNode, bindingIndex, styles, false);
|
||||
styles = NO_CHANGE;
|
||||
}
|
||||
|
||||
const updated = _stylingMap(index, context, bindingIndex, styles, false, deferStylingUpdate());
|
||||
const updated = _stylingMap(index, context, bindingIndex, styles, false);
|
||||
if (ngDevMode) {
|
||||
ngDevMode.styleMap++;
|
||||
if (updated) {
|
||||
|
@ -254,12 +265,17 @@ export function ɵɵclassMap(classes: {[className: string]: any} | NO_CHANGE | s
|
|||
classMapInternal(getSelectedIndex(), classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function for applying a class string or key/value map of classes to an element.
|
||||
*
|
||||
* The reason why this function has been separated from `ɵɵclassMap` is because
|
||||
* it is also called from `ɵɵclassMapInterpolate`.
|
||||
*/
|
||||
export function classMapInternal(
|
||||
elementIndex: number, classes: {[className: string]: any} | NO_CHANGE | string | null) {
|
||||
elementIndex: number, classes: {[className: string]: any} | NO_CHANGE | string | null): void {
|
||||
const lView = getLView();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const context = getClassesContext(tNode);
|
||||
const directiveIndex = getActiveDirectiveStylingIndex();
|
||||
|
||||
// if a value is interpolated then it may render a `NO_CHANGE` value.
|
||||
// in this case we do not need to do anything, but the binding index
|
||||
|
@ -270,13 +286,12 @@ export function classMapInternal(
|
|||
// inputs are only evaluated from a template binding into a directive, therefore,
|
||||
// there should not be a situation where a directive host bindings function
|
||||
// evaluates the inputs (this should only happen in the template function)
|
||||
if (!directiveIndex && hasClassInput(tNode) && classes !== NO_CHANGE) {
|
||||
if (!isHostStyling() && hasClassInput(tNode) && classes !== NO_CHANGE) {
|
||||
updateDirectiveInputValue(context, lView, tNode, bindingIndex, classes, true);
|
||||
classes = NO_CHANGE;
|
||||
}
|
||||
|
||||
const updated =
|
||||
_stylingMap(elementIndex, context, bindingIndex, classes, true, deferStylingUpdate());
|
||||
const updated = _stylingMap(elementIndex, context, bindingIndex, classes, true);
|
||||
if (ngDevMode) {
|
||||
ngDevMode.classMap++;
|
||||
if (updated) {
|
||||
|
@ -293,27 +308,52 @@ export function classMapInternal(
|
|||
*/
|
||||
function _stylingMap(
|
||||
elementIndex: number, context: TStylingContext, bindingIndex: number,
|
||||
value: {[key: string]: any} | string | null, isClassBased: boolean, defer: boolean) {
|
||||
activateStylingMapFeature();
|
||||
const lView = getLView();
|
||||
value: {[key: string]: any} | string | null, isClassBased: boolean): boolean {
|
||||
let updated = false;
|
||||
|
||||
const lView = getLView();
|
||||
const directiveIndex = getActiveDirectiveId();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const oldValue = lView[bindingIndex];
|
||||
const oldValue = lView[bindingIndex] as StylingMapArray | null;
|
||||
const hostBindingsMode = isHostStyling();
|
||||
const sanitizer = getCurrentStyleSanitizer();
|
||||
|
||||
const valueHasChanged = hasValueChanged(oldValue, value);
|
||||
const stylingMapArr =
|
||||
value === NO_CHANGE ? NO_CHANGE : normalizeIntoStylingMap(oldValue, value, !isClassBased);
|
||||
if (isClassBased) {
|
||||
updateClassBinding(
|
||||
context, lView, native, null, bindingIndex, stylingMapArr, defer, valueHasChanged);
|
||||
|
||||
// Direct Apply Case: bypass context resolution and apply the
|
||||
// style/class map values directly to the element
|
||||
if (allowDirectStyling(context, hostBindingsMode)) {
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
updated = applyStylingMapDirectly(
|
||||
renderer, context, native, lView, bindingIndex, stylingMapArr as StylingMapArray,
|
||||
isClassBased ? setClass : setStyle, sanitizer, valueHasChanged);
|
||||
} else {
|
||||
const sanitizer = getCurrentStyleSanitizer();
|
||||
updateStyleBinding(
|
||||
context, lView, native, null, bindingIndex, stylingMapArr, sanitizer, defer,
|
||||
valueHasChanged);
|
||||
// Context Resolution (or first update) Case: save the map value
|
||||
// and defer to the context to flush and apply the style/class binding
|
||||
// value to the element.
|
||||
if (isClassBased) {
|
||||
updateClassViaContext(
|
||||
context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr,
|
||||
valueHasChanged);
|
||||
} else {
|
||||
updateStyleViaContext(
|
||||
context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, sanitizer,
|
||||
valueHasChanged);
|
||||
}
|
||||
|
||||
if (valueHasChanged) {
|
||||
updated = true;
|
||||
setElementExitFn(applyStyling);
|
||||
}
|
||||
|
||||
activateStylingMapFeature();
|
||||
}
|
||||
|
||||
return valueHasChanged;
|
||||
markStylingStateAsDirty();
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -338,13 +378,15 @@ function updateDirectiveInputValue(
|
|||
// even if the value has changed we may not want to emit it to the
|
||||
// directive input(s) in the event that it is falsy during the
|
||||
// first update pass.
|
||||
if (newValue || isContextLocked(context)) {
|
||||
const inputs = tNode.inputs ![isClassBased ? 'class' : 'style'] !;
|
||||
if (newValue || isContextLocked(context, false)) {
|
||||
const inputName = isClassBased ? 'class' : 'style';
|
||||
const inputs = tNode.inputs ![inputName] !;
|
||||
const initialValue = getInitialStylingValue(context);
|
||||
const value = normalizeStylingDirectiveInputValue(initialValue, newValue, isClassBased);
|
||||
setInputsForProperty(lView, inputs, value);
|
||||
}
|
||||
lView[bindingIndex] = newValue;
|
||||
setValue(lView, bindingIndex, newValue);
|
||||
setElementExitFn(applyStyling);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,7 +404,7 @@ function normalizeStylingDirectiveInputValue(
|
|||
|
||||
// we only concat values if there is an initial value, otherwise we return the value as is.
|
||||
// Note that this is to satisfy backwards-compatibility in Angular.
|
||||
if (initialValue.length > 0) {
|
||||
if (initialValue.length) {
|
||||
if (isClassBased) {
|
||||
value = concatString(initialValue, forceClassesAsString(bindingValue));
|
||||
} else {
|
||||
|
@ -377,24 +419,21 @@ function normalizeStylingDirectiveInputValue(
|
|||
/**
|
||||
* Flushes all styling code to the element.
|
||||
*
|
||||
* This function is designed to be called from the template and hostBindings
|
||||
* functions and may be called multiple times depending whether multiple
|
||||
* sources of styling exist. If called multiple times, only the last call
|
||||
* to `stlyingApply()` will render styling to the element.
|
||||
*
|
||||
* @codeGenApi
|
||||
* This function is designed to be scheduled from any of the four styling instructions
|
||||
* in this file. When called it will flush all style and class bindings to the element
|
||||
* via the context resolution algorithm.
|
||||
*/
|
||||
export function ɵɵstylingApply() {
|
||||
function applyStyling(): void {
|
||||
const elementIndex = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const tNode = getTNode(elementIndex, lView);
|
||||
const renderer = getRenderer(tNode, lView);
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
const directiveIndex = getActiveDirectiveStylingIndex();
|
||||
const sanitizer = getCurrentStyleSanitizer();
|
||||
const classesContext = isStylingContext(tNode.classes) ? tNode.classes as TStylingContext : null;
|
||||
const stylesContext = isStylingContext(tNode.styles) ? tNode.styles as TStylingContext : null;
|
||||
flushStyling(
|
||||
renderer, lView, getClassesContext(tNode), getStylesContext(tNode), native, directiveIndex,
|
||||
sanitizer);
|
||||
renderer, lView, classesContext, stylesContext, native, getActiveDirectiveId(), sanitizer);
|
||||
setCurrentStyleSanitizer(null);
|
||||
}
|
||||
|
||||
|
@ -417,12 +456,12 @@ export function registerInitialStylingOnTNode(
|
|||
if (typeof attr == 'number') {
|
||||
mode = attr;
|
||||
} else if (mode == AttributeMarker.Classes) {
|
||||
classes = classes || [''];
|
||||
classes = classes || allocStylingMapArray();
|
||||
addItemToStylingMap(classes, attr, true);
|
||||
hasAdditionalInitialStyling = true;
|
||||
} else if (mode == AttributeMarker.Styles) {
|
||||
const value = attrs[++i] as string | null;
|
||||
styles = styles || [''];
|
||||
styles = styles || allocStylingMapArray();
|
||||
addItemToStylingMap(styles, attr, value);
|
||||
hasAdditionalInitialStyling = true;
|
||||
}
|
||||
|
@ -450,33 +489,6 @@ function updateRawValueOnContext(context: TStylingContext | StylingMapArray, val
|
|||
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value;
|
||||
}
|
||||
|
||||
export function getActiveDirectiveStylingIndex(): number {
|
||||
// whenever a directive's hostBindings function is called a uniqueId value
|
||||
// is assigned. Normally this is enough to help distinguish one directive
|
||||
// from another for the styling context, but there are situations where a
|
||||
// sub-class directive could inherit and assign styling in concert with a
|
||||
// parent directive. To help the styling code distinguish between a parent
|
||||
// sub-classed directive the inheritance depth is taken into account as well.
|
||||
return getActiveDirectiveId() + getActiveDirectiveSuperClassDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary function that will update the max directive index value in
|
||||
* both the classes and styles contexts present on the provided `tNode`.
|
||||
*
|
||||
* This code is only used because the `select(n)` code functionality is not
|
||||
* yet 100% functional. The `select(n)` instruction cannot yet evaluate host
|
||||
* bindings function code in sync with the associated template function code.
|
||||
* For this reason the styling algorithm needs to track the last directive index
|
||||
* value so that it knows exactly when to render styling to the element since
|
||||
* `stylingApply()` is called multiple times per CD (`stylingApply` will be
|
||||
* removed once `select(n)` is fixed).
|
||||
*/
|
||||
function updateLastDirectiveIndex(tNode: TNode, directiveIndex: number) {
|
||||
_updateLastDirectiveIndex(getClassesContext(tNode), directiveIndex);
|
||||
_updateLastDirectiveIndex(getStylesContext(tNode), directiveIndex);
|
||||
}
|
||||
|
||||
function getStylesContext(tNode: TNode): TStylingContext {
|
||||
return getContext(tNode, false);
|
||||
}
|
||||
|
@ -488,10 +500,10 @@ function getClassesContext(tNode: TNode): TStylingContext {
|
|||
/**
|
||||
* Returns/instantiates a styling context from/to a `tNode` instance.
|
||||
*/
|
||||
function getContext(tNode: TNode, isClassBased: boolean) {
|
||||
function getContext(tNode: TNode, isClassBased: boolean): TStylingContext {
|
||||
let context = isClassBased ? tNode.classes : tNode.styles;
|
||||
if (!isStylingContext(context)) {
|
||||
context = allocTStylingContext(context);
|
||||
context = allocTStylingContext(context as StylingMapArray | null);
|
||||
if (ngDevMode) {
|
||||
attachStylingDebugObject(context as TStylingContext);
|
||||
}
|
||||
|
@ -526,18 +538,14 @@ function resolveStylePropValue(
|
|||
return resolvedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a style/class binding update should be applied later.
|
||||
*
|
||||
* This function will decide whether a binding should be applied immediately
|
||||
* or later (just before the styles/classes are flushed to the element). The
|
||||
* reason why this feature exists is because of super/sub directive inheritance.
|
||||
* Angular will evaluate host bindings on the super directive first and the sub
|
||||
* directive, but the styling bindings on the sub directive are of higher priority
|
||||
* than the super directive. For this reason all styling bindings that take place
|
||||
* in this circumstance will need to be deferred until later so that they can be
|
||||
* applied together and in a different order (the algorithm handles that part).
|
||||
*/
|
||||
function deferStylingUpdate(): boolean {
|
||||
return getActiveDirectiveSuperClassHeight() > 0;
|
||||
function markStylingStateAsDirty(): void {
|
||||
setActiveElementFlag(ActiveElementFlags.ResetStylesOnExit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the style/class binding being applied was executed within a host bindings
|
||||
* function.
|
||||
*/
|
||||
function isHostStyling(): boolean {
|
||||
return isHostStylingActive(getActiveDirectiveId());
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ import {LView} from '../interfaces/view';
|
|||
* The `TStylingContext` unites all template styling bindings (i.e.
|
||||
* `[class]` and `[style]` bindings) as well as all host-level
|
||||
* styling bindings (for components and directives) together into
|
||||
* a single manifest. It is used each time there are one or more
|
||||
* styling bindings present for an element.
|
||||
* a single manifest
|
||||
*
|
||||
* The styling context is stored on a `TNode` on and there are
|
||||
* two instances of it: one for classes and another for styles.
|
||||
|
@ -37,6 +36,10 @@ import {LView} from '../interfaces/view';
|
|||
* tNode.classes = [ ... a context only for classes ... ];
|
||||
* ```
|
||||
*
|
||||
* The styling context is created each time there are one or more
|
||||
* styling bindings (style or class bindings) present for an element,
|
||||
* but is only created once per `TNode`.
|
||||
*
|
||||
* `tNode.styles` and `tNode.classes` can be an instance of the following:
|
||||
*
|
||||
* ```typescript
|
||||
|
@ -62,36 +65,48 @@ import {LView} from '../interfaces/view';
|
|||
* storing actual styling binding values, the lView binding index values
|
||||
* are stored within the context. (static nature means it is more compact.)
|
||||
*
|
||||
* The code below shows a breakdown of two instances of `TStylingContext`
|
||||
* (one for `tNode.styles` and another for `tNode.classes`):
|
||||
*
|
||||
* ```typescript
|
||||
* // <div [class.active]="c" // lView binding index = 20
|
||||
* // [style.width]="x" // lView binding index = 21
|
||||
* // [style.height]="y"> // lView binding index = 22
|
||||
* tNode.stylesContext = [
|
||||
* [], // initial values array
|
||||
* 0, // the context config value
|
||||
* // ...
|
||||
* // </div>
|
||||
* tNode.styles = [
|
||||
* 0, // the context config value (see `TStylingContextConfig`)
|
||||
* 1, // the total amount of sources present (only `1` b/c there are only template
|
||||
* bindings)
|
||||
* [null], // initial values array (an instance of `StylingMapArray`)
|
||||
*
|
||||
* 0b001, // guard mask for width
|
||||
* 2, // total entries for width
|
||||
* 'width', // the property name
|
||||
* 21, // the binding location for the "x" binding in the lView
|
||||
* null,
|
||||
* 0, // config entry for the property (see `TStylingContextPropConfigFlags`)
|
||||
* 0b010, // template guard mask for height
|
||||
* 0, // host bindings guard mask for height
|
||||
* 'height', // the property name
|
||||
* 22, // the binding location for the "y" binding in the lView
|
||||
* null, // the default value for height
|
||||
*
|
||||
* 0b010, // guard mask for height
|
||||
* 2, // total entries for height
|
||||
* 'height', // the property name
|
||||
* 22, // the binding location for the "y" binding in the lView
|
||||
* null,
|
||||
* 0, // config entry for the property (see `TStylingContextPropConfigFlags`)
|
||||
* 0b001, // template guard mask for width
|
||||
* 0, // host bindings guard mask for width
|
||||
* 'width', // the property name
|
||||
* 21, // the binding location for the "x" binding in the lView
|
||||
* null, // the default value for width
|
||||
* ];
|
||||
*
|
||||
* tNode.classesContext = [
|
||||
* [], // initial values array
|
||||
* 0, // the context config value
|
||||
* tNode.classes = [
|
||||
* 0, // the context config value (see `TStylingContextConfig`)
|
||||
* 1, // the total amount of sources present (only `1` b/c there are only template
|
||||
* bindings)
|
||||
* [null], // initial values array (an instance of `StylingMapArray`)
|
||||
*
|
||||
* 0b001, // guard mask for active
|
||||
* 2, // total entries for active
|
||||
* 'active', // the property name
|
||||
* 20, // the binding location for the "c" binding in the lView
|
||||
* null,
|
||||
* 0, // config entry for the property (see `TStylingContextPropConfigFlags`)
|
||||
* 0b001, // template guard mask for width
|
||||
* 0, // host bindings guard mask for width
|
||||
* 'active', // the property name
|
||||
* 20, // the binding location for the "c" binding in the lView
|
||||
* null, // the default value for the `active` class
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
|
@ -100,19 +115,25 @@ import {LView} from '../interfaces/view';
|
|||
*
|
||||
* ```typescript
|
||||
* context = [
|
||||
* CONFIG, // the styling context config value
|
||||
* //...
|
||||
* guardMask,
|
||||
* totalEntries,
|
||||
* configValue,
|
||||
* templateGuardMask,
|
||||
* hostBindingsGuardMask,
|
||||
* propName,
|
||||
* bindingIndices...,
|
||||
* ...bindingIndices...,
|
||||
* defaultValue
|
||||
* //...
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* Below is a breakdown of each value:
|
||||
*
|
||||
* - **guardMask**:
|
||||
* - **configValue**:
|
||||
* Property-specific configuration values. The only config setting
|
||||
* that is implemented right now is whether or not to sanitize the
|
||||
* value.
|
||||
*
|
||||
* - **templateGuardMask**:
|
||||
* A numeric value where each bit represents a binding index
|
||||
* location. Each binding index location is assigned based on
|
||||
* a local counter value that increments each time an instruction
|
||||
|
@ -136,18 +157,23 @@ import {LView} from '../interfaces/view';
|
|||
* efficient way to flip all bits on the mask when a special kind
|
||||
* of caching scenario occurs or when there are more than 32 bindings).
|
||||
*
|
||||
* - **totalEntries**:
|
||||
* Each property present in the contains various binding sources of
|
||||
* where the styling data could come from. This includes template
|
||||
* level bindings, directive/component host bindings as well as the
|
||||
* default value (or static value) all writing to the same property.
|
||||
* This value depicts how many binding source entries exist for the
|
||||
* property.
|
||||
* - **hostBindingsGuardMask**:
|
||||
* Another instance of a guard mask that is specific to host bindings.
|
||||
* This behaves exactly the same way as does the `templateGuardMask`,
|
||||
* but will not contain any binding information processed in the template.
|
||||
* The reason why there are two instances of guard masks (one for the
|
||||
* template and another for host bindings) is because the template bindings
|
||||
* are processed before host bindings and the state information is not
|
||||
* carried over into the host bindings code. As soon as host bindings are
|
||||
* processed for an element the counter and state-based bit mask values are
|
||||
* set to `0`.
|
||||
*
|
||||
* The reason why the totalEntries value is needed is because the
|
||||
* styling context is dynamic in size and it's not possible
|
||||
* for the flushing or update algorithms to know when and where
|
||||
* a property starts and ends without it.
|
||||
* ```
|
||||
* <div [style.width]="x" // binding index = 21 (counter index = 0)
|
||||
* [style.height]="y" // binding index = 22 (counter index = 1)
|
||||
* dir-that-sets-width // binding index = 30 (counter index = 0)
|
||||
* dir-that-sets-width> // binding index = 31 (counter index = 1)
|
||||
* ```
|
||||
*
|
||||
* - **propName**:
|
||||
* The CSS property name or class name (e.g `width` or `active`).
|
||||
|
@ -165,15 +191,20 @@ import {LView} from '../interfaces/view';
|
|||
* - **defaultValue**:
|
||||
* This is the default that will always be applied to the element if
|
||||
* and when all other binding sources return a result that is null.
|
||||
* Usually this value is null but it can also be a static value that
|
||||
* Usually this value is `null` but it can also be a static value that
|
||||
* is intercepted when the tNode is first constructured (e.g.
|
||||
* `<div style="width:200px">` has a default value of `200px` for
|
||||
* the `width` property).
|
||||
*
|
||||
* Each time a new binding is encountered it is registered into the
|
||||
* context. The context then is continually updated until the first
|
||||
* styling apply call has been called (this is triggered by the
|
||||
* `stylingApply()` instruction for the active element).
|
||||
* styling apply call has been called (which is automatically scheduled
|
||||
* to be called once an element exits during change detection). Note that
|
||||
* each entry in the context is stored in alphabetical order.
|
||||
*
|
||||
* Once styling has been flushed for the first time for an element the
|
||||
* context will set as locked (this prevents bindings from being added
|
||||
* to the context later on).
|
||||
*
|
||||
* # How Styles/Classes are Rendered
|
||||
* Each time a styling instruction (e.g. `[class.name]`, `[style.prop]`,
|
||||
|
@ -190,15 +221,24 @@ import {LView} from '../interfaces/view';
|
|||
* function updateStyleProp(prop: string, value: string) {
|
||||
* const lView = getLView();
|
||||
* const bindingIndex = BINDING_INDEX++;
|
||||
* const indexForStyle = localStylesCounter++;
|
||||
*
|
||||
* // update the local counter value
|
||||
* const indexForStyle = stylingState.stylesCount++;
|
||||
* if (lView[bindingIndex] !== value) {
|
||||
* lView[bindingIndex] = value;
|
||||
* localBitMaskForStyles |= 1 << indexForStyle;
|
||||
*
|
||||
* // tell the local state that we have updated a style value
|
||||
* // by updating the bit mask
|
||||
* stylingState.bitMaskForStyles |= 1 << indexForStyle;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## The Apply Algorithm
|
||||
* Once all the bindings have updated a `bitMask` value will be populated.
|
||||
* This `bitMask` value is used in the apply algorithm (which is called
|
||||
* context resolution).
|
||||
*
|
||||
* ## The Apply Algorithm (Context Resolution)
|
||||
* As explained above, each time a binding updates its value, the resulting
|
||||
* value is stored in the `lView` array. These styling values have yet to
|
||||
* be flushed to the element.
|
||||
|
@ -283,94 +323,147 @@ import {LView} from '../interfaces/view';
|
|||
*/
|
||||
export interface TStylingContext extends
|
||||
Array<number|string|number|boolean|null|StylingMapArray|{}> {
|
||||
/** Configuration data for the context */
|
||||
[TStylingContextIndex.ConfigPosition]: TStylingConfig;
|
||||
|
||||
/** The total amount of sources present in the context */
|
||||
[TStylingContextIndex.TotalSourcesPosition]: number;
|
||||
|
||||
/** Initial value position for static styles */
|
||||
[TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray;
|
||||
|
||||
/** Configuration data for the context */
|
||||
[TStylingContextIndex.ConfigPosition]: TStylingConfigFlags;
|
||||
|
||||
/** Temporary value used to track directive index entries until
|
||||
the old styling code is fully removed. The reason why this
|
||||
is required is to figure out which directive is last and,
|
||||
when encountered, trigger a styling flush to happen */
|
||||
[TStylingContextIndex.LastDirectiveIndexPosition]: number;
|
||||
|
||||
/** The bit guard value for all map-based bindings on an element */
|
||||
[TStylingContextIndex.MapBindingsBitGuardPosition]: number;
|
||||
|
||||
/** The total amount of map-based bindings present on an element */
|
||||
[TStylingContextIndex.MapBindingsValuesCountPosition]: number;
|
||||
|
||||
/** The prop value for map-based bindings (there actually isn't a
|
||||
* value at all, but this is just used in the context to avoid
|
||||
* having any special code to update the binding information for
|
||||
* map-based entries). */
|
||||
[TStylingContextIndex.MapBindingsPropPosition]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A series of flags used to configure the config value present within a
|
||||
* `TStylingContext` value.
|
||||
* A series of flags used to configure the config value present within an instance of
|
||||
* `TStylingContext`.
|
||||
*/
|
||||
export const enum TStylingConfigFlags {
|
||||
export const enum TStylingConfig {
|
||||
/**
|
||||
* The initial state of the styling context config
|
||||
* The initial state of the styling context config.
|
||||
*/
|
||||
Initial = 0b0,
|
||||
Initial = 0b0000000,
|
||||
|
||||
/**
|
||||
* A flag which marks the context as being locked.
|
||||
* Whether or not there are prop-based bindings present.
|
||||
*
|
||||
* The styling context is constructed across an element template
|
||||
* function as well as any associated hostBindings functions. When
|
||||
* this occurs, the context itself is open to mutation and only once
|
||||
* it has been flushed once then it will be locked for good (no extra
|
||||
* bindings can be added to it).
|
||||
* Examples include:
|
||||
* 1. `<div [style.prop]="x">`
|
||||
* 2. `<div [class.prop]="x">`
|
||||
* 3. `@HostBinding('style.prop') x`
|
||||
* 4. `@HostBinding('class.prop') x`
|
||||
*/
|
||||
Locked = 0b1,
|
||||
HasPropBindings = 0b0000001,
|
||||
|
||||
/**
|
||||
* Whether or not to store the state between updates in a global storage map.
|
||||
* Whether or not there are map-based bindings present.
|
||||
*
|
||||
* This flag helps the algorithm avoid storing all state values temporarily in
|
||||
* a storage map (that lives in `state.ts`). The flag is only flipped to true if
|
||||
* and when an element contains style/class bindings that exist both on the
|
||||
* template-level as well as within host bindings on the same element. This is a
|
||||
* rare case, and a storage map is required so that the state values can be restored
|
||||
* between the template code running and the host binding code executing.
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x">`
|
||||
* 2. `<div [class]="x">`
|
||||
* 3. `@HostBinding('style') x`
|
||||
* 4. `@HostBinding('class') x`
|
||||
*/
|
||||
PersistStateValues = 0b10,
|
||||
HasMapBindings = 0b0000010,
|
||||
|
||||
/**
|
||||
* Whether or not there are map-based and prop-based bindings present.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x" [style.prop]="y">`
|
||||
* 2. `<div [class]="x" [style.prop]="y">`
|
||||
* 3. `<div [style]="x" dir-that-sets-some-prop>`
|
||||
* 4. `<div [class]="x" dir-that-sets-some-class>`
|
||||
*/
|
||||
HasPropAndMapBindings = 0b0000011,
|
||||
|
||||
/**
|
||||
* Whether or not there are two or more sources for a single property in the context.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. prop + prop: `<div [style.width]="x" dir-that-sets-width>`
|
||||
* 2. map + prop: `<div [style]="x" [style.prop]>`
|
||||
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
|
||||
*/
|
||||
HasCollisions = 0b0000100,
|
||||
|
||||
/**
|
||||
* Whether or not the context contains initial styling values.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div style="width:200px">`
|
||||
* 2. `<div class="one two three">`
|
||||
* 3. `@Directive({ host: { 'style': 'width:200px' } })`
|
||||
* 4. `@Directive({ host: { 'class': 'one two three' } })`
|
||||
*/
|
||||
HasInitialStyling = 0b00001000,
|
||||
|
||||
/**
|
||||
* Whether or not the context contains one or more template bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `<div [style]="x">`
|
||||
* 2. `<div [style.width]="x">`
|
||||
* 3. `<div [class]="x">`
|
||||
* 4. `<div [class.name]="x">`
|
||||
*/
|
||||
HasTemplateBindings = 0b00010000,
|
||||
|
||||
/**
|
||||
* Whether or not the context contains one or more host bindings.
|
||||
*
|
||||
* Examples include:
|
||||
* 1. `@HostBinding('style') x`
|
||||
* 2. `@HostBinding('style.width') x`
|
||||
* 3. `@HostBinding('class') x`
|
||||
* 4. `@HostBinding('class.name') x`
|
||||
*/
|
||||
HasHostBindings = 0b00100000,
|
||||
|
||||
/**
|
||||
* Whether or not the template bindings are allowed to be registered in the context.
|
||||
*
|
||||
* This flag is after one or more template-based style/class bindings were
|
||||
* set and processed for an element. Once the bindings are processed then a call
|
||||
* to stylingApply is issued and the lock will be put into place.
|
||||
*
|
||||
* Note that this is only set once.
|
||||
*/
|
||||
TemplateBindingsLocked = 0b01000000,
|
||||
|
||||
/**
|
||||
* Whether or not the host bindings are allowed to be registered in the context.
|
||||
*
|
||||
* This flag is after one or more host-based style/class bindings were
|
||||
* set and processed for an element. Once the bindings are processed then a call
|
||||
* to stylingApply is issued and the lock will be put into place.
|
||||
*
|
||||
* Note that this is only set once.
|
||||
*/
|
||||
HostBindingsLocked = 0b10000000,
|
||||
|
||||
/** A Mask of all the configurations */
|
||||
Mask = 0b11,
|
||||
Mask = 0b11111111,
|
||||
|
||||
/** Total amount of configuration bits used */
|
||||
TotalBits = 2,
|
||||
TotalBits = 8,
|
||||
}
|
||||
|
||||
/**
|
||||
* An index of position and offset values used to natigate the `TStylingContext`.
|
||||
* An index of position and offset values used to navigate the `TStylingContext`.
|
||||
*/
|
||||
export const enum TStylingContextIndex {
|
||||
InitialStylingValuePosition = 0,
|
||||
ConfigPosition = 1,
|
||||
LastDirectiveIndexPosition = 2,
|
||||
|
||||
// index/offset values for map-based entries (i.e. `[style]`
|
||||
// and `[class]` bindings).
|
||||
MapBindingsPosition = 3,
|
||||
MapBindingsBitGuardPosition = 3,
|
||||
MapBindingsValuesCountPosition = 4,
|
||||
MapBindingsPropPosition = 5,
|
||||
MapBindingsBindingsStartPosition = 6,
|
||||
ConfigPosition = 0,
|
||||
TotalSourcesPosition = 1,
|
||||
InitialStylingValuePosition = 2,
|
||||
ValuesStartPosition = 3,
|
||||
|
||||
// each tuple entry in the context
|
||||
// (mask, count, prop, ...bindings||default-value)
|
||||
ConfigAndGuardOffset = 0,
|
||||
ValuesCountOffset = 1,
|
||||
PropOffset = 2,
|
||||
BindingsStartOffset = 3,
|
||||
MinTupleLength = 4,
|
||||
// (config, templateBitGuard, hostBindingBitGuard, prop, ...bindings||default-value)
|
||||
ConfigOffset = 0,
|
||||
TemplateBitGuardOffset = 1,
|
||||
HostBindingsBitGuardOffset = 2,
|
||||
PropOffset = 3,
|
||||
BindingsStartOffset = 4
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -387,8 +480,8 @@ export const enum TStylingContextPropConfigFlags {
|
|||
* A function used to apply or remove styling from an element for a given property.
|
||||
*/
|
||||
export interface ApplyStylingFn {
|
||||
(renderer: Renderer3|ProceduralRenderer3|null, element: RElement, prop: string,
|
||||
value: string|null, bindingIndex?: number|null): void;
|
||||
(renderer: Renderer3|ProceduralRenderer3|null, element: RElement, prop: string, value: any,
|
||||
bindingIndex?: number|null): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -419,12 +512,12 @@ export interface StylingMapArray extends Array<{}|string|number|null> {
|
|||
* An index of position and offset points for any data stored within a `StylingMapArray` instance.
|
||||
*/
|
||||
export const enum StylingMapArrayIndex {
|
||||
/** The location of the raw key/value map instance used last to populate the array entries */
|
||||
RawValuePosition = 0,
|
||||
|
||||
/** Where the values start in the array */
|
||||
ValuesStartPosition = 1,
|
||||
|
||||
/** The location of the raw key/value map instance used last to populate the array entries */
|
||||
RawValuePosition = 0,
|
||||
|
||||
/** The size of each property/value entry */
|
||||
TupleSize = 2,
|
||||
|
||||
|
@ -458,8 +551,9 @@ export const enum StylingMapArrayIndex {
|
|||
*/
|
||||
export interface SyncStylingMapsFn {
|
||||
(context: TStylingContext, renderer: Renderer3|ProceduralRenderer3|null, element: RElement,
|
||||
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn|null,
|
||||
mode: StylingMapsSyncMode, targetProp?: string|null, defaultValue?: string|null): boolean;
|
||||
data: LStylingData, sourceIndex: number, applyStylingFn: ApplyStylingFn,
|
||||
sanitizer: StyleSanitizeFn|null, mode: StylingMapsSyncMode, targetProp?: string|null,
|
||||
defaultValue?: boolean|string|null): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -477,4 +571,10 @@ export const enum StylingMapsSyncMode {
|
|||
|
||||
/** Skip applying the target prop/value entry */
|
||||
SkipTargetProp = 0b100,
|
||||
|
||||
/** Iterate over inner maps map values in the context */
|
||||
RecurseInnerMaps = 0b1000,
|
||||
|
||||
/** Only check to see if a value was set somewhere in each map */
|
||||
CheckValuesOnly = 0b10000,
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
* 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 {unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
|
||||
|
||||
import {setStylingMapsSyncFn} from './bindings';
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex} from './interfaces';
|
||||
import {concatString, getBindingValue, getMapProp, getMapValue, getValuesCount, hyphenate, isStylingValueDefined, setMapValue} from './util';
|
||||
import {getBindingValue, getMapProp, getMapValue, getValue, getValuesCount, isStylingValueDefined} from './util';
|
||||
|
||||
|
||||
|
||||
|
@ -23,6 +24,13 @@ import {concatString, getBindingValue, getMapProp, getMapValue, getValuesCount,
|
|||
* --------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enables support for map-based styling bindings (e.g. `[style]` and `[class]` bindings).
|
||||
*/
|
||||
export function activateStylingMapFeature() {
|
||||
setStylingMapsSyncFn(syncStylingMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to apply styling values presently within any map-based bindings on an element.
|
||||
*
|
||||
|
@ -53,7 +61,7 @@ import {concatString, getBindingValue, getMapProp, getMapValue, getValuesCount,
|
|||
* value is marked as dirty.
|
||||
*
|
||||
* Styling values are applied once CD exits the element (which happens when
|
||||
* the `select(n)` instruction is called or the template function exits). When
|
||||
* the `advance(n)` instruction is called or the template function exits). When
|
||||
* this occurs, all prop-based bindings are applied. If a map-based binding is
|
||||
* present then a special flushing function (called a sync function) is made
|
||||
* available and it will be called each time a styling property is flushed.
|
||||
|
@ -105,14 +113,14 @@ import {concatString, getBindingValue, getMapProp, getMapValue, getValuesCount,
|
|||
*/
|
||||
export const syncStylingMap: SyncStylingMapsFn =
|
||||
(context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
|
||||
mode: StylingMapsSyncMode, targetProp?: string | null,
|
||||
defaultValue?: string | null): boolean => {
|
||||
data: LStylingData, sourceIndex: number, applyStylingFn: ApplyStylingFn,
|
||||
sanitizer: StyleSanitizeFn | null, mode: StylingMapsSyncMode, targetProp?: string | null,
|
||||
defaultValue?: string | boolean | null): boolean => {
|
||||
let targetPropValueWasApplied = false;
|
||||
|
||||
// once the map-based styling code is activate it is never deactivated. For this reason a
|
||||
// check to see if the current styling context has any map based bindings is required.
|
||||
const totalMaps = getValuesCount(context, TStylingContextIndex.MapBindingsPosition);
|
||||
const totalMaps = getValuesCount(context);
|
||||
if (totalMaps) {
|
||||
let runTheSyncAlgorithm = true;
|
||||
const loopUntilEnd = !targetProp;
|
||||
|
@ -121,7 +129,7 @@ export const syncStylingMap: SyncStylingMapsFn =
|
|||
// hasn't been flagged to apply values (it only traverses values) then
|
||||
// there is no point in iterating over the array because nothing will
|
||||
// be applied to the element.
|
||||
if (loopUntilEnd && (mode & ~StylingMapsSyncMode.ApplyAllValues)) {
|
||||
if (loopUntilEnd && (mode & StylingMapsSyncMode.ApplyAllValues) === 0) {
|
||||
runTheSyncAlgorithm = false;
|
||||
targetPropValueWasApplied = true;
|
||||
}
|
||||
|
@ -129,7 +137,7 @@ export const syncStylingMap: SyncStylingMapsFn =
|
|||
if (runTheSyncAlgorithm) {
|
||||
targetPropValueWasApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp || null,
|
||||
0, defaultValue || null);
|
||||
sourceIndex, defaultValue || null);
|
||||
}
|
||||
|
||||
if (loopUntilEnd) {
|
||||
|
@ -153,83 +161,104 @@ function innerSyncStylingMap(
|
|||
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
|
||||
mode: StylingMapsSyncMode, targetProp: string | null, currentMapIndex: number,
|
||||
defaultValue: string | null): boolean {
|
||||
defaultValue: string | boolean | null): boolean {
|
||||
const totalMaps = getValuesCount(context) - 1; // maps have no default value
|
||||
const mapsLimit = totalMaps - 1;
|
||||
const recurseInnerMaps =
|
||||
currentMapIndex < mapsLimit && (mode & StylingMapsSyncMode.RecurseInnerMaps) !== 0;
|
||||
const checkValuesOnly = (mode & StylingMapsSyncMode.CheckValuesOnly) !== 0;
|
||||
|
||||
if (checkValuesOnly) {
|
||||
// inner modes do not check values ever (that can only happen
|
||||
// when sourceIndex === 0)
|
||||
mode &= ~StylingMapsSyncMode.CheckValuesOnly;
|
||||
}
|
||||
|
||||
let targetPropValueWasApplied = false;
|
||||
const totalMaps = getValuesCount(context, TStylingContextIndex.MapBindingsPosition);
|
||||
if (currentMapIndex < totalMaps) {
|
||||
const bindingIndex = getBindingValue(
|
||||
context, TStylingContextIndex.MapBindingsPosition, currentMapIndex) as number;
|
||||
const stylingMapArr = data[bindingIndex] as StylingMapArray;
|
||||
|
||||
if (currentMapIndex <= mapsLimit) {
|
||||
let cursor = getCurrentSyncCursor(currentMapIndex);
|
||||
while (cursor < stylingMapArr.length) {
|
||||
const prop = getMapProp(stylingMapArr, cursor);
|
||||
const iteratedTooFar = targetProp && prop > targetProp;
|
||||
const isTargetPropMatched = !iteratedTooFar && prop === targetProp;
|
||||
const value = getMapValue(stylingMapArr, cursor);
|
||||
const valueIsDefined = isStylingValueDefined(value);
|
||||
const bindingIndex = getBindingValue(
|
||||
context, TStylingContextIndex.ValuesStartPosition, currentMapIndex) as number;
|
||||
const stylingMapArr = getValue<StylingMapArray>(data, bindingIndex);
|
||||
|
||||
// the recursive code is designed to keep applying until
|
||||
// it reaches or goes past the target prop. If and when
|
||||
// this happens then it will stop processing values, but
|
||||
// all other map values must also catch up to the same
|
||||
// point. This is why a recursive call is still issued
|
||||
// even if the code has iterated too far.
|
||||
const innerMode =
|
||||
iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched);
|
||||
const innerProp = iteratedTooFar ? targetProp : prop;
|
||||
let valueApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp,
|
||||
currentMapIndex + 1, defaultValue);
|
||||
if (stylingMapArr) {
|
||||
while (cursor < stylingMapArr.length) {
|
||||
const prop = getMapProp(stylingMapArr, cursor);
|
||||
const iteratedTooFar = targetProp && prop > targetProp;
|
||||
const isTargetPropMatched = !iteratedTooFar && prop === targetProp;
|
||||
const value = getMapValue(stylingMapArr, cursor);
|
||||
const valueIsDefined = isStylingValueDefined(value);
|
||||
|
||||
if (iteratedTooFar) {
|
||||
if (!targetPropValueWasApplied) {
|
||||
targetPropValueWasApplied = valueApplied;
|
||||
// the recursive code is designed to keep applying until
|
||||
// it reaches or goes past the target prop. If and when
|
||||
// this happens then it will stop processing values, but
|
||||
// all other map values must also catch up to the same
|
||||
// point. This is why a recursive call is still issued
|
||||
// even if the code has iterated too far.
|
||||
const innerMode =
|
||||
iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched);
|
||||
|
||||
const innerProp = iteratedTooFar ? targetProp : prop;
|
||||
let valueApplied = recurseInnerMaps ?
|
||||
innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp,
|
||||
currentMapIndex + 1, defaultValue) :
|
||||
false;
|
||||
|
||||
if (iteratedTooFar) {
|
||||
if (!targetPropValueWasApplied) {
|
||||
targetPropValueWasApplied = valueApplied;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
if (!valueApplied && isValueAllowedToBeApplied(mode, isTargetPropMatched)) {
|
||||
valueApplied = true;
|
||||
|
||||
if (!checkValuesOnly) {
|
||||
const useDefault = isTargetPropMatched && !valueIsDefined;
|
||||
const bindingIndexToApply = isTargetPropMatched ? bindingIndex : null;
|
||||
|
||||
let finalValue: any;
|
||||
if (useDefault) {
|
||||
finalValue = defaultValue;
|
||||
} else {
|
||||
finalValue = sanitizer ?
|
||||
sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) :
|
||||
(value ? unwrapSafeValue(value) : null);
|
||||
}
|
||||
|
||||
applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply);
|
||||
}
|
||||
}
|
||||
|
||||
targetPropValueWasApplied = valueApplied && isTargetPropMatched;
|
||||
cursor += StylingMapArrayIndex.TupleSize;
|
||||
}
|
||||
setCurrentSyncCursor(currentMapIndex, cursor);
|
||||
|
||||
if (!valueApplied && isValueAllowedToBeApplied(mode, isTargetPropMatched)) {
|
||||
const useDefault = isTargetPropMatched && !valueIsDefined;
|
||||
const valueToApply = useDefault ? defaultValue : value;
|
||||
const bindingIndexToApply = useDefault ? bindingIndex : null;
|
||||
const finalValue = sanitizer ?
|
||||
sanitizer(prop, valueToApply, StyleSanitizeMode.ValidateAndSanitize) :
|
||||
valueToApply;
|
||||
applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply);
|
||||
valueApplied = true;
|
||||
// this is a fallback case in the event that the styling map is `null` for this
|
||||
// binding but there are other map-based bindings that need to be evaluated
|
||||
// afterwards. If the `prop` value is falsy then the intention is to cycle
|
||||
// through all of the properties in the remaining maps as well. If the current
|
||||
// styling map is too short then there are no values to iterate over. In either
|
||||
// case the follow-up maps need to be iterated over.
|
||||
if (recurseInnerMaps &&
|
||||
(stylingMapArr.length === StylingMapArrayIndex.ValuesStartPosition || !targetProp)) {
|
||||
targetPropValueWasApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp,
|
||||
currentMapIndex + 1, defaultValue);
|
||||
}
|
||||
|
||||
targetPropValueWasApplied = valueApplied && isTargetPropMatched;
|
||||
cursor += StylingMapArrayIndex.TupleSize;
|
||||
}
|
||||
setCurrentSyncCursor(currentMapIndex, cursor);
|
||||
|
||||
// this is a fallback case in the event that the styling map is `null` for this
|
||||
// binding but there are other map-based bindings that need to be evaluated
|
||||
// afterwards. If the `prop` value is falsy then the intention is to cycle
|
||||
// through all of the properties in the remaining maps as well. If the current
|
||||
// styling map is too short then there are no values to iterate over. In either
|
||||
// case the follow-up maps need to be iterated over.
|
||||
if (stylingMapArr.length === StylingMapArrayIndex.ValuesStartPosition || !targetProp) {
|
||||
return innerSyncStylingMap(
|
||||
} else if (recurseInnerMaps) {
|
||||
targetPropValueWasApplied = innerSyncStylingMap(
|
||||
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp,
|
||||
currentMapIndex + 1, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return targetPropValueWasApplied;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enables support for map-based styling bindings (e.g. `[style]` and `[class]` bindings).
|
||||
*/
|
||||
export function activateStylingMapFeature() {
|
||||
setStylingMapsSyncFn(syncStylingMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine the mode for the inner recursive call.
|
||||
*
|
||||
|
@ -276,8 +305,8 @@ function resolveInnerMapMode(
|
|||
* - But do not allow if the current prop is set to be skipped.
|
||||
* 2. Otherwise if the current prop is permitted then allow.
|
||||
*/
|
||||
function isValueAllowedToBeApplied(mode: number, isTargetPropMatched: boolean) {
|
||||
let doApplyValue = (mode & StylingMapsSyncMode.ApplyAllValues) > 0;
|
||||
function isValueAllowedToBeApplied(mode: StylingMapsSyncMode, isTargetPropMatched: boolean) {
|
||||
let doApplyValue = (mode & StylingMapsSyncMode.ApplyAllValues) !== 0;
|
||||
if (!doApplyValue) {
|
||||
if (mode & StylingMapsSyncMode.ApplyTargetProp) {
|
||||
doApplyValue = isTargetPropMatched;
|
||||
|
@ -320,126 +349,3 @@ function getCurrentSyncCursor(mapIndex: number) {
|
|||
function setCurrentSyncCursor(mapIndex: number, indexValue: number) {
|
||||
MAP_CURSORS[mapIndex] = indexValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert a {key:value} map into a `StylingMapArray` array.
|
||||
*
|
||||
* This function will either generate a new `StylingMapArray` instance
|
||||
* or it will patch the provided `newValues` map value into an
|
||||
* existing `StylingMapArray` value (this only happens if `bindingValue`
|
||||
* is an instance of `StylingMapArray`).
|
||||
*
|
||||
* If a new key/value map is provided with an old `StylingMapArray`
|
||||
* value then all properties will be overwritten with their new
|
||||
* values or with `null`. This means that the array will never
|
||||
* shrink in size (but it will also not be created and thrown
|
||||
* away whenever the {key:value} map entries change).
|
||||
*/
|
||||
export function normalizeIntoStylingMap(
|
||||
bindingValue: null | StylingMapArray,
|
||||
newValues: {[key: string]: any} | string | null | undefined,
|
||||
normalizeProps?: boolean): StylingMapArray {
|
||||
const stylingMapArr: StylingMapArray = Array.isArray(bindingValue) ? bindingValue : [null];
|
||||
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = newValues || null;
|
||||
|
||||
// because the new values may not include all the properties
|
||||
// that the old ones had, all values are set to `null` before
|
||||
// the new values are applied. This way, when flushed, the
|
||||
// styling algorithm knows exactly what style/class values
|
||||
// to remove from the element (since they are `null`).
|
||||
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
|
||||
j += StylingMapArrayIndex.TupleSize) {
|
||||
setMapValue(stylingMapArr, j, null);
|
||||
}
|
||||
|
||||
let props: string[]|null = null;
|
||||
let map: {[key: string]: any}|undefined|null;
|
||||
let allValuesTrue = false;
|
||||
if (typeof newValues === 'string') { // [class] bindings allow string values
|
||||
if (newValues.length) {
|
||||
props = newValues.split(/\s+/);
|
||||
allValuesTrue = true;
|
||||
}
|
||||
} else {
|
||||
props = newValues ? Object.keys(newValues) : null;
|
||||
map = newValues;
|
||||
}
|
||||
|
||||
if (props) {
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i] as string;
|
||||
const newProp = normalizeProps ? hyphenate(prop) : prop;
|
||||
const value = allValuesTrue ? true : map ![prop];
|
||||
addItemToStylingMap(stylingMapArr, newProp, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
return stylingMapArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the provided item into the provided styling array at the right spot.
|
||||
*
|
||||
* The `StylingMapArray` type is a sorted key/value array of entries. This means
|
||||
* that when a new entry is inserted it must be placed at the right spot in the
|
||||
* array. This function figures out exactly where to place it.
|
||||
*/
|
||||
export function addItemToStylingMap(
|
||||
stylingMapArr: StylingMapArray, prop: string, value: string | boolean | null,
|
||||
allowOverwrite?: boolean) {
|
||||
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
|
||||
j += StylingMapArrayIndex.TupleSize) {
|
||||
const propAtIndex = getMapProp(stylingMapArr, j);
|
||||
if (prop <= propAtIndex) {
|
||||
let applied = false;
|
||||
if (propAtIndex === prop) {
|
||||
const valueAtIndex = stylingMapArr[j];
|
||||
if (allowOverwrite || !isStylingValueDefined(valueAtIndex)) {
|
||||
applied = true;
|
||||
setMapValue(stylingMapArr, j, value);
|
||||
}
|
||||
} else {
|
||||
applied = true;
|
||||
stylingMapArr.splice(j, 0, prop, value);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
}
|
||||
|
||||
stylingMapArr.push(prop, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided styling map array into a string.
|
||||
*
|
||||
* Classes => `one two three`
|
||||
* Styles => `prop:value; prop2:value2`
|
||||
*/
|
||||
export function stylingMapToString(map: StylingMapArray, isClassBased: boolean): string {
|
||||
let str = '';
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i) as string;
|
||||
const attrValue = concatString(prop, isClassBased ? '' : value, ':');
|
||||
str = concatString(str, attrValue, isClassBased ? ' ' : '; ');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided styling map array into a key value map.
|
||||
*/
|
||||
export function stylingMapToStringMap(map: StylingMapArray | null): {[key: string]: any} {
|
||||
let stringMap: {[key: string]: any} = {};
|
||||
if (map) {
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i) as string;
|
||||
stringMap[prop] = value;
|
||||
}
|
||||
}
|
||||
return stringMap;
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 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 {RElement} from '../interfaces/renderer';
|
||||
import {TEMPLATE_DIRECTIVE_INDEX} from './util';
|
||||
|
||||
/**
|
||||
* --------
|
||||
*
|
||||
* // TODO(matsko): add updateMask info
|
||||
*
|
||||
* This file contains all state-based logic for styling in Angular.
|
||||
*
|
||||
* Styling in Angular is evaluated with a series of styling-specific
|
||||
|
@ -23,79 +23,92 @@
|
|||
* exited in change detection (once all the instructions are run for
|
||||
* that element).
|
||||
*
|
||||
* There are, however, situations where the state-based values
|
||||
* need to be stored and used at a later point. This ONLY occurs when
|
||||
* there are template-level as well as host-binding-level styling
|
||||
* instructions on the same element. The example below shows exactly
|
||||
* what could be:
|
||||
*
|
||||
* ```html
|
||||
* <!-- two sources of styling: the template and the directive -->
|
||||
* <div [style.width]="width" dir-that-sets-height></div>
|
||||
* ```
|
||||
*
|
||||
* If and when this situation occurs, the current styling state is
|
||||
* stored in a storage map value and then later accessed once the
|
||||
* host bindings are evaluated. Once styling for the current element
|
||||
* is over then the map entry will be cleared.
|
||||
*
|
||||
* To learn more about the algorithm see `TStylingContext`.
|
||||
*
|
||||
* --------
|
||||
*/
|
||||
|
||||
let _stylingState: StylingState|null = null;
|
||||
const _stateStorage = new Map<any, StylingState>();
|
||||
|
||||
// this value is not used outside this file and is only here
|
||||
// as a caching check for when the element changes.
|
||||
let _stylingElement: any = null;
|
||||
|
||||
/**
|
||||
* Used as a state reference for update values between style/class binding instructions.
|
||||
*
|
||||
* In addition to storing the element and bit-mask related values, the state also
|
||||
* stores the `sourceIndex` value. The `sourceIndex` value is an incremented value
|
||||
* that identifies what "source" (i.e. the template, a specific directive by index or
|
||||
* component) is currently applying its styling bindings to the element.
|
||||
*/
|
||||
export interface StylingState {
|
||||
/** The element that is currently being processed */
|
||||
element: RElement|null;
|
||||
|
||||
/** The directive index that is currently active (`0` === template) */
|
||||
directiveIndex: number;
|
||||
|
||||
/** The source (column) index that is currently active (`0` === template) */
|
||||
sourceIndex: number;
|
||||
|
||||
/** The classes update bit mask value that is processed during each class binding */
|
||||
classesBitMask: number;
|
||||
|
||||
/** The classes update bit index value that is processed during each class binding */
|
||||
classesIndex: number;
|
||||
|
||||
/** The styles update bit mask value that is processed during each style binding */
|
||||
stylesBitMask: number;
|
||||
|
||||
/** The styles update bit index value that is processed during each style binding */
|
||||
stylesIndex: number;
|
||||
}
|
||||
|
||||
export const STYLING_INDEX_START_VALUE = 1;
|
||||
export const BIT_MASK_START_VALUE = 0;
|
||||
// these values will get filled in the very first time this is accessed...
|
||||
const _state: StylingState = {
|
||||
element: null,
|
||||
directiveIndex: -1,
|
||||
sourceIndex: -1,
|
||||
classesBitMask: -1,
|
||||
classesIndex: -1,
|
||||
stylesBitMask: -1,
|
||||
stylesIndex: -1,
|
||||
};
|
||||
|
||||
export function getStylingState(element: any, readFromMap?: boolean): StylingState {
|
||||
if (!_stylingElement || element !== _stylingElement) {
|
||||
_stylingElement = element;
|
||||
if (readFromMap) {
|
||||
_stylingState = _stateStorage.get(element) || null;
|
||||
ngDevMode && ngDevMode.stylingReadPersistedState++;
|
||||
}
|
||||
_stylingState = _stylingState || {
|
||||
classesBitMask: BIT_MASK_START_VALUE,
|
||||
classesIndex: STYLING_INDEX_START_VALUE,
|
||||
stylesBitMask: BIT_MASK_START_VALUE,
|
||||
stylesIndex: STYLING_INDEX_START_VALUE,
|
||||
};
|
||||
const BIT_MASK_START_VALUE = 0;
|
||||
|
||||
// the `0` start value is reserved for [map]-based entries
|
||||
const INDEX_START_VALUE = 1;
|
||||
|
||||
/**
|
||||
* Returns (or instantiates) the styling state for the given element.
|
||||
*
|
||||
* Styling state is accessed and processed each time a style or class binding
|
||||
* is evaluated.
|
||||
*
|
||||
* If and when the provided `element` doesn't match the current element in the
|
||||
* state then this means that styling was recently cleared or the element has
|
||||
* changed in change detection. In both cases the styling state is fully reset.
|
||||
*
|
||||
* If and when the provided `directiveIndex` doesn't match the current directive
|
||||
* index in the state then this means that a new source has introduced itself into
|
||||
* the styling code (or, in other words, another directive or component has started
|
||||
* to apply its styling host bindings to the element).
|
||||
*/
|
||||
export function getStylingState(element: RElement, directiveIndex: number): StylingState {
|
||||
if (_state.element !== element) {
|
||||
_state.element = element;
|
||||
_state.directiveIndex = directiveIndex;
|
||||
_state.sourceIndex = directiveIndex === TEMPLATE_DIRECTIVE_INDEX ? 0 : 1;
|
||||
_state.classesBitMask = BIT_MASK_START_VALUE;
|
||||
_state.classesIndex = INDEX_START_VALUE;
|
||||
_state.stylesBitMask = BIT_MASK_START_VALUE;
|
||||
_state.stylesIndex = INDEX_START_VALUE;
|
||||
} else if (_state.directiveIndex !== directiveIndex) {
|
||||
_state.directiveIndex = directiveIndex;
|
||||
_state.sourceIndex++;
|
||||
}
|
||||
return _stylingState !;
|
||||
return _state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the styling state so that it can be used by another element's styling code.
|
||||
*/
|
||||
export function resetStylingState() {
|
||||
_stylingState = null;
|
||||
_stylingElement = null;
|
||||
}
|
||||
|
||||
export function storeStylingState(element: any, state: StylingState) {
|
||||
ngDevMode && ngDevMode.stylingWritePersistedState++;
|
||||
_stateStorage.set(element, state);
|
||||
}
|
||||
|
||||
export function deleteStylingStateFromStorage(element: any) {
|
||||
_stateStorage.delete(element);
|
||||
}
|
||||
|
||||
export function resetAllStylingState() {
|
||||
resetStylingState();
|
||||
_stateStorage.clear();
|
||||
_state.element = null;
|
||||
}
|
||||
|
|
|
@ -7,14 +7,13 @@
|
|||
*/
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {LView} from '../interfaces/view';
|
||||
import {getCurrentStyleSanitizer} from '../state';
|
||||
import {attachDebugObject} from '../util/debug_utils';
|
||||
|
||||
import {applyStyling} from './bindings';
|
||||
import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex} from './interfaces';
|
||||
import {applyStylingViaContext} from './bindings';
|
||||
import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from './interfaces';
|
||||
import {activateStylingMapFeature} from './map_based_bindings';
|
||||
import {getDefaultValue, getGuardMask, getProp, getValuesCount, isContextLocked, isMapBased, isSanitizationRequired} from './util';
|
||||
import {allowDirectStyling as _allowDirectStyling, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasConfig, isContextLocked, isMapBased, isSanitizationRequired} from './util';
|
||||
|
||||
|
||||
|
||||
|
@ -53,20 +52,32 @@ export interface DebugStyling {
|
|||
/** The associated TStylingContext instance */
|
||||
context: TStylingContext;
|
||||
|
||||
/** Which configuration flags are active (see `TStylingContextConfig`) */
|
||||
config: {
|
||||
hasMapBindings: boolean; //
|
||||
hasPropBindings: boolean; //
|
||||
hasCollisions: boolean; //
|
||||
hasTemplateBindings: boolean; //
|
||||
hasHostBindings: boolean; //
|
||||
templateBindingsLocked: boolean; //
|
||||
hostBindingsLocked: boolean; //
|
||||
allowDirectStyling: boolean; //
|
||||
};
|
||||
|
||||
/**
|
||||
* A summarization of each style/class property
|
||||
* present in the context.
|
||||
* present in the context
|
||||
*/
|
||||
summary: {[key: string]: LStylingSummary};
|
||||
summary: {[propertyName: string]: LStylingSummary};
|
||||
|
||||
/**
|
||||
* A key/value map of all styling properties and their
|
||||
* runtime values.
|
||||
* runtime values
|
||||
*/
|
||||
values: {[key: string]: string | number | null | boolean};
|
||||
values: {[propertyName: string]: string | number | null | boolean};
|
||||
|
||||
/**
|
||||
* Overrides the sanitizer used to process styles.
|
||||
* Overrides the sanitizer used to process styles
|
||||
*/
|
||||
overrideSanitizer(sanitizer: StyleSanitizeFn|null): void;
|
||||
}
|
||||
|
@ -83,9 +94,15 @@ export interface TStylingTupleSummary {
|
|||
|
||||
/**
|
||||
* The bit guard mask that is used to compare and protect against
|
||||
* styling changes when and styling bindings update
|
||||
* styling changes when any template style/class bindings update
|
||||
*/
|
||||
guardMask: number;
|
||||
templateBitMask: number;
|
||||
|
||||
/**
|
||||
* The bit guard mask that is used to compare and protect against
|
||||
* styling changes when any host style/class bindings update
|
||||
*/
|
||||
hostBindingsBitMask: number;
|
||||
|
||||
/**
|
||||
* Whether or not the entry requires sanitization
|
||||
|
@ -93,18 +110,18 @@ export interface TStylingTupleSummary {
|
|||
sanitizationRequired: boolean;
|
||||
|
||||
/**
|
||||
* The default value that will be applied if any bindings are falsy.
|
||||
* The default value that will be applied if any bindings are falsy
|
||||
*/
|
||||
defaultValue: string|boolean|null;
|
||||
|
||||
/**
|
||||
* All bindingIndex sources that have been registered for this style.
|
||||
* All bindingIndex sources that have been registered for this style
|
||||
*/
|
||||
sources: (number|null|string)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context.
|
||||
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context
|
||||
*/
|
||||
export function attachStylingDebugObject(context: TStylingContext) {
|
||||
const debug = new TStylingContextDebug(context);
|
||||
|
@ -121,7 +138,8 @@ export function attachStylingDebugObject(context: TStylingContext) {
|
|||
class TStylingContextDebug {
|
||||
constructor(public readonly context: TStylingContext) {}
|
||||
|
||||
get isLocked() { return isContextLocked(this.context); }
|
||||
get isTemplateLocked() { return isContextLocked(this.context, true); }
|
||||
get isHostBindingsLocked() { return isContextLocked(this.context, false); }
|
||||
|
||||
/**
|
||||
* Returns a detailed summary of each styling entry in the context.
|
||||
|
@ -130,30 +148,36 @@ class TStylingContextDebug {
|
|||
*/
|
||||
get entries(): {[prop: string]: TStylingTupleSummary} {
|
||||
const context = this.context;
|
||||
const totalColumns = getValuesCount(context);
|
||||
const entries: {[prop: string]: TStylingTupleSummary} = {};
|
||||
const start = TStylingContextIndex.MapBindingsPosition;
|
||||
const start = getPropValuesStartPosition(context);
|
||||
let i = start;
|
||||
while (i < context.length) {
|
||||
const valuesCount = getValuesCount(context, i);
|
||||
// the context may contain placeholder values which are populated ahead of time,
|
||||
// but contain no actual binding values. In this situation there is no point in
|
||||
// classifying this as an "entry" since no real data is stored here yet.
|
||||
if (valuesCount) {
|
||||
const prop = getProp(context, i);
|
||||
const guardMask = getGuardMask(context, i);
|
||||
const defaultValue = getDefaultValue(context, i);
|
||||
const sanitizationRequired = isSanitizationRequired(context, i);
|
||||
const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset;
|
||||
const prop = getProp(context, i);
|
||||
const templateBitMask = getGuardMask(context, i, false);
|
||||
const hostBindingsBitMask = getGuardMask(context, i, true);
|
||||
const defaultValue = getDefaultValue(context, i);
|
||||
const sanitizationRequired = isSanitizationRequired(context, i);
|
||||
const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset;
|
||||
|
||||
const sources: (number | string | null)[] = [];
|
||||
for (let j = 0; j < valuesCount; j++) {
|
||||
sources.push(context[bindingsStartPosition + j] as number | string | null);
|
||||
const sources: (number | string | null)[] = [];
|
||||
|
||||
for (let j = 0; j < totalColumns; j++) {
|
||||
const bindingIndex = context[bindingsStartPosition + j] as number | string | null;
|
||||
if (bindingIndex !== 0) {
|
||||
sources.push(bindingIndex);
|
||||
}
|
||||
|
||||
entries[prop] = {prop, guardMask, sanitizationRequired, valuesCount, defaultValue, sources};
|
||||
}
|
||||
|
||||
i += TStylingContextIndex.BindingsStartOffset + valuesCount;
|
||||
entries[prop] = {
|
||||
prop,
|
||||
templateBitMask,
|
||||
hostBindingsBitMask,
|
||||
sanitizationRequired,
|
||||
valuesCount: sources.length, defaultValue, sources,
|
||||
};
|
||||
|
||||
i += TStylingContextIndex.BindingsStartOffset + totalColumns;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
@ -191,6 +215,29 @@ export class NodeStylingDebug implements DebugStyling {
|
|||
return entries;
|
||||
}
|
||||
|
||||
get config() {
|
||||
const hasMapBindings = hasConfig(this.context, TStylingConfig.HasMapBindings);
|
||||
const hasPropBindings = hasConfig(this.context, TStylingConfig.HasPropBindings);
|
||||
const hasCollisions = hasConfig(this.context, TStylingConfig.HasCollisions);
|
||||
const hasTemplateBindings = hasConfig(this.context, TStylingConfig.HasTemplateBindings);
|
||||
const hasHostBindings = hasConfig(this.context, TStylingConfig.HasHostBindings);
|
||||
const templateBindingsLocked = hasConfig(this.context, TStylingConfig.TemplateBindingsLocked);
|
||||
const hostBindingsLocked = hasConfig(this.context, TStylingConfig.HostBindingsLocked);
|
||||
const allowDirectStyling =
|
||||
_allowDirectStyling(this.context, false) || _allowDirectStyling(this.context, true);
|
||||
|
||||
return {
|
||||
hasMapBindings, //
|
||||
hasPropBindings, //
|
||||
hasCollisions, //
|
||||
hasTemplateBindings, //
|
||||
hasHostBindings, //
|
||||
templateBindingsLocked, //
|
||||
hostBindingsLocked, //
|
||||
allowDirectStyling, //
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a key/value map of all the styles/classes that were last applied to the element.
|
||||
*/
|
||||
|
@ -205,16 +252,23 @@ export class NodeStylingDebug implements DebugStyling {
|
|||
// element is only used when the styling algorithm attempts to
|
||||
// style the value (and we mock out the stylingApplyFn anyway).
|
||||
const mockElement = {} as any;
|
||||
const hasMaps = getValuesCount(this.context, TStylingContextIndex.MapBindingsPosition) > 0;
|
||||
const hasMaps = hasConfig(this.context, TStylingConfig.HasMapBindings);
|
||||
if (hasMaps) {
|
||||
activateStylingMapFeature();
|
||||
}
|
||||
|
||||
const mapFn: ApplyStylingFn =
|
||||
(renderer: any, element: RElement, prop: string, value: string | null,
|
||||
bindingIndex?: number | null) => { fn(prop, value, bindingIndex || null); };
|
||||
bindingIndex?: number | null) => fn(prop, value, bindingIndex || null);
|
||||
|
||||
const sanitizer = this._isClassBased ? null : (this._sanitizer || getCurrentStyleSanitizer());
|
||||
applyStyling(this.context, null, mockElement, this._data, true, mapFn, sanitizer);
|
||||
|
||||
// run the template bindings
|
||||
applyStylingViaContext(
|
||||
this.context, null, mockElement, this._data, true, mapFn, sanitizer, false);
|
||||
|
||||
// and also the host bindings
|
||||
applyStylingViaContext(
|
||||
this.context, null, mockElement, this._data, true, mapFn, sanitizer, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,35 @@
|
|||
* 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 {unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {TNode, TNodeFlags} from '../interfaces/node';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {StylingMapArray, StylingMapArrayIndex, TStylingConfigFlags, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
|
||||
|
||||
const MAP_BASED_ENTRY_PROP_NAME = '--MAP--';
|
||||
const TEMPLATE_DIRECTIVE_INDEX = 0;
|
||||
import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
|
||||
|
||||
export const MAP_BASED_ENTRY_PROP_NAME = '[MAP]';
|
||||
export const TEMPLATE_DIRECTIVE_INDEX = 0;
|
||||
|
||||
/**
|
||||
* Default fallback value for a styling binding.
|
||||
*
|
||||
* A value of `null` is used here which signals to the styling algorithm that
|
||||
* the styling value is not present. This way if there are no other values
|
||||
* detected then it will be removed once the style/class property is dirty and
|
||||
* diffed within the styling algorithm present in `flushStyling`.
|
||||
*/
|
||||
export const DEFAULT_BINDING_VALUE = null;
|
||||
|
||||
export const DEFAULT_BINDING_INDEX = 0;
|
||||
|
||||
const DEFAULT_TOTAL_SOURCES = 1;
|
||||
|
||||
// The first bit value reflects a map-based binding value's bit.
|
||||
// The reason why it's always activated for every entry in the map
|
||||
// is so that if any map-binding values update then all other prop
|
||||
// based bindings will pass the guard check automatically without
|
||||
// any extra code or flags.
|
||||
export const DEFAULT_GUARD_MASK_VALUE = 0b1;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the `TStylingContext`.
|
||||
|
@ -21,84 +44,83 @@ const TEMPLATE_DIRECTIVE_INDEX = 0;
|
|||
* `TStylingContext` with the initial values (see `interfaces.ts` for more info).
|
||||
*/
|
||||
export function allocTStylingContext(initialStyling?: StylingMapArray | null): TStylingContext {
|
||||
// because map-based bindings deal with a dynamic set of values, there
|
||||
// is no way to know ahead of time whether or not sanitization is required.
|
||||
// For this reason the configuration will always mark sanitization as active
|
||||
// (this means that when map-based values are applied then sanitization will
|
||||
// be checked against each property).
|
||||
const mapBasedConfig = TStylingContextPropConfigFlags.SanitizationRequired;
|
||||
initialStyling = initialStyling || allocStylingMapArray();
|
||||
return [
|
||||
initialStyling || [''], // empty initial-styling map value
|
||||
TStylingConfigFlags.Initial,
|
||||
TEMPLATE_DIRECTIVE_INDEX,
|
||||
mapBasedConfig,
|
||||
0,
|
||||
MAP_BASED_ENTRY_PROP_NAME,
|
||||
TStylingConfig.Initial, // 1) config for the styling context
|
||||
DEFAULT_TOTAL_SOURCES, // 2) total amount of styling sources (template, directives, etc...)
|
||||
initialStyling, // 3) initial styling values
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provided directive as the last directive index in the provided `TStylingContext`.
|
||||
*
|
||||
* Styling in Angular can be applied from the template as well as multiple sources of
|
||||
* host bindings. This means that each binding function (the template function or the
|
||||
* hostBindings functions) will generate styling instructions as well as a styling
|
||||
* apply function (i.e. `stylingApply()`). Because host bindings functions and the
|
||||
* template function are independent from one another this means that the styling apply
|
||||
* function will be called multiple times. By tracking the last directive index (which
|
||||
* is what happens in this function) the styling algorithm knows exactly when to flush
|
||||
* styling (which is when the last styling apply function is executed).
|
||||
*/
|
||||
export function updateLastDirectiveIndex(
|
||||
context: TStylingContext, lastDirectiveIndex: number): void {
|
||||
if (lastDirectiveIndex === TEMPLATE_DIRECTIVE_INDEX) {
|
||||
const currentValue = context[TStylingContextIndex.LastDirectiveIndexPosition];
|
||||
if (currentValue > TEMPLATE_DIRECTIVE_INDEX) {
|
||||
// This means that a directive or two contained a host bindings function, but
|
||||
// now the template function also contains styling. When this combination of sources
|
||||
// comes up then we need to tell the context to store the state between updates
|
||||
// (because host bindings evaluation happens after template binding evaluation).
|
||||
markContextToPersistState(context);
|
||||
}
|
||||
} else {
|
||||
context[TStylingContextIndex.LastDirectiveIndexPosition] = lastDirectiveIndex;
|
||||
}
|
||||
export function allocStylingMapArray(): StylingMapArray {
|
||||
return [''];
|
||||
}
|
||||
|
||||
function getConfig(context: TStylingContext) {
|
||||
export function getConfig(context: TStylingContext) {
|
||||
return context[TStylingContextIndex.ConfigPosition];
|
||||
}
|
||||
|
||||
export function setConfig(context: TStylingContext, value: number) {
|
||||
export function hasConfig(context: TStylingContext, flag: TStylingConfig) {
|
||||
return (getConfig(context) & flag) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not to apply styles/classes directly or via context resolution.
|
||||
*
|
||||
* There are three cases that are matched here:
|
||||
* 1. context is locked for template or host bindings (depending on `hostBindingsMode`)
|
||||
* 2. There are no collisions (i.e. properties with more than one binding)
|
||||
* 3. There are only "prop" or "map" bindings present, but not both
|
||||
*/
|
||||
export function allowDirectStyling(context: TStylingContext, hostBindingsMode: boolean): boolean {
|
||||
const config = getConfig(context);
|
||||
return ((config & getLockedConfig(hostBindingsMode)) !== 0) &&
|
||||
((config & TStylingConfig.HasCollisions) === 0) &&
|
||||
((config & TStylingConfig.HasPropAndMapBindings) !== TStylingConfig.HasPropAndMapBindings);
|
||||
}
|
||||
|
||||
export function setConfig(context: TStylingContext, value: TStylingConfig): void {
|
||||
context[TStylingContextIndex.ConfigPosition] = value;
|
||||
}
|
||||
|
||||
export function getProp(context: TStylingContext, index: number) {
|
||||
export function patchConfig(context: TStylingContext, flag: TStylingConfig): void {
|
||||
context[TStylingContextIndex.ConfigPosition] |= flag;
|
||||
}
|
||||
|
||||
export function getProp(context: TStylingContext, index: number): string {
|
||||
return context[index + TStylingContextIndex.PropOffset] as string;
|
||||
}
|
||||
|
||||
function getPropConfig(context: TStylingContext, index: number): number {
|
||||
return (context[index + TStylingContextIndex.ConfigAndGuardOffset] as number) &
|
||||
return (context[index + TStylingContextIndex.ConfigOffset] as number) &
|
||||
TStylingContextPropConfigFlags.Mask;
|
||||
}
|
||||
|
||||
export function isSanitizationRequired(context: TStylingContext, index: number) {
|
||||
return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) > 0;
|
||||
export function isSanitizationRequired(context: TStylingContext, index: number): boolean {
|
||||
return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) !==
|
||||
0;
|
||||
}
|
||||
|
||||
export function getGuardMask(context: TStylingContext, index: number) {
|
||||
const configGuardValue = context[index + TStylingContextIndex.ConfigAndGuardOffset] as number;
|
||||
return configGuardValue >> TStylingContextPropConfigFlags.TotalBits;
|
||||
export function getGuardMask(
|
||||
context: TStylingContext, index: number, isHostBinding: boolean): number {
|
||||
const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset :
|
||||
TStylingContextIndex.TemplateBitGuardOffset);
|
||||
return context[position] as number;
|
||||
}
|
||||
|
||||
export function setGuardMask(context: TStylingContext, index: number, maskValue: number) {
|
||||
const config = getPropConfig(context, index);
|
||||
const guardMask = maskValue << TStylingContextPropConfigFlags.TotalBits;
|
||||
context[index + TStylingContextIndex.ConfigAndGuardOffset] = config | guardMask;
|
||||
export function setGuardMask(
|
||||
context: TStylingContext, index: number, maskValue: number, isHostBinding: boolean) {
|
||||
const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset :
|
||||
TStylingContextIndex.TemplateBitGuardOffset);
|
||||
context[position] = maskValue;
|
||||
}
|
||||
|
||||
export function getValuesCount(context: TStylingContext, index: number) {
|
||||
return context[index + TStylingContextIndex.ValuesCountOffset] as number;
|
||||
export function getValuesCount(context: TStylingContext): number {
|
||||
return getTotalSources(context) + 1;
|
||||
}
|
||||
|
||||
export function getTotalSources(context: TStylingContext): number {
|
||||
return context[TStylingContextIndex.TotalSourcesPosition];
|
||||
}
|
||||
|
||||
export function getBindingValue(context: TStylingContext, index: number, offset: number) {
|
||||
|
@ -106,39 +128,44 @@ export function getBindingValue(context: TStylingContext, index: number, offset:
|
|||
}
|
||||
|
||||
export function getDefaultValue(context: TStylingContext, index: number): string|boolean|null {
|
||||
const valuesCount = getValuesCount(context, index);
|
||||
return context[index + TStylingContextIndex.BindingsStartOffset + valuesCount - 1] as string |
|
||||
return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] as
|
||||
string |
|
||||
boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary function which determines whether or not a context is
|
||||
* allowed to be flushed based on the provided directive index.
|
||||
*/
|
||||
export function allowStylingFlush(context: TStylingContext | null, index: number) {
|
||||
return (context && index === context[TStylingContextIndex.LastDirectiveIndexPosition]) ? true :
|
||||
false;
|
||||
export function setDefaultValue(
|
||||
context: TStylingContext, index: number, value: string | boolean | null) {
|
||||
return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] =
|
||||
value;
|
||||
}
|
||||
|
||||
export function lockContext(context: TStylingContext) {
|
||||
setConfig(context, getConfig(context) | TStylingConfigFlags.Locked);
|
||||
export function setValue(data: LStylingData, bindingIndex: number, value: any) {
|
||||
data[bindingIndex] = value;
|
||||
}
|
||||
|
||||
export function isContextLocked(context: TStylingContext): boolean {
|
||||
return (getConfig(context) & TStylingConfigFlags.Locked) > 0;
|
||||
export function getValue<T = any>(data: LStylingData, bindingIndex: number): T|null {
|
||||
return bindingIndex > 0 ? data[bindingIndex] as T : null;
|
||||
}
|
||||
|
||||
export function stateIsPersisted(context: TStylingContext): boolean {
|
||||
return (getConfig(context) & TStylingConfigFlags.PersistStateValues) > 0;
|
||||
export function lockContext(context: TStylingContext, hostBindingsMode: boolean): void {
|
||||
patchConfig(context, getLockedConfig(hostBindingsMode));
|
||||
}
|
||||
|
||||
export function markContextToPersistState(context: TStylingContext) {
|
||||
setConfig(context, getConfig(context) | TStylingConfigFlags.PersistStateValues);
|
||||
export function isContextLocked(context: TStylingContext, hostBindingsMode: boolean): boolean {
|
||||
return hasConfig(context, getLockedConfig(hostBindingsMode));
|
||||
}
|
||||
|
||||
export function getLockedConfig(hostBindingsMode: boolean) {
|
||||
return hostBindingsMode ? TStylingConfig.HostBindingsLocked :
|
||||
TStylingConfig.TemplateBindingsLocked;
|
||||
}
|
||||
|
||||
export function getPropValuesStartPosition(context: TStylingContext) {
|
||||
return TStylingContextIndex.MapBindingsBindingsStartPosition +
|
||||
context[TStylingContextIndex.MapBindingsValuesCountPosition];
|
||||
let startPosition = TStylingContextIndex.ValuesStartPosition;
|
||||
if (hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
startPosition += TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
|
||||
}
|
||||
return startPosition;
|
||||
}
|
||||
|
||||
export function isMapBased(prop: string) {
|
||||
|
@ -197,17 +224,23 @@ export function getStylingMapArray(value: TStylingContext | StylingMapArray | nu
|
|||
StylingMapArray|null {
|
||||
return isStylingContext(value) ?
|
||||
(value as TStylingContext)[TStylingContextIndex.InitialStylingValuePosition] :
|
||||
value;
|
||||
value as StylingMapArray;
|
||||
}
|
||||
|
||||
export function isStylingContext(value: TStylingContext | StylingMapArray | null): boolean {
|
||||
// the StylingMapArray is in the format of [initial, prop, string, prop, string]
|
||||
// and this is the defining value to distinguish between arrays
|
||||
return Array.isArray(value) &&
|
||||
value.length >= TStylingContextIndex.MapBindingsBindingsStartPosition &&
|
||||
return Array.isArray(value) && value.length >= TStylingContextIndex.ValuesStartPosition &&
|
||||
typeof value[1] !== 'string';
|
||||
}
|
||||
|
||||
export function isStylingMapArray(value: TStylingContext | StylingMapArray | null): boolean {
|
||||
// the StylingMapArray is in the format of [initial, prop, string, prop, string]
|
||||
// and this is the defining value to distinguish between arrays
|
||||
return Array.isArray(value) &&
|
||||
(typeof(value as StylingMapArray)[StylingMapArrayIndex.ValuesStartPosition] === 'string');
|
||||
}
|
||||
|
||||
export function getInitialStylingValue(context: TStylingContext | StylingMapArray | null): string {
|
||||
const map = getStylingMapArray(context);
|
||||
return map && (map[StylingMapArrayIndex.RawValuePosition] as string | null) || '';
|
||||
|
@ -225,6 +258,13 @@ export function getMapProp(map: StylingMapArray, index: number): string {
|
|||
return map[index + StylingMapArrayIndex.PropOffset] as string;
|
||||
}
|
||||
|
||||
const MAP_DIRTY_VALUE =
|
||||
typeof ngDevMode !== 'undefined' && ngDevMode ? {} : {MAP_DIRTY_VALUE: true};
|
||||
|
||||
export function setMapAsDirty(map: StylingMapArray): void {
|
||||
map[StylingMapArrayIndex.RawValuePosition] = MAP_DIRTY_VALUE;
|
||||
}
|
||||
|
||||
export function setMapValue(
|
||||
map: StylingMapArray, index: number, value: string | boolean | null): void {
|
||||
map[index + StylingMapArrayIndex.ValueOffset] = value;
|
||||
|
@ -253,3 +293,130 @@ export function forceStylesAsString(styles: {[key: string]: any} | null | undefi
|
|||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function isHostStylingActive(directiveOrSourceId: number): boolean {
|
||||
return directiveOrSourceId !== TEMPLATE_DIRECTIVE_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided styling map array into a string.
|
||||
*
|
||||
* Classes => `one two three`
|
||||
* Styles => `prop:value; prop2:value2`
|
||||
*/
|
||||
export function stylingMapToString(map: StylingMapArray, isClassBased: boolean): string {
|
||||
let str = '';
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i) as string;
|
||||
const attrValue = concatString(prop, isClassBased ? '' : value, ':');
|
||||
str = concatString(str, attrValue, isClassBased ? ' ' : '; ');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided styling map array into a key value map.
|
||||
*/
|
||||
export function stylingMapToStringMap(map: StylingMapArray | null): {[key: string]: any} {
|
||||
let stringMap: {[key: string]: any} = {};
|
||||
if (map) {
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i) as string;
|
||||
stringMap[prop] = value;
|
||||
}
|
||||
}
|
||||
return stringMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the provided item into the provided styling array at the right spot.
|
||||
*
|
||||
* The `StylingMapArray` type is a sorted key/value array of entries. This means
|
||||
* that when a new entry is inserted it must be placed at the right spot in the
|
||||
* array. This function figures out exactly where to place it.
|
||||
*/
|
||||
export function addItemToStylingMap(
|
||||
stylingMapArr: StylingMapArray, prop: string, value: string | boolean | null,
|
||||
allowOverwrite?: boolean) {
|
||||
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
|
||||
j += StylingMapArrayIndex.TupleSize) {
|
||||
const propAtIndex = getMapProp(stylingMapArr, j);
|
||||
if (prop <= propAtIndex) {
|
||||
let applied = false;
|
||||
if (propAtIndex === prop) {
|
||||
const valueAtIndex = stylingMapArr[j];
|
||||
if (allowOverwrite || !isStylingValueDefined(valueAtIndex)) {
|
||||
applied = true;
|
||||
setMapValue(stylingMapArr, j, value);
|
||||
}
|
||||
} else {
|
||||
applied = true;
|
||||
stylingMapArr.splice(j, 0, prop, value);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
}
|
||||
|
||||
stylingMapArr.push(prop, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert a {key:value} map into a `StylingMapArray` array.
|
||||
*
|
||||
* This function will either generate a new `StylingMapArray` instance
|
||||
* or it will patch the provided `newValues` map value into an
|
||||
* existing `StylingMapArray` value (this only happens if `bindingValue`
|
||||
* is an instance of `StylingMapArray`).
|
||||
*
|
||||
* If a new key/value map is provided with an old `StylingMapArray`
|
||||
* value then all properties will be overwritten with their new
|
||||
* values or with `null`. This means that the array will never
|
||||
* shrink in size (but it will also not be created and thrown
|
||||
* away whenever the `{key:value}` map entries change).
|
||||
*/
|
||||
export function normalizeIntoStylingMap(
|
||||
bindingValue: null | StylingMapArray,
|
||||
newValues: {[key: string]: any} | string | null | undefined,
|
||||
normalizeProps?: boolean): StylingMapArray {
|
||||
const stylingMapArr: StylingMapArray = Array.isArray(bindingValue) ? bindingValue : [null];
|
||||
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = newValues || null;
|
||||
|
||||
// because the new values may not include all the properties
|
||||
// that the old ones had, all values are set to `null` before
|
||||
// the new values are applied. This way, when flushed, the
|
||||
// styling algorithm knows exactly what style/class values
|
||||
// to remove from the element (since they are `null`).
|
||||
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
|
||||
j += StylingMapArrayIndex.TupleSize) {
|
||||
setMapValue(stylingMapArr, j, null);
|
||||
}
|
||||
|
||||
let props: string[]|null = null;
|
||||
let map: {[key: string]: any}|undefined|null;
|
||||
let allValuesTrue = false;
|
||||
if (typeof newValues === 'string') { // [class] bindings allow string values
|
||||
if (newValues.length) {
|
||||
props = newValues.split(/\s+/);
|
||||
allValuesTrue = true;
|
||||
}
|
||||
} else {
|
||||
props = newValues ? Object.keys(newValues) : null;
|
||||
map = newValues;
|
||||
}
|
||||
|
||||
if (props) {
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i] as string;
|
||||
const newProp = normalizeProps ? hyphenate(prop) : prop;
|
||||
const value = allValuesTrue ? true : map ![prop];
|
||||
addItemToStylingMap(stylingMapArr, newProp, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
return stylingMapArr;
|
||||
}
|
||||
|
|
|
@ -61,9 +61,7 @@ export interface SafeResourceUrl extends SafeValue {}
|
|||
|
||||
|
||||
abstract class SafeValueImpl implements SafeValue {
|
||||
constructor(public changingThisBreaksApplicationSecurity: string) {
|
||||
// empty
|
||||
}
|
||||
constructor(public changingThisBreaksApplicationSecurity: string) {}
|
||||
|
||||
abstract getTypeName(): string;
|
||||
|
||||
|
@ -89,10 +87,10 @@ class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
|
|||
getTypeName() { return BypassType.ResourceUrl; }
|
||||
}
|
||||
|
||||
export function unwrapSafeValue(value: SafeValue): string {
|
||||
export function unwrapSafeValue(value: string | SafeValue): string {
|
||||
return value instanceof SafeValueImpl ?
|
||||
(value as SafeValueImpl).changingThisBreaksApplicationSecurity :
|
||||
'';
|
||||
(value as string);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -59,8 +59,6 @@ declare global {
|
|||
flushStyling: number;
|
||||
classesApplied: number;
|
||||
stylesApplied: number;
|
||||
stylingWritePersistedState: number;
|
||||
stylingReadPersistedState: number;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,8 +99,6 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
|
|||
flushStyling: 0,
|
||||
classesApplied: 0,
|
||||
stylesApplied: 0,
|
||||
stylingWritePersistedState: 0,
|
||||
stylingReadPersistedState: 0,
|
||||
};
|
||||
|
||||
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
|
||||
|
|
|
@ -15,6 +15,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
|||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('new styling integration', () => {
|
||||
beforeEach(() => resetStylingCounters());
|
||||
|
||||
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
|
||||
.it('should apply single property styles/classes to the element and default to any static styling values',
|
||||
() => {
|
||||
|
@ -124,6 +126,74 @@ describe('new styling integration', () => {
|
|||
expect(element.style.width).toEqual('600px');
|
||||
});
|
||||
|
||||
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
|
||||
.it('should only run stylingFlush once when there are no collisions between styling properties',
|
||||
() => {
|
||||
@Directive({selector: '[dir-with-styling]'})
|
||||
class DirWithStyling {
|
||||
@HostBinding('style.font-size') public fontSize = '100px';
|
||||
}
|
||||
|
||||
@Component({selector: 'comp-with-styling'})
|
||||
class CompWithStyling {
|
||||
@HostBinding('style.width') public width = '900px';
|
||||
|
||||
@HostBinding('style.height') public height = '900px';
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<comp-with-styling
|
||||
[style.opacity]="opacity"
|
||||
dir-with-styling>...</comp-with-styling>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
opacity: string|null = '0.5';
|
||||
@ViewChild(CompWithStyling, {static: true})
|
||||
compWithStyling: CompWithStyling|null = null;
|
||||
@ViewChild(DirWithStyling, {static: true}) dirWithStyling: DirWithStyling|null = null;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const component = fixture.componentInstance;
|
||||
const element = fixture.nativeElement.querySelector('comp-with-styling');
|
||||
const node = getDebugNode(element) !;
|
||||
|
||||
const styles = node.styles !;
|
||||
const config = styles.config;
|
||||
expect(config.hasCollisions).toBeFalsy();
|
||||
expect(config.hasMapBindings).toBeFalsy();
|
||||
expect(config.hasPropBindings).toBeTruthy();
|
||||
expect(config.allowDirectStyling).toBeTruthy();
|
||||
|
||||
expect(element.style.opacity).toEqual('0.5');
|
||||
expect(element.style.width).toEqual('900px');
|
||||
expect(element.style.height).toEqual('900px');
|
||||
expect(element.style.fontSize).toEqual('100px');
|
||||
|
||||
// once for the template flush and again for the host bindings
|
||||
expect(ngDevMode !.flushStyling).toEqual(2);
|
||||
resetStylingCounters();
|
||||
|
||||
component.opacity = '0.6';
|
||||
component.compWithStyling !.height = '100px';
|
||||
component.compWithStyling !.width = '100px';
|
||||
component.dirWithStyling !.fontSize = '50px';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.style.opacity).toEqual('0.6');
|
||||
expect(element.style.width).toEqual('100px');
|
||||
expect(element.style.height).toEqual('100px');
|
||||
expect(element.style.fontSize).toEqual('50px');
|
||||
|
||||
// there is no need to flush styling since the styles are applied directly
|
||||
expect(ngDevMode !.flushStyling).toEqual(0);
|
||||
});
|
||||
|
||||
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
|
||||
.it('should combine all styling across the template, directive and component host bindings',
|
||||
() => {
|
||||
|
@ -240,6 +310,7 @@ describe('new styling integration', () => {
|
|||
|
||||
fixture.componentInstance.w3 = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(styles.values).toEqual({
|
||||
'width': '200px',
|
||||
});
|
||||
|
@ -309,16 +380,22 @@ describe('new styling integration', () => {
|
|||
.it('should apply map-based style and class entries', () => {
|
||||
@Component({template: '<div [style]="s" [class]="c"></div>'})
|
||||
class Cmp {
|
||||
public c !: {[key: string]: any};
|
||||
updateClasses(prop: string) {
|
||||
this.c = {...this.c || {}};
|
||||
this.c[prop] = true;
|
||||
public c: {[key: string]: any}|null = null;
|
||||
updateClasses(classes: string) {
|
||||
const c = this.c || (this.c = {});
|
||||
Object.keys(this.c).forEach(className => { c[className] = false; });
|
||||
classes.split(/\s+/).forEach(className => { c[className] = true; });
|
||||
}
|
||||
|
||||
public s !: {[key: string]: any};
|
||||
public s: {[key: string]: any}|null = null;
|
||||
updateStyles(prop: string, value: string|number|null) {
|
||||
this.s = {...this.s || {}};
|
||||
this.s[prop] = value;
|
||||
const s = this.s || (this.s = {});
|
||||
Object.assign(s, {[prop]: value});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.s = null;
|
||||
this.c = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,22 +409,47 @@ describe('new styling integration', () => {
|
|||
|
||||
const element = fixture.nativeElement.querySelector('div');
|
||||
const node = getDebugNode(element) !;
|
||||
const styles = node.styles !;
|
||||
const classes = node.classes !;
|
||||
let styles = node.styles !;
|
||||
let classes = node.classes !;
|
||||
|
||||
const stylesSummary = styles.summary;
|
||||
const widthSummary = stylesSummary['width'];
|
||||
let stylesSummary = styles.summary;
|
||||
let widthSummary = stylesSummary['width'];
|
||||
expect(widthSummary.prop).toEqual('width');
|
||||
expect(widthSummary.value).toEqual('100px');
|
||||
|
||||
const heightSummary = stylesSummary['height'];
|
||||
let heightSummary = stylesSummary['height'];
|
||||
expect(heightSummary.prop).toEqual('height');
|
||||
expect(heightSummary.value).toEqual('200px');
|
||||
|
||||
const classesSummary = classes.summary;
|
||||
const abcSummary = classesSummary['abc'];
|
||||
let classesSummary = classes.summary;
|
||||
let abcSummary = classesSummary['abc'];
|
||||
expect(abcSummary.prop).toEqual('abc');
|
||||
expect(abcSummary.value as any).toEqual(true);
|
||||
expect(abcSummary.value).toBeTruthy();
|
||||
|
||||
comp.reset();
|
||||
comp.updateStyles('width', '500px');
|
||||
comp.updateStyles('height', null);
|
||||
comp.updateClasses('def');
|
||||
fixture.detectChanges();
|
||||
|
||||
styles = node.styles !;
|
||||
classes = node.classes !;
|
||||
|
||||
stylesSummary = styles.summary;
|
||||
widthSummary = stylesSummary['width'];
|
||||
expect(widthSummary.value).toEqual('500px');
|
||||
|
||||
heightSummary = stylesSummary['height'];
|
||||
expect(heightSummary.value).toEqual(null);
|
||||
|
||||
classesSummary = classes.summary;
|
||||
abcSummary = classesSummary['abc'];
|
||||
expect(abcSummary.prop).toEqual('abc');
|
||||
expect(abcSummary.value).toBeFalsy();
|
||||
|
||||
let defSummary = classesSummary['def'];
|
||||
expect(defSummary.prop).toEqual('def');
|
||||
expect(defSummary.value).toBeTruthy();
|
||||
});
|
||||
|
||||
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
|
||||
|
@ -385,6 +487,7 @@ describe('new styling integration', () => {
|
|||
const node = getDebugNode(element) !;
|
||||
|
||||
const styles = node.styles !;
|
||||
|
||||
expect(styles.values).toEqual({
|
||||
'width': '555px',
|
||||
'color': 'red',
|
||||
|
@ -509,7 +612,8 @@ describe('new styling integration', () => {
|
|||
resetStylingCounters();
|
||||
fixture.detectChanges();
|
||||
|
||||
assertStyleCounters(1, 0);
|
||||
// the width is applied both in TEMPLATE and in HOST_BINDINGS mode
|
||||
assertStyleCounters(2, 0);
|
||||
assertStyle(element, 'width', '999px');
|
||||
assertStyle(element, 'height', '123px');
|
||||
|
||||
|
@ -517,8 +621,8 @@ describe('new styling integration', () => {
|
|||
resetStylingCounters();
|
||||
fixture.detectChanges();
|
||||
|
||||
// both are applied because the map was altered
|
||||
assertStyleCounters(2, 0);
|
||||
// the width is only applied once
|
||||
assertStyleCounters(1, 0);
|
||||
assertStyle(element, 'width', '0px');
|
||||
assertStyle(element, 'height', '123px');
|
||||
|
||||
|
@ -526,8 +630,8 @@ describe('new styling integration', () => {
|
|||
resetStylingCounters();
|
||||
fixture.detectChanges();
|
||||
|
||||
// all three are applied because the map was altered
|
||||
assertStyleCounters(3, 0);
|
||||
// only the width and color have changed
|
||||
assertStyleCounters(2, 0);
|
||||
assertStyle(element, 'width', '1000px');
|
||||
assertStyle(element, 'height', '123px');
|
||||
assertStyle(element, 'color', 'red');
|
||||
|
@ -536,7 +640,9 @@ describe('new styling integration', () => {
|
|||
resetStylingCounters();
|
||||
fixture.detectChanges();
|
||||
|
||||
assertStyleCounters(1, 0);
|
||||
// height gets applied twice and all other
|
||||
// values get applied
|
||||
assertStyleCounters(4, 0);
|
||||
assertStyle(element, 'width', '1000px');
|
||||
assertStyle(element, 'height', '1000px');
|
||||
assertStyle(element, 'color', 'red');
|
||||
|
@ -545,8 +651,7 @@ describe('new styling integration', () => {
|
|||
resetStylingCounters();
|
||||
fixture.detectChanges();
|
||||
|
||||
// all four are applied because the map was altered
|
||||
assertStyleCounters(4, 0);
|
||||
assertStyleCounters(5, 0);
|
||||
assertStyle(element, 'width', '2000px');
|
||||
assertStyle(element, 'height', '1000px');
|
||||
assertStyle(element, 'color', 'blue');
|
||||
|
@ -557,62 +662,13 @@ describe('new styling integration', () => {
|
|||
fixture.detectChanges();
|
||||
|
||||
// all four are applied because the map was altered
|
||||
assertStyleCounters(3, 1);
|
||||
assertStyleCounters(4, 1);
|
||||
assertStyle(element, 'width', '2000px');
|
||||
assertStyle(element, 'height', '1000px');
|
||||
assertStyle(element, 'color', 'blue');
|
||||
assertStyle(element, 'opacity', '');
|
||||
});
|
||||
|
||||
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
|
||||
.it('should only persist state values in a local map if template AND host styling is used together',
|
||||
() => {
|
||||
@Directive({selector: '[dir-that-sets-styling]'})
|
||||
class Dir {
|
||||
@HostBinding('style.width') w = '100px';
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #a dir-that-sets-styling></div>
|
||||
<div #b [style.width]="w"></div>
|
||||
<div #c dir-that-sets-styling [style.width]="w"></div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
w = '200px';
|
||||
@ViewChild('a', {read: Dir, static: true}) a !: Dir;
|
||||
@ViewChild('c', {read: Dir, static: true}) c !: Dir;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp, Dir]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
resetStylingCounters();
|
||||
|
||||
comp.a.w = '999px';
|
||||
comp.w = '999px';
|
||||
comp.c.w = '999px';
|
||||
fixture.detectChanges();
|
||||
expect(ngDevMode !.stylingWritePersistedState).toEqual(totalUpdates(1));
|
||||
|
||||
comp.a.w = '888px';
|
||||
fixture.detectChanges();
|
||||
expect(ngDevMode !.stylingWritePersistedState).toEqual(totalUpdates(2));
|
||||
|
||||
comp.c.w = '777px';
|
||||
fixture.detectChanges();
|
||||
expect(ngDevMode !.stylingWritePersistedState).toEqual(totalUpdates(3));
|
||||
|
||||
function totalUpdates(value: number) {
|
||||
// this is doubled because detectChanges is run twice to
|
||||
// see to check for checkNoChanges
|
||||
return value * 2;
|
||||
}
|
||||
});
|
||||
|
||||
onlyInIvy('only ivy has style/class bindings debugging support')
|
||||
.it('should sanitize style values before writing them', () => {
|
||||
@Component({
|
||||
|
@ -910,7 +966,6 @@ describe('new styling integration', () => {
|
|||
|
||||
TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
const dirOne = fixture.nativeElement.querySelector('dir-one');
|
||||
|
@ -937,10 +992,10 @@ describe('new styling integration', () => {
|
|||
|
||||
@Component({
|
||||
template: `
|
||||
<div class="a" [style.width.px]="w" one></div>
|
||||
<div class="b" [style.height.px]="h" one two></div>
|
||||
<div class="c" [style.color]="c" two></div>
|
||||
`
|
||||
<div class="a" [style.width.px]="w" one></div>
|
||||
<div class="b" [style.height.px]="h" one two></div>
|
||||
<div class="c" [style.color]="c" two></div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
w = 100;
|
||||
|
@ -1092,6 +1147,27 @@ describe('new styling integration', () => {
|
|||
expect(div.style.height).toEqual('200px');
|
||||
expect(div.style.opacity).toEqual('1');
|
||||
});
|
||||
|
||||
it('should allow [ngStyle] and [ngClass] to be used together', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<div [ngClass]="c" [ngStyle]="s"></div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
c: any = 'foo bar';
|
||||
s: any = {width: '200px'};
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const div = fixture.nativeElement.querySelector('div');
|
||||
expect(div.style.width).toEqual('200px');
|
||||
expect(div.classList.contains('foo')).toBeTruthy();
|
||||
expect(div.classList.contains('bar')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
function assertStyleCounters(countForSet: number, countForRemove: number) {
|
||||
|
|
|
@ -628,7 +628,6 @@ describe('styling', () => {
|
|||
expect(childDir.parent).toBeAnInstanceOf(TestDir);
|
||||
expect(testDirDiv.classList).not.toContain('with-button');
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('Hello');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -155,6 +155,9 @@
|
|||
{
|
||||
"name": "_currentNamespace"
|
||||
},
|
||||
{
|
||||
"name": "_elementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "_global"
|
||||
},
|
||||
|
@ -165,7 +168,7 @@
|
|||
"name": "_selectedIndex"
|
||||
},
|
||||
{
|
||||
"name": "_stateStorage"
|
||||
"name": "_state"
|
||||
},
|
||||
{
|
||||
"name": "addComponentLogic"
|
||||
|
@ -176,6 +179,9 @@
|
|||
{
|
||||
"name": "addToViewTree"
|
||||
},
|
||||
{
|
||||
"name": "allocStylingMapArray"
|
||||
},
|
||||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
|
@ -254,6 +260,9 @@
|
|||
{
|
||||
"name": "executeContentQueries"
|
||||
},
|
||||
{
|
||||
"name": "executeElementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndCheckHooks"
|
||||
},
|
||||
|
@ -395,6 +404,9 @@
|
|||
{
|
||||
"name": "getStylingMapArray"
|
||||
},
|
||||
{
|
||||
"name": "hasActiveElementFlag"
|
||||
},
|
||||
{
|
||||
"name": "hasClassInput"
|
||||
},
|
||||
|
@ -572,9 +584,6 @@
|
|||
{
|
||||
"name": "renderView"
|
||||
},
|
||||
{
|
||||
"name": "resetAllStylingState"
|
||||
},
|
||||
{
|
||||
"name": "resetComponentState"
|
||||
},
|
||||
|
|
|
@ -134,6 +134,9 @@
|
|||
{
|
||||
"name": "__window"
|
||||
},
|
||||
{
|
||||
"name": "_elementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "_global"
|
||||
},
|
||||
|
@ -144,7 +147,7 @@
|
|||
"name": "_selectedIndex"
|
||||
},
|
||||
{
|
||||
"name": "_stateStorage"
|
||||
"name": "_state"
|
||||
},
|
||||
{
|
||||
"name": "addToViewTree"
|
||||
|
@ -209,6 +212,9 @@
|
|||
{
|
||||
"name": "executeCheckHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeElementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndCheckHooks"
|
||||
},
|
||||
|
@ -314,6 +320,9 @@
|
|||
{
|
||||
"name": "getSelectedIndex"
|
||||
},
|
||||
{
|
||||
"name": "hasActiveElementFlag"
|
||||
},
|
||||
{
|
||||
"name": "hasParentInjector"
|
||||
},
|
||||
|
@ -419,9 +428,6 @@
|
|||
{
|
||||
"name": "renderView"
|
||||
},
|
||||
{
|
||||
"name": "resetAllStylingState"
|
||||
},
|
||||
{
|
||||
"name": "resetComponentState"
|
||||
},
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
{
|
||||
"name": "DECLARATION_VIEW"
|
||||
},
|
||||
{
|
||||
"name": "DEFAULT_BINDING_INDEX"
|
||||
},
|
||||
{
|
||||
"name": "DEFAULT_BINDING_VALUE"
|
||||
},
|
||||
|
@ -48,7 +51,7 @@
|
|||
"name": "DEFAULT_GUARD_MASK_VALUE"
|
||||
},
|
||||
{
|
||||
"name": "DEFAULT_SIZE_VALUE"
|
||||
"name": "DEFAULT_TOTAL_SOURCES"
|
||||
},
|
||||
{
|
||||
"name": "DefaultIterableDiffer"
|
||||
|
@ -89,6 +92,9 @@
|
|||
{
|
||||
"name": "HOST"
|
||||
},
|
||||
{
|
||||
"name": "INDEX_START_VALUE"
|
||||
},
|
||||
{
|
||||
"name": "INJECTOR"
|
||||
},
|
||||
|
@ -108,7 +114,7 @@
|
|||
"name": "MAP_BASED_ENTRY_PROP_NAME"
|
||||
},
|
||||
{
|
||||
"name": "MIN_DIRECTIVE_ID"
|
||||
"name": "MAP_DIRTY_VALUE"
|
||||
},
|
||||
{
|
||||
"name": "MONKEY_PATCH_KEY_NAME"
|
||||
|
@ -212,9 +218,6 @@
|
|||
{
|
||||
"name": "STYLING_INDEX_FOR_MAP_BINDING"
|
||||
},
|
||||
{
|
||||
"name": "STYLING_INDEX_START_VALUE"
|
||||
},
|
||||
{
|
||||
"name": "SWITCH_ELEMENT_REF_FACTORY"
|
||||
},
|
||||
|
@ -224,6 +227,9 @@
|
|||
{
|
||||
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
|
||||
},
|
||||
{
|
||||
"name": "SafeValueImpl"
|
||||
},
|
||||
{
|
||||
"name": "SkipSelf"
|
||||
},
|
||||
|
@ -395,6 +401,9 @@
|
|||
{
|
||||
"name": "_devMode"
|
||||
},
|
||||
{
|
||||
"name": "_elementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "_global"
|
||||
},
|
||||
|
@ -405,16 +414,7 @@
|
|||
"name": "_selectedIndex"
|
||||
},
|
||||
{
|
||||
"name": "_stateStorage"
|
||||
},
|
||||
{
|
||||
"name": "_stylingElement"
|
||||
},
|
||||
{
|
||||
"name": "_stylingProp"
|
||||
},
|
||||
{
|
||||
"name": "_stylingState"
|
||||
"name": "_state"
|
||||
},
|
||||
{
|
||||
"name": "_symbolIterator"
|
||||
|
@ -422,12 +422,6 @@
|
|||
{
|
||||
"name": "activeDirectiveId"
|
||||
},
|
||||
{
|
||||
"name": "activeDirectiveSuperClassDepthPosition"
|
||||
},
|
||||
{
|
||||
"name": "activeDirectiveSuperClassHeight"
|
||||
},
|
||||
{
|
||||
"name": "addBindingIntoContext"
|
||||
},
|
||||
|
@ -437,6 +431,9 @@
|
|||
{
|
||||
"name": "addItemToStylingMap"
|
||||
},
|
||||
{
|
||||
"name": "addNewSourceColumn"
|
||||
},
|
||||
{
|
||||
"name": "addRemoveViewFromContainer"
|
||||
},
|
||||
|
@ -446,6 +443,9 @@
|
|||
{
|
||||
"name": "addToViewTree"
|
||||
},
|
||||
{
|
||||
"name": "allocStylingMapArray"
|
||||
},
|
||||
{
|
||||
"name": "allocTStylingContext"
|
||||
},
|
||||
|
@ -453,7 +453,7 @@
|
|||
"name": "allocateNewContextEntry"
|
||||
},
|
||||
{
|
||||
"name": "allowStylingFlush"
|
||||
"name": "allowDirectStyling"
|
||||
},
|
||||
{
|
||||
"name": "appendChild"
|
||||
|
@ -470,6 +470,15 @@
|
|||
{
|
||||
"name": "applyStyling"
|
||||
},
|
||||
{
|
||||
"name": "applyStylingValue"
|
||||
},
|
||||
{
|
||||
"name": "applyStylingValueDirectly"
|
||||
},
|
||||
{
|
||||
"name": "applyStylingViaContext"
|
||||
},
|
||||
{
|
||||
"name": "applyToElementOrContainer"
|
||||
},
|
||||
|
@ -524,9 +533,6 @@
|
|||
{
|
||||
"name": "containerInternal"
|
||||
},
|
||||
{
|
||||
"name": "contextHasUpdates"
|
||||
},
|
||||
{
|
||||
"name": "contextLView"
|
||||
},
|
||||
|
@ -584,18 +590,6 @@
|
|||
{
|
||||
"name": "defaultScheduler"
|
||||
},
|
||||
{
|
||||
"name": "deferBindingRegistration"
|
||||
},
|
||||
{
|
||||
"name": "deferStylingUpdate"
|
||||
},
|
||||
{
|
||||
"name": "deferredBindingQueue"
|
||||
},
|
||||
{
|
||||
"name": "deleteStylingStateFromStorage"
|
||||
},
|
||||
{
|
||||
"name": "destroyLView"
|
||||
},
|
||||
|
@ -629,6 +623,9 @@
|
|||
{
|
||||
"name": "executeContentQueries"
|
||||
},
|
||||
{
|
||||
"name": "executeElementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndCheckHooks"
|
||||
},
|
||||
|
@ -666,10 +663,10 @@
|
|||
"name": "findExistingListener"
|
||||
},
|
||||
{
|
||||
"name": "findViaComponent"
|
||||
"name": "findInitialStylingValue"
|
||||
},
|
||||
{
|
||||
"name": "flushDeferredBindings"
|
||||
"name": "findViaComponent"
|
||||
},
|
||||
{
|
||||
"name": "flushStyling"
|
||||
|
@ -689,15 +686,6 @@
|
|||
{
|
||||
"name": "getActiveDirectiveId"
|
||||
},
|
||||
{
|
||||
"name": "getActiveDirectiveStylingIndex"
|
||||
},
|
||||
{
|
||||
"name": "getActiveDirectiveSuperClassDepth"
|
||||
},
|
||||
{
|
||||
"name": "getActiveDirectiveSuperClassHeight"
|
||||
},
|
||||
{
|
||||
"name": "getBeforeNodeForView"
|
||||
},
|
||||
|
@ -746,6 +734,9 @@
|
|||
{
|
||||
"name": "getDebugContext"
|
||||
},
|
||||
{
|
||||
"name": "getDefaultValue"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveDef"
|
||||
},
|
||||
|
@ -785,6 +776,9 @@
|
|||
{
|
||||
"name": "getLViewParent"
|
||||
},
|
||||
{
|
||||
"name": "getLockedConfig"
|
||||
},
|
||||
{
|
||||
"name": "getMapProp"
|
||||
},
|
||||
|
@ -893,21 +887,36 @@
|
|||
{
|
||||
"name": "getTViewCleanup"
|
||||
},
|
||||
{
|
||||
"name": "getTotalSources"
|
||||
},
|
||||
{
|
||||
"name": "getTypeName"
|
||||
},
|
||||
{
|
||||
"name": "getTypeNameForDebugging"
|
||||
},
|
||||
{
|
||||
"name": "getValue"
|
||||
},
|
||||
{
|
||||
"name": "getValuesCount"
|
||||
},
|
||||
{
|
||||
"name": "handleError"
|
||||
},
|
||||
{
|
||||
"name": "hasActiveElementFlag"
|
||||
},
|
||||
{
|
||||
"name": "hasClassInput"
|
||||
},
|
||||
{
|
||||
"name": "hasConfig"
|
||||
},
|
||||
{
|
||||
"name": "hasDirectives"
|
||||
},
|
||||
{
|
||||
"name": "hasParentInjector"
|
||||
},
|
||||
|
@ -936,7 +945,7 @@
|
|||
"name": "initNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeInputAndOutputAliases"
|
||||
"name": "initializeTNodeInputs"
|
||||
},
|
||||
{
|
||||
"name": "injectElementRef"
|
||||
|
@ -1010,6 +1019,12 @@
|
|||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
{
|
||||
"name": "isHostStyling"
|
||||
},
|
||||
{
|
||||
"name": "isHostStylingActive"
|
||||
},
|
||||
{
|
||||
"name": "isJsObject"
|
||||
},
|
||||
|
@ -1085,24 +1100,21 @@
|
|||
{
|
||||
"name": "markAsComponentHost"
|
||||
},
|
||||
{
|
||||
"name": "markContextToPersistState"
|
||||
},
|
||||
{
|
||||
"name": "markDirty"
|
||||
},
|
||||
{
|
||||
"name": "markDirtyIfOnPush"
|
||||
},
|
||||
{
|
||||
"name": "markStylingStateAsDirty"
|
||||
},
|
||||
{
|
||||
"name": "markViewDirty"
|
||||
},
|
||||
{
|
||||
"name": "matchTemplateAttribute"
|
||||
},
|
||||
{
|
||||
"name": "maybeApplyStyling"
|
||||
},
|
||||
{
|
||||
"name": "namespaceHTMLInternal"
|
||||
},
|
||||
|
@ -1139,6 +1151,9 @@
|
|||
{
|
||||
"name": "normalizeBitMaskValue"
|
||||
},
|
||||
{
|
||||
"name": "patchConfig"
|
||||
},
|
||||
{
|
||||
"name": "postProcessBaseDirective"
|
||||
},
|
||||
|
@ -1202,6 +1217,9 @@
|
|||
{
|
||||
"name": "renderDetachView"
|
||||
},
|
||||
{
|
||||
"name": "renderHostBindingsAsStale"
|
||||
},
|
||||
{
|
||||
"name": "renderInitialStyling"
|
||||
},
|
||||
|
@ -1214,9 +1232,6 @@
|
|||
{
|
||||
"name": "renderView"
|
||||
},
|
||||
{
|
||||
"name": "resetAllStylingState"
|
||||
},
|
||||
{
|
||||
"name": "resetComponentState"
|
||||
},
|
||||
|
@ -1250,6 +1265,9 @@
|
|||
{
|
||||
"name": "selectView"
|
||||
},
|
||||
{
|
||||
"name": "setActiveElementFlag"
|
||||
},
|
||||
{
|
||||
"name": "setActiveHostElement"
|
||||
},
|
||||
|
@ -1262,9 +1280,6 @@
|
|||
{
|
||||
"name": "setClass"
|
||||
},
|
||||
{
|
||||
"name": "setConfig"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentDirectiveDef"
|
||||
},
|
||||
|
@ -1274,9 +1289,15 @@
|
|||
{
|
||||
"name": "setCurrentStyleSanitizer"
|
||||
},
|
||||
{
|
||||
"name": "setDefaultValue"
|
||||
},
|
||||
{
|
||||
"name": "setDirectiveStylingInput"
|
||||
},
|
||||
{
|
||||
"name": "setElementExitFn"
|
||||
},
|
||||
{
|
||||
"name": "setGuardMask"
|
||||
},
|
||||
|
@ -1298,6 +1319,9 @@
|
|||
{
|
||||
"name": "setIsNotParent"
|
||||
},
|
||||
{
|
||||
"name": "setMapAsDirty"
|
||||
},
|
||||
{
|
||||
"name": "setMapValue"
|
||||
},
|
||||
|
@ -1316,18 +1340,15 @@
|
|||
{
|
||||
"name": "setUpAttributes"
|
||||
},
|
||||
{
|
||||
"name": "setValue"
|
||||
},
|
||||
{
|
||||
"name": "shouldSearchParent"
|
||||
},
|
||||
{
|
||||
"name": "stateIsPersisted"
|
||||
},
|
||||
{
|
||||
"name": "storeCleanupFn"
|
||||
},
|
||||
{
|
||||
"name": "storeStylingState"
|
||||
},
|
||||
{
|
||||
"name": "stringify"
|
||||
},
|
||||
|
@ -1337,6 +1358,9 @@
|
|||
{
|
||||
"name": "stylingMapToString"
|
||||
},
|
||||
{
|
||||
"name": "stylingProp"
|
||||
},
|
||||
{
|
||||
"name": "syncViewWithBlueprint"
|
||||
},
|
||||
|
@ -1358,26 +1382,23 @@
|
|||
{
|
||||
"name": "unwrapRNode"
|
||||
},
|
||||
{
|
||||
"name": "unwrapSafeValue"
|
||||
},
|
||||
{
|
||||
"name": "updateBindingData"
|
||||
},
|
||||
{
|
||||
"name": "updateClassBinding"
|
||||
"name": "updateClassViaContext"
|
||||
},
|
||||
{
|
||||
"name": "updateInitialStylingOnContext"
|
||||
},
|
||||
{
|
||||
"name": "updateLastDirectiveIndex"
|
||||
},
|
||||
{
|
||||
"name": "updateLastDirectiveIndex"
|
||||
},
|
||||
{
|
||||
"name": "updateRawValueOnContext"
|
||||
},
|
||||
{
|
||||
"name": "updateStyleBinding"
|
||||
"name": "updateStyleViaContext"
|
||||
},
|
||||
{
|
||||
"name": "viewAttachedToChangeDetector"
|
||||
|
@ -1436,12 +1457,6 @@
|
|||
{
|
||||
"name": "ɵɵrestoreView"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵstyling"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵstylingApply"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵtemplate"
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {NgForOfContext} from '@angular/common';
|
||||
|
||||
import {ɵɵdefineComponent} from '../../src/render3/definition';
|
||||
import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
|
||||
import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
|
||||
import {AttributeMarker} from '../../src/render3/interfaces/node';
|
||||
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, getSanitizationBypassType, unwrapSafeValue} from '../../src/sanitization/bypass';
|
||||
import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
|
@ -20,11 +20,7 @@ import {NgForOf} from './common_with_def';
|
|||
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||
|
||||
describe('instructions', () => {
|
||||
function createAnchor() {
|
||||
ɵɵelementStart(0, 'a');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
function createAnchor() { ɵɵelement(0, 'a'); }
|
||||
|
||||
function createDiv(initialClasses?: string[] | null, initialStyles?: string[] | null) {
|
||||
const attrs: any[] = [];
|
||||
|
@ -34,9 +30,7 @@ describe('instructions', () => {
|
|||
if (initialStyles) {
|
||||
attrs.push(AttributeMarker.Styles, ...initialStyles);
|
||||
}
|
||||
ɵɵelementStart(0, 'div', attrs);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelement(0, 'div', attrs);
|
||||
}
|
||||
|
||||
function createScript() { ɵɵelement(0, 'script'); }
|
||||
|
@ -156,7 +150,6 @@ describe('instructions', () => {
|
|||
t.update(() => {
|
||||
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
|
||||
ɵɵstyleProp('background-image', 'url("http://server")');
|
||||
ɵɵstylingApply();
|
||||
});
|
||||
// nothing is set because sanitizer suppresses it.
|
||||
expect(t.html).toEqual('<div></div>');
|
||||
|
@ -164,7 +157,6 @@ describe('instructions', () => {
|
|||
t.update(() => {
|
||||
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
|
||||
ɵɵstyleProp('background-image', bypassSanitizationTrustStyle('url("http://server2")'));
|
||||
ɵɵstylingApply();
|
||||
});
|
||||
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
|
||||
.toEqual('url("http://server2")');
|
||||
|
@ -173,17 +165,12 @@ describe('instructions', () => {
|
|||
|
||||
describe('styleMap', () => {
|
||||
function createDivWithStyle() {
|
||||
ɵɵelementStart(0, 'div', [AttributeMarker.Styles, 'height', '10px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelement(0, 'div', [AttributeMarker.Styles, 'height', '10px']);
|
||||
}
|
||||
|
||||
it('should add style', () => {
|
||||
const fixture = new TemplateFixture(createDivWithStyle, () => {}, 1);
|
||||
fixture.update(() => {
|
||||
ɵɵstyleMap({'background-color': 'red'});
|
||||
ɵɵstylingApply();
|
||||
});
|
||||
fixture.update(() => { ɵɵstyleMap({'background-color': 'red'}); });
|
||||
expect(fixture.html).toEqual('<div style="background-color: red; height: 10px;"></div>');
|
||||
});
|
||||
|
||||
|
@ -205,7 +192,6 @@ describe('instructions', () => {
|
|||
'filter': 'filter',
|
||||
'width': 'width'
|
||||
});
|
||||
ɵɵstylingApply();
|
||||
});
|
||||
|
||||
const props = detectedValues.sort();
|
||||
|
@ -216,18 +202,11 @@ describe('instructions', () => {
|
|||
});
|
||||
|
||||
describe('elementClass', () => {
|
||||
function createDivWithStyling() {
|
||||
ɵɵelementStart(0, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
function createDivWithStyling() { ɵɵelement(0, 'div'); }
|
||||
|
||||
it('should add class', () => {
|
||||
const fixture = new TemplateFixture(createDivWithStyling, () => {}, 1);
|
||||
fixture.update(() => {
|
||||
ɵɵclassMap('multiple classes');
|
||||
ɵɵstylingApply();
|
||||
});
|
||||
fixture.update(() => { ɵɵclassMap('multiple classes'); });
|
||||
expect(fixture.html).toEqual('<div class="classes multiple"></div>');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {RendererType2} from '../../src/render/api';
|
||||
import {getLContext} from '../../src/render3/context_discovery';
|
||||
import {AttributeMarker, ɵɵadvance, ɵɵattribute, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵhostProperty, ɵɵproperty} from '../../src/render3/index';
|
||||
import {ɵɵallocHostVars, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {ɵɵallocHostVars, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
|
@ -630,12 +630,7 @@ describe('element discovery', () => {
|
|||
vars: 0,
|
||||
template: (rf: RenderFlags, ctx: StructuredComp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'section');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵstylingApply();
|
||||
ɵɵelement(0, 'section');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ɵɵadvance} from '../../../../src/render3/instructions/advance';
|
||||
import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
|
||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
|
||||
import {refreshView} from '../../../../src/render3/instructions/shared';
|
||||
import {RenderFlags} from '../../../../src/render3/interfaces/definition';
|
||||
import {TVIEW} from '../../../../src/render3/interfaces/view';
|
||||
import {ɵɵclassMap, ɵɵstyleMap, ɵɵstyling, ɵɵstylingApply} from '../../../../src/render3/styling_next/instructions';
|
||||
import {ɵɵclassMap, ɵɵstyleMap} from '../../../../src/render3/styling_next/instructions';
|
||||
import {createBenchmark} from '../micro_bench';
|
||||
import {setupRootViewWithEmbeddedViews} from '../setup';
|
||||
|
||||
|
@ -31,79 +31,49 @@ import {setupRootViewWithEmbeddedViews} from '../setup';
|
|||
function testTemplate(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
ɵɵelementStart(1, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(2, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(3, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(4, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(5, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(6, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(7, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(8, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(9, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(10, 'div');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelement(1, 'div');
|
||||
ɵɵelement(2, 'div');
|
||||
ɵɵelement(3, 'div');
|
||||
ɵɵelement(4, 'div');
|
||||
ɵɵelement(5, 'div');
|
||||
ɵɵelement(6, 'div');
|
||||
ɵɵelement(7, 'div');
|
||||
ɵɵelement(8, 'div');
|
||||
ɵɵelement(9, 'div');
|
||||
ɵɵelement(10, 'div');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '0px', height: '0px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '10px', height: '100px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '20px', height: '200px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '30px', height: '300px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '40px', height: '400px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '50px', height: '500px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '60px', height: '600px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '70px', height: '700px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '80px', height: '800px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleMap({width: '90px', height: '900px'});
|
||||
ɵɵclassMap('one two');
|
||||
ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ɵɵadvance} from '../../../../src/render3/instructions/advance';
|
||||
import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
|
||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
|
||||
import {refreshView} from '../../../../src/render3/instructions/shared';
|
||||
import {RenderFlags} from '../../../../src/render3/interfaces/definition';
|
||||
import {AttributeMarker} from '../../../../src/render3/interfaces/node';
|
||||
import {TVIEW} from '../../../../src/render3/interfaces/view';
|
||||
import {ɵɵclassProp, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply} from '../../../../src/render3/styling_next/instructions';
|
||||
import {ɵɵclassProp, ɵɵstyleProp} from '../../../../src/render3/styling_next/instructions';
|
||||
import {createBenchmark} from '../micro_bench';
|
||||
import {setupRootViewWithEmbeddedViews} from '../setup';
|
||||
|
||||
|
@ -32,89 +32,50 @@ import {setupRootViewWithEmbeddedViews} from '../setup';
|
|||
function testTemplate(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div', [AttributeMarker.Classes, 'list']);
|
||||
ɵɵelementStart(
|
||||
1, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
2, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
3, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
4, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
5, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
6, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
7, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
8, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
9, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(
|
||||
ɵɵelement(1, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(2, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(3, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(4, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(5, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(6, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(7, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(8, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(9, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵelement(
|
||||
10, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '0px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '100px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '200px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '300px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '400px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '500px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '600px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '700px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '800px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('width', '900px');
|
||||
ɵɵclassProp('scale', true);
|
||||
ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,4 +95,4 @@ while (refreshTime()) {
|
|||
console.profileEnd();
|
||||
|
||||
// report results
|
||||
styleAndClassBindingBenchmark.report();
|
||||
styleAndClassBindingBenchmark.report();
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ɵɵadvance} from '../../../../src/render3/instructions/advance';
|
||||
import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
|
||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
|
||||
import {refreshView} from '../../../../src/render3/instructions/shared';
|
||||
import {RenderFlags} from '../../../../src/render3/interfaces/definition';
|
||||
import {TVIEW} from '../../../../src/render3/interfaces/view';
|
||||
import {ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply} from '../../../../src/render3/styling_next/instructions';
|
||||
import {ɵɵstyleProp} from '../../../../src/render3/styling_next/instructions';
|
||||
import {createBenchmark} from '../micro_bench';
|
||||
import {setupRootViewWithEmbeddedViews} from '../setup';
|
||||
|
||||
|
@ -31,69 +31,39 @@ import {setupRootViewWithEmbeddedViews} from '../setup';
|
|||
function testTemplate(rf: RenderFlags, ctx: any) {
|
||||
if (rf & 1) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
ɵɵelementStart(1, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(2, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(3, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(4, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(5, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(6, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(7, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(8, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(9, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(10, 'button');
|
||||
ɵɵstyling();
|
||||
ɵɵelementEnd();
|
||||
ɵɵelement(1, 'button');
|
||||
ɵɵelement(2, 'button');
|
||||
ɵɵelement(3, 'button');
|
||||
ɵɵelement(4, 'button');
|
||||
ɵɵelement(5, 'button');
|
||||
ɵɵelement(6, 'button');
|
||||
ɵɵelement(7, 'button');
|
||||
ɵɵelement(8, 'button');
|
||||
ɵɵelement(9, 'button');
|
||||
ɵɵelement(10, 'button');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color1');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color2');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color3');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color4');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color5');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color6');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color7');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color8');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color9');
|
||||
ɵɵstylingApply();
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', 'color10');
|
||||
ɵɵstylingApply();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 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 {normalizeIntoStylingMap as createMap} from '../../../src/render3/styling_next/map_based_bindings';
|
||||
import {normalizeIntoStylingMap as createMap} from '../../../src/render3/styling_next/util';
|
||||
|
||||
describe('map-based bindings', () => {
|
||||
describe('StylingMapArray construction', () => {
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {DEFAULT_GUARD_MASK_VALUE, registerBinding} from '@angular/core/src/render3/styling_next/bindings';
|
||||
import {registerBinding} from '@angular/core/src/render3/styling_next/bindings';
|
||||
import {attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug';
|
||||
import {DEFAULT_GUARD_MASK_VALUE} from '@angular/core/src/render3/styling_next/util';
|
||||
|
||||
import {allocTStylingContext} from '../../../src/render3/styling_next/util';
|
||||
|
||||
describe('styling context', () => {
|
||||
|
@ -15,33 +17,36 @@ describe('styling context', () => {
|
|||
const context = debug.context;
|
||||
expect(debug.entries).toEqual({});
|
||||
|
||||
registerBinding(context, 1, 'width', '100px');
|
||||
registerBinding(context, 1, 0, 'width', '100px');
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
guardMask: buildGuardMask(),
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: ['100px'],
|
||||
});
|
||||
|
||||
registerBinding(context, 2, 'width', 20);
|
||||
registerBinding(context, 2, 0, 'width', 20);
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
sanitizationRequired: false,
|
||||
valuesCount: 2,
|
||||
guardMask: buildGuardMask(2),
|
||||
templateBitMask: buildGuardMask(2),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: [20, '100px'],
|
||||
});
|
||||
|
||||
registerBinding(context, 3, 'height', 10);
|
||||
registerBinding(context, 4, 'height', 15);
|
||||
registerBinding(context, 3, 0, 'height', 10);
|
||||
registerBinding(context, 4, 1, 'height', 15);
|
||||
expect(debug.entries['height']).toEqual({
|
||||
prop: 'height',
|
||||
valuesCount: 3,
|
||||
sanitizationRequired: false,
|
||||
guardMask: buildGuardMask(3, 4),
|
||||
templateBitMask: buildGuardMask(3),
|
||||
hostBindingsBitMask: buildGuardMask(4),
|
||||
defaultValue: null,
|
||||
sources: [10, 15, null],
|
||||
});
|
||||
|
@ -52,13 +57,14 @@ describe('styling context', () => {
|
|||
const context = debug.context;
|
||||
expect(debug.entries).toEqual({});
|
||||
|
||||
registerBinding(context, 1, 'width', 123);
|
||||
registerBinding(context, 1, 'width', 123);
|
||||
registerBinding(context, 1, 0, 'width', 123);
|
||||
registerBinding(context, 1, 0, 'width', 123);
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 2,
|
||||
sanitizationRequired: false,
|
||||
guardMask: buildGuardMask(1),
|
||||
templateBitMask: buildGuardMask(1),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: null,
|
||||
sources: [123, null],
|
||||
});
|
||||
|
@ -68,33 +74,36 @@ describe('styling context', () => {
|
|||
const debug = makeContextWithDebug();
|
||||
const context = debug.context;
|
||||
|
||||
registerBinding(context, 1, 'width', null);
|
||||
registerBinding(context, 1, 0, 'width', null);
|
||||
const x = debug.entries['width'];
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
guardMask: buildGuardMask(),
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: null,
|
||||
sources: [null]
|
||||
});
|
||||
|
||||
registerBinding(context, 1, 'width', '100px');
|
||||
registerBinding(context, 1, 0, 'width', '100px');
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
guardMask: buildGuardMask(),
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: ['100px']
|
||||
});
|
||||
|
||||
registerBinding(context, 1, 'width', '200px');
|
||||
registerBinding(context, 1, 0, 'width', '200px');
|
||||
expect(debug.entries['width']).toEqual({
|
||||
prop: 'width',
|
||||
valuesCount: 1,
|
||||
sanitizationRequired: false,
|
||||
guardMask: buildGuardMask(),
|
||||
templateBitMask: buildGuardMask(),
|
||||
hostBindingsBitMask: buildGuardMask(),
|
||||
defaultValue: '100px',
|
||||
sources: ['100px']
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('styling debugging tools', () => {
|
|||
const data: any[] = [];
|
||||
const d = new NodeStylingDebug(context, data);
|
||||
|
||||
registerBinding(context, 0, 'width', null);
|
||||
registerBinding(context, 0, 0, 'width', null);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
@ -27,7 +27,7 @@ describe('styling debugging tools', () => {
|
|||
},
|
||||
});
|
||||
|
||||
registerBinding(context, 0, 'width', '100px');
|
||||
registerBinding(context, 0, 0, 'width', '100px');
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
@ -39,7 +39,7 @@ describe('styling debugging tools', () => {
|
|||
const someBindingIndex1 = 1;
|
||||
data[someBindingIndex1] = '200px';
|
||||
|
||||
registerBinding(context, 0, 'width', someBindingIndex1);
|
||||
registerBinding(context, 0, 0, 'width', someBindingIndex1);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
@ -51,7 +51,7 @@ describe('styling debugging tools', () => {
|
|||
const someBindingIndex2 = 2;
|
||||
data[someBindingIndex2] = '500px';
|
||||
|
||||
registerBinding(context, 0, 'width', someBindingIndex2);
|
||||
registerBinding(context, 0, 1, 'width', someBindingIndex2);
|
||||
expect(d.summary).toEqual({
|
||||
width: {
|
||||
prop: 'width',
|
||||
|
|
|
@ -1076,10 +1076,6 @@ export declare function ɵɵstylePropInterpolateV(prop: string, values: any[], v
|
|||
|
||||
export declare function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void;
|
||||
|
||||
export declare function ɵɵstyling(): void;
|
||||
|
||||
export declare function ɵɵstylingApply(): void;
|
||||
|
||||
export declare function ɵɵtemplate(index: number, templateFn: ComponentTemplate<any> | null, consts: number, vars: number, tagName?: string | null, attrs?: TAttributes | null, localRefs?: string[] | null, localRefExtractor?: LocalRefExtractor): void;
|
||||
|
||||
export declare function ɵɵtemplateRefExtractor(tNode: TNode, currentView: LView): ViewEngine_TemplateRef<unknown> | null;
|
||||
|
|
Loading…
Reference in New Issue