Skip to content

Commit 8acba4a

Browse files
janechuCopilot
andcommitted
Add FAST Element extension subpaths
Expose attributeMap and observerMap from extension subpaths, keep declarative re-exports, and support schema-driven usage with optional definition schemas. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 33f1a43 commit 8acba4a

226 files changed

Lines changed: 11635 additions & 1966 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

change/@microsoft-fast-element-106e0dee-a966-4f2d-9d9b-857c10599260.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"type": "minor",
3-
"comment": "Add definition-scoped declarative map extensions.",
3+
"comment": "Add schema-driven attributeMap and observerMap extension subpaths, optional definition schema, and observerMap schema configuration.",
44
"packageName": "@microsoft/fast-element",
55
"email": "7559015+janechu@users.noreply.github.com",
66
"dependentChangeType": "none"

packages/fast-element/DECLARATIVE_DESIGN.md

Lines changed: 106 additions & 44 deletions
Large diffs are not rendered by default.

packages/fast-element/DECLARATIVE_HTML.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ debug messages when they create templates. Hydratable `ViewTemplate` support is
3434
installed only when `enableHydration()` is called from
3535
`@microsoft/fast-element/hydration.js`.
3636

37+
`observerMap()` and `attributeMap()` remain available from the declarative
38+
entrypoint for existing declarative imports. New code should prefer the
39+
extension subpaths, `@microsoft/fast-element/extensions/observer-map.js` and
40+
`@microsoft/fast-element/extensions/attribute-map.js`, especially when using
41+
the maps without declarative templates.
42+
3743
Example:
3844
```html
3945
<my-custom-element greeting="Hello world">
@@ -129,8 +135,15 @@ so callbacks for different elements may interleave.
129135
## `observerMap`
130136

131137
When the `observerMap()` extension is applied to an element definition,
132-
`@microsoft/fast-element/declarative.js` automatically sets up deep reactive
133-
observation for root properties discovered in the template.
138+
it automatically sets up deep reactive observation for root properties
139+
discovered in the template. Declarative templates assign `definition.schema`
140+
during template resolution, so `observerMap()` has schema data automatically.
141+
For non-declarative/manual schemas, import from the extension subpath and pass
142+
`observerMap({ schema })`.
143+
144+
```typescript
145+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
146+
```
134147

135148
For finer control, pass a configuration object with a `properties` key that maps root property names to a recursive path tree:
136149

@@ -181,12 +194,42 @@ When `properties` is omitted, all root properties are observed. When
181194
`properties` is present but empty (`{ properties: {} }`), no root properties
182195
are observed.
183196

197+
Manual schema example:
198+
199+
```typescript
200+
import { FASTElement, Schema } from "@microsoft/fast-element";
201+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
202+
203+
class MyElement extends FASTElement {}
204+
205+
const schema = new Schema("my-element");
206+
schema.addPath({
207+
rootPropertyName: "user",
208+
pathConfig: {
209+
type: "default",
210+
parentContext: null,
211+
currentContext: null,
212+
path: "user.name",
213+
},
214+
childrenMap: null,
215+
});
216+
217+
MyElement.define({ name: "my-element" }, [observerMap({ schema })]);
218+
```
219+
184220
## `attributeMap`
185221

186222
When the `attributeMap()` extension is applied to an element definition,
187-
`@microsoft/fast-element/declarative.js` automatically creates reactive `@attr`
188-
properties for every **leaf binding** in the template — simple expressions like
189-
`{{foo}}` or `id="{{fooBar}}"` that have no nested properties.
223+
it automatically creates reactive `@attr` properties for every **leaf binding**
224+
in the template — simple expressions like `{{foo}}` or `id="{{fooBar}}"` that
225+
have no nested properties. Declarative templates provide the schema
226+
automatically. For non-declarative/manual schemas, place the optional `schema`
227+
on the FAST element definition and import `attributeMap()` from its extension
228+
subpath.
229+
230+
```typescript
231+
import { attributeMap } from "@microsoft/fast-element/extensions/attribute-map.js";
232+
```
190233

191234
By default, the binding key is treated as a camelCase property name and the HTML
192235
attribute name is derived by converting it to kebab-case. Properties already

packages/fast-element/DECLARATIVE_MIGRATION.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,12 @@ Configuration types have moved from `template.ts` to their owning modules. If yo
115115
| `import type { ObserverMapConfig } from "./template.js"` | `import type { ObserverMapConfig } from "./observer-map.js"` |
116116
| `import type { AttributeMapConfig } from "./template.js"` | `import type { AttributeMapConfig } from "./attribute-map.js"` |
117117

118-
Public imports now come from
118+
Public declarative imports now come from
119119
`@microsoft/fast-element/declarative.js` rather than
120-
`@microsoft/fast-html`.
120+
`@microsoft/fast-html`. Existing declarative imports for `attributeMap()` and
121+
`observerMap()` remain valid. New code that only needs the map extensions should
122+
prefer `@microsoft/fast-element/extensions/attribute-map.js` and
123+
`@microsoft/fast-element/extensions/observer-map.js`.
121124

122125
### Schema changes
123126

@@ -168,8 +171,45 @@ implementation is internal and is defined automatically by `declarativeTemplate(
168171
| `TemplateElement.config(callbacks)` / `HydrationLifecycleCallbacks` | Per-element callbacks via `declarativeTemplate(callbacks)` and global hydration callbacks via `enableHydration(options)` |
169172
| `TemplateElement.options({ "my-el": { attributeMap, observerMap } })` | Define extensions: `MyElement.define(definition, [attributeMap(...), observerMap(...)])` |
170173
| `ElementOptions` / `ElementOptionsDictionary` | No replacement |
171-
| `AttributeMap` / `ObserverMap` public entrypoint exports | `attributeMap()` / `observerMap()` extension helpers and their config types |
174+
| `AttributeMap` / `ObserverMap` class exports from the old declarative public surface | `attributeMap()` / `observerMap()` extension helpers and their config types |
172175

173176
Hydration is also no longer installed by `@microsoft/fast-element/declarative.js`.
174177
Call `enableHydration()` from `@microsoft/fast-element/hydration.js` before FAST
175178
elements connect when prerendered Declarative Shadow DOM should be reused.
179+
180+
## Extension subpaths and optional definition schema
181+
182+
`attributeMap()` and `observerMap()` are now schema-driven extensions that are
183+
factored away from declarative templating. Prefer importing them from their
184+
dedicated subpaths for tree-shaken or non-declarative use:
185+
186+
```ts
187+
import { attributeMap } from "@microsoft/fast-element/extensions/attribute-map.js";
188+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
189+
```
190+
191+
`FASTElementDefinition.schema` is optional. `declarativeTemplate()` assigns it
192+
automatically when it parses `<f-template>` markup. Manual schema users can pass
193+
a schema in the element definition, and `observerMap()` can also take a schema
194+
directly in configuration:
195+
196+
```ts
197+
import { FASTElement, Schema } from "@microsoft/fast-element";
198+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
199+
200+
class MyElement extends FASTElement {}
201+
202+
const schema = new Schema("my-element");
203+
schema.addPath({
204+
rootPropertyName: "user",
205+
pathConfig: {
206+
type: "default",
207+
parentContext: null,
208+
currentContext: null,
209+
path: "user.name",
210+
},
211+
childrenMap: null,
212+
});
213+
214+
MyElement.define({ name: "my-element" }, [observerMap({ schema })]);
215+
```

packages/fast-element/DECLARATIVE_SCHEMA_OBSERVER_MAP.md

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Schema and Observer Map Architecture
22

3-
This document provides a technical explainer for how the `Schema` and `ObserverMap` classes work together to describe data objects and automatically observe them in FAST HTML templates using the f-template system.
3+
This document provides a technical explainer for how the `Schema` and
4+
`ObserverMap` classes work together to describe data objects and automatically
5+
observe them in FAST elements. Declarative `<f-template>` markup creates schemas
6+
automatically, but the schema and observer map implementation are factored into
7+
shared components and extension subpaths so they can also be used without
8+
declarative templating.
49

510
## Table of Contents
611

@@ -22,9 +27,10 @@ The Schema and Observer Map architecture enables automatic observation of comple
2227

2328
### Key Components
2429

25-
- **Schema Class**: Generates JSON schemas that describe the structure and binding paths of data objects based on template analysis
30+
- **Schema Class**: Generates JSON schemas that describe the structure and binding paths of data objects based on template analysis or manually registered paths
2631
- **Observer Map Class**: Uses the schema information to automatically define observable properties and create proxies for nested object observation
2732
- **f-template Integration**: Template processing automatically populates schemas and configures observer maps during template compilation
33+
- **Extension Subpaths**: `@microsoft/fast-element/extensions/observer-map.js` and `@microsoft/fast-element/extensions/attribute-map.js` expose the map helpers independently from declarative templating
2834

2935
### Supported Data Types
3036

@@ -58,7 +64,7 @@ The `Schema` class is responsible for building JSON Schema definitions that map
5864
```typescript
5965
constructor(name: string)
6066
```
61-
Creates a new schema instance for a specific custom element name and initializes an instance-level `schemaMap`. The instance also registers itself in the module-level `schemaRegistry` for cross-element `$ref` resolution.
67+
Creates a new schema instance for a specific custom element name and initializes an instance-level `schemaMap`. The instance also registers itself in the module-level `schemaRegistry` for cross-element `$ref` resolution. `FASTElementDefinition.schema` is optional; declarative templates assign it after parsing, while manual schema users can pass one in the element definition or directly to `observerMap({ schema })`.
6268

6369
#### addPath
6470
```typescript
@@ -142,9 +148,9 @@ When a root property transitions from `undefined` to a defined value, the observ
142148
2. Creates proxies using the `assignObservables` utility function
143149
3. Establishes deep observation of nested properties based on the schema of that root property
144150

145-
## Integration with f-template
151+
## Integration with f-template and manual schemas
146152

147-
The Schema and Observer Map classes integrate seamlessly with the f-template system:
153+
The Schema and Observer Map classes integrate seamlessly with the f-template system, but they are not tied to it:
148154

149155
### Template Processing Flow
150156

@@ -156,10 +162,12 @@ The Schema and Observer Map classes integrate seamlessly with the f-template sys
156162
### Configuration
157163

158164
Observer Map functionality is enabled through the `observerMap()` definition
159-
extension:
165+
extension. Prefer the extension subpath for new imports; the declarative
166+
entrypoint continues to re-export `observerMap()` for existing declarative code:
160167

161168
```typescript
162-
import { declarativeTemplate, observerMap } from "@microsoft/fast-element/declarative.js";
169+
import { declarativeTemplate } from "@microsoft/fast-element/declarative.js";
170+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
163171

164172
MyElement.define(
165173
{
@@ -174,6 +182,48 @@ When the `properties` key is omitted, all root properties discovered in the
174182
template are observed. Add a `properties` object to opt into selective
175183
observation behavior.
176184

185+
For non-declarative/manual schema use, pass the schema in the observer map
186+
configuration:
187+
188+
```typescript
189+
import { FASTElement, Schema } from "@microsoft/fast-element";
190+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
191+
192+
class MyElement extends FASTElement {}
193+
194+
const schema = new Schema("my-custom-element");
195+
schema.addPath({
196+
rootPropertyName: "user",
197+
pathConfig: {
198+
type: "default",
199+
parentContext: null,
200+
currentContext: null,
201+
path: "user.name",
202+
},
203+
childrenMap: null,
204+
});
205+
206+
MyElement.define(
207+
{
208+
name: "my-custom-element",
209+
},
210+
[observerMap({ schema })]
211+
);
212+
```
213+
214+
You can also attach a manual schema to the FAST element definition. This is the
215+
path used automatically by `declarativeTemplate()` after it parses a template:
216+
217+
```typescript
218+
MyElement.define(
219+
{
220+
name: "my-custom-element",
221+
schema,
222+
},
223+
[observerMap()]
224+
);
225+
```
226+
177227
## Initial Path Processing Flow
178228

179229
Here's how binding paths flow through the system during initial template processing:
@@ -410,7 +460,7 @@ The schema system tracks binding contexts using special metadata:
410460
You can inspect generated schemas using the module-level `schemaRegistry` import:
411461

412462
```typescript
413-
import { schemaRegistry } from "@microsoft/fast-element/declarative.js";
463+
import { schemaRegistry } from "@microsoft/fast-element";
414464
415465
// Get all schemas for an element:
416466
const elementSchemas = schemaRegistry.get('my-element');
@@ -426,8 +476,7 @@ To verify that observer mapping ran, inspect the generated schema and the
426476
observable accessors on the element prototype:
427477

428478
```typescript
429-
import { Observable } from "@microsoft/fast-element";
430-
import { schemaRegistry } from "@microsoft/fast-element/declarative.js";
479+
import { Observable, schemaRegistry } from "@microsoft/fast-element";
431480
432481
const schemas = schemaRegistry.get("my-element");
433482
const accessors = Observable.getAccessors(MyElement.prototype).map(a => a.name);

packages/fast-element/DESIGN.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ For deep dives into specific areas, see the linked detailed documents.
4343
| Reactive data binding | `Observable`, `ExpressionNotifier`, `oneWay`/`oneTime`/`listener` bindings |
4444
| Declarative templating | `html` tagged template literal → `ViewTemplate` → compiled `HTMLView` |
4545
| Declarative HTML runtime | `@microsoft/fast-element/declarative.js``declarativeTemplate()`, `TemplateParser`, `Schema`, `attributeMap()`, `observerMap()` |
46+
| Schema-driven extensions | `@microsoft/fast-element/extensions/attribute-map.js` and `@microsoft/fast-element/extensions/observer-map.js` → tree-shakeable map helpers usable with declarative or manually supplied schemas |
4647
| Async DOM updates | `Updates` queue (batched, `requestAnimationFrame`-aligned) |
4748
| Scoped styles | `css` tagged template literal → `ElementStyles``adoptedStylesheets` / `<style>` |
4849
| Dependency injection | `DI` container, `@inject`, `@singleton`, `@transient`, resolvers |
@@ -326,9 +327,14 @@ FAST Element also owns the declarative HTML runtime that previously lived in a
326327
separate package. The dedicated `@microsoft/fast-element/declarative.js`
327328
entrypoint exports the functional public API: `declarativeTemplate()`,
328329
`attributeMap()`, `observerMap()`, `TemplateParser`, `Schema`,
329-
`schemaRegistry`, and related config types. The `<f-template>` element is an
330-
internal native `HTMLElement` publisher that `declarativeTemplate()` defines in
331-
the target registry; it is not part of the public API.
330+
`schemaRegistry`, and related config types. Existing declarative imports remain
331+
supported. For consumers that only need map helpers, the preferred entrypoints
332+
are `@microsoft/fast-element/extensions/attribute-map.js` and
333+
`@microsoft/fast-element/extensions/observer-map.js`. These subpaths keep the
334+
schema-driven map extensions factored away from declarative templating so they
335+
can also be used with manually supplied schemas. The `<f-template>` element is
336+
an internal native `HTMLElement` publisher that `declarativeTemplate()` defines
337+
in the target registry; it is not part of the public API.
332338

333339
The declarative runtime intentionally reuses the same FAST Element primitives as
334340
the imperative `html` API:
@@ -339,9 +345,11 @@ the imperative `html` API:
339345
- `TemplateParser` lowers declarative syntax to the same `strings` / `values`
340346
shape used by `ViewTemplate.create()`.
341347
- `attributeMap()` and `observerMap()` are `FASTElementExtension` factories.
342-
They register schema transforms on the element definition, and those
343-
transforms run after parsing in deterministic order (`attributeMap()` before
344-
`observerMap()`).
348+
With `declarativeTemplate()` they register schema transforms on the element
349+
definition, and those transforms run after parsing in deterministic order
350+
(`attributeMap()` before `observerMap()`). Outside declarative templates,
351+
`attributeMap()` uses `definition.schema`, while `observerMap()` can use
352+
either `definition.schema` or `observerMap({ schema })`.
345353

346354
The `src/declarative.ts` entrypoint is pure at module evaluation time. Running a
347355
declarative API lazily installs declarative debug messages only. Hydration hooks
@@ -548,19 +556,24 @@ src/
548556
├── components/
549557
│ ├── fast-element.ts # FASTElement, @customElement
550558
│ ├── element-controller.ts # ElementController, Stages
551-
│ ├── fast-definitions.ts # FASTElementDefinition
559+
│ ├── fast-definitions.ts # FASTElementDefinition, optional definition schema
560+
│ ├── schema.ts # Schema class and schemaRegistry
561+
│ ├── definition-schema-transforms.ts # Definition-scoped schema transform storage
552562
│ └── attributes.ts # AttributeDefinition, @attr, converters
563+
├── extensions/
564+
│ ├── observer-map.ts # observerMap() extension and proxy-backed observation helpers
565+
│ └── attribute-map.ts # attributeMap() extension and automatic @attr helpers
553566
├── di/
554567
│ └── di.ts # DI container, decorators, resolvers, Registration
555568
├── context.ts # Context, FASTContext, Context protocol
556569
├── declarative/
557570
│ ├── template.ts # declarativeTemplate() and internal f-template publisher
558571
│ ├── template-parser.ts # Declarative HTML parser → ViewTemplate strings/values
559-
│ ├── schema.ts # Binding schema builder + schemaRegistry
560-
│ ├── definition-options.ts # Definition-scoped declarative schema transforms
561-
│ ├── observer-map.ts # observerMap() extension and proxy-backed observation helpers
562-
│ ├── attribute-map.ts # attributeMap() extension and automatic @attr helpers
563-
│ ├── utilities.ts # Declarative parsing and proxy utilities
572+
│ ├── schema.ts # Compatibility re-export for Schema
573+
│ ├── definition-options.ts # Compatibility re-export for schema transforms
574+
│ ├── observer-map.ts # Compatibility re-export for observerMap()
575+
│ ├── attribute-map.ts # Compatibility re-export for attributeMap()
576+
│ ├── utilities.ts # Declarative parsing utilities
564577
│ └── syntax.ts # Declarative syntax constants
565578
├── state/
566579
│ ├── state.ts # state() helper (beta)

packages/fast-element/MIGRATION.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ removed `@microsoft/fast-html` package.
9999
| `TemplateElement.define({ name: "f-template" })` | No manual definition; `declarativeTemplate()` defines FAST's internal `<f-template>` publisher in the target registry |
100100
| `TemplateElement.config(callbacks)` / `HydrationLifecycleCallbacks` | Per-element callbacks via `declarativeTemplate(callbacks)` and global callbacks via `enableHydration(options)` |
101101
| `TemplateElement.options(...)`, `ElementOptions`, `ElementOptionsDictionary` | Define extensions: `attributeMap(...)` and `observerMap(...)` passed as the second argument to `define()` |
102-
| `AttributeMap` / `ObserverMap` exports from `declarative.js` | `attributeMap()` / `observerMap()` extension helpers and their config types |
102+
| `AttributeMap` / `ObserverMap` exports from the old declarative public surface | `attributeMap()` / `observerMap()` extension helpers and their config types |
103103

104104
### Migration steps
105105

@@ -124,7 +124,9 @@ removed `@microsoft/fast-html` package.
124124
2. Replace `TemplateElement.options()` with definition extensions:
125125

126126
```typescript
127-
import { attributeMap, declarativeTemplate, observerMap } from "@microsoft/fast-element/declarative.js";
127+
import { declarativeTemplate } from "@microsoft/fast-element/declarative.js";
128+
import { attributeMap } from "@microsoft/fast-element/extensions/attribute-map.js";
129+
import { observerMap } from "@microsoft/fast-element/extensions/observer-map.js";
128130

129131
MyElement.define(
130132
{
@@ -135,6 +137,13 @@ removed `@microsoft/fast-html` package.
135137
);
136138
```
137139

140+
Existing `attributeMap()` and `observerMap()` imports from
141+
`@microsoft/fast-element/declarative.js` remain valid for declarative
142+
templates. Prefer the extension subpaths when only the schema-driven maps are
143+
needed or when using manually supplied schemas. `FASTElementDefinition.schema`
144+
is optional; `declarativeTemplate()` assigns it automatically, and
145+
`observerMap()` can take a manual schema with `observerMap({ schema })`.
146+
138147
3. Replace `TemplateElement.config()` with `declarativeTemplate(callbacks)` for
139148
per-element callbacks and `enableHydration(options)` for global hydration
140149
callbacks. Hydration is not installed by `declarative.js`; call

0 commit comments

Comments
 (0)