Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/fast-html/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,33 @@ Browser-only bindings:
- Event bindings
- Attribute directives

#### Binding arguments

Content bindings support pipe-separated arguments that modify binding behaviour:

```
{{path|key1:value1|key2:value2}}
```

- The **first segment** (before the first `|`) is always the property path.
- Each **subsequent segment** is a `key:value` pair that configures how the binding behaves.

#### Content binding

```html
{{text}}
```

#### Static accessor

A static accessor reads a property value once when the element connects and does not set up any reactive observers. Use `|binding:none` to opt out of reactivity:

```html
{{text|binding:none}}
```

This is equivalent to a one-time read of `x.text` — changes to the property after the initial render will not update the DOM.

#### Event binding

Event bindings must include the `()` as well as being preceeded by `@` in keeping with `@microsoft/fast-element` tagged template `html` syntax.
Expand Down
18 changes: 9 additions & 9 deletions packages/fast-html/api-extractor.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../api-extractor.json",
"mainEntryPointFilePath": "./dist/dts/index.d.ts",
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "./dist/fast-html.untrimmed.d.ts",
"betaTrimmedFilePath": "./dist/fast-html.d.ts"
}
}
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../api-extractor.json",
"mainEntryPointFilePath": "./dist/dts/index.d.ts",
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "./dist/fast-html.untrimmed.d.ts",
"betaTrimmedFilePath": "./dist/fast-html.d.ts"
}
}
4 changes: 2 additions & 2 deletions packages/fast-html/src/components/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function waitForAncestorHydration(element: HTMLElement): Promise<void> {
* @public
*/
export function RenderableFASTElement<T extends Constructable<FASTElement>>(
BaseCtor: T
BaseCtor: T,
): T {
const C = class extends BaseCtor {
deferHydration: boolean = true;
Expand Down Expand Up @@ -89,7 +89,7 @@ export function RenderableFASTElement<T extends Constructable<FASTElement>>(

attr({ mode: "boolean", attribute: deferHydrationAttribute })(
C.prototype,
"deferHydration"
"deferHydration",
);

return C;
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-html/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export { RenderableFASTElement } from "./element.js";
export { ObserverMap } from "./observer-map.js";
export {
ObserverMapOption,
TemplateElement,
type ElementOptions,
type ElementOptionsDictionary,
type HydrationLifecycleCallbacks,
ObserverMapOption,
TemplateElement,
} from "./template.js";
12 changes: 6 additions & 6 deletions packages/fast-html/src/components/observer-map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Observable } from "@microsoft/fast-element/observable.js";
import { assignObservables, deepMerge } from "./utilities.js";
import type { JSONSchema, Schema } from "./schema.js";
import { assignObservables, deepMerge } from "./utilities.js";

/**
* ObserverMap provides functionality for caching binding paths, extracting root properties,
Expand Down Expand Up @@ -30,7 +30,7 @@ export class ObserverMap {

this.classPrototype[changedMethodName] = this.defineChanged(
propertyName,
existingChangedMethod
existingChangedMethod,
);
}
}
Expand All @@ -46,7 +46,7 @@ export class ObserverMap {
target: any,
rootProperty: string,
object: any,
schema: Schema
schema: Schema,
): typeof Proxy {
let proxiedObject = object;

Expand All @@ -55,7 +55,7 @@ export class ObserverMap {
schema.getSchema(rootProperty) as JSONSchema,
proxiedObject,
target,
rootProperty
rootProperty,
);

return proxiedObject;
Expand All @@ -70,7 +70,7 @@ export class ObserverMap {
*/
private defineChanged = (
propertyName: string,
existingChangedMethod?: (prev: any, next: any) => void
existingChangedMethod?: (prev: any, next: any) => void,
): ((prev: any, next: any) => void) => {
const getAndAssignObservablesAlias = this.getAndAssignObservables;
const schema = this.schema;
Expand All @@ -92,7 +92,7 @@ export class ObserverMap {
this,
propertyName,
next,
schema
schema,
);
}
} else if (!isObjectAssignment) {
Expand Down
78 changes: 56 additions & 22 deletions packages/fast-html/src/components/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ test.describe("Schema", async () => {
const schemaA = schema.getSchema("a");

expect(schemaA).not.toBe(null);
expect(schemaA!.$id).toEqual("https://fast.design/schemas/my-custom-element/a.json");
expect(schemaA!.$id).toEqual(
"https://fast.design/schemas/my-custom-element/a.json",
);
expect(schemaA!.$schema).toEqual("https://json-schema.org/draft/2019-09/schema");
});
test("should add a property and cast the schema as type object if a nested path is given", async () => {
Expand Down Expand Up @@ -240,7 +242,7 @@ test.describe("Schema", async () => {
type: "repeat",
path: "user.posts",
currentContext: "post",
parentContext: "user"
parentContext: "user",
},
childrenMap: null,
});
Expand Down Expand Up @@ -286,7 +288,7 @@ test.describe("Schema", async () => {
type: "repeat",
path: "user.posts",
currentContext: "post",
parentContext: "user"
parentContext: "user",
},
childrenMap: null,
});
Expand All @@ -308,7 +310,7 @@ test.describe("Schema", async () => {
type: "repeat",
path: "post.meta.tags",
currentContext: "tag",
parentContext: "post"
parentContext: "post",
},
childrenMap: null,
});
Expand All @@ -331,12 +333,22 @@ test.describe("Schema", async () => {
expect(schemaA!.$defs?.["post"].properties["c"]).toBeDefined();
expect(schemaA!.$defs?.["post"].properties["c"].properties["d"]).toBeDefined();
expect(schemaA!.$defs?.["post"].properties["meta"]).toBeDefined();
expect(schemaA!.$defs?.["post"].properties["meta"].properties["tags"]).toBeDefined();
expect(schemaA!.$defs?.["post"].properties["meta"].properties["tags"].items).toBeDefined();
expect(schemaA!.$defs?.["post"].properties["meta"].properties["tags"].items.$ref).toEqual("#/$defs/tag");
expect(
schemaA!.$defs?.["post"].properties["meta"].properties["tags"],
).toBeDefined();
expect(
schemaA!.$defs?.["post"].properties["meta"].properties["tags"].items,
).toBeDefined();
expect(
schemaA!.$defs?.["post"].properties["meta"].properties["tags"].items.$ref,
).toEqual("#/$defs/tag");
expect(schemaA!.$defs?.["tag"]).toBeDefined();
expect(schemaA!.$defs?.["tag"].$fast_context).toEqual("tags");
expect(schemaA!.$defs?.["tag"].$fast_parent_contexts).toEqual([null, "user", "post"]);
expect(schemaA!.$defs?.["tag"].$fast_parent_contexts).toEqual([
null,
"user",
"post",
]);
});
test("should define an anyOf with a $ref to another schema", async () => {
const schema = new Schema("my-custom-element");
Expand All @@ -358,11 +370,15 @@ test.describe("Schema", async () => {
const schemaA = schema.getSchema("a");

expect(schemaA).not.toBe(null);
expect(schemaA!.$id).toEqual("https://fast.design/schemas/my-custom-element/a.json");
expect(schemaA!.$id).toEqual(
"https://fast.design/schemas/my-custom-element/a.json",
);
expect(schemaA!.$schema).toEqual("https://json-schema.org/draft/2019-09/schema");
expect(schemaA!.anyOf).not.toBeUndefined();
expect(schemaA!.anyOf).toHaveLength(1);
expect(schemaA!.anyOf?.[0].$ref).toEqual("https://fast.design/schemas/my-custom-element-2/b.json");
expect(schemaA!.anyOf?.[0].$ref).toEqual(
"https://fast.design/schemas/my-custom-element-2/b.json",
);
});
test("should define an anyOf with a $ref to multiple schemas", async () => {
const schema = new Schema("my-custom-element");
Expand Down Expand Up @@ -397,12 +413,18 @@ test.describe("Schema", async () => {
const schemaA = schema.getSchema("a");

expect(schemaA).not.toBe(null);
expect(schemaA!.$id).toEqual("https://fast.design/schemas/my-custom-element/a.json");
expect(schemaA!.$id).toEqual(
"https://fast.design/schemas/my-custom-element/a.json",
);
expect(schemaA!.$schema).toEqual("https://json-schema.org/draft/2019-09/schema");
expect(schemaA!.anyOf).not.toBeUndefined();
expect(schemaA!.anyOf).toHaveLength(2);
expect(schemaA!.anyOf?.[0].$ref).toEqual("https://fast.design/schemas/my-custom-element-2/b.json");
expect(schemaA!.anyOf?.[1].$ref).toEqual("https://fast.design/schemas/my-custom-element-3/c.json");
expect(schemaA!.anyOf?.[0].$ref).toEqual(
"https://fast.design/schemas/my-custom-element-2/b.json",
);
expect(schemaA!.anyOf?.[1].$ref).toEqual(
"https://fast.design/schemas/my-custom-element-3/c.json",
);
});
test("should define an anyOf with a $ref to another schema in a nested object", async () => {
const schema = new Schema("my-custom-element");
Expand Down Expand Up @@ -430,15 +452,17 @@ test.describe("Schema", async () => {
},
childrenMap: {
customElementName: "my-custom-element-2",
attributeName: "test"
attributeName: "test",
},
});

schemaA = schema.getSchema("a");

expect(schemaA!.properties.b.properties.c).toBeDefined();
expect(schemaA!.properties.b.properties.c.anyOf).not.toBeUndefined();
expect(schemaA!.properties.b.properties.c.anyOf[0].$ref).toEqual("https://fast.design/schemas/my-custom-element-2/test.json");
expect(schemaA!.properties.b.properties.c.anyOf[0].$ref).toEqual(
"https://fast.design/schemas/my-custom-element-2/test.json",
);
});
test("should define an anyOf with a $ref to multiple schemas in a nested object", async () => {
const schema = new Schema("my-custom-element");
Expand Down Expand Up @@ -466,7 +490,7 @@ test.describe("Schema", async () => {
},
childrenMap: {
customElementName: "my-custom-element-2",
attributeName: "test"
attributeName: "test",
},
});

Expand All @@ -480,16 +504,20 @@ test.describe("Schema", async () => {
},
childrenMap: {
customElementName: "my-custom-element-3",
attributeName: "test-2"
attributeName: "test-2",
},
});

schemaA = schema.getSchema("a");

expect(schemaA!.properties.b.properties.c).toBeDefined();
expect(schemaA!.properties.b.properties.c.anyOf).not.toBeUndefined();
expect(schemaA!.properties.b.properties.c.anyOf[0].$ref).toEqual("https://fast.design/schemas/my-custom-element-2/test.json");
expect(schemaA!.properties.b.properties.c.anyOf[1].$ref).toEqual("https://fast.design/schemas/my-custom-element-3/test-2.json");
expect(schemaA!.properties.b.properties.c.anyOf[0].$ref).toEqual(
"https://fast.design/schemas/my-custom-element-2/test.json",
);
expect(schemaA!.properties.b.properties.c.anyOf[1].$ref).toEqual(
"https://fast.design/schemas/my-custom-element-3/test-2.json",
);
});
test("should define an anyOf with a $ref in a nested object in a context", async () => {
const schema = new Schema("my-custom-element");
Expand All @@ -515,7 +543,7 @@ test.describe("Schema", async () => {
},
childrenMap: {
customElementName: "my-custom-element-2",
attributeName: "c"
attributeName: "c",
},
});

Expand All @@ -529,7 +557,13 @@ test.describe("Schema", async () => {
expect(schemaA!.$defs?.["user"].properties).toBeDefined();
expect(schemaA!.$defs?.["user"].properties["a"]).toBeDefined();
expect(schemaA!.$defs?.["user"].properties["a"].properties["b"]).toBeDefined();
expect(schemaA!.$defs?.["user"].properties["a"].properties["b"].anyOf).toHaveLength(1);
expect(schemaA!.$defs?.["user"].properties["a"].properties["b"].anyOf[0][refPropertyName]).toEqual("https://fast.design/schemas/my-custom-element-2/c.json");
expect(
schemaA!.$defs?.["user"].properties["a"].properties["b"].anyOf,
).toHaveLength(1);
expect(
schemaA!.$defs?.["user"].properties["a"].properties["b"].anyOf[0][
refPropertyName
],
).toEqual("https://fast.design/schemas/my-custom-element-2/c.json");
});
});
Loading
Loading