@microsoft/fast-element 2.10.4 → 3.0.0-rc.2

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.
Files changed (211) hide show
  1. package/CHANGELOG.md +52 -2
  2. package/README.md +244 -1
  3. package/dist/arrays/arrays.api.json +2621 -0
  4. package/dist/context/context.api.json +13 -13
  5. package/dist/declarative/declarative.api.json +8483 -0
  6. package/dist/di/di.api.json +16 -16
  7. package/dist/dts/__test__/helpers.d.ts +6 -0
  8. package/dist/dts/array-observer.d.ts +2 -0
  9. package/dist/dts/arrays.d.ts +2 -0
  10. package/dist/dts/attr.d.ts +1 -0
  11. package/dist/dts/binding/binding.d.ts +15 -5
  12. package/dist/dts/binding/one-time.d.ts +1 -1
  13. package/dist/dts/binding/one-way.d.ts +1 -1
  14. package/dist/dts/binding/signal.d.ts +6 -6
  15. package/dist/dts/binding/two-way.d.ts +2 -1
  16. package/dist/dts/binding.d.ts +7 -0
  17. package/dist/dts/components/attributes.d.ts +1 -4
  18. package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
  19. package/dist/dts/components/element-controller.d.ts +80 -114
  20. package/dist/dts/components/element-hydration.d.ts +1 -1
  21. package/dist/dts/components/enable-hydration.d.ts +54 -0
  22. package/dist/dts/components/fast-definitions.d.ts +98 -46
  23. package/dist/dts/components/fast-element.d.ts +43 -16
  24. package/dist/dts/components/hydration-tracker.d.ts +83 -0
  25. package/dist/dts/components/hydration.d.ts +23 -53
  26. package/dist/dts/components/schema.d.ts +205 -0
  27. package/dist/dts/context.d.ts +13 -13
  28. package/dist/dts/css.d.ts +3 -0
  29. package/dist/dts/debug.d.ts +5 -1
  30. package/dist/dts/declarative/attribute-map.d.ts +58 -0
  31. package/dist/dts/declarative/debug.d.ts +4 -0
  32. package/dist/dts/declarative/index.d.ts +14 -0
  33. package/dist/dts/declarative/interfaces.d.ts +8 -0
  34. package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
  35. package/dist/dts/declarative/observer-map.d.ts +89 -0
  36. package/dist/dts/declarative/runtime.d.ts +5 -0
  37. package/dist/dts/declarative/syntax.d.ts +21 -0
  38. package/dist/dts/declarative/template-bridge.d.ts +33 -0
  39. package/dist/dts/declarative/template-parser.d.ts +98 -0
  40. package/dist/dts/declarative/template.d.ts +10 -0
  41. package/dist/dts/declarative/utilities.d.ts +358 -0
  42. package/dist/dts/di/di.d.ts +7 -7
  43. package/dist/dts/directives/children.d.ts +2 -0
  44. package/dist/dts/directives/node-observation.d.ts +2 -0
  45. package/dist/dts/directives/ref.d.ts +2 -0
  46. package/dist/dts/directives/repeat.d.ts +4 -0
  47. package/dist/dts/directives/slotted.d.ts +2 -0
  48. package/dist/dts/directives/when.d.ts +3 -0
  49. package/dist/dts/dom-policy.d.ts +23 -5
  50. package/dist/dts/dom.d.ts +4 -16
  51. package/dist/dts/html.d.ts +5 -0
  52. package/dist/dts/hydration/diagnostics.d.ts +93 -0
  53. package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
  54. package/dist/dts/hydration/messages.d.ts +62 -0
  55. package/dist/dts/hydration/runtime.d.ts +7 -0
  56. package/dist/dts/hydration/target-builder.d.ts +40 -12
  57. package/dist/dts/hydration.d.ts +18 -0
  58. package/dist/dts/index.d.ts +42 -42
  59. package/dist/dts/index.debug.d.ts +0 -1
  60. package/dist/dts/index.rollup.debug.d.ts +0 -1
  61. package/dist/dts/interfaces.d.ts +2 -49
  62. package/dist/dts/observable.d.ts +3 -6
  63. package/dist/dts/observation/arrays.d.ts +1 -1
  64. package/dist/dts/observation/observable.d.ts +3 -3
  65. package/dist/dts/observation/update-queue.d.ts +1 -1
  66. package/dist/dts/platform.d.ts +45 -8
  67. package/dist/dts/registry.d.ts +1 -0
  68. package/dist/dts/render.d.ts +7 -0
  69. package/dist/dts/schema.d.ts +1 -0
  70. package/dist/dts/state/exports.d.ts +1 -1
  71. package/dist/dts/state/state.d.ts +2 -2
  72. package/dist/dts/styles/css-directive.d.ts +5 -12
  73. package/dist/dts/styles/css.d.ts +5 -7
  74. package/dist/dts/styles/element-styles.d.ts +0 -10
  75. package/dist/dts/styles.d.ts +6 -0
  76. package/dist/dts/templating/compiler.d.ts +1 -1
  77. package/dist/dts/templating/html-binding-directive.d.ts +10 -2
  78. package/dist/dts/templating/html-directive.d.ts +19 -1
  79. package/dist/dts/templating/hydration-view.d.ts +130 -0
  80. package/dist/dts/templating/render.d.ts +1 -1
  81. package/dist/dts/templating/repeat.d.ts +1 -1
  82. package/dist/dts/templating/template.d.ts +15 -7
  83. package/dist/dts/templating/view.d.ts +25 -102
  84. package/dist/dts/templating.d.ts +10 -0
  85. package/dist/dts/testing/exports.d.ts +2 -2
  86. package/dist/dts/testing/fakes.d.ts +4 -4
  87. package/dist/dts/updates.d.ts +1 -0
  88. package/dist/dts/volatile.d.ts +2 -0
  89. package/dist/esm/__test__/helpers.js +22 -0
  90. package/dist/esm/__test__/setup-node.js +18 -0
  91. package/dist/esm/array-observer.js +1 -0
  92. package/dist/esm/arrays.js +1 -0
  93. package/dist/esm/attr.js +1 -0
  94. package/dist/esm/binding/normalize.js +1 -1
  95. package/dist/esm/binding/signal.js +4 -4
  96. package/dist/esm/binding/two-way.js +3 -3
  97. package/dist/esm/binding.js +4 -0
  98. package/dist/esm/components/attributes.js +18 -11
  99. package/dist/esm/components/definition-schema-transforms.js +23 -0
  100. package/dist/esm/components/element-controller.js +206 -270
  101. package/dist/esm/components/element-hydration.js +1 -1
  102. package/dist/esm/components/enable-hydration.js +124 -0
  103. package/dist/esm/components/fast-definitions.js +219 -56
  104. package/dist/esm/components/fast-element.js +18 -27
  105. package/dist/esm/components/hydration-tracker.js +122 -0
  106. package/dist/esm/components/hydration.js +137 -140
  107. package/dist/esm/components/schema.js +253 -0
  108. package/dist/esm/context.js +6 -6
  109. package/dist/esm/css.js +3 -0
  110. package/dist/esm/debug.js +27 -26
  111. package/dist/esm/declarative/attribute-map.js +122 -0
  112. package/dist/esm/declarative/debug.js +4 -0
  113. package/dist/esm/declarative/index.js +4 -0
  114. package/dist/esm/declarative/interfaces.js +9 -0
  115. package/dist/esm/declarative/observer-map-utilities.js +565 -0
  116. package/dist/esm/declarative/observer-map.js +216 -0
  117. package/dist/esm/declarative/runtime.js +14 -0
  118. package/dist/esm/declarative/syntax.js +36 -0
  119. package/dist/esm/declarative/template-bridge.js +160 -0
  120. package/dist/esm/declarative/template-parser.js +306 -0
  121. package/dist/esm/declarative/template.js +143 -0
  122. package/dist/esm/declarative/utilities.js +1069 -0
  123. package/dist/esm/di/di.js +8 -9
  124. package/dist/esm/directives/children.js +1 -0
  125. package/dist/esm/directives/node-observation.js +1 -0
  126. package/dist/esm/directives/ref.js +1 -0
  127. package/dist/esm/directives/repeat.js +1 -0
  128. package/dist/esm/directives/slotted.js +1 -0
  129. package/dist/esm/directives/when.js +1 -0
  130. package/dist/esm/dom-policy.js +35 -6
  131. package/dist/esm/dom.js +1 -1
  132. package/dist/esm/html.js +2 -0
  133. package/dist/esm/hydration/diagnostics.js +50 -0
  134. package/dist/esm/hydration/hydration-debugger.js +112 -0
  135. package/dist/esm/hydration/messages.js +84 -0
  136. package/dist/esm/hydration/runtime.js +33 -0
  137. package/dist/esm/hydration/target-builder.js +144 -91
  138. package/dist/esm/hydration.js +6 -0
  139. package/dist/esm/index.debug.js +2 -1
  140. package/dist/esm/index.js +38 -29
  141. package/dist/esm/index.rollup.debug.js +3 -2
  142. package/dist/esm/index.rollup.js +1 -1
  143. package/dist/esm/interfaces.js +2 -45
  144. package/dist/esm/metadata.js +2 -8
  145. package/dist/esm/observable.js +1 -4
  146. package/dist/esm/observation/arrays.js +1 -1
  147. package/dist/esm/observation/notifier.js +2 -4
  148. package/dist/esm/observation/observable.js +5 -5
  149. package/dist/esm/observation/update-queue.js +47 -58
  150. package/dist/esm/platform.js +31 -30
  151. package/dist/esm/registry.js +1 -0
  152. package/dist/esm/render.js +1 -0
  153. package/dist/esm/schema.js +1 -0
  154. package/dist/esm/state/exports.js +1 -1
  155. package/dist/esm/styles/css-directive.js +1 -2
  156. package/dist/esm/styles/css.js +15 -56
  157. package/dist/esm/styles/element-styles.js +69 -15
  158. package/dist/esm/styles.js +2 -0
  159. package/dist/esm/templating/html-binding-directive.js +11 -9
  160. package/dist/esm/templating/hydration-view.js +228 -0
  161. package/dist/esm/templating/render.js +39 -18
  162. package/dist/esm/templating/repeat.js +69 -33
  163. package/dist/esm/templating/template.js +7 -7
  164. package/dist/esm/templating/view.js +25 -234
  165. package/dist/esm/templating.js +7 -0
  166. package/dist/esm/testing/exports.js +2 -2
  167. package/dist/esm/testing/fixture.js +2 -2
  168. package/dist/esm/testing/timeout.js +2 -2
  169. package/dist/esm/updates.js +1 -0
  170. package/dist/esm/volatile.js +1 -0
  171. package/dist/fast-element.api.json +14389 -11138
  172. package/dist/fast-element.d.ts +3651 -809
  173. package/dist/fast-element.debug.js +5666 -4722
  174. package/dist/fast-element.debug.min.js +2 -2
  175. package/dist/fast-element.js +5394 -4381
  176. package/dist/fast-element.min.js +2 -2
  177. package/dist/fast-element.untrimmed.d.ts +923 -472
  178. package/dist/hydration/hydration.api.json +6460 -0
  179. package/dist/styles/styles.api.json +2672 -0
  180. package/package.json +165 -45
  181. package/ARCHITECTURE_FASTELEMENT.md +0 -63
  182. package/ARCHITECTURE_HTML_TAGGED_TEMPLATE_LITERAL.md +0 -36
  183. package/ARCHITECTURE_INTRO.md +0 -10
  184. package/ARCHITECTURE_OVERVIEW.md +0 -52
  185. package/ARCHITECTURE_UPDATES.md +0 -11
  186. package/CHANGELOG.json +0 -2275
  187. package/DESIGN.md +0 -510
  188. package/api-extractor.context.json +0 -14
  189. package/api-extractor.di.json +0 -14
  190. package/biome.json +0 -4
  191. package/dist/dts/components/install-hydration.d.ts +0 -1
  192. package/dist/dts/pending-task.d.ts +0 -32
  193. package/dist/dts/styles/css-binding-directive.d.ts +0 -60
  194. package/dist/dts/templating/install-hydratable-view-templates.d.ts +0 -1
  195. package/dist/esm/components/install-hydration.js +0 -3
  196. package/dist/esm/pending-task.js +0 -28
  197. package/dist/esm/polyfills.js +0 -60
  198. package/dist/esm/styles/css-binding-directive.js +0 -76
  199. package/dist/esm/templating/install-hydratable-view-templates.js +0 -23
  200. package/docs/ACKNOWLEDGEMENTS.md +0 -12
  201. package/docs/api-report.api.md +0 -1122
  202. package/docs/context/api-report.api.md +0 -69
  203. package/docs/di/api-report.api.md +0 -315
  204. package/docs/fast-element-2-changes.md +0 -15
  205. package/playwright.config.ts +0 -26
  206. package/scripts/run-api-extractor.js +0 -51
  207. package/test/index.html +0 -11
  208. package/test/main.ts +0 -104
  209. package/test/vite.config.ts +0 -19
  210. package/tsconfig.api-extractor.json +0 -6
  211. /package/dist/dts/{polyfills.d.ts → __test__/setup-node.d.ts} +0 -0
