diff --git a/packages/web-components/fast-element/docs/api-report.md b/packages/web-components/fast-element/docs/api-report.md index 9988035ed87..64a2bc47a9a 100644 --- a/packages/web-components/fast-element/docs/api-report.md +++ b/packages/web-components/fast-element/docs/api-report.md @@ -108,12 +108,11 @@ export class BindingBehavior implements Behavior { } // @public -export class BindingDirective extends Directive { +export class BindingDirective extends NamedTargetDirective { constructor(binding: Binding); // (undocumented) binding: Binding; createBehavior(target: Node): BindingBehavior; - createPlaceholder: (index: number) => string; targetAtContent(): void; get targetName(): string | undefined; set targetName(value: string | undefined); @@ -351,6 +350,12 @@ export type Mutable = { -readonly [P in keyof T]: T[P]; }; +// @public +export abstract class NamedTargetDirective extends Directive { + createPlaceholder: (index: number) => string; + abstract targetName: string | undefined; +} + // @public export interface NodeBehaviorOptions { filter?(value: Node, index: number, array: Node[]): boolean; diff --git a/packages/web-components/fast-element/src/directives/binding.ts b/packages/web-components/fast-element/src/directives/binding.ts index 48b0a936960..3ef6655019c 100644 --- a/packages/web-components/fast-element/src/directives/binding.ts +++ b/packages/web-components/fast-element/src/directives/binding.ts @@ -7,7 +7,7 @@ import { import { Observable } from "../observation/observable"; import { DOM } from "../dom"; import { SyntheticView } from "../view"; -import { Directive } from "./directive"; +import { NamedTargetDirective } from "./directive"; import { Behavior } from "./behavior"; function normalBind( @@ -187,7 +187,7 @@ function updateClassTarget(this: BindingBehavior, value: string): void { * A directive that configures data binding to element content and attributes. * @public */ -export class BindingDirective extends Directive { +export class BindingDirective extends NamedTargetDirective { private cleanedTargetName?: string; private originalTargetName?: string; private bind: typeof normalBind = normalBind; @@ -195,20 +195,13 @@ export class BindingDirective extends Directive { private updateTarget: typeof updateAttributeTarget = updateAttributeTarget; private isBindingVolatile: boolean; - /** - * Creates a placeholder string based on the directive's index within the template. - * @param index - The index of the directive within the template. - */ - public createPlaceholder: (index: number) => string = - DOM.createInterpolationPlaceholder; - /** * Creates an instance of BindingDirective. * @param binding - A binding that returns the data used to update the DOM. */ public constructor(public binding: Binding) { super(); - this.isBindingVolatile = Observable.isVolatileBinding(this.bind); + this.isBindingVolatile = Observable.isVolatileBinding(this.binding); } /** diff --git a/packages/web-components/fast-element/src/directives/directive.ts b/packages/web-components/fast-element/src/directives/directive.ts index d6c6ed5444d..d9cea5854ea 100644 --- a/packages/web-components/fast-element/src/directives/directive.ts +++ b/packages/web-components/fast-element/src/directives/directive.ts @@ -24,6 +24,25 @@ export abstract class Directive implements BehaviorFactory { public abstract createBehavior(target: Node): Behavior; } +/** + * A {@link Directive} that targets a named attribute or property on a node or object. + * @public + */ +export abstract class NamedTargetDirective extends Directive { + /** + * Gets/sets the name of the attribute or property that this + * directive is targeting on the associated node or object. + */ + public abstract targetName: string | undefined; + + /** + * Creates a placeholder string based on the directive's index within the template. + * @param index - The index of the directive within the template. + */ + public createPlaceholder: (index: number) => string = + DOM.createInterpolationPlaceholder; +} + /** * Describes the shape of a behavior constructor that can be created by * an {@link AttachedBehaviorDirective}. diff --git a/packages/web-components/fast-element/src/template.spec.ts b/packages/web-components/fast-element/src/template.spec.ts index cb51f5875b1..e768ea6dfa3 100644 --- a/packages/web-components/fast-element/src/template.spec.ts +++ b/packages/web-components/fast-element/src/template.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { html, ViewTemplate } from "./template"; import { DOM } from "./dom"; import { BindingDirective } from "./directives/binding"; -import { Directive } from "./directives/directive"; +import { Directive, NamedTargetDirective } from "./directives/directive"; describe(`The html tag template helper`, () => { it(`transforms a string into a ViewTemplate.`, () => { @@ -135,42 +135,57 @@ describe(`The html tag template helper`, () => { type: "mixed, back-to-back string, number, expression, and directive", location: "at the beginning", template: html`${stringValue}${numberValue}${x => x.value}${new TestDirective()} end`, - result: `${stringValue}${numberValue}${DOM.createInterpolationPlaceholder(0)}${DOM.createBlockPlaceholder(1)} end`, + result: `${stringValue}${numberValue}${DOM.createInterpolationPlaceholder( + 0 + )}${DOM.createBlockPlaceholder(1)} end`, expectDirectives: [BindingDirective, TestDirective], }, { type: "mixed, back-to-back string, number, expression, and directive", location: "in the middle", template: html`beginning ${stringValue}${numberValue}${x => x.value}${new TestDirective()} end`, - result: `beginning ${stringValue}${numberValue}${DOM.createInterpolationPlaceholder(0)}${DOM.createBlockPlaceholder(1)} end`, + result: `beginning ${stringValue}${numberValue}${DOM.createInterpolationPlaceholder( + 0 + )}${DOM.createBlockPlaceholder(1)} end`, expectDirectives: [BindingDirective, TestDirective], }, { type: "mixed, back-to-back string, number, expression, and directive", location: "at the end", template: html`beginning ${stringValue}${numberValue}${x => x.value}${new TestDirective()}`, - result: `beginning ${stringValue}${numberValue}${DOM.createInterpolationPlaceholder(0)}${DOM.createBlockPlaceholder(1)}`, + result: `beginning ${stringValue}${numberValue}${DOM.createInterpolationPlaceholder( + 0 + )}${DOM.createBlockPlaceholder(1)}`, expectDirectives: [BindingDirective, TestDirective], }, { type: "mixed, separated string, number, expression, and directive", location: "at the beginning", - template: html`${stringValue}separator${numberValue}separator${x => x.value}separator${new TestDirective()} end`, - result: `${stringValue}separator${numberValue}separator${DOM.createInterpolationPlaceholder(0)}separator${DOM.createBlockPlaceholder(1)} end`, + template: html`${stringValue}separator${numberValue}separator${x => + x.value}separator${new TestDirective()} end`, + result: `${stringValue}separator${numberValue}separator${DOM.createInterpolationPlaceholder( + 0 + )}separator${DOM.createBlockPlaceholder(1)} end`, expectDirectives: [BindingDirective, TestDirective], }, { type: "mixed, separated string, number, expression, and directive", location: "in the middle", - template: html`beginning ${stringValue}separator${numberValue}separator${x => x.value}separator${new TestDirective()} end`, - result: `beginning ${stringValue}separator${numberValue}separator${DOM.createInterpolationPlaceholder(0)}separator${DOM.createBlockPlaceholder(1)} end`, + template: html`beginning ${stringValue}separator${numberValue}separator${x => + x.value}separator${new TestDirective()} end`, + result: `beginning ${stringValue}separator${numberValue}separator${DOM.createInterpolationPlaceholder( + 0 + )}separator${DOM.createBlockPlaceholder(1)} end`, expectDirectives: [BindingDirective, TestDirective], }, { type: "mixed, separated string, number, expression, and directive", location: "at the end", - template: html`beginning ${stringValue}separator${numberValue}separator${x => x.value}separator${new TestDirective()}`, - result: `beginning ${stringValue}separator${numberValue}separator${DOM.createInterpolationPlaceholder(0)}separator${DOM.createBlockPlaceholder(1)}`, + template: html`beginning ${stringValue}separator${numberValue}separator${x => + x.value}separator${new TestDirective()}`, + result: `beginning ${stringValue}separator${numberValue}separator${DOM.createInterpolationPlaceholder( + 0 + )}separator${DOM.createBlockPlaceholder(1)}`, expectDirectives: [BindingDirective, TestDirective], }, ]; @@ -199,4 +214,35 @@ describe(`The html tag template helper`, () => { ":someAttribute" ); }); + + it(`captures a case-sensitive property name when used with a binding`, () => { + const template = html` x.value)}>`; + const placeholder = DOM.createInterpolationPlaceholder(0); + + expect(template.html).to.equal( + `` + ); + expect((template.directives[0] as NamedTargetDirective).targetName).to.equal( + ":someAttribute" + ); + }); + + it(`captures a case-sensitive property name when used with a named target directive`, () => { + class TestDirective extends NamedTargetDirective { + targetName: string | undefined; + createBehavior(target: Node) { + return { bind() {}, unbind() {} }; + } + } + + const template = html``; + const placeholder = DOM.createInterpolationPlaceholder(0); + + expect(template.html).to.equal( + `` + ); + expect((template.directives[0] as NamedTargetDirective).targetName).to.equal( + ":someAttribute" + ); + }); }); diff --git a/packages/web-components/fast-element/src/template.ts b/packages/web-components/fast-element/src/template.ts index dd117f1e969..8b1c897d4d1 100644 --- a/packages/web-components/fast-element/src/template.ts +++ b/packages/web-components/fast-element/src/template.ts @@ -2,7 +2,7 @@ import { compileTemplate } from "./template-compiler"; import { ElementView, HTMLView, SyntheticView } from "./view"; import { DOM } from "./dom"; import { Behavior, BehaviorFactory } from "./directives/behavior"; -import { Directive } from "./directives/directive"; +import { Directive, NamedTargetDirective } from "./directives/directive"; import { BindingDirective } from "./directives/binding"; import { defaultExecutionContext, Binding } from "./observation/observable"; @@ -226,10 +226,12 @@ export function html( if (typeof value === "function") { value = new BindingDirective(value as Binding); + } + if (value instanceof NamedTargetDirective) { const match = lastAttributeNameRegex.exec(currentString); if (match !== null) { - (value as BindingDirective).targetName = match[2]; + value.targetName = match[2]; } }