@ -39,6 +39,12 @@ export class Identifiers {
|
|||||||
|
|
||||||
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||||
|
|
||||||
|
|
||||||
|
static namespace: o.ExternalReference = {name: 'ɵN', moduleName: CORE};
|
||||||
|
static namespaceHTML: o.ExternalReference = {name: 'ɵNH', moduleName: CORE};
|
||||||
|
static namespaceMathML: o.ExternalReference = {name: 'ɵNM', moduleName: CORE};
|
||||||
|
static namespaceSVG: o.ExternalReference = {name: 'ɵNS', moduleName: CORE};
|
||||||
|
|
||||||
static interpolation1: o.ExternalReference = {name: 'ɵi1', moduleName: CORE};
|
static interpolation1: o.ExternalReference = {name: 'ɵi1', moduleName: CORE};
|
||||||
static interpolation2: o.ExternalReference = {name: 'ɵi2', moduleName: CORE};
|
static interpolation2: o.ExternalReference = {name: 'ɵi2', moduleName: CORE};
|
||||||
static interpolation3: o.ExternalReference = {name: 'ɵi3', moduleName: CORE};
|
static interpolation3: o.ExternalReference = {name: 'ɵi3', moduleName: CORE};
|
||||||
|
@ -19,6 +19,7 @@ import {HtmlParser} from '../../ml_parser/html_parser';
|
|||||||
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
|
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
||||||
import * as o from '../../output/output_ast';
|
import * as o from '../../output/output_ast';
|
||||||
|
import {ExternalReference} from '../../output/output_ast';
|
||||||
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
||||||
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
|
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
|
||||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||||
@ -51,6 +52,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
private _valueConverter: ValueConverter;
|
private _valueConverter: ValueConverter;
|
||||||
private _unsupported = unsupported;
|
private _unsupported = unsupported;
|
||||||
private _bindingScope: BindingScope;
|
private _bindingScope: BindingScope;
|
||||||
|
private _namespace = R3.namespaceHTML;
|
||||||
|
|
||||||
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
|
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
|
||||||
private _inI18nSection: boolean = false;
|
private _inI18nSection: boolean = false;
|
||||||
@ -220,6 +222,26 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
this.instruction(this._creationCode, ngContent.sourceSpan, R3.projection, ...parameters);
|
this.instruction(this._creationCode, ngContent.sourceSpan, R3.projection, ...parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the namespace instruction function based on the current element
|
||||||
|
* @param ivyElementName An system element name, can include colons like :svg:svg
|
||||||
|
*/
|
||||||
|
getNamespaceInstruction(ivyElementName: string) {
|
||||||
|
switch (ivyElementName) {
|
||||||
|
case ':svg:svg':
|
||||||
|
return R3.namespaceSVG;
|
||||||
|
case ':math:math':
|
||||||
|
return R3.namespaceMathML;
|
||||||
|
default:
|
||||||
|
return this._namespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addNamespaceInstruction(nsInstruction: ExternalReference, element: t.Element) {
|
||||||
|
this._namespace = nsInstruction;
|
||||||
|
this.instruction(this._creationCode, element.sourceSpan, nsInstruction);
|
||||||
|
}
|
||||||
|
|
||||||
visitElement(element: t.Element) {
|
visitElement(element: t.Element) {
|
||||||
const elementIndex = this.allocateDataSlot();
|
const elementIndex = this.allocateDataSlot();
|
||||||
const referenceDataSlots = new Map<string, number>();
|
const referenceDataSlots = new Map<string, number>();
|
||||||
@ -315,11 +337,20 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
this._creationCode.push(...i18nMessages);
|
this._creationCode.push(...i18nMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEmptyElement = element.outputs.length === 0 && element.children.length === 0;
|
const isEmptyElement = element.children.length === 0 && element.outputs.length === 0;
|
||||||
|
|
||||||
|
|
||||||
const implicit = o.variable(CONTEXT_NAME);
|
const implicit = o.variable(CONTEXT_NAME);
|
||||||
|
|
||||||
|
const wasInNamespace = this._namespace;
|
||||||
|
const currentNamespace = this.getNamespaceInstruction(element.name);
|
||||||
|
// If the namespace is changing now, include an instruction to change it
|
||||||
|
// during element creation.
|
||||||
|
if (currentNamespace !== wasInNamespace) {
|
||||||
|
this.addNamespaceInstruction(currentNamespace, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isEmptyElement) {
|
if (isEmptyElement) {
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
|
this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
|
||||||
@ -328,6 +359,14 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
this._creationCode, element.sourceSpan, R3.elementStart,
|
this._creationCode, element.sourceSpan, R3.elementStart,
|
||||||
...trimTrailingNulls(parameters));
|
...trimTrailingNulls(parameters));
|
||||||
|
|
||||||
|
// If the element happens to be an SVG <foreignObject>, we need to switch
|
||||||
|
// to the HTML namespace inside of it
|
||||||
|
if (element.name === ':svg:foreignObject') {
|
||||||
|
// NOTE(benlesh): this may cause extremem corner-case bugs if someone was to do something
|
||||||
|
// like <math>...<foreignObject></foreignObject>...</math>.
|
||||||
|
this.addNamespaceInstruction(R3.namespaceHTML, element);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate Listeners (outputs)
|
// Generate Listeners (outputs)
|
||||||
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
||||||
const elName = sanitizeIdentifier(element.name);
|
const elName = sanitizeIdentifier(element.name);
|
||||||
@ -384,6 +423,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
}
|
}
|
||||||
// Restore the state before exiting this node
|
// Restore the state before exiting this node
|
||||||
this._inI18nSection = wasInI18nSection;
|
this._inI18nSection = wasInI18nSection;
|
||||||
|
this._namespace = wasInNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitTemplate(template: t.Template) {
|
visitTemplate(template: t.Template) {
|
||||||
|
@ -66,6 +66,50 @@ describe('compiler compliance', () => {
|
|||||||
expectEmit(result.source, template, 'Incorrect template');
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should translate DOM structure for SVG', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`<div class="my-app" title="Hello"><svg><circle cx="50" cy="100" r="25"/></svg></div>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The factory should look like this:
|
||||||
|
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
||||||
|
|
||||||
|
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||||
|
const template = `
|
||||||
|
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||||
|
…
|
||||||
|
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵE(0, 'div', $e0_attrs$);
|
||||||
|
$r3$.ɵNS();
|
||||||
|
$r3$.ɵE(1, ':svg:svg');
|
||||||
|
$r3$.ɵEe(2, ':svg:circle', $e2_attrs$);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
|
||||||
|
expectEmit(result.source, factory, 'Incorrect factory');
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
});
|
||||||
|
|
||||||
it('should bind to element properties', () => {
|
it('should bind to element properties', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
@ -1230,6 +1274,78 @@ describe('compiler compliance', () => {
|
|||||||
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support a let variable and reference for SVG', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
...shared,
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {ForOfDirective} from './shared/for_of';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`<svg><g *for="let item of items"><circle></circle></g></svg>\`
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
items = [{ data: 42 }, { data: 42 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComponent, ForOfDirective]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(chuckj): Enforce this when the directives are specified
|
||||||
|
const ForDirectiveDefinition = `
|
||||||
|
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: ForOfDirective,
|
||||||
|
selectors: [['', 'forOf', '']],
|
||||||
|
factory: function ForOfDirective_Factory() {
|
||||||
|
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||||
|
},
|
||||||
|
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
||||||
|
inputs: {forOf: 'forOf'}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MyComponentDefinition = `
|
||||||
|
const $_c0$ = ['for','','forOf',''];
|
||||||
|
…
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
selectors: [['my-component']],
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(rf:IDENT,ctx:IDENT){
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵNS();
|
||||||
|
$r3$.ɵE(0,':svg:svg');
|
||||||
|
$r3$.ɵC(1,MyComponent__svg_g_Template_1,null,$_c0$);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
if (rf & 2) { $r3$.ɵp(1,'forOf',$r3$.ɵb(ctx.items)); }
|
||||||
|
function MyComponent__svg_g_Template_1(rf:IDENT,ctx0:IDENT) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵE(0,':svg:g');
|
||||||
|
$r3$.ɵEe(1,':svg:circle');
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directives: [ForOfDirective]
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
// TODO(chuckj): Enforce this when the directives are specified
|
||||||
|
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
|
||||||
|
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support accessing parent template variables', () => {
|
it('should support accessing parent template variables', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
|
@ -45,6 +45,10 @@ export {
|
|||||||
i7 as ɵi7,
|
i7 as ɵi7,
|
||||||
i8 as ɵi8,
|
i8 as ɵi8,
|
||||||
iV as ɵiV,
|
iV as ɵiV,
|
||||||
|
N as ɵN,
|
||||||
|
NH as ɵNH,
|
||||||
|
NM as ɵNM,
|
||||||
|
NS as ɵNS,
|
||||||
pb1 as ɵpb1,
|
pb1 as ɵpb1,
|
||||||
pb2 as ɵpb2,
|
pb2 as ɵpb2,
|
||||||
pb3 as ɵpb3,
|
pb3 as ɵpb3,
|
||||||
|
@ -54,6 +54,11 @@ export {
|
|||||||
elementStyle as s,
|
elementStyle as s,
|
||||||
elementStyleNamed as sn,
|
elementStyleNamed as sn,
|
||||||
|
|
||||||
|
namespace as N,
|
||||||
|
namespaceHTML as NH,
|
||||||
|
namespaceMathML as NM,
|
||||||
|
namespaceSVG as NS,
|
||||||
|
|
||||||
listener as L,
|
listener as L,
|
||||||
store as st,
|
store as st,
|
||||||
load as ld,
|
load as ld,
|
||||||
|
@ -496,6 +496,7 @@ export function renderEmbeddedTemplate<T>(
|
|||||||
rf = RenderFlags.Create;
|
rf = RenderFlags.Create;
|
||||||
}
|
}
|
||||||
oldView = enterView(viewNode.data, viewNode);
|
oldView = enterView(viewNode.data, viewNode);
|
||||||
|
namespaceHTML();
|
||||||
tView.template !(rf, context);
|
tView.template !(rf, context);
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
refreshView();
|
refreshView();
|
||||||
@ -521,6 +522,7 @@ export function renderComponentOrTemplate<T>(
|
|||||||
rendererFactory.begin();
|
rendererFactory.begin();
|
||||||
}
|
}
|
||||||
if (template) {
|
if (template) {
|
||||||
|
namespaceHTML();
|
||||||
template(getRenderFlags(hostView), componentOrContext !);
|
template(getRenderFlags(hostView), componentOrContext !);
|
||||||
refreshView();
|
refreshView();
|
||||||
} else {
|
} else {
|
||||||
@ -2186,6 +2188,7 @@ export function detectChangesInternal<T>(hostView: LView, hostNode: LElementNode
|
|||||||
const template = hostView.tView.template !;
|
const template = hostView.tView.template !;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
namespaceHTML();
|
||||||
template(getRenderFlags(hostView), component);
|
template(getRenderFlags(hostView), component);
|
||||||
refreshView();
|
refreshView();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -545,6 +545,9 @@
|
|||||||
{
|
{
|
||||||
"name": "markViewDirty"
|
"name": "markViewDirty"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "namespaceHTML"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "notImplemented"
|
"name": "notImplemented"
|
||||||
},
|
},
|
||||||
|
@ -55,6 +55,44 @@ describe('elements', () => {
|
|||||||
.toEqual('<div class="my-app" title="Hello">Hello <b>World</b>!</div>');
|
.toEqual('<div class="my-app" title="Hello">Hello <b>World</b>!</div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should translate DOM structure with SVG', () => {
|
||||||
|
type $MyComponent$ = MyComponent;
|
||||||
|
|
||||||
|
// Important: keep arrays outside of function to not create new instances.
|
||||||
|
const $e0_attrs$ = ['class', 'my-app', 'title', 'Hello'];
|
||||||
|
const $e2_attrs$ = ['cx', '50', 'cy', '100', 'r', '25'];
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template:
|
||||||
|
`<div class="my-app" title="Hello"><svg><circle cx="50" cy="100" r="25"/></svg></div>`
|
||||||
|
})
|
||||||
|
class MyComponent {
|
||||||
|
// NORMATIVE
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
selectors: [['my-component']],
|
||||||
|
factory: () => new MyComponent(),
|
||||||
|
template: function(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵE(0, 'div', $e0_attrs$);
|
||||||
|
$r3$.ɵNS();
|
||||||
|
$r3$.ɵE(1, 'svg');
|
||||||
|
$r3$.ɵEe(2, 'circle', $e2_attrs$);
|
||||||
|
$r3$.ɵe();
|
||||||
|
$r3$.ɵNH();
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// /NORMATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(toHtml(renderComponent(MyComponent)))
|
||||||
|
.toEqual(
|
||||||
|
'<div class="my-app" title="Hello"><svg><circle cx="50" cy="100" r="25"></circle></svg></div>');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support local refs', () => {
|
it('should support local refs', () => {
|
||||||
type $LocalRefComp$ = LocalRefComp;
|
type $LocalRefComp$ = LocalRefComp;
|
||||||
|
|
||||||
|
@ -133,6 +133,57 @@ describe('template variables', () => {
|
|||||||
expect(toHtml(renderComponent(MyComponent))).toEqual('<ul></ul>');
|
expect(toHtml(renderComponent(MyComponent))).toEqual('<ul></ul>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support a let variable and reference inside of SVG', () => {
|
||||||
|
type $MyComponent$ = MyComponent;
|
||||||
|
const $_c0$ = ['for', '', 'forOf', ''];
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
name: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: `<svg><g *for="let item of items"><circle></circle></g></svg>`
|
||||||
|
})
|
||||||
|
class MyComponent {
|
||||||
|
items = [{data: 42}, {data: 42}];
|
||||||
|
|
||||||
|
// NORMATIVE
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
selectors: [['my-component']],
|
||||||
|
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||||
|
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵNS();
|
||||||
|
$r3$.ɵE(0, 'svg');
|
||||||
|
$r3$.ɵC(1, MyComponent__svg_g_Template_1, null, $_c0$);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||||
|
}
|
||||||
|
function MyComponent__svg_g_Template_1(rf: $RenderFlags$, ctx0: $MyComponent$) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵE(0, 'g');
|
||||||
|
$r3$.ɵEe(1, 'circle');
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// /NORMATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
// NON-NORMATIVE
|
||||||
|
(MyComponent.ngComponentDef as ComponentDef<any>).directiveDefs =
|
||||||
|
[ForOfDirective.ngDirectiveDef];
|
||||||
|
// /NON-NORMATIVE
|
||||||
|
|
||||||
|
// TODO(chuckj): update when the changes to enable ngForOf lands.
|
||||||
|
expect(toHtml(renderComponent(MyComponent))).toEqual('<svg></svg>');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support accessing parent template variables', () => {
|
it('should support accessing parent template variables', () => {
|
||||||
type $MyComponent$ = MyComponent;
|
type $MyComponent$ = MyComponent;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user