@@ -0,0 +1,124 @@
1
+ import { installHydrationDiagnostic } from "../hydration/diagnostics.js";
2
+ import { ensureHydrationRuntime } from "../hydration/runtime.js";
3
+ import { SourceLifetime } from "../observation/observable.js";
4
+ import { ElementController } from "./element-controller.js";
5
+ import { isHydratable } from "./hydration.js";
6
+ import { HydrationTracker } from "./hydration-tracker.js";
7
+ export { StopHydration } from "./hydration-tracker.js";
8
+ /**
9
+ * No-op handler used during prerendered bind to discard the
10
+ * upgrade-time burst of attributeChangedCallbacks.
11
+ */
12
+ function noopAttributeHandler() { }
13
+ let tracker = null;
14
+ let hookInstalled = false;
15
+ /**
16
+ * Enables hydration support for prerendered FAST elements.
17
+ *
18
+ * @remarks
19
+ * Call this before any FAST elements connect to the DOM. Hydration
20
+ * logic is not active unless this function is called, keeping
21
+ * `FASTElement` lightweight for client-side-only applications.
22
+ *
23
+ * Safe to call multiple times — the hydration hook is installed once
24
+ * and subsequent calls merge their options into the shared tracker.
25
+ * By default, the hook stops hydrating new prerendered elements after
26
+ * the global `hydrationComplete` callback. Set
27
+ * `stopHydration` to `StopHydration.never` for streaming scenarios
28
+ * that append hydratable Declarative Shadow DOM after the initial batch.
29
+ *
30
+ * Pass `debugger: hydrationDebugger()` to swap the default minimal
31
+ * hydration mismatch error message for a rich "Expected / Received"
32
+ * report including the SSR HTML snippet and structured
33
+ * `expected`/`received` fields on `HydrationBindingError` /
34
+ * `HydrationTargetElementError`. The debugger module is tree-shaken
35
+ * out of production hydration bundles unless explicitly imported.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { enableHydration, StopHydration } from "@microsoft/fast-element/hydration.js";
40
+ *
41
+ * enableHydration({
42
+ * stopHydration: StopHydration.never,
43
+ * hydrationComplete() {
44
+ * console.log("hydration complete");
45
+ * },
46
+ * });
47
+ * ```
48
+ *
49
+ * @example Rich hydration mismatch diagnostics
50
+ * ```ts
51
+ * import { enableHydration, hydrationDebugger } from "@microsoft/fast-element/hydration.js";
52
+ *
53
+ * enableHydration({ debugger: hydrationDebugger() });
54
+ * ```
55
+ *
56
+ * @param options - Optional global hydration callbacks and behavior.
57
+ * @public
58
+ */
59
+ export function enableHydration(options) {
60
+ ensureHydrationRuntime();
61
+ if (options === null || options === void 0 ? void 0 : options.debugger) {
62
+ installHydrationDiagnostic(options.debugger.diagnostic);
63
+ }
64
+ if (!hookInstalled) {
65
+ tracker = new HydrationTracker(options !== null && options !== void 0 ? options : {});
66
+ hookInstalled = true;
67
+ const activeTracker = tracker;
68
+ ElementController.installHydrationHook((controller, template, element, host) => {
69
+ var _a, _b;
70
+ if (!activeTracker.shouldHydrate || !isHydratable(template)) {
71
+ return false;
72
+ }
73
+ const callbacks = controller.definition.lifecycleCallbacks;
74
+ activeTracker.add(element);
75
+ try {
76
+ try {
77
+ (_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.elementWillHydrate) === null || _a === void 0 ? void 0 : _a.call(callbacks, element);
78
+ }
79
+ catch (_c) {
80
+ // A lifecycle callback must never prevent hydration.
81
+ }
82
+ const firstChild = host.firstChild;
83
+ const lastChild = host.lastChild;
84
+ const view = template.hydrate(firstChild, lastChild, element);
85
+ controller.view = view;
86
+ const realHandler = controller.onAttributeChangedCallback;
87
+ controller.onAttributeChangedCallback = noopAttributeHandler;
88
+ try {
89
+ view._skipAttrUpdates = true;
90
+ view.isPrerendered = Promise.resolve(true);
91
+ view.isHydrated = Promise.resolve(true);
92
+ view.bind(controller.source);
93
+ view._skipAttrUpdates = false;
94
+ }
95
+ finally {
96
+ controller.onAttributeChangedCallback = realHandler;
97
+ }
98
+ view.sourceLifetime =
99
+ SourceLifetime.coupled;
100
+ controller.hasExistingShadowRoot = false;
101
+ try {
102
+ (_b = callbacks === null || callbacks === void 0 ? void 0 : callbacks.elementDidHydrate) === null || _b === void 0 ? void 0 : _b.call(callbacks, element);
103
+ }
104
+ catch (_d) {
105
+ // A lifecycle callback must never prevent post-hydration work.
106
+ }
107
+ }
108
+ finally {
109
+ activeTracker.remove(element);
110
+ }
111
+ return true;
112
+ });
113
+ }
114
+ else if (options && tracker) {
115
+ // Merge options into existing tracker for subsequent calls
116
+ tracker.mergeOptions(options);
117
+ }
118
+ }
119
+ /**
120
+ * The attribute used to defer hydration of an element.
121
+ * Retained for intersection observer viewport hydration rendering.
122
+ * @beta
123
+ */
124
+ export const deferHydrationAttribute = "defer-hydration";
@@ -7,27 +7,171 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- var _a;
11
- import { isString, KernelServiceId } from "../interfaces.js";
10
+ import { isFunction, isString } from "../interfaces.js";
12
11
  import { Observable } from "../observation/observable.js";
