feat(ivy): support for i18n & ICU expressions (#27101)

PR Close #27101
This commit is contained in:
Olivier Combe 2018-11-13 09:36:30 +01:00 committed by Miško Hevery
parent 92b05652b2
commit e22a302cad
25 changed files with 2980 additions and 2537 deletions

View File

@ -83,23 +83,23 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nEnd(); $r3$.ɵi18nEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(2, "div"); $r3$.ɵelementStart(2, "div");
$r3$.ɵi18nAttribute(3, $_c2$); $r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵtext(4, "Content B"); $r3$.ɵtext(4, "Content B");
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(5, "div"); $r3$.ɵelementStart(5, "div");
$r3$.ɵi18nAttribute(6, $_c4$); $r3$.ɵi18nAttributes(6, $_c4$);
$r3$.ɵtext(7, "Content C"); $r3$.ɵtext(7, "Content C");
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(8, "div"); $r3$.ɵelementStart(8, "div");
$r3$.ɵi18nAttribute(9, $_c6$); $r3$.ɵi18nAttributes(9, $_c6$);
$r3$.ɵtext(10, "Content D"); $r3$.ɵtext(10, "Content D");
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(11, "div"); $r3$.ɵelementStart(11, "div");
$r3$.ɵi18nAttribute(12, $_c8$); $r3$.ɵi18nAttributes(12, $_c8$);
$r3$.ɵtext(13, "Content E"); $r3$.ɵtext(13, "Content E");
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(14, "div"); $r3$.ɵelementStart(14, "div");
$r3$.ɵi18nAttribute(15, $_c10$); $r3$.ɵi18nAttributes(15, $_c10$);
$r3$.ɵtext(16, "Content F"); $r3$.ɵtext(16, "Content F");
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
@ -142,7 +142,7 @@ describe('i18n support in the view compiler', () => {
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c0$); $r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵi18nAttribute(1, $_c2$); $r3$.ɵi18nAttributes(1, $_c2$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
} }
@ -207,10 +207,10 @@ describe('i18n support in the view compiler', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c0$); $r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵpipe(1, "uppercase"); $r3$.ɵpipe(1, "uppercase");
$r3$.ɵi18nAttribute(2, $_c4$); $r3$.ɵi18nAttributes(2, $_c4$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(3, "div", $_c5$); $r3$.ɵelementStart(3, "div", $_c5$);
$r3$.ɵi18nAttribute(4, $_c8$); $r3$.ɵi18nAttributes(4, $_c8$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
@ -265,7 +265,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementStart(0, "div"); $r3$.ɵelementStart(0, "div");
$r3$.ɵelementStart(1, "div"); $r3$.ɵelementStart(1, "div");
$r3$.ɵpipe(2, "uppercase"); $r3$.ɵpipe(2, "uppercase");
$r3$.ɵi18nAttribute(3, $_c2$); $r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
@ -345,10 +345,10 @@ describe('i18n support in the view compiler', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵelementStart(0, "div", $_c0$); $r3$.ɵelementStart(0, "div", $_c0$);
$r3$.ɵpipe(1, "uppercase"); $r3$.ɵpipe(1, "uppercase");
$r3$.ɵi18nAttribute(2, $_c4$); $r3$.ɵi18nAttributes(2, $_c4$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementStart(3, "div", $_c5$); $r3$.ɵelementStart(3, "div", $_c5$);
$r3$.ɵi18nAttribute(4, $_c8$); $r3$.ɵi18nAttributes(4, $_c8$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
@ -403,7 +403,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementStart(0, "div"); $r3$.ɵelementStart(0, "div");
$r3$.ɵelementStart(1, "div"); $r3$.ɵelementStart(1, "div");
$r3$.ɵpipe(2, "uppercase"); $r3$.ɵpipe(2, "uppercase");
$r3$.ɵi18nAttribute(3, $_c2$); $r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
@ -693,7 +693,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementStart(0, "div"); $r3$.ɵelementStart(0, "div");
$r3$.ɵi18nStart(1, $MSG_APP_SPEC_TS_0$); $r3$.ɵi18nStart(1, $MSG_APP_SPEC_TS_0$);
$r3$.ɵelementStart(2, "span"); $r3$.ɵelementStart(2, "span");
$r3$.ɵi18nAttribute(3, $_c2$); $r3$.ɵi18nAttributes(3, $_c2$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵi18nEnd(); $r3$.ɵi18nEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
@ -701,7 +701,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nStart(5, $MSG_APP_SPEC_TS_3$); $r3$.ɵi18nStart(5, $MSG_APP_SPEC_TS_3$);
$r3$.ɵpipe(6, "uppercase"); $r3$.ɵpipe(6, "uppercase");
$r3$.ɵelementStart(7, "span"); $r3$.ɵelementStart(7, "span");
$r3$.ɵi18nAttribute(8, $_c5$); $r3$.ɵi18nAttributes(8, $_c5$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
$r3$.ɵi18nEnd(); $r3$.ɵi18nEnd();
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();

View File

@ -95,7 +95,7 @@ export class Identifiers {
static pipeBind4: o.ExternalReference = {name: 'ɵpipeBind4', moduleName: CORE}; static pipeBind4: o.ExternalReference = {name: 'ɵpipeBind4', moduleName: CORE};
static pipeBindV: o.ExternalReference = {name: 'ɵpipeBindV', moduleName: CORE}; static pipeBindV: o.ExternalReference = {name: 'ɵpipeBindV', moduleName: CORE};
static i18nAttribute: o.ExternalReference = {name: 'ɵi18nAttribute', moduleName: CORE}; static i18nAttributes: o.ExternalReference = {name: 'ɵi18nAttributes', moduleName: CORE};
static i18nExp: o.ExternalReference = {name: 'ɵi18nExp', moduleName: CORE}; static i18nExp: o.ExternalReference = {name: 'ɵi18nExp', moduleName: CORE};
static i18nStart: o.ExternalReference = {name: 'ɵi18nStart', moduleName: CORE}; static i18nStart: o.ExternalReference = {name: 'ɵi18nStart', moduleName: CORE};
static i18nEnd: o.ExternalReference = {name: 'ɵi18nEnd', moduleName: CORE}; static i18nEnd: o.ExternalReference = {name: 'ɵi18nEnd', moduleName: CORE};

View File

@ -490,7 +490,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (i18nAttrArgs.length) { if (i18nAttrArgs.length) {
const index: o.Expression = o.literal(this.allocateDataSlot()); const index: o.Expression = o.literal(this.allocateDataSlot());
const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true); const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true);
this.creationInstruction(element.sourceSpan, R3.i18nAttribute, [index, args]); this.creationInstruction(element.sourceSpan, R3.i18nAttributes, [index, args]);
if (hasBindings) { if (hasBindings) {
this.updateInstruction(element.sourceSpan, R3.i18nApply, [index]); this.updateInstruction(element.sourceSpan, R3.i18nApply, [index]);
} }

View File

@ -108,24 +108,12 @@ export {
PipeDef as ɵPipeDef, PipeDef as ɵPipeDef,
PipeDefWithMeta as ɵPipeDefWithMeta, PipeDefWithMeta as ɵPipeDefWithMeta,
whenRendered as ɵwhenRendered, whenRendered as ɵwhenRendered,
i18nAttribute as ɵi18nAttribute, i18nAttributes as ɵi18nAttributes,
i18nExp as ɵi18nExp, i18nExp as ɵi18nExp,
i18nStart as ɵi18nStart, i18nStart as ɵi18nStart,
i18nEnd as ɵi18nEnd, i18nEnd as ɵi18nEnd,
i18nApply as ɵi18nApply, i18nApply as ɵi18nApply,
i18nExpMapping as ɵi18nExpMapping, i18nIcuReplaceVars as ɵi18nIcuReplaceVars,
i18nInterpolation1 as ɵi18nInterpolation1,
i18nInterpolation2 as ɵi18nInterpolation2,
i18nInterpolation3 as ɵi18nInterpolation3,
i18nInterpolation4 as ɵi18nInterpolation4,
i18nInterpolation5 as ɵi18nInterpolation5,
i18nInterpolation6 as ɵi18nInterpolation6,
i18nInterpolation7 as ɵi18nInterpolation7,
i18nInterpolation8 as ɵi18nInterpolation8,
i18nInterpolationV as ɵi18nInterpolationV,
i18nMapping as ɵi18nMapping,
I18nInstruction as ɵI18nInstruction,
I18nExpInstruction as ɵI18nExpInstruction,
WRAP_RENDERER_FACTORY2 as ɵWRAP_RENDERER_FACTORY2, WRAP_RENDERER_FACTORY2 as ɵWRAP_RENDERER_FACTORY2,
setClassMetadata as ɵsetClassMetadata, setClassMetadata as ɵsetClassMetadata,
} from './render3/index'; } from './render3/index';

View File

@ -205,12 +205,12 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
### I18N ### I18N
| Feature | Runtime | Spec | Compiler | | Feature | Runtime | Spec | Compiler |
| ----------------------------------- | ------- | -------- | -------- | | ----------------------------------- | ------- | -------- | -------- |
| i18nStart | | ✅ | ✅ | | i18nStart | | ✅ | ✅ |
| i18nEnd | | ✅ | ✅ | | i18nEnd | | ✅ | ✅ |
| i18nAttributes | | ✅ | ✅ | | i18nAttributes | | ✅ | ✅ |
| i18nExp | | ✅ | ✅ | | i18nExp | | ✅ | ✅ |
| i18nApply | | ✅ | ✅ | | i18nApply | | ✅ | ✅ |
| ICU expressions | | ✅ | ❌ | | ICU expressions | | ✅ | ❌ |
| closure support for g3 | ✅ | ✅ | ❌ | | closure support for g3 | ✅ | ✅ | ❌ |
| runtime service for external world | ❌ | ❌ | ❌ | | runtime service for external world | ❌ | ❌ | ❌ |
| migration tool | ❌ | ❌ | ❌ | | migration tool | ❌ | ❌ | ❌ |

View File

@ -12,11 +12,11 @@ For example index `123` may point to a component instance in the `LViewData` but
The layout is as such: The layout is as such:
| Section | `LViewData` | `TView.data` | Section | `LViewData` | `TView.data`
| ---------- | --------------------------------------------- | -------------------------------------------------- | ---------- | ------------------------------------------------------------ | --------------------------------------------------
| `HEADER` | contextual data | mostly `null` | `HEADER` | contextual data | mostly `null`
| `CONSTS` | DOM, pipe, and local ref instances | | `CONSTS` | DOM, pipe, and local ref instances |
| `VARS` | binding values | property names | `VARS` | binding values | property names
| `EXPANDO` | host bindings; directive instances; providers | host prop names; directive tokens; provider tokens | `EXPANDO` | host bindings; directive instances; providers; dynamic nodes | host prop names; directive tokens; provider tokens; `null`
## `HEADER` ## `HEADER`

View File

@ -23,7 +23,7 @@ class MyComponent {
``` ```
NOTE: NOTE:
- There really is only two kinds of i18n text. - There really is only two kinds of i18n text.
1. In attribute as in `i18n-title`. 1. In attribute as in `title` (with `i18n-title` present).
2. In element body marked as as `<div i18n>`. 2. In element body marked as as `<div i18n>`.
- The element body i18n can have internal DOM structure which may consist of sub-templates. - The element body i18n can have internal DOM structure which may consist of sub-templates.
@ -160,8 +160,8 @@ i18nAttributes(1, MSG_div_attr);
``` ```
The above instruction checks the `TView.data` cache at position `1` and if empty will create `I18nUpdateOpCodes` like so: The above instruction checks the `TView.data` cache at position `1` and if empty will create `I18nUpdateOpCodes` like so:
```typescript ```typescript
<I18nUpdateOpCodes>[ const i18nUpdateOpCodes = <I18nUpdateOpCodes>[
// The following OpCodes represent: `<div i18n-title="Hello <20>0<EFBFBD>!">` // The following OpCodes represent: `<div i18n-title title="Hello <20>0<EFBFBD>!">`
// If `changeMask & 0b11` // If `changeMask & 0b11`
// has changed then execute update OpCodes. // has changed then execute update OpCodes.
// has NOT changed then skip `7` values and start processing next OpCodes. // has NOT changed then skip `7` values and start processing next OpCodes.
@ -170,12 +170,13 @@ The above instruction checks the `TView.data` cache at position `1` and if empty
'Hello ', // accumulate('Hello '); 'Hello ', // accumulate('Hello ');
-1, // accumulate(-1); -1, // accumulate(-1);
'!', // accumulate('!'); '!', // accumulate('!');
// Update attribute: `elementAttribute(1, 'title', accumulatorFlush(null));` // Update attribute: `elementAttribute(0, 'title', accumulatorFlush(null));`
// NOTE: `null` means don't sanitize // NOTE: `null` means don't sanitize
1 << SHIFT_REF | Attr, 'title', null, 0 << SHIFT_REF | Attr, 'title', null,
] ]
``` ```
NOTE: NOTE:
- `i18nAttributes` updates the attributes of the previous element.
- If there is more than one attribute which needs to be internationalized it is added to the array as `[attributeName, translation]` tuple. - If there is more than one attribute which needs to be internationalized it is added to the array as `[attributeName, translation]` tuple.
- Even attributes which don't have bindings must go through `i18nAttributes` so that they correctly work with i18n in server environment. - Even attributes which don't have bindings must go through `i18nAttributes` so that they correctly work with i18n in server environment.
@ -216,7 +217,7 @@ will generate
```typescript ```typescript
// Text broken down to allow addition of comments (Generated code will not have comments) // Text broken down to allow addition of comments (Generated code will not have comments)
const MSG_div = const MSG_div =
'List: ' 'List: ' +
'<27>*2:1<>' + // template(2, MyComponent_NgIf_Template_0, ...); '<27>*2:1<>' + // template(2, MyComponent_NgIf_Template_0, ...);
'<27>#1:1<>' + // elementStart(1, 'ul'); '<27>#1:1<>' + // elementStart(1, 'ul');
'<27>*2:2<>' + // template(2, MyComponent_NgIf_NgFor_Template_1, ...); '<27>*2:2<>' + // template(2, MyComponent_NgIf_NgFor_Template_1, ...);
@ -298,7 +299,7 @@ i18nEnd(); // The instruction which is responsible for inserting text node
The `i18nStart` generates these instructions which are cached in the `TView` and then processed by `i18nEnd`. The `i18nStart` generates these instructions which are cached in the `TView` and then processed by `i18nEnd`.
```typescript ```typescript
<TI18n>{ const tI18n = <TI18n>{
vars: 2, // Number of slots to allocate in EXPANDO. vars: 2, // Number of slots to allocate in EXPANDO.
expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100 expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100
create: <I18nMutateOpCodes>[ // Processed by `i18nEnd` create: <I18nMutateOpCodes>[ // Processed by `i18nEnd`
@ -362,7 +363,7 @@ This case is more complex because it contains an ICU.
ICUs are pre-parsed and then stored in the `TVIEW.data` as follows. ICUs are pre-parsed and then stored in the `TVIEW.data` as follows.
```typescript ```typescript
<TI18n>{ const tI18n = <TI18n>{
vars: 3 + Math.max(4, 3, 3), // Number of slots to allocate in EXPANDO. (Max of all ICUs + fixed) vars: 3 + Math.max(4, 3, 3), // Number of slots to allocate in EXPANDO. (Max of all ICUs + fixed)
expandoStartIndex: 200, // Assume in this example EXPANDO starts at 200 expandoStartIndex: 200, // Assume in this example EXPANDO starts at 200
create: <I18nMutateOpCodes>[ create: <I18nMutateOpCodes>[
@ -379,16 +380,16 @@ ICUs are pre-parsed and then stored in the `TVIEW.data` as follows.
// has NOT changed then skip `2` values and start processing next OpCodes. // has NOT changed then skip `2` values and start processing next OpCodes.
0b1, 2, 0b1, 2,
-1, // accumulate(-1); -1, // accumulate(-1);
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/, accumulatorFlush());` // Switch ICU: `icuSwitchCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuSwitch, 200 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks in the ICU. // NOTE: the bit mask here is the logical OR of all of the masks in the ICU.
0b1, 1, 0b1, 1,
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/);` // Update ICU: `icuUpdateCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
// SHIFT_ICU: points to: `i18nStart(0, MSG_div, 1);` // SHIFT_REF: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_REF: is an index into which ICU is being updated. In our example we only have // SHIFT_ICU: is an index into which ICU is being updated. In our example we only have
// one ICU so it is 0-th ICU to update. // one ICU so it is 0-th ICU to update.
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuUpdate, 200 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
], ],
icus: [ icus: [
<TIcu>{ <TIcu>{
@ -544,7 +545,7 @@ The OpCodes require that offsets for the EXPANDO index for the reference.
The question is how do we compute this: The question is how do we compute this:
```typescript ```typescript
<TI18n>{ const tI18n = <TI18n>{
vars: 1, vars: 1,
expandoStartIndex: 100, // Retrieved from `tView.blueprint.length` at i18nStart invocation. expandoStartIndex: 100, // Retrieved from `tView.blueprint.length` at i18nStart invocation.
create: <I18nMutateOpCodes>[ create: <I18nMutateOpCodes>[
@ -623,7 +624,7 @@ The rules for attribute ICUs should be the same as for normal ICUs.
For this reason we would like to reuse as much code as possible for parsing and processing of the ICU for simplicity and consistency. For this reason we would like to reuse as much code as possible for parsing and processing of the ICU for simplicity and consistency.
```typescript ```typescript
<TI18n>{ const tI18n = <TI18n>{
vars: 0, // Number of slots to allocate in EXPANDO. (Max of all ICUs + fixed) vars: 0, // Number of slots to allocate in EXPANDO. (Max of all ICUs + fixed)
expandoStartIndex: 200, // Assume in this example EXPANDO starts at 200 expandoStartIndex: 200, // Assume in this example EXPANDO starts at 200
create: <I18nMutateOpCodes>[ create: <I18nMutateOpCodes>[
@ -635,18 +636,18 @@ For this reason we would like to reuse as much code as possible for parsing and
// has NOT changed then skip `2` values and start processing next OpCodes. // has NOT changed then skip `2` values and start processing next OpCodes.
0b1, 2, 0b1, 2,
-1, // accumulate(-1) -1, // accumulate(-1)
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/, accumulatorFlush());` // Switch ICU: `icuSwitchCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuSwitch, 200 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks in the ICU. // NOTE: the bit mask here is the logical OR of all of the masks in the ICU.
0b1, 4, 0b1, 4,
'You have ', // accumulate('You have '); 'You have ', // accumulate('You have ');
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/);` // Update ICU: `icuUpdateCase(lViewData[200 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
// SHIFT_ICU: points to: `i18nStart(0, MSG_div, 1);` // SHIFT_REF: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_REF: is an index into which ICU is being updated. In our example we only have // SHIFT_ICU: is an index into which ICU is being updated. In our example we only have
// one ICU so it is 0-th ICU to update. // one ICU so it is 0-th ICU to update.
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuUpdate, 200 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
'.', // accumulate('.'); '.', // accumulate('.');
@ -699,7 +700,7 @@ For this reason we would like to reuse as much code as possible for parsing and
// has changed then execute update OpCodes. // has changed then execute update OpCodes.
// has NOT changed then skip `1` values and start processing next OpCodes. // has NOT changed then skip `1` values and start processing next OpCodes.
-1, 2, -1, 2,
-1, // accumulate(lviewData[bindIndex-1]); -1, // accumulate(lViewData[bindIndex-1]);
'emails', // accumulate('no emails'); 'emails', // accumulate('no emails');
] ]
] ]
@ -724,7 +725,7 @@ Given
``` ```
The above needs to be parsed into: The above needs to be parsed into:
```TypeScript ```TypeScript
{ const icu = {
type: 'plural', // or 'select' type: 'plural', // or 'select'
expressionBindingIndex: 0, // from <20>0<EFBFBD>, expressionBindingIndex: 0, // from <20>0<EFBFBD>,
cases: [ cases: [
@ -757,7 +758,7 @@ NOTE: The updates to attributes with placeholders require that we go through san
## Translation without top level element ## Translation without top level element
Placing `i18n` attribute on an existing elements is easy because the element defines parent and the translated element can be insert synchronously. Placing `i18n` attribute on an existing elements is easy because the element defines parent and the translated element can be inserted synchronously.
For virtual elements such as `<ng-container>` or `<ng-template>` this is more complicated because there is no common root element to insert into. For virtual elements such as `<ng-container>` or `<ng-template>` this is more complicated because there is no common root element to insert into.
In such a case the `i18nStart` acts as the element to insert into. In such a case the `i18nStart` acts as the element to insert into.
This is similar to `<ng-container>` behavior. This is similar to `<ng-container>` behavior.
@ -783,7 +784,7 @@ function MyComponent_Template_0(rf: RenderFlags, ctx: any) {
Which would get parsed into: Which would get parsed into:
```typescript ```typescript
<TI18n>{ const tI18n = <TI18n>{
vars: 2, // Number of slots to allocate in EXPANDO. vars: 2, // Number of slots to allocate in EXPANDO.
expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100 expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100
create: <I18nMutateOpCodes>[ // Processed by `i18nEnd` create: <I18nMutateOpCodes>[ // Processed by `i18nEnd`
@ -869,7 +870,7 @@ NOTE:
The internal data structure will be: The internal data structure will be:
```typescript ```typescript
<TI18n>{ const tI18n = <TI18n>{
vars: 2, // Number of slots to allocate in EXPANDO. vars: 2, // Number of slots to allocate in EXPANDO.
expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100 expandoStartIndex: 100, // Assume in this example EXPANDO starts at 100
create: <I18nMutateOpCodes>[ // Processed by `i18nEnd` create: <I18nMutateOpCodes>[ // Processed by `i18nEnd`
@ -881,16 +882,16 @@ The internal data structure will be:
// has NOT changed then skip `2` values and start processing next OpCodes. // has NOT changed then skip `2` values and start processing next OpCodes.
0b1, 2, 0b1, 2,
-1, // accumulate(-1); -1, // accumulate(-1);
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/, accumulatorFlush());` // Switch ICU: `icuSwitchCase(lViewData[100 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuSwitch, 100 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks in the ICU. // NOTE: the bit mask here is the logical OR of all of the masks in the ICU.
0b1, 1, 0b1, 1,
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 0 /*SHIFT_REF*/);` // Update ICU: `icuUpdateCase(lViewData[100 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
// SHIFT_ICU: points to: `i18nStart(0, MSG_div, 1);` // SHIFT_REF: points to: `i18nStart(0, MSG_div, 1);`
// SHIFT_REF: is an index into which ICU is being updated. In our example we only have // SHIFT_ICU: is an index into which ICU is being updated. In our example we only have
// one ICU so it is 0-th ICU to update. // one ICU so it is 0-th ICU to update.
0 << SHIFT_ICU | 0 << SHIFT_REF | IcuUpdate, 100 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
], ],
icus: [ icus: [
<TIcu>{ // {<7B>0<EFBFBD>, plural, =0 {zero} other {<7B>0<EFBFBD> <!--subICU-->}} <TIcu>{ // {<7B>0<EFBFBD>, plural, =0 {zero} other {<7B>0<EFBFBD> <!--subICU-->}}
@ -906,7 +907,7 @@ The internal data structure will be:
'', 1 << SHIFT_PARENT | AppendChild, // Expando location: 100 '', 1 << SHIFT_PARENT | AppendChild, // Expando location: 100
COMMENT_MARKER, '', 0 << SHIFT_PARENT | AppendChild, // Expando location: 101 COMMENT_MARKER, '', 0 << SHIFT_PARENT | AppendChild, // Expando location: 101
], ],
] ],
remove: [ remove: [
<I18nMutateOpCodes>[ // Case: `0`: `{zero}` <I18nMutateOpCodes>[ // Case: `0`: `{zero}`
1 << SHIFT_PARENT | 100 << SHIFT_REF | Remove, 1 << SHIFT_PARENT | 100 << SHIFT_REF | Remove,
@ -921,16 +922,16 @@ The internal data structure will be:
], ],
<I18nMutateOpCodes>[ // Case: `other`: `{<7B>0<EFBFBD> <!--subICU-->}` <I18nMutateOpCodes>[ // Case: `other`: `{<7B>0<EFBFBD> <!--subICU-->}`
0b1, 3, 0b1, 3,
-2, ' ', 100 << SHIFT_REF | Text // Case: `<60>0<EFBFBD> ` -2, ' ', 100 << SHIFT_REF | Text, // Case: `<60>0<EFBFBD> `
0b10, 5, 0b10, 5,
-1 -1,
// Switch ICU: `icuSwitchCase(lViewData[0 /*SHIFT_ICU*/], 1 /*SHIFT_REF*/, accumulatorFlush());` // Switch ICU: `icuSwitchCase(lViewData[101 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/, accumulatorFlush());`
0 << SHIFT_ICU | 1 << SHIFT_REF | IcuSwitch, 101 << SHIFT_REF | 0 << SHIFT_ICU | IcuSwitch,
// NOTE: the bit mask here is the logical OR of all of the masks int the ICU. // NOTE: the bit mask here is the logical OR of all of the masks int the ICU.
0b10, 1, 0b10, 1,
// Update ICU: `icuUpdateCase(lViewData[0 /*SHIFT_ICU*/], 1 /*SHIFT_REF*/);` // Update ICU: `icuUpdateCase(lViewData[101 /*SHIFT_REF*/], 0 /*SHIFT_ICU*/);`
0 << SHIFT_ICU | 1 << SHIFT_REF | IcuUpdate, 101 << SHIFT_REF | 0 << SHIFT_ICU | IcuUpdate,
], ],
] ]
}, },
@ -1057,7 +1058,7 @@ Here is a more complete example.
Given this Angular's template: Given this Angular's template:
```HTML ```HTML
<div i18n-title="Hello {{name}}!" i18n="Some description."> <div i18n-title title="Hello {{name}}!" i18n="Some description.">
{{count}} is rendered as: {{count}} is rendered as:
<b *ngIf="true"> <b *ngIf="true">
{ count, plural, { count, plural,
@ -1111,7 +1112,7 @@ To generate code where the extracted i18n messages have the same ids, the `ngtsc
Given this Angular's template: Given this Angular's template:
```HTML ```HTML
<div i18n-title="Hello {{name}}!" i18n="Some description."> <div i18n-title title="Hello {{name}}!" i18n="Some description.">
{{count}} is rendered as: {{count}} is rendered as:
<b *ngIf="true"> <b *ngIf="true">
{ count, plural, { count, plural,

File diff suppressed because it is too large Load Diff

View File

@ -87,24 +87,12 @@ export {
} from './state'; } from './state';
export { export {
i18nAttribute, i18nAttributes,
i18nExp, i18nExp,
i18nStart, i18nStart,
i18nEnd, i18nEnd,
i18nApply, i18nApply,
i18nMapping, i18nIcuReplaceVars,
i18nInterpolation1,
i18nInterpolation2,
i18nInterpolation3,
i18nInterpolation4,
i18nInterpolation5,
i18nInterpolation6,
i18nInterpolation7,
i18nInterpolation8,
i18nInterpolationV,
i18nExpMapping,
I18nInstruction,
I18nExpInstruction
} from './i18n'; } from './i18n';
export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref'; export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';

View File

@ -7,7 +7,6 @@
*/ */
import './ng_dev_mode'; import './ng_dev_mode';
import {resolveForwardRef} from '../di/forward_ref'; import {resolveForwardRef} from '../di/forward_ref';
import {InjectionToken} from '../di/injection_token'; import {InjectionToken} from '../di/injection_token';
import {InjectFlags} from '../di/injector_compatibility'; import {InjectFlags} from '../di/injector_compatibility';
@ -16,7 +15,6 @@ import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {Type} from '../type'; import {Type} from '../type';
import {noop} from '../util/noop'; import {noop} from '../util/noop';
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert'; import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
import {attachPatchData, getComponentViewByInstance} from './context_discovery'; import {attachPatchData, getComponentViewByInstance} from './context_discovery';
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di'; import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
@ -25,11 +23,12 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container'; import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE, NodeInjectorFactory} from './interfaces/injector'; import {INJECTOR_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player'; import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query'; import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {StylingIndex} from './interfaces/styling'; import {StylingIndex} from './interfaces/styling';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
@ -42,7 +41,6 @@ import {getStylingContext} from './styling/util';
import {NO_CHANGE} from './tokens'; import {NO_CHANGE} from './tokens';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util'; import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
/** /**
* A permanent marker promise which signifies that the current CD tree is * A permanent marker promise which signifies that the current CD tree is
* clean. * clean.
@ -54,11 +52,6 @@ const enum BindingDirection {
Output, Output,
} }
/**
* Function used to sanitize the value before writing it into the renderer.
*/
type SanitizerFn = (value: any) => string;
/** /**
* Refreshes the view, executing the following steps in that order: * Refreshes the view, executing the following steps in that order:
* triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host * triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host
@ -197,9 +190,13 @@ export function createNodeAtIndex(
export function createNodeAtIndex( export function createNodeAtIndex(
index: number, type: TNodeType.ElementContainer, native: RComment, name: null, index: number, type: TNodeType.ElementContainer, native: RComment, name: null,
attrs: TAttributes | null): TElementContainerNode; attrs: TAttributes | null): TElementContainerNode;
export function createNodeAtIndex(
index: number, type: TNodeType.IcuContainer, native: RComment, name: null,
attrs: TAttributes | null): TElementContainerNode;
export function createNodeAtIndex( export function createNodeAtIndex(
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null, index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode { attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&
TIcuContainerNode {
const viewData = getViewData(); const viewData = getViewData();
const tView = getTView(); const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET; const adjustedIndex = index + HEADER_OFFSET;
@ -233,7 +230,7 @@ export function createNodeAtIndex(
setPreviousOrParentTNode(tNode); setPreviousOrParentTNode(tNode);
setIsParent(true); setIsParent(true);
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode & return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
TProjectionNode; TProjectionNode & TIcuContainerNode;
} }
export function createViewNode(index: number, view: LViewData) { export function createViewNode(index: number, view: LViewData) {
@ -255,11 +252,12 @@ export function createViewNode(index: number, view: LViewData) {
* i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future * i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future
* template passes. * template passes.
*/ */
export function adjustBlueprintForNewNode(view: LViewData) { export function allocExpando(view: LViewData) {
const tView = view[TVIEW]; const tView = view[TVIEW];
if (tView.firstTemplatePass) { if (tView.firstTemplatePass) {
tView.expandoStartIndex++; tView.expandoStartIndex++;
tView.blueprint.push(null); tView.blueprint.push(null);
tView.data.push(null);
view.push(null); view.push(null);
} }
} }
@ -914,7 +912,7 @@ export function elementEnd(): void {
* @param sanitizer An optional function used to sanitize the value. * @param sanitizer An optional function used to sanitize the value.
*/ */
export function elementAttribute( export function elementAttribute(
index: number, name: string, value: any, sanitizer?: SanitizerFn): void { index: number, name: string, value: any, sanitizer?: SanitizerFn | null): void {
if (value !== NO_CHANGE) { if (value !== NO_CHANGE) {
const viewData = getViewData(); const viewData = getViewData();
const renderer = getRenderer(); const renderer = getRenderer();
@ -947,7 +945,7 @@ export function elementAttribute(
*/ */
export function elementProperty<T>( export function elementProperty<T>(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void { index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null): void {
if (value === NO_CHANGE) return; if (value === NO_CHANGE) return;
const viewData = getViewData(); const viewData = getViewData();
const element = getNativeByIndex(index, viewData) as RElement | RComment; const element = getNativeByIndex(index, viewData) as RElement | RComment;

View File

@ -16,24 +16,32 @@
* *
* See: `I18nCreateOpCodes` for example of usage. * See: `I18nCreateOpCodes` for example of usage.
*/ */
import {SanitizerFn} from './sanitization';
export const enum I18nMutateOpCode { export const enum I18nMutateOpCode {
/// Stores shift amount for bits 17-2 that contain reference index. /// Stores shift amount for bits 17-3 that contain reference index.
SHIFT_REF = 2, SHIFT_REF = 3,
/// Stores shift amount for bits 31-17 that contain parent index. /// Stores shift amount for bits 31-17 that contain parent index.
SHIFT_PARENT = 17, SHIFT_PARENT = 17,
/// Mask for OpCode /// Mask for OpCode
MASK_OPCODE = 0b11, MASK_OPCODE = 0b111,
/// Mask for reference index. /// Mask for reference index.
MASK_REF = ((2 ^ 16) - 1) << SHIFT_REF, MASK_REF = ((2 ^ 16) - 1) << SHIFT_REF,
/// OpCode to select a node. (next OpCode will contain the operation.) /// OpCode to select a node. (next OpCode will contain the operation.)
Select = 0b00, Select = 0b000,
/// OpCode to append the current node to `PARENT`. /// OpCode to append the current node to `PARENT`.
AppendChild = 0b01, AppendChild = 0b001,
/// OpCode to insert the current node to `PARENT` before `REF`. /// OpCode to insert the current node to `PARENT` before `REF`.
InsertBefore = 0b10, InsertBefore = 0b010,
/// OpCode to remove the `REF` node from `PARENT`. /// OpCode to remove the `REF` node from `PARENT`.
Remove = 0b11, Remove = 0b011,
/// OpCode to set the attribute of a node.
Attr = 0b100,
/// OpCode to simulate elementEnd()
ElementEnd = 0b101,
/// OpCode to read the remove OpCodes for the nested ICU
RemoveNestedIcu = 0b110,
} }
/** /**
@ -114,8 +122,8 @@ export interface COMMENT_MARKER { marker: 'comment'; }
* // For removing existing nodes * // For removing existing nodes
* // -------------------------------------------------- * // --------------------------------------------------
* // const node = lViewData[1]; * // const node = lViewData[1];
* // lViewData[2].remove(node); * // removeChild(tView.data(1), node, lViewData);
* 2 << SHIFT_PARENT | 1 << SHIFT_REF | Remove, * 1 << SHIFT_REF | Remove,
* *
* // For writing attributes * // For writing attributes
* // -------------------------------------------------- * // --------------------------------------------------
@ -178,7 +186,7 @@ export const enum I18nUpdateOpCode {
* } * }
* ``` * ```
* We can assume that each call to `i18nExp` sets an internal `changeMask` bit depending on the * We can assume that each call to `i18nExp` sets an internal `changeMask` bit depending on the
* index of `i18nExp` index. * index of `i18nExp`.
* *
* OpCodes * OpCodes
* ``` * ```
@ -222,7 +230,7 @@ export const enum I18nUpdateOpCode {
* ``` * ```
* *
*/ */
export interface I18nUpdateOpCodes extends Array<string|number|((text: string) => string | null)> {} export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null> {}
/** /**
* Store information for the i18n translation block. * Store information for the i18n translation block.
@ -355,10 +363,6 @@ export interface TIcu {
update: I18nUpdateOpCodes[]; update: I18nUpdateOpCodes[];
} }
/** // Note: This hack is necessary so we don't erroneously get a circular dependency
* Stores currently selected case in each ICU. // failure based on types.
* export const unusedValueExportToPlacateAjd = 1;
* For each ICU in translation, the `Li18n` stores the currently selected case for the current
* `LView`. For perf reasons this array is only created if a translation block has an ICU.
*/
export interface LI18n extends Array<number> {}

View File

@ -21,6 +21,7 @@ export const enum TNodeType {
Element = 0b011, Element = 0b011,
ViewOrElement = 0b010, ViewOrElement = 0b010,
ElementContainer = 0b100, ElementContainer = 0b100,
IcuContainer = 0b101,
} }
/** /**
@ -255,7 +256,7 @@ export interface TNode {
parent: TElementNode|TContainerNode|null; parent: TElementNode|TContainerNode|null;
/** /**
* If this node is part of an i18n block, it indicates whether this container is part of the DOM * If this node is part of an i18n block, it indicates whether this node is part of the DOM.
* If this node is not part of an i18n block, this field is null. * If this node is not part of an i18n block, this field is null.
*/ */
detached: boolean|null; detached: boolean|null;
@ -358,7 +359,6 @@ export interface TContainerNode extends TNode {
projection: null; projection: null;
} }
/** Static data for an <ng-container> */ /** Static data for an <ng-container> */
export interface TElementContainerNode extends TNode { export interface TElementContainerNode extends TNode {
/** Index in the LViewData[] array. */ /** Index in the LViewData[] array. */
@ -369,6 +369,21 @@ export interface TElementContainerNode extends TNode {
projection: null; projection: null;
} }
/** Static data for an ICU expression */
export interface TIcuContainerNode extends TNode {
/** Index in the LViewData[] array. */
index: number;
child: TElementNode|TTextNode|null;
parent: TElementNode|TElementContainerNode|null;
tViews: null;
projection: null;
/**
* Indicates the current active case for an ICU expression.
* It is null when there is no active case.
*/
activeCaseIndex: number|null;
}
/** Static data for a view */ /** Static data for a view */
export interface TViewNode extends TNode { export interface TViewNode extends TNode {
/** If -1, it's a dynamically created view. Otherwise, it is the view block ID. */ /** If -1, it's a dynamically created view. Otherwise, it is the view block ID. */

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Function used to sanitize the value before writing it into the renderer.
*/
export type SanitizerFn = (value: any) => string;

View File

@ -11,9 +11,9 @@ import {Injector} from '../../di/injector';
import {QueryList} from '../../linker'; import {QueryList} from '../../linker';
import {Sanitizer} from '../../sanitization/security'; import {Sanitizer} from '../../sanitization/security';
import {Type} from '../../type'; import {Type} from '../../type';
import {LContainer} from './container'; import {LContainer} from './container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition';
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TElementNode, TNode, TViewNode} from './node'; import {TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player'; import {PlayerHandler} from './player';
import {LQueries} from './query'; import {LQueries} from './query';
@ -297,7 +297,7 @@ export interface TView {
/** Whether or not this template has been processed. */ /** Whether or not this template has been processed. */
firstTemplatePass: boolean; firstTemplatePass: boolean;
/** Static data equivalent of LView.data[]. Contains TNodes. */ /** Static data equivalent of LView.data[]. Contains TNodes, PipeDefInternal or TI18n. */
data: TData; data: TData;
/** /**
@ -535,7 +535,7 @@ export type HookData = (number | (() => void))[];
*/ */
export type TData = export type TData =
(TNode | PipeDef<any>| DirectiveDef<any>| ComponentDef<any>| number | Type<any>| (TNode | PipeDef<any>| DirectiveDef<any>| ComponentDef<any>| number | Type<any>|
InjectionToken<any>| null)[]; InjectionToken<any>| TI18n | I18nUpdateOpCodes | null)[];
// Note: This hack is necessary so we don't erroneously get a circular dependency // Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types. // failure based on types.

View File

@ -97,7 +97,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵtextBinding': r3.textBinding, 'ɵtextBinding': r3.textBinding,
'ɵembeddedViewStart': r3.embeddedViewStart, 'ɵembeddedViewStart': r3.embeddedViewStart,
'ɵembeddedViewEnd': r3.embeddedViewEnd, 'ɵembeddedViewEnd': r3.embeddedViewEnd,
'ɵi18nAttribute': r3.i18nAttribute, 'ɵi18nAttributes': r3.i18nAttributes,
'ɵi18nExp': r3.i18nExp, 'ɵi18nExp': r3.i18nExp,
'ɵi18nStart': r3.i18nStart, 'ɵi18nStart': r3.i18nStart,
'ɵi18nEnd': r3.i18nEnd, 'ɵi18nEnd': r3.i18nEnd,

View File

@ -21,8 +21,23 @@ const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
/** Retrieves the parent element of a given node. */ /** Retrieves the parent element of a given node. */
export function getParentNative(tNode: TNode, currentView: LViewData): RElement|RComment|null { export function getParentNative(tNode: TNode, currentView: LViewData): RElement|RComment|null {
return tNode.parent == null ? getHostNative(currentView) : if (tNode.parent == null) {
getNativeByTNode(tNode.parent, currentView); return getHostNative(currentView);
} else {
const parentTNode = getFirstParentNative(tNode);
return getNativeByTNode(parentTNode, currentView);
}
}
/**
* Get the first parent of a node that isn't an IcuContainer TNode
*/
function getFirstParentNative(tNode: TNode): TNode {
let parent = tNode.parent;
while (parent && parent.type === TNodeType.IcuContainer) {
parent = parent.parent;
}
return parent !;
} }
/** /**
@ -578,17 +593,22 @@ function canInsertNativeChildOfView(viewTNode: TViewNode, view: LViewData): bool
* *
* *
* @param parent The parent where the child will be inserted into. * @param tNode The tNode of the node that we want to insert.
* @param currentView Current LView being processed. * @param currentView Current LView being processed.
* @return boolean Whether the child should be inserted now (or delayed until later). * @return boolean Whether the node should be inserted now (or delayed until later).
*/ */
export function canInsertNativeNode(tNode: TNode, currentView: LViewData): boolean { export function canInsertNativeNode(tNode: TNode, currentView: LViewData): boolean {
let currentNode = tNode; let currentNode = tNode;
let parent: TNode|null = tNode.parent; let parent: TNode|null = tNode.parent;
if (tNode.parent && tNode.parent.type === TNodeType.ElementContainer) { if (tNode.parent) {
if (tNode.parent.type === TNodeType.ElementContainer) {
currentNode = getHighestElementContainer(tNode); currentNode = getHighestElementContainer(tNode);
parent = currentNode.parent; parent = currentNode.parent;
} else if (tNode.parent.type === TNodeType.IcuContainer) {
currentNode = getFirstParentNative(currentNode);
parent = currentNode.parent;
}
} }
if (parent === null) parent = currentView[HOST_NODE]; if (parent === null) parent = currentView[HOST_NODE];
@ -639,7 +659,7 @@ export function nativeNextSibling(renderer: Renderer3, node: RNode): RNode|null
* @returns Whether or not the child was appended * @returns Whether or not the child was appended
*/ */
export function appendChild( export function appendChild(
childEl: RNode | null, childTNode: TNode, currentView: LViewData): boolean { childEl: RNode | null = null, childTNode: TNode, currentView: LViewData): boolean {
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) { if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
const renderer = currentView[RENDERER]; const renderer = currentView[RENDERER];
const parentEl = getParentNative(childTNode, currentView); const parentEl = getParentNative(childTNode, currentView);
@ -655,6 +675,9 @@ export function appendChild(
} else if (parentTNode.type === TNodeType.ElementContainer) { } else if (parentTNode.type === TNodeType.ElementContainer) {
const renderParent = getRenderParent(childTNode, currentView) !; const renderParent = getRenderParent(childTNode, currentView) !;
nativeInsertBefore(renderer, renderParent, childEl, parentEl); nativeInsertBefore(renderer, renderParent, childEl, parentEl);
} else if (parentTNode.type === TNodeType.IcuContainer) {
const icuAnchorNode = getNativeByTNode(childTNode.parent !, currentView) !as RElement;
nativeInsertBefore(renderer, parentEl as RElement, childEl, icuAnchorNode);
} else { } else {
isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) : isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) :
parentEl !.appendChild(childEl); parentEl !.appendChild(childEl);

View File

@ -157,6 +157,10 @@ export function getCurrentView(): OpaqueViewState {
return viewData as any as OpaqueViewState; return viewData as any as OpaqueViewState;
} }
export function _getViewData(): LViewData {
return viewData;
}
/** /**
* Restores `contextViewData` to the given OpaqueViewState instance. * Restores `contextViewData` to the given OpaqueViewState instance.
* *

View File

@ -253,3 +253,15 @@ export const defaultScheduler =
(typeof requestAnimationFrame !== 'undefined' && requestAnimationFrame || // browser only (typeof requestAnimationFrame !== 'undefined' && requestAnimationFrame || // browser only
setTimeout // everything else setTimeout // everything else
).bind(global); ).bind(global);
/**
* Equivalent to ES6 spread, add each item to an array.
*
* @param items The items to add
* @param arr The array to which you want to add the items
*/
export function addAllToArray(items: any[], arr: any[]) {
for (let i = 0; i < items.length; i++) {
arr.push(items[i]);
}
}

View File

@ -57,14 +57,14 @@ const INLINE_ELEMENTS = merge(
'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' + 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' +
'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video')); 'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video'));
const VALID_ELEMENTS = export const VALID_ELEMENTS =
merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS); merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS);
// Attributes that have href and hence need to be sanitized // Attributes that have href and hence need to be sanitized
const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href'); export const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href');
// Attributes that have special href set hence need to be sanitized // Attributes that have special href set hence need to be sanitized
const SRCSET_ATTRS = tagSet('srcset'); export const SRCSET_ATTRS = tagSet('srcset');
const HTML_ATTRS = tagSet( const HTML_ATTRS = tagSet(
'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' + 'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' +
@ -81,7 +81,7 @@ const HTML_ATTRS = tagSet(
// can be sanitized, but they increase security surface area without a legitimate use case, so they // can be sanitized, but they increase security surface area without a legitimate use case, so they
// are left out here. // are left out here.
const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS); export const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS);
/** /**
* SanitizingHtmlSerializer serializes a DOM fragment, stripping out any unsafe elements and unsafe * SanitizingHtmlSerializer serializes a DOM fragment, stripping out any unsafe elements and unsafe
@ -265,7 +265,7 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string
} }
} }
function getTemplateContent(el: Node): Node|null { export function getTemplateContent(el: Node): Node|null {
return 'content' in (el as any /** Microsoft/TypeScript#21517 */) && isTemplateElement(el) ? return 'content' in (el as any /** Microsoft/TypeScript#21517 */) && isTemplateElement(el) ?
el.content : el.content :
null; null;

View File

@ -626,6 +626,9 @@
{ {
"name": "getElementDepthCount" "name": "getElementDepthCount"
}, },
{
"name": "getFirstParentNative"
},
{ {
"name": "getFirstTemplatePass" "name": "getFirstTemplatePass"
}, },

View File

@ -260,6 +260,9 @@
{ {
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
{
"name": "getFirstParentNative"
},
{ {
"name": "getFirstTemplatePass" "name": "getFirstTemplatePass"
}, },

View File

@ -854,6 +854,9 @@
{ {
"name": "getErrorLogger" "name": "getErrorLogger"
}, },
{
"name": "getFirstParentNative"
},
{ {
"name": "getFirstTemplatePass" "name": "getFirstTemplatePass"
}, },
@ -1113,7 +1116,7 @@
"name": "markViewDirty" "name": "markViewDirty"
}, },
{ {
"name": "merge" "name": "merge$1"
}, },
{ {
"name": "mergeAll" "name": "mergeAll"

View File

@ -668,6 +668,9 @@
{ {
"name": "getElementDepthCount" "name": "getElementDepthCount"
}, },
{
"name": "getFirstParentNative"
},
{ {
"name": "getFirstTemplatePass" "name": "getFirstTemplatePass"
}, },

View File

@ -681,7 +681,7 @@
"name": "PlatformRef" "name": "PlatformRef"
}, },
{ {
"name": "Plural" "name": "Plural$1"
}, },
{ {
"name": "QUERIES" "name": "QUERIES"
@ -1697,6 +1697,9 @@
{ {
"name": "getErrorLogger" "name": "getErrorLogger"
}, },
{
"name": "getFirstParentNative"
},
{ {
"name": "getFirstTemplatePass" "name": "getFirstTemplatePass"
}, },
@ -1860,7 +1863,7 @@
"name": "getPlayerContext" "name": "getPlayerContext"
}, },
{ {
"name": "getPluralCategory" "name": "getPluralCategory$1"
}, },
{ {
"name": "getPointers" "name": "getPointers"

File diff suppressed because it is too large Load Diff