@lushly-dev/fast-element 0.0.0-stage → 3.0.1-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ FAST - https://www.fast.design/
2
+
3
+ MIT License
4
+
5
+ Copyright (c) Microsoft Corporation. All rights reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE
package/README.md CHANGED
@@ -1,3 +1,281 @@
1
- # Temporary Holding Version
1
+ # FAST Element
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![npm version](https://badge.fury.io/js/%40microsoft%2Ffast-element.svg)](https://badge.fury.io/js/%40microsoft%2Ffast-element)
5
+
6
+ The `fast-element` library is a lightweight means to easily build performant, memory-efficient, standards-compliant Web Components. FAST Elements work in every major browser and can be used in combination with any front-end framework or even without a framework.
7
+
8
+ ## Installation
9
+
10
+ ### From NPM
11
+
12
+ To install the latest `fast-element` library using `npm`:
13
+
14
+ ```shell
15
+ npm install --save @lushly-dev/fast-element
16
+ ```
17
+
18
+ Within your JavaScript or TypeScript code, you can then import library APIs like this:
19
+
20
+ ```ts
21
+ import { FASTElement } from '@lushly-dev/fast-element';
22
+ ```
23
+
24
+ ### From CDN
25
+
26
+ A pre-bundled script that contains all APIs needed to build web components with FAST Element is available on CDN. You can use this script by adding [`type="module"`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) to the script element and then importing from the CDN.
27
+
28
+ ```html
29
+ <!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <script type="module">
33
+ import { FASTElement } from "https://cdn.jsdelivr.net/npm/@lushly-dev/fast-element/dist/fast-element.min.js";
34
+
35
+ // your code here
36
+ </script>
37
+ </head>
38
+ <!-- ... -->
39
+ </html>
40
+ ```
41
+
42
+ The markup above always references the latest release. When deploying to production, you will want to ship with a specific version. Here's an example of the markup for that:
43
+
44
+ ```html
45
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@lushly-dev/fast-element@1.6.2/dist/fast-element.min.js"></script>
46
+ ```
47
+
48
+ :::note
49
+ For simplicity, examples throughout the documentation will assume the library has been installed from NPM, but you can always replace the import location with the CDN URL.
50
+ :::
51
+
52
+ :::tip
53
+ Looking for a quick guide on building components? Check out [our Cheat Sheet](../resources/cheat-sheet.md#building-components).
54
+ :::
55
+
56
+ ## Browser Requirements
57
+
58
+ FAST Element v3 assumes a client-side browser `Window` runtime with native
59
+ `globalThis`, Custom Elements, DOM, Shadow DOM, and `requestAnimationFrame`.
60
+ The `FAST` object is now a module-scoped export (not on `globalThis`). If you
61
+ need to support an older browser without `globalThis`, load that polyfill before
62
+ importing `@lushly-dev/fast-element`.
63
+
64
+ The FAST Element client runtime is not intended to execute in workers,
65
+ server-side JavaScript runtimes, or other non-window hosts. Use server-side
66
+ rendering tools to produce HTML for the browser, and run FAST Element in the
67
+ client window where custom elements connect, render, update, and hydrate.
68
+
69
+ ## Debug entrypoint
70
+
71
+ `@lushly-dev/fast-element/debug.js` exports `enableDebug()` instead of configuring
72
+ FAST at import time. The development root export and debug rollup bundle still
73
+ enable debug behavior automatically.
74
+
75
+ ```ts
76
+ import { enableDebug } from "@lushly-dev/fast-element/debug.js";
77
+
78
+ enableDebug();
79
+ ```
80
+
81
+ ## Export Sizes
82
+
83
+ Bundle sizes for each tree-shakeable export are tracked in [`SIZES.md`](./SIZES.md) and regenerated on every build. See the [Export Sizes](https://www.fast.design/docs/3.x/resources/export-sizes/) documentation page for the latest numbers.
84
+
85
+ ## Root Imports and Path Exports
86
+
87
+ The root `@lushly-dev/fast-element` entrypoint exports the FAST Element
88
+ implementation APIs, including the element base class, kernel, controller,
89
+ definition APIs, template APIs, binding helpers, directives, styles, and schema
90
+ helpers. The FAST element registry is also available from
91
+ `@lushly-dev/fast-element/registry.js` for focused definition lookups and
92
+ `fastElementRegistry.whenRegistered(tagName)` registration promises.
93
+ Declarative, hydration, context, and dependency injection APIs are available
94
+ from their focused path exports.
95
+
96
+ Focused package path exports remain available for consumers that want to import
97
+ a narrower entrypoint directly. The website's
98
+ [Path Exports](https://www.fast.design/docs/3.x/advanced/path-exports/) page is
99
+ generated from `package.json` and lists each supported path.
100
+
101
+ ## Dynamic Style Application
102
+
103
+ Import `css`, `CSSTemplateTag`, `CSSValue`, and style APIs such as
104
+ `ElementStyles`, `CSSDirective`, `cssDirective`, `ComposableStyles`,
105
+ `HostBehavior`, `HostController`, `StyleStrategy`, and `StyleTarget` from
106
+ `@lushly-dev/fast-element`:
107
+
108
+ ```ts
109
+ import { ElementStyles, css } from "@lushly-dev/fast-element";
110
+ ```
111
+
112
+ When runtime state or external signals need to add or remove styles, create the
113
+ `ElementStyles` with `css` and toggle it through
114
+ `this.$fastController.addStyles()` / `this.$fastController.removeStyles()` from
115
+ the element lifecycle or change handlers.
116
+
117
+ `css` templates remain static style definitions. Runtime CSS bindings and
118
+ behavior-producing CSS directives are no longer supported; keep the condition on
119
+ the element and toggle a separate `ElementStyles` instance through the
120
+ controller when styles need to change.
121
+
122
+ ## Declarative HTML
123
+
124
+ FAST Element publishes its declarative HTML runtime from
125
+ `@lushly-dev/fast-element/declarative.js`. This entrypoint exports the
126
+ functional APIs for declarative templates: `declarativeTemplate()`,
127
+ `TemplateParser`, and related configuration types. `attributeMap()` and
128
+ `observerMap()` are available from their own focused path exports. The
129
+ declarative runtime is pure at import time; declarative APIs lazily install only
130
+ declarative debug messages. Hydration is separate and remains opt-in through
131
+ `enableHydration()` from `@lushly-dev/fast-element/hydration.js`.
132
+
133
+ ```ts
134
+ import { FASTElement } from "@lushly-dev/fast-element";
135
+ import { declarativeTemplate } from "@lushly-dev/fast-element/declarative.js";
136
+
137
+ class MyElement extends FASTElement {}
138
+
139
+ MyElement.define({
140
+ name: "my-element",
141
+ template: declarativeTemplate(),
142
+ });
143
+ ```
144
+
145
+ `declarativeTemplate()` automatically defines FAST's internal native
146
+ `<f-template>` publisher in the relevant registry, resolves the matching
147
+ `<f-template name="my-element">`, and keeps the definition template concrete
148
+ before `define()` resolves. If multiple matching `<f-template>` elements are
149
+ connected, the first connected element supplies the template and later duplicates
150
+ do not reassign it. Consumers should not import or define the `<f-template>`
151
+ implementation directly.
152
+
153
+ Declarative schema behavior is enabled with define extensions:
154
+
155
+ ```ts
156
+ import { attributeMap } from "@lushly-dev/fast-element/attribute-map.js";
157
+ import { declarativeTemplate } from "@lushly-dev/fast-element/declarative.js";
158
+ import { observerMap } from "@lushly-dev/fast-element/observer-map.js";
159
+
160
+ MyElement.define(
161
+ {
162
+ name: "my-element",
163
+ template: declarativeTemplate(),
164
+ },
165
+ [attributeMap(), observerMap()],
166
+ );
167
+ ```
168
+
169
+ `attributeMap()` creates `@attr`-style accessors for leaf bindings, and
170
+ `observerMap()` creates deep observable accessors for discovered root
171
+ properties. Declarative templates assign `definition.schema` during template
172
+ resolution so these extensions always have schema data when used with
173
+ `declarativeTemplate()`. For non-declarative/manual schema use, import `Schema`
174
+ from `@lushly-dev/fast-element` and pass it on the element definition;
175
+ `observerMap()` can also receive
176
+ `observerMap({ schema })` directly. When both extensions are present, attribute
177
+ mapping runs before observer mapping.
178
+
179
+ Declarative utilities such as `deepMerge` are available from
180
+ `@lushly-dev/fast-element/declarative-utilities.js`. See
181
+ [`docs/declarative/syntax.md`](./docs/declarative/syntax.md) for declarative implementation
182
+ details and the
183
+ [Declarative HTML docs](https://fast.design/docs/3.x/declarative-templates/overview/)
184
+ for guides on writing f-templates, defining declarative elements, and
185
+ server-side rendering. Declarative event bindings use `$e` for the DOM event
186
+ object and `$c` for the execution context.
187
+
188
+ ## Prerendered Content Optimization
189
+
190
+ Hydration of prerendered content is **opt-in**. Call `enableHydration()` from `@lushly-dev/fast-element/hydration.js` before any FAST elements connect to activate the hydration path:
191
+
192
+ ```typescript
193
+ import { enableHydration } from "@lushly-dev/fast-element/hydration.js";
194
+
195
+ const hydration = enableHydration();
196
+
197
+ await hydration.whenHydrated();
198
+ console.log("hydration complete");
199
+ ```
200
+
201
+ By default, hydration handles the initial prerendered batch and then no-ops
202
+ after the active hydration batch completes. If your app streams Declarative
203
+ Shadow DOM after the initial batch, keep the hydration hook active. In this
204
+ mode, `enableHydration().whenHydrated()` intentionally remains pending because
205
+ hydration never reaches a global completion point.
206
+
207
+ ```typescript
208
+ import { enableHydration, StopHydration } from "@lushly-dev/fast-element/hydration.js";
209
+
210
+ enableHydration({
211
+ stopHydration: StopHydration.never,
212
+ });
213
+ ```
214
+
215
+ When hydration is enabled and a FAST element connects with an existing shadow root (from server-side rendering or declarative shadow DOM), `ElementController` detects this and hydrates instead of re-rendering. Two properties on the controller let you inspect the result:
216
+
217
+ - **`isPrerendered: Promise<boolean>`** — resolves `true` when the element had a declarative shadow root (DSD) at connect time, regardless of whether hydration ran.
218
+ - **`isHydrated: Promise<boolean>`** — resolves `true` only when hydration actually ran successfully.
219
+
220
+ This enables several optimizations:
221
+
222
+ - **Hydration instead of re-render**: The template uses `hydrate()` to map existing DOM nodes to binding targets rather than cloning new DOM.
223
+ - **Declarative template resolution**: `declarativeTemplate()` waits for the
224
+ matching `<f-template>` before `define()` completes, so connected elements
225
+ hydrate with a concrete template.
226
+ - **Attribute skip**: `onAttributeChangedCallback()` skips processing during initial upgrade when the element is prerendered, since server-rendered attribute values are already correct.
227
+ - **Binding skip**: `HTMLBindingDirective.bind()` skips `updateTarget` for `attribute` and `booleanAttribute` aspect types when the view is prerendered.
228
+
229
+ ### Hydration Mismatch Diagnostics
230
+
231
+ If a prerendered DOM diverges from the client template in a way FAST cannot
232
+ reconcile (the `render()` empty-boundary and `repeat()` count-mismatch cases
233
+ recover silently), `HydrationBindingError` or `HydrationTargetElementError` is
234
+ thrown. By default the message is a single line pointing at the opt-in
235
+ `hydrationDebugger()`. Install the debugger to get a rich
236
+ "Expected … / Received …" report with the SSR HTML snippet and structured
237
+ `expected` / `received` fields on the thrown error:
238
+
239
+ ```typescript
240
+ import { enableHydration, hydrationDebugger } from "@lushly-dev/fast-element/hydration.js";
241
+
242
+ enableHydration({ debugger: hydrationDebugger() });
243
+ ```
244
+
245
+ The debugger module is tree-shaken out of production hydration bundles when
246
+ not imported, so the rich diagnostic helpers only land in builds that opt in.
247
+
248
+ Component authors can await both promises to distinguish prerendered content from successful hydration:
249
+
250
+ ```typescript
251
+ const controller = this.$fastController;
252
+ const prerendered = await controller.isPrerendered;
253
+ const hydrated = await controller.isHydrated;
254
+
255
+ if (prerendered && !hydrated) {
256
+ // Had DSD but hydration wasn't enabled — client-side rendered
257
+ }
258
+ ```
259
+
260
+ Custom directives can also await `controller.isPrerendered` and `controller.isHydrated` (both `Promise<boolean>` on the `ViewController` interface) to determine how the view's content was rendered.
261
+
262
+ ## Define Extensions
263
+
264
+ The static `define()` method on a `FASTElement` subclass accepts an optional second argument — an array of extension callbacks that are invoked with the resolved element definition before the element is registered with the platform. This enables a plugin pattern where reusable behaviors can hook into element registration.
265
+
266
+ ```typescript
267
+ import { FASTElement, type FASTElementExtension } from "@lushly-dev/fast-element";
268
+
269
+ function logger(): FASTElementExtension {
270
+ return definition => {
271
+ console.log(`Defining element: ${definition.name}`);
272
+ };
273
+ }
274
+
275
+ class MyComponent extends FASTElement {
276
+ // component code
277
+ }
278
+
279
+ MyComponent.define({ name: "my-component", template, styles }, [logger()]);
280
+ ```
281
+ Each extension receives the full `FASTElementDefinition`, which includes the resolved element name, type, template, styles, and attribute metadata. Extensions run before `customElements.define()`, so any setup they perform is available when existing DOM elements are upgraded.
@@ -0,0 +1,15 @@
1
+ # FAST HTML to declarative HTML migration rules
2
+
3
+ This folder contains `.yml` rules for converting common FAST tagged-template
4
+ patterns to FAST Element v3 declarative HTML using
5
+ [ast-grep](https://ast-grep.github.io/).
6
+
7
+ Run the rules from the FAST Element package root with:
8
+
9
+ ```bash
10
+ pnpm exec sg scan --config migration/fast-html/sgconfig.yml path/to/source
11
+ ```
12
+
13
+ The generated markup is a migration aid, not a semantic guarantee. Move the
14
+ resulting template into an `.html` file, use `$e` and `$c` for event/context
15
+ arguments, and verify it with the declarative Playwright suite.
@@ -0,0 +1,38 @@
1
+ # https://ast-grep.github.io/guide/rule-config.html#rule
2
+ # from: ${ref("defaultSlot")}
3
+ # to: f-ref="{defaultSlot}"
4
+ id: binding - convert attribute directives
5
+ language: typescript
6
+ utils:
7
+ call-expression:
8
+ has:
9
+ nthChild: 1
10
+ kind: call_expression
11
+ all:
12
+ - matches: identifier
13
+ - matches: arguments
14
+ identifier:
15
+ has:
16
+ nthChild: 1
17
+ kind: identifier
18
+ pattern: $DIRECTIVE
19
+ arguments:
20
+ has:
21
+ kind: arguments
22
+ matches: string
23
+ string:
24
+ has:
25
+ kind: string
26
+ matches: string-fragment
27
+ string-fragment:
28
+ has:
29
+ kind: string_fragment
30
+ pattern: $STRING
31
+ constraints:
32
+ DIRECTIVE:
33
+ regex: ref|slotted|children
34
+ rule:
35
+ kind: template_substitution
36
+ matches: call-expression
37
+ fix:
38
+ 'f-$DIRECTIVE="{$STRING}"'
@@ -0,0 +1,41 @@
1
+ # https://ast-grep.github.io/guide/rule-config.html#rule
2
+ # from: ${(x, c) => x.foo(c.event)}
3
+ # to: {foo($e)}
4
+ id: binding - convert call expression with event argument
5
+ language: typescript
6
+ utils:
7
+ arrow-function:
8
+ has:
9
+ kind: arrow_function
10
+ matches: call-expression
11
+ call-expression:
12
+ has:
13
+ kind: call_expression
14
+ all:
15
+ - matches: handler-arguments
16
+ - matches: handler-expression
17
+ handler-expression:
18
+ has:
19
+ kind: member_expression
20
+ matches: handler-expression-property
21
+ handler-expression-property:
22
+ has:
23
+ kind: property_identifier
24
+ pattern: $A
25
+ handler-arguments:
26
+ has:
27
+ kind: arguments
28
+ matches: handler-arguments-member-expression
29
+ handler-arguments-member-expression:
30
+ has:
31
+ kind: member_expression
32
+ matches: handler-arguments-property-identifier
33
+ handler-arguments-property-identifier:
34
+ has:
35
+ kind: property_identifier
36
+ pattern: $B
37
+ rule:
38
+ kind: template_substitution
39
+ matches: arrow-function
40
+ fix:
41
+ '{$A($e)}'
@@ -0,0 +1,33 @@
1
+ # https://ast-grep.github.io/guide/rule-config.html#rule
2
+ # from: ${x => x.foo}
3
+ # to: {{foo}}
4
+ id: binding - convert member expression
5
+ language: typescript
6
+ utils:
7
+ arrow-function:
8
+ has:
9
+ kind: arrow_function
10
+ all:
11
+ - matches: identifier
12
+ - matches: member-expression
13
+ member-expression:
14
+ has:
15
+ kind: member_expression
16
+ nthChild: 2
17
+ all:
18
+ - matches: identifier
19
+ - matches: property-identifier
20
+ identifier:
21
+ has:
22
+ kind: identifier
23
+ nthChild: 1
24
+ property-identifier:
25
+ has:
26
+ kind: property_identifier
27
+ nthChild: 2
28
+ pattern: $A
29
+ rule:
30
+ kind: template_substitution
31
+ matches: arrow-function
32
+ fix:
33
+ '{{$A}}'
@@ -0,0 +1,16 @@
1
+ # https://ast-grep.github.io/guide/rule-config.html#rule
2
+ # from: export const template = html`<slot></slot>`
3
+ # to: `<slot></slot>`
4
+ id: convert tag function to template literal
5
+ language: typescript
6
+ utils:
7
+ is-tag-template:
8
+ has:
9
+ kind: template_string
10
+ pattern: $B
11
+ rule:
12
+ kind: call_expression
13
+ matches: is-tag-template
14
+ pattern: $A
15
+ fix:
16
+ '$B'