13
- import { createTypeRegistry, FAST } from "../platform.js";
12
+ import { createTypeRegistry, } from "../platform.js";
14
13
  import { ElementStyles } from "../styles/element-styles.js";
15
- import { AttributeConfiguration, AttributeDefinition } from "./attributes.js";
14
+ import { AttributeDefinition } from "./attributes.js";
16
15
  const defaultShadowOptions = { mode: "open" };
17
16
  const defaultElementOptions = {};
18
17
  const fastElementBaseTypes = new Set();
19
18
  /**
20
- * The FAST custom element registry
19
+ * The FAST custom element registry.
20
+ * @remarks
21
+ * This registry stores FAST element definitions by constructor so consumers can
22
+ * look up the `FASTElementDefinition` associated with an element type or instance.
23
+ * @public
24
+ */
25
+ export const fastElementRegistry = createTypeRegistry();
26
+ const templateResolvers = new WeakMap();
27
+ const pendingTemplateResolutions = new WeakMap();
28
+ const templateResolutionErrors = new WeakMap();
29
+ const registeredTypesByRegistry = new WeakMap();
30
+ const extensionRegistries = new WeakMap();
31
+ const lateAttributeLookups = new WeakMap();
32
+ function isFASTElementTemplateResolver(value) {
33
+ return isFunction(value);
34
+ }
35
+ function isPromiseLike(value) {
36
+ return typeof (value === null || value === void 0 ? void 0 : value.then) === "function";
37
+ }
38
+ function getRegisteredTypes(registry = customElements) {
39
+ if (registry === customElements) {
40
+ return FASTElementDefinition.isRegistered;
41
+ }
42
+ let registeredTypes = registeredTypesByRegistry.get(registry);
43
+ if (!registeredTypes) {
44
+ registeredTypes = {};
45
+ registeredTypesByRegistry.set(registry, registeredTypes);
46
+ }
47
+ return registeredTypes;
48
+ }
49
+ function finalizeResolvedTemplate(definition, template) {
50
+ var _a, _b;
51
+ pendingTemplateResolutions.delete(definition);
52
+ if (definition.template === void 0 && template !== void 0) {
53
+ definition.template = template;
54
+ (_b = (_a = definition.lifecycleCallbacks) === null || _a === void 0 ? void 0 : _a.templateDidUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, definition.name);
55
+ }
56
+ if (definition.template !== void 0) {
57
+ templateResolutionErrors.delete(definition);
58
+ templateResolvers.delete(definition);
59
+ return definition.template;
60
+ }
61
+ return void 0;
62
+ }
63
+ /**
64
+ * Applies extension callbacks to a FAST element definition.
65
+ * @internal
66
+ */
67
+ export function applyFASTElementExtensions(definition, registry = definition.registry, extensions) {
68
+ if (!(extensions === null || extensions === void 0 ? void 0 : extensions.length)) {
69
+ return;
70
+ }
71
+ const typedDefinition = definition;
72
+ let registries = extensionRegistries.get(typedDefinition);
73
+ if (registries === null || registries === void 0 ? void 0 : registries.has(registry)) {
74
+ return;
75
+ }
76
+ if (registries === void 0) {
77
+ registries = new WeakSet();
78
+ extensionRegistries.set(typedDefinition, registries);
79
+ }
80
+ registries.add(registry);
81
+ for (const extension of extensions) {
82
+ extension(definition);
83
+ }
84
+ }
85
+ /**
86
+ * Tracks attribute definitions that were added after the element was already
87
+ * registered with the platform and therefore are not covered by the browser's
88
+ * static observedAttributes snapshot.
21
89
  * @internal
22
90
  */
