Skip to content

Commit

Permalink
feat: aspected html directive exposes metadata (#5739)
Browse files Browse the repository at this point in the history
* feat: aspected html directive exposes metadata

* test: expand tests around aspected directives

* Change files

* chore: add missing comment to change file

* replace Parser with Compiler

* fix: remove dependency on this from markup helpers

Co-authored-by: EisenbergEffect <[email protected]>
Co-authored-by: nicholasrice <[email protected]>
  • Loading branch information
3 people committed May 31, 2022
1 parent 0fa34f8 commit a8b52f5
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 246 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "feat: aspected html directive exposes metadata",
"packageName": "@microsoft/fast-element",
"email": "[email protected]",
"dependentChangeType": "patch"
}
55 changes: 20 additions & 35 deletions packages/web-components/fast-element/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ export interface Accessor {

// @public
export abstract class AspectedHTMLDirective extends HTMLDirective {
// Warning: (ae-forgotten-export) The symbol "Aspect" needs to be exported by the entry point index.d.ts
abstract readonly aspect: Aspect;
abstract readonly binding?: Binding;
abstract captureSource(source: string): void;
createPlaceholder: (index: number) => string;
// (undocumented)
abstract setAspect(value: string): void;
abstract readonly source: string;
abstract readonly target: string;
}

// @public
Expand Down Expand Up @@ -80,20 +84,7 @@ export interface BindingConfig<T = any> {
}

// @alpha (undocumented)
export interface BindingMode {
// (undocumented)
attribute: BindingType;
// (undocumented)
booleanAttribute: BindingType;
// (undocumented)
content: BindingType;
// (undocumented)
event: BindingType;
// (undocumented)
property: BindingType;
// (undocumented)
tokenList: BindingType;
}
export type BindingMode = Record<Aspect, BindingType>;

// @public
export interface BindingObserver<TSource = any, TReturn = any, TParent = any> extends Notifier {
Expand Down Expand Up @@ -140,7 +131,16 @@ export class ChildrenDirective extends NodeObservationDirective<ChildrenDirectiv
export type ChildrenDirectiveOptions<T = any> = ChildListDirectiveOptions<T> | SubtreeDirectiveOptions<T>;

// @public
export function compileTemplate(html: string | HTMLTemplateElement, directives: ReadonlyArray<HTMLDirective>): HTMLTemplateCompilationResult;
export type CompilationStrategy = (
html: string | HTMLTemplateElement,
directives: readonly HTMLDirective[]) => HTMLTemplateCompilationResult;

// @public
export const Compiler: {
compile(html: string | HTMLTemplateElement, directives: ReadonlyArray<HTMLDirective>): HTMLTemplateCompilationResult;
setDefaultStrategy(strategy: CompilationStrategy): void;
aggregate(parts: (string | HTMLDirective)[]): HTMLDirective;
};

// @public
export type ComposableStyles = string | ElementStyles | CSSStyleSheet;
Expand Down Expand Up @@ -350,11 +350,6 @@ export interface HTMLTemplateCompilationResult {
createView(hostBindingTarget?: Element): HTMLView;
}

// @public
export type HTMLTemplateCompiler = (
html: string | HTMLTemplateElement,
directives: readonly HTMLDirective[]) => HTMLTemplateCompilationResult;

// @public
export class HTMLView<TSource = any, TParent = any, TGrandparent = any> implements ElementView<TSource, TParent, TGrandparent>, SyntheticView<TSource, TParent, TGrandparent> {
constructor(fragment: DocumentFragment, factories: ReadonlyArray<ViewBehaviorFactory>, targets: ViewBehaviorTargets);
Expand All @@ -371,14 +366,6 @@ export class HTMLView<TSource = any, TParent = any, TGrandparent = any> implemen
unbind(): void;
}

// @public
export abstract class InlinableHTMLDirective extends AspectedHTMLDirective {
// (undocumented)
abstract readonly binding: Binding;
// (undocumented)
abstract readonly rawAspect?: string;
}

// Warning: (ae-internal-missing-underscore) The name "KernelServiceId" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal
Expand All @@ -395,9 +382,9 @@ export const enum KernelServiceId {

// @public
export const Markup: Readonly<{
interpolation(index: number): string;
attribute(index: number): string;
comment(index: number): string;
interpolation: (index: number) => string;
attribute: (index: number) => string;
comment: (index: number) => string;
}>;

// Warning: (ae-internal-missing-underscore) The name "Mutable" should be prefixed with an underscore because the declaration is marked as @internal
Expand Down Expand Up @@ -457,7 +444,6 @@ export const oneTime: BindingConfig<DefaultBindingOptions> & BindingConfigResolv
// @public
export const Parser: Readonly<{
parse(value: string, directives: readonly HTMLDirective[]): (string | HTMLDirective)[] | null;
aggregate(parts: (string | HTMLDirective)[]): HTMLDirective;
}>;

// @public
Expand Down Expand Up @@ -645,7 +631,6 @@ export class ViewTemplate<TSource = any, TParent = any, TGrandparent = any> impl
readonly directives: ReadonlyArray<HTMLDirective>;
readonly html: string | HTMLTemplateElement;
render(source: TSource, host: Node, hostBindingTarget?: Element): HTMLView<TSource, TParent, TGrandparent>;
static setDefaultCompiler(compiler: HTMLTemplateCompiler): void;
}

// @public
Expand Down
1 change: 0 additions & 1 deletion packages/web-components/fast-element/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export {
ViewBehaviorFactory,
HTMLDirective,
AspectedHTMLDirective,
InlinableHTMLDirective,
} from "./templating/html-directive.js";
export * from "./templating/ref.js";
export * from "./templating/when.js";
Expand Down
83 changes: 40 additions & 43 deletions packages/web-components/fast-element/src/templating/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
Observable,
} from "../observation/observable.js";
import {
InlinableHTMLDirective,
Aspect,
AspectedHTMLDirective,
ViewBehavior,
ViewBehaviorTargets,
} from "./html-directive.js";
Expand Down Expand Up @@ -38,14 +39,7 @@ export const notSupportedBindingType: BindingType = () => {
/**
* @alpha
*/
export interface BindingMode {
attribute: BindingType;
booleanAttribute: BindingType;
property: BindingType;
content: BindingType;
tokenList: BindingType;
event: BindingType;
}
export type BindingMode = Record<Aspect, BindingType>;

/**
* @alpha
Expand Down Expand Up @@ -252,14 +246,12 @@ class TargetUpdateBinding extends BindingBase {
eventType: BindingType = notSupportedBindingType
): BindingMode {
return Object.freeze({
attribute: this.createType(DOM.setAttribute),
booleanAttribute: this.createType(DOM.setBooleanAttribute),
property: this.createType(
(target, aspect, value) => (target[aspect] = value)
),
content: createContentBinding(this).createType(updateContentTarget),
tokenList: this.createType(updateTokenListTarget),
event: eventType,
[Aspect.attribute]: this.createType(DOM.setAttribute),
[Aspect.booleanAttribute]: this.createType(DOM.setBooleanAttribute),
[Aspect.property]: this.createType((t, a, v) => (t[a] = v)),
[Aspect.content]: createContentBinding(this).createType(updateContentTarget),
[Aspect.tokenList]: this.createType(updateTokenListTarget),
[Aspect.event]: eventType,
});
}

Expand All @@ -274,7 +266,7 @@ class OneTimeBinding extends TargetUpdateBinding {
const target = targets[directive.targetId];
this.updateTarget(
target,
directive.aspect!,
directive.target!,
directive.binding(source, context),
source,
context
Expand Down Expand Up @@ -306,7 +298,7 @@ class OnSignalBinding extends TargetUpdateBinding {
const handler = (target[directive.uniqueId] = () => {
this.updateTarget(
target,
directive.aspect!,
directive.target!,
directive.binding(source, context),
source,
context
Expand Down Expand Up @@ -378,7 +370,7 @@ class OnChangeBinding extends TargetUpdateBinding {

this.updateTarget(
target,
directive.aspect!,
directive.target!,
observer.observe(source, context),
source,
context
Expand All @@ -401,7 +393,7 @@ class OnChangeBinding extends TargetUpdateBinding {
const context = (observer as any).context;
this.updateTarget(
target,
this.directive.aspect!,
this.directive.target!,
observer.observe(source, context!),
source,
context
Expand Down Expand Up @@ -430,7 +422,7 @@ class EventListener extends BindingBase {
const target = targets[directive.targetId] as FASTEventSource;
target.$fastSource = source;
target.$fastContext = context;
target.addEventListener(directive.aspect!, this, directive.options);
target.addEventListener(directive.target!, this, directive.options);
}

unbind(source: any, context: ExecutionContext, targets: ViewBehaviorTargets): void {
Expand All @@ -440,7 +432,7 @@ class EventListener extends BindingBase {
protected removeEventListener(target: FASTEventSource): void {
target.$fastSource = null;
target.$fastContext = null;
target.removeEventListener(this.directive.aspect!, this, this.directive.options);
target.removeEventListener(this.directive.target!, this, this.directive.options);
}

handleEvent(event: Event): void {
Expand Down Expand Up @@ -504,11 +496,12 @@ export const signal = <T = any>(options: string | Binding<T>): BindingConfig<T>
/**
* @internal
*/
export class HTMLBindingDirective extends InlinableHTMLDirective {
private factory!: BindingBehaviorFactory;
export class HTMLBindingDirective extends AspectedHTMLDirective {
private factory: BindingBehaviorFactory | null = null;

public readonly rawAspect?: string;
public readonly aspect?: string;
public readonly source: string = "";
public readonly target: string = "";
public readonly aspect: Aspect = Aspect.content;

public constructor(
public binding: Binding,
Expand All @@ -518,53 +511,57 @@ export class HTMLBindingDirective extends InlinableHTMLDirective {
super();
}

public setAspect(value: string): void {
(this as Mutable<this>).rawAspect = value;
public captureSource(value: string): void {
(this as Mutable<this>).source = value;

if (!value) {
return;
}

switch (value[0]) {
case ":":
(this as Mutable<this>).aspect = value.substr(1);
switch (this.aspect) {
(this as Mutable<this>).target = value.substring(1);
switch (this.target) {
case "innerHTML":
const binding = this.binding;
/* eslint-disable-next-line */
this.binding = (s, c) => DOM.createHTML(binding(s, c));
this.factory = this.mode.property(this);
(this as Mutable<this>).aspect = Aspect.property;
break;
case "classList":
this.factory = this.mode.tokenList(this);
(this as Mutable<this>).aspect = Aspect.tokenList;
break;
default:
this.factory = this.mode.property(this);
(this as Mutable<this>).aspect = Aspect.property;
break;
}
break;
case "?":
(this as Mutable<this>).aspect = value.substr(1);
this.factory = this.mode.booleanAttribute(this);
(this as Mutable<this>).target = value.substring(1);
(this as Mutable<this>).aspect = Aspect.booleanAttribute;
break;
case "@":
(this as Mutable<this>).aspect = value.substr(1);
this.factory = this.mode.event(this);
(this as Mutable<this>).target = value.substring(1);
(this as Mutable<this>).aspect = Aspect.event;
break;
default:
if (value === "class") {
(this as Mutable<this>).aspect = "className";
this.factory = this.mode.property(this);
(this as Mutable<this>).target = "className";
(this as Mutable<this>).aspect = Aspect.property;
} else {
(this as Mutable<this>).aspect = value;
this.factory = this.mode.attribute(this);
(this as Mutable<this>).target = value;
(this as Mutable<this>).aspect = Aspect.attribute;
}
break;
}
}

createBehavior(targets: ViewBehaviorTargets): ViewBehavior {
return (this.factory ?? this.mode.content(this)).createBehavior(targets);
if (this.factory == null) {
this.factory = this.mode[this.aspect](this);
}

return this.factory.createBehavior(targets);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { css } from "../styles/css";
import type { StyleTarget } from "../styles/element-styles";
import { toHTML, uniqueElementName } from "../__test__/helpers";
import { bind, HTMLBindingDirective } from "./binding";
import { compileTemplate } from "./compiler";
import { Compiler } from "./compiler";
import type { HTMLDirective, ViewBehaviorFactory } from "./html-directive";
import { html } from "./template";

Expand All @@ -22,7 +22,7 @@ interface CompilationResultInternals {

describe("The template compiler", () => {
function compile(html: string, directives: HTMLDirective[]) {
return compileTemplate(html, directives) as any as CompilationResultInternals;
return Compiler.compile(html, directives) as any as CompilationResultInternals;
}

function inline(index: number) {
Expand Down
Loading

0 comments on commit a8b52f5

Please sign in to comment.