23
- export const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
91
+ export function trackLateAttributeDefinition(definition, attribute) {
92
+ const typedDefinition = definition;
93
+ let lateAttributeLookup = lateAttributeLookups.get(typedDefinition);
94
+ if (lateAttributeLookup === void 0) {
95
+ const lookup = Object.create(null);
96
+ lateAttributeLookups.set(typedDefinition, lookup);
97
+ lateAttributeLookup = lookup;
98
+ }
99
+ lateAttributeLookup[attribute.attribute] = attribute;
100
+ }
24
101
  /**
25
- * Values for the `templateOptions` property.
26
- * @alpha
102
+ * Gets the attribute definitions that were added after platform registration.
103
+ * @internal
27
104
  */
28
- export const TemplateOptions = {
29
- deferAndHydrate: "defer-and-hydrate",
30
- };
105
+ export function getLateAttributeLookup(definition) {
106
+ var _a;
107
+ return ((_a = lateAttributeLookups.get(definition)) !== null && _a !== void 0 ? _a : null);
108
+ }
109
+ /**
110
+ * Resolves the concrete template for a FAST element definition when the
111
+ * definition was composed with a template resolver.
112
+ * @internal
113
+ */
114
+ export function resolveFASTElementTemplate(definition) {
115
+ if (definition.template !== void 0) {
116
+ templateResolutionErrors.delete(definition);
117
+ return definition.template;
118
+ }
119
+ const pendingResolution = pendingTemplateResolutions.get(definition);
120
+ if (pendingResolution) {
121
+ return pendingResolution;
122
+ }
123
+ const templateResolver = templateResolvers.get(definition);
124
+ if (!templateResolver) {
125
+ return void 0;
126
+ }
127
+ templateResolutionErrors.delete(definition);
128
+ let template;
129
+ try {
130
+ template = templateResolver(definition);
131
+ }
132
+ catch (error) {
133
+ templateResolutionErrors.set(definition, error);
134
+ throw error;
135
+ }
136
+ if (isPromiseLike(template)) {
137
+ const resolution = Promise.resolve(template)
138
+ .then(resolvedTemplate => finalizeResolvedTemplate(definition, resolvedTemplate))
139
+ .catch(error => {
140
+ pendingTemplateResolutions.delete(definition);
141
+ templateResolutionErrors.set(definition, error);
142
+ throw error;
143
+ });
144
+ pendingTemplateResolutions.set(definition, resolution);
145
+ return resolution;
146
+ }
147
+ return finalizeResolvedTemplate(definition, template);
148
+ }
149
+ /**
150
+ * Indicates whether a definition still has a pending template resolver.
151
+ * @internal
152
+ */
153
+ export function hasFASTElementTemplateResolver(definition) {
154
+ return templateResolvers.has(definition);
155
+ }
156
+ /**
157
+ * Gets any pending template resolution error for a FAST element definition.
158
+ * @internal
159
+ */
160
+ export function getFASTElementTemplateError(definition) {
161
+ return templateResolutionErrors.get(definition);
162
+ }
163
+ /**
164
+ * Sets or clears the template resolution error for a FAST element definition.
165
+ * @internal
166
+ */
167
+ export function setFASTElementTemplateError(definition, error) {
168
+ const typedDefinition = definition;
169
+ if (error === void 0) {
170
+ templateResolutionErrors.delete(typedDefinition);
171
+ return;
172
+ }
173
+ templateResolutionErrors.set(typedDefinition, error);
174
+ }
31
175
  /**
32
176
  * Defines metadata for a FASTElement.
33
177
  * @public
@@ -39,17 +183,22 @@ export class FASTElementDefinition {
39
183
  get isDefined() {
40
184
  return this.platformDefined;
41
185
  }
42
- constructor(type, nameOrConfig = type.definition) {
43
- var _b;
186
+ constructor(type, nameOrConfig = type
187
+ .definition) {
188
+ var _a;
44
189
  this.platformDefined = false;
45
190
  if (isString(nameOrConfig)) {
46
191
  nameOrConfig = { name: nameOrConfig };
47
192
  }
48
193
  this.type = type;
49
194
  this.name = nameOrConfig.name;
50
- this.template = nameOrConfig.template;
51
- this.templateOptions = nameOrConfig.templateOptions;
52
- this.registry = (_b = nameOrConfig.registry) !== null && _b !== void 0 ? _b : customElements;
195
+ this.registry = (_a = nameOrConfig.registry) !== null && _a !== void 0 ? _a : customElements;
196
+ if (isFASTElementTemplateResolver(nameOrConfig.template)) {
197
+ templateResolvers.set(this, nameOrConfig.template);
198
+ }
199
+ else {
200
+ this.template = nameOrConfig.template;
201
+ }
53
202
  const proto = type.prototype;
54
203
  const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
55
204
  const observedAttributes = new Array(attributes.length);
@@ -80,23 +229,47 @@ export class FASTElementDefinition {
80
229
  ? defaultElementOptions
81
230
  : Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
82
231
  this.styles = ElementStyles.normalize(nameOrConfig.styles);
232
+ this.schema = nameOrConfig.schema;
83
233
  fastElementRegistry.register(this);
84
- Observable.defineProperty(_a.isRegistered, this.name);
85
- _a.isRegistered[this.name] = this.type;
234
+ const registeredTypes = getRegisteredTypes(this.registry);
235
+ if (!Object.prototype.hasOwnProperty.call(registeredTypes, this.name)) {
236
+ Observable.defineProperty(registeredTypes, this.name);
237
+ }
238
+ registeredTypes[this.name] = this.type;
86
239
  }
87
240
  /**
88
241
  * Defines a custom element based on this definition.
89
242
  * @param registry - The element registry to define the element in.
243
+ * @param extensions - An optional array of extension callbacks to invoke
244
+ * with this definition before platform registration.
90
245
  * @remarks
91
246
  * This operation is idempotent per registry.
92
247
  */
93
- define(registry = this.registry) {
94
- var _b, _c;
248
+ define(registry = this.registry, extensions) {
249
+ var _a, _b;
95
250
  const type = this.type;
96
251
  if (!registry.get(this.name)) {
252
+ applyFASTElementExtensions(this, registry, extensions);
253
+ if (this.template === void 0 && templateResolvers.has(this)) {
254
+ void Promise.resolve()
255
+ .then(() => resolveFASTElementTemplate(this))
256
+ .then(template => {
257
+ var _a, _b;
258
+ if (template !== void 0 && !registry.get(this.name)) {
259
+ this.platformDefined = true;
260
+ registry.define(this.name, type, this.elementOptions);
261
+ (_b = (_a = this.lifecycleCallbacks) === null || _a === void 0 ? void 0 : _a.elementDidDefine) === null || _b === void 0 ? void 0 : _b.call(_a, this.name);
262
+ }
263
+ })
264
+ .catch(error => {
265
+ setFASTElementTemplateError(this, error);
266
+ Observable.notify(this, "template");
267
+ });
268
+ return this;
269
+ }
97
270
  this.platformDefined = true;
98
271
  registry.define(this.name, type, this.elementOptions);
99
- (_c = (_b = this.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.elementDidDefine) === null || _c === void 0 ? void 0 : _c.call(_b, this.name);
272
+ (_b = (_a = this.lifecycleCallbacks) === null || _a === void 0 ? void 0 : _a.elementDidDefine) === null || _b === void 0 ? void 0 : _b.call(_a, this.name);
100
273
  }
101
274
  return this;
102
275
  }
@@ -107,11 +280,11 @@ export class FASTElementDefinition {
107
280
  * that describes the element to define.
108
281
  */
109
282
  static compose(type, nameOrDef) {
110
- if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
111
- return new _a(class extends type {
112
- }, nameOrDef);
113
- }
114
- return new _a(type, nameOrDef);
283
+ const definition = fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)
284
+ ? new FASTElementDefinition(class extends type {
285
+ }, nameOrDef)
286
+ : new FASTElementDefinition(type, nameOrDef);
287
+ return Promise.resolve(definition);
115
288
  }
116
289
  /**
117
290
  * Registers a FASTElement base type.
@@ -121,33 +294,7 @@ export class FASTElementDefinition {
121
294
  static registerBaseType(type) {
122
295
  fastElementBaseTypes.add(type);
123
296
  }
124
- /**
125
- * Creates an instance of FASTElementDefinition asynchronously. This option assumes
126
- * that a template and shadowOptions will be provided and completes when those requirements
127
- * are met.
128
- * @param type - The type this definition is being created for.
129
- * @param nameOrDef - The name of the element to define or a config object
130
- * that describes the element to define.
131
- * @alpha
132
- */
133
- static composeAsync(type, nameOrDef) {
134
- return new Promise(resolve => {
135
- if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
136
- resolve(new _a(class extends type {
137
- }, nameOrDef));
138
- }
139
- const definition = new _a(type, nameOrDef);
140
- Observable.getNotifier(definition).subscribe({
141
- handleChange: () => {
142
- var _b, _c;
143
- (_c = (_b = definition.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.templateDidUpdate) === null || _c === void 0 ? void 0 : _c.call(_b, definition.name);
144
- resolve(definition);
145
- },
146
- }, "template");
147
- });
148
- }
149
297
  }
150
- _a = FASTElementDefinition;
151
298
  /**
152
299
  * The definition has been registered to the FAST element registry.
153
300
  */
@@ -167,12 +314,28 @@ FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
167
314
  * @param name - The name of the defined custom element.
168
315
  * @alpha
169
316
  */
170
- FASTElementDefinition.registerAsync = (name) => __awaiter(void 0, void 0, void 0, function* () {
317
+ FASTElementDefinition.register = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, registry = customElements) {
318
+ const registeredTypes = getRegisteredTypes(registry);
319
+ if (!Object.prototype.hasOwnProperty.call(registeredTypes, name)) {
320
+ Observable.defineProperty(registeredTypes, name);
321
+ }
171
322
  return new Promise(resolve => {
172
- if (_a.isRegistered[name]) {
173
- resolve(_a.isRegistered[name]);
323
+ if (registeredTypes[name]) {
324
+ resolve(registeredTypes[name]);
325
+ return;
174
326
  }
175
- Observable.getNotifier(_a.isRegistered).subscribe({ handleChange: () => resolve(_a.isRegistered[name]) }, name);
327
+ const notifier = Observable.getNotifier(registeredTypes);
328
+ const subscriber = {
329
+ handleChange: () => {
330
+ const value = registeredTypes[name];
331
+ if (!value) {
332
+ return;
333
+ }
334
+ notifier.unsubscribe(subscriber, name);
335
+ resolve(value);
336
+ },
337
+ };
338
+ notifier.subscribe(subscriber, name);
176
339
  });
177
340
  });
178
341
  Observable.defineProperty(FASTElementDefinition.prototype, "template");
@@ -1,6 +1,6 @@
1
1
  import { isFunction } from "../interfaces.js";
2
2
  import { ElementController } from "./element-controller.js";
3
- import { FASTElementDefinition, } from "./fast-definitions.js";
3
+ import { applyFASTElementExtensions, FASTElementDefinition, resolveFASTElementTemplate, } from "./fast-definitions.js";
4
4
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
5
5
  function createFASTElement(BaseType) {
6
6
  const type = class extends BaseType {
@@ -31,29 +31,25 @@ function compose(type, nameOrDef) {
31
31
  }
32
32
  return FASTElementDefinition.compose(this, type);
33
33
  }
34
- function defineAsync(type, nameOrDef) {
35
- if (isFunction(type)) {
36
- return new Promise(resolve => {
37
- FASTElementDefinition.composeAsync(type, nameOrDef).then(value => {
38
- resolve(value);
39
- });
40
- }).then(value => {
41
- return value.define().type;
42
- });
43
- }
44
- return new Promise(resolve => {
45
- FASTElementDefinition.composeAsync(this, type).then(value => {
46
- resolve(value);
47
- });
48
- }).then(value => {
49
- return value.define().type;
50
- });
34
+ function isPromiseLike(value) {
35
+ return typeof (value === null || value === void 0 ? void 0 : value.then) === "function";
51
36
  }
52
- function define(type, nameOrDef) {
53
- if (isFunction(type)) {
54
- return FASTElementDefinition.compose(type, nameOrDef).define().type;
37
+ function define(type, nameOrDef, extensions) {
38
+ if (Array.isArray(nameOrDef)) {
39
+ extensions = nameOrDef;
40
+ nameOrDef = undefined;
55
41
  }
56
- return FASTElementDefinition.compose(this, type).define().type;
42
+ const composePromise = isFunction(type)
43
+ ? FASTElementDefinition.compose(type, nameOrDef)
44
+ : FASTElementDefinition.compose(this, type);
45
+ return composePromise.then(def => {
46
+ applyFASTElementExtensions(def, def.registry, extensions);
47
+ const template = resolveFASTElementTemplate(def);
48
+ if (isPromiseLike(template)) {
49
+ return template.then(() => def.define().type);
50
+ }
51
+ return def.define().type;
52
+ });
57
53
  }
58
54
  function from(BaseType) {
59
55
  return createFASTElement(BaseType);
@@ -82,11 +78,6 @@ export const FASTElement = Object.assign(createFASTElement(HTMLElement), {
82
78
  * @public
83
79
  */
84
80
  compose,
85
- /**
86
- * Defines metadata for a FASTElement which can be used after it has been resolved to define the element.
87
- * @alpha
88
- */
89
- defineAsync,
90
81
  });
91
82
  /**
92
83
  * Decorator: Defines a platform custom element based on `FASTElement`.
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Describes when FAST should stop hydrating newly connected prerendered elements.
3
+ * @public
4
+ */
5
+ export const StopHydration = Object.freeze({
6
+ /**
7
+ * Stop hydrating new prerendered elements after the active hydration batch completes.
8
+ */
9
+ hydrationComplete: "hydration-complete",
10
+ /**
11
+ * Keep the hydration hook active for later prerendered elements.
12
+ */
13
+ never: "never",
14
+ });
15
+ /**
16
+ * Tracks prerendered elements through the hydration lifecycle and
17
+ * fires global callbacks at start and completion. Per-element callbacks
18
+ * (`elementWillHydrate`, `elementDidHydrate`) are handled through
19
+ * definition-level `TemplateLifecycleCallbacks`.
20
+ *
21
+ * @public
22
+ */
23
+ export class HydrationTracker {
24
+ constructor(options) {
25
+ var _a;
26
+ this.elements = new Set();
27
+ this.started = false;
28
+ this.completed = false;
29
+ this.checkTimer = null;
30
+ this.callbacks = options;
31
+ this.stopHydration = (_a = options.stopHydration) !== null && _a !== void 0 ? _a : StopHydration.hydrationComplete;
32
+ }
33
+ /**
34
+ * Indicates whether the hydration hook should attempt to hydrate
35
+ * prerendered elements.
36
+ */
37
+ get shouldHydrate() {
38
+ return !this.completed || this.stopHydration === StopHydration.never;
39
+ }
40
+ /**
41
+ * Registers an element as pending hydration.
42
+ * Fires `hydrationStarted` on the first call.
43
+ */
44
+ add(element) {
45
+ var _a, _b;
46
+ this.completed = false;
47
+ if (!this.started) {
48
+ this.started = true;
49
+ try {
50
+ (_b = (_a = this.callbacks).hydrationStarted) === null || _b === void 0 ? void 0 : _b.call(_a);
51
+ }
52
+ catch (_c) {
53
+ // A lifecycle callback must never prevent hydration.
54
+ }
55
+ }
56
+ this.elements.add(element);
57
+ }
58
+ /**
59
+ * Removes an element from the pending set and schedules
60
+ * a debounced completion check.
61
+ */
62
+ remove(element) {
63
+ this.elements.delete(element);
64
+ // Debounce: reset on every removal so we wait until no
65
+ // new elements arrive before declaring complete.
66
+ if (this.checkTimer !== null) {
67
+ clearTimeout(this.checkTimer);
68
+ }
69
+ if (this.elements.size === 0) {
70
+ this.checkTimer = setTimeout(() => {
71
+ var _a, _b;
72
+ this.checkTimer = null;
73
+ if (this.elements.size === 0) {
74
+ try {
75
+ (_b = (_a = this.callbacks).hydrationComplete) === null || _b === void 0 ? void 0 : _b.call(_a);
76
+ }
77
+ catch (_c) {
78
+ // A lifecycle callback must never prevent post-hydration cleanup.
79
+ }
80
+ finally {
81
+ this.started = false;
82
+ this.completed = true;
83
+ }
84
+ }
85
+ }, 0);
86
+ }
87
+ }
88
+ /**
89
+ * Merges additional options into the tracker, chaining
90
+ * callbacks so both the original and new callbacks fire.
91
+ */
92
+ mergeOptions(incoming) {
93
+ const prev = this.callbacks;
94
+ this.callbacks = {
95
+ hydrationStarted: chainCallback(prev.hydrationStarted, incoming.hydrationStarted),
96
+ hydrationComplete: chainCallback(prev.hydrationComplete, incoming.hydrationComplete),
97
+ };
98
+ if (incoming.stopHydration !== void 0) {
99
+ this.stopHydration = incoming.stopHydration;
100
+ }
101
+ }
102
+ }
103
+ function chainCallback(first, second) {
104
+ if (!first)
105
+ return second;
106
+ if (!second)
107
+ return first;
108
+ return () => {
109
+ try {
110
+ first();
111
+ }
112
+ catch (_a) {
113
+ // Isolate callbacks so one consumer cannot suppress another.
114
+ }
115
+ try {
116
+ second();
117
+ }
118
+ catch (_b) {
119
+ // Isolate callbacks so one consumer cannot suppress another.
120
+ }
121
+ };
122
+ }