@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
@@ -3,13 +3,7 @@ import { PropertyChangeNotifier } from "../observation/notifier.js";
3
3
  import { ExecutionContext, Observable, SourceLifetime, } from "../observation/observable.js";
4
4
  import { FAST, makeSerializationNoop } from "../platform.js";
5
5
  import { ElementStyles } from "../styles/element-styles.js";
6
- import { UnobservableMutationObserver } from "../utilities.js";
7
- import { FASTElementDefinition, TemplateOptions, } from "./fast-definitions.js";
8
- import { deferHydrationAttribute, HydrationMarkup, isHydratable } from "./hydration.js";
9
- /**
10
- * @deprecated Use the export from ./hydration.js instead.
11
- */
12
- export { deferHydrationAttribute } from "./hydration.js";
6
+ import { FASTElementDefinition, getLateAttributeLookup, } from "./fast-definitions.js";
13
7
  const defaultEventOptions = {
14
8
  bubbles: true,
15
9
  composed: true,
@@ -17,6 +11,7 @@ const defaultEventOptions = {
17
11
  };
18
12
  const isConnectedPropertyName = "isConnected";
19
13
  const shadowRoots = new WeakMap();
14
+ const lateAttributeObserver = Symbol("fast-late-attribute-observer");
20
15
  function getShadowRoot(element) {
21
16
  var _a, _b;
22
17
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
@@ -41,7 +36,7 @@ export var Stages;
41
36
  * Controls the lifecycle and rendering of a `FASTElement`.
42
37
  * @public
43
38
  */
44
- export class ElementController extends PropertyChangeNotifier {
39
+ export class ElementController {
45
40
  /**
46
41
  * Indicates whether or not the custom element has been
47
42
  * connected to the document.
@@ -54,14 +49,16 @@ export class ElementController extends PropertyChangeNotifier {
54
49
  * The context the expression is evaluated against.
55
50
  */
56
51
  get context() {
57
- var _a, _b;
52
+ var _a;
53
+ var _b;
58
54
  return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
59
55
  }
60
56
  /**
61
57
  * Indicates whether the controller is bound.
62
58
  */
63
59
  get isBound() {
64
- var _a, _b;
60
+ var _a;
61
+ var _b;
65
62
  return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
66
63
  }
67
64
  /**
@@ -163,7 +160,6 @@ export class ElementController extends PropertyChangeNotifier {
163
160
  * @internal
164
161
  */
165
162
  constructor(element, definition) {
166
- super(element);
167
163
  /**
168
164
  * A map of observable properties that were set on the element before upgrade.
169
165
  */
@@ -176,6 +172,22 @@ export class ElementController extends PropertyChangeNotifier {
176
172
  * Indicates whether the element has an existing shadow root (e.g. from declarative shadow DOM).
177
173
  */
178
174
  this.hasExistingShadowRoot = false;
175
+ /**
176
+ * Resolves `true` when the element had an existing shadow root
177
+ * (from SSR or declarative shadow DOM) at connect time, `false`
178
+ * otherwise.
179
+ */
180
+ this.isPrerendered = new Promise(resolve => {
181
+ this._resolvePrerendered = resolve;
182
+ });
183
+ /**
184
+ * Resolves `true` after prerendered content has been successfully
185
+ * hydrated, or `false` when the component is client-side rendered
186
+ * or hydration is not enabled.
187
+ */
188
+ this.isHydrated = new Promise(resolve => {
189
+ this._resolveHydrated = resolve;
190
+ });
179
191
  /**
180
192
  * The template used to render the component.
181
193
  */
@@ -219,14 +231,12 @@ export class ElementController extends PropertyChangeNotifier {
219
231
  * If `null` then the element is managing its own rendering.
220
232
  */
221
233
  this.view = null;
234
+ this._notifier = new PropertyChangeNotifier(element);
222
235
  this.source = element;
223
236
  this.definition = definition;
224
237
  this.shadowOptions = definition.shadowOptions;
225
- // Capture any observable values that were set by the binding engine before
226
- // the browser upgraded the element. Then delete the property since it will
227
- // shadow the getter/setter that is required to make the observable operate.
228
- // Later, in the connect callback, we'll re-apply the values.
229
- const accessors = Observable.getAccessors(element);
238
+ const prototype = Reflect.getPrototypeOf(element);
239
+ const accessors = prototype === null ? [] : Observable.getAccessors(prototype);
230
240
  if (accessors.length > 0) {
231
241
  const boundObservables = (this.boundObservables = Object.create(null));
232
242
  for (let i = 0, ii = accessors.length; i < ii; ++i) {
@@ -237,7 +247,42 @@ export class ElementController extends PropertyChangeNotifier {
237
247
  boundObservables[propertyName] = value;
238
248
  }
239
249
  }
250
+ if (Object.keys(boundObservables).length === 0) {
251
+ this.boundObservables = null;
252
+ }
240
253
  }
254
+ // Capture any observable values that were set after construction but before
255
+ // the first connect (for example, late-defined declarative accessors).
256
+ this.captureBoundObservables();
257
+ }
258
+ /**
259
+ * The subject that subscribers will receive notifications for.
260
+ */
261
+ get subject() {
262
+ return this._notifier.subject;
263
+ }
264
+ /**
265
+ * Notifies all subscribers of a property change.
266
+ * @param args - The property name that changed.
267
+ */
268
+ notify(args) {
269
+ this._notifier.notify(args);
270
+ }
271
+ /**
272
+ * Subscribes to notification of changes in the element's state.
273
+ * @param subscriber - The object that is subscribing for change notification.
274
+ * @param propertyToWatch - The name of the property to watch for changes.
275
+ */
276
+ subscribe(subscriber, propertyToWatch) {
277
+ this._notifier.subscribe(subscriber, propertyToWatch);
278
+ }
279
+ /**
280
+ * Unsubscribes from notification of changes in the element's state.
281
+ * @param subscriber - The object that is unsubscribing from change notification.
282
+ * @param propertyToUnwatch - The name of the property to unsubscribe from.
283
+ */
284
+ unsubscribe(subscriber, propertyToUnwatch) {
285
+ this._notifier.unsubscribe(subscriber, propertyToUnwatch);
241
286
  }
242
287
  /**
243
288
  * Registers an unbind handler with the controller.
@@ -308,13 +353,7 @@ export class ElementController extends PropertyChangeNotifier {
308
353
  target.append(styles);
309
354
  }
310
355
  else if (!styles.isAttachedTo(source)) {
311
- const sourceBehaviors = styles.behaviors;
312
356
  styles.addStylesTo(source);
313
- if (sourceBehaviors !== null) {
314
- for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
315
- this.addBehavior(sourceBehaviors[i]);
316
- }
317
- }
318
357
  }
319
358
  }
320
359
  /**
@@ -332,13 +371,7 @@ export class ElementController extends PropertyChangeNotifier {
332
371
  target.removeChild(styles);
333
372
  }
334
373
  else if (styles.isAttachedTo(source)) {
335
- const sourceBehaviors = styles.behaviors;
336
374
  styles.removeStylesFrom(source);
337
- if (sourceBehaviors !== null) {
338
- for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
339
- this.removeBehavior(sourceBehaviors[i]);
340
- }
341
- }
342
375
  }
343
376
  }
344
377
  /**
@@ -349,6 +382,9 @@ export class ElementController extends PropertyChangeNotifier {
349
382
  return;
350
383
  }
351
384
  this.stage = Stages.connecting;
385
+ this.captureBoundObservables();
386
+ this.syncLateAttributes();
387
+ this.observeLateAttributes();
352
388
  this.bindObservables();
353
389
  this.connectBehaviors();
354
390
  if (this.needsInitialization) {
@@ -377,6 +413,99 @@ export class ElementController extends PropertyChangeNotifier {
377
413
  this.boundObservables = null;
378
414
  }
379
415
  }
416
+ /**
417
+ * Captures own-properties that shadow observable accessors on the prototype so
418
+ * they can be rebound through the accessor before rendering.
419
+ */
420
+ captureBoundObservables() {
421
+ const element = this.source;
422
+ const propertyNames = Object.getOwnPropertyNames(element);
423
+ const hasPrototypeAccessor = (propertyName) => {
424
+ let currentTarget = Reflect.getPrototypeOf(element);
425
+ while (currentTarget !== null) {
426
+ const descriptor = Reflect.getOwnPropertyDescriptor(currentTarget, propertyName);
427
+ if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.get) || (descriptor === null || descriptor === void 0 ? void 0 : descriptor.set)) {
428
+ return true;
429
+ }
430
+ currentTarget = Reflect.getPrototypeOf(currentTarget);
431
+ }
432
+ return false;
433
+ };
434
+ let boundObservables = this.boundObservables;
435
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
436
+ const ownPropertyName = propertyNames[i];
437
+ const propertyName = (ownPropertyName[0] === "_" ? ownPropertyName.slice(1) : ownPropertyName);
438
+ if (!hasPrototypeAccessor(propertyName)) {
439
+ continue;
440
+ }
441
+ const value = element[propertyName];
442
+ const isBackingField = ownPropertyName !== propertyName;
443
+ const isRebindableObject = value !== null &&
444
+ typeof value === "object" &&
445
+ !(value === null || value === void 0 ? void 0 : value.$isProxy) &&
446
+ !(Array.isArray(value) && (value === null || value === void 0 ? void 0 : value.$fastController));
447
+ if (value === void 0) {
448
+ if (!isBackingField) {
449
+ delete element[ownPropertyName];
450
+ }
451
+ continue;
452
+ }
453
+ if (isBackingField && !isRebindableObject) {
454
+ continue;
455
+ }
456
+ delete element[ownPropertyName];
457
+ (boundObservables !== null && boundObservables !== void 0 ? boundObservables : (boundObservables = this.boundObservables = Object.create(null)))[propertyName] = value;
458
+ }
459
+ }
460
+ /**
461
+ * Synchronizes late-defined attribute-map attributes from the live DOM to the
462
+ * associated property values before the initial render occurs.
463
+ */
464
+ syncLateAttributes() {
465
+ const lateAttributes = getLateAttributeLookup(this.definition);
466
+ if (lateAttributes === null) {
467
+ return;
468
+ }
469
+ for (const attributeName of Object.keys(lateAttributes)) {
470
+ if (!this.source.hasAttribute(attributeName)) {
471
+ continue;
472
+ }
473
+ this.onAttributeChangedCallback(attributeName, null, this.source.getAttribute(attributeName));
474
+ }
475
+ }
476
+ /**
477
+ * Observes late-defined attribute-map attributes that the platform does not
478
+ * surface through attributeChangedCallback because they were added after
479
+ * customElements.define() completed.
480
+ */
481
+ observeLateAttributes() {
482
+ const lateAttributes = getLateAttributeLookup(this.definition);
483
+ if (lateAttributes === null) {
484
+ return;
485
+ }
486
+ const element = this.source;
487
+ if (element[lateAttributeObserver] !== void 0) {
488
+ return;
489
+ }
490
+ element[lateAttributeObserver] = new MutationObserver(records => {
491
+ const controller = element.$fastController;
492
+ const lateAttributes = getLateAttributeLookup(controller.definition);
493
+ if (lateAttributes === null) {
494
+ return;
495
+ }
496
+ for (let i = 0, ii = records.length; i < ii; ++i) {
497
+ const attributeName = records[i].attributeName;
498
+ if (attributeName === null || lateAttributes[attributeName] === void 0) {
499
+ continue;
500
+ }
501
+ controller.onAttributeChangedCallback(attributeName, null, element.getAttribute(attributeName));
502
+ }
503
+ });
504
+ element[lateAttributeObserver].observe(element, {
505
+ attributes: true,
506
+ attributeFilter: Object.keys(lateAttributes),
507
+ });
508
+ }
380
509
  /**
381
510
  * Connects any existing behaviors on the associated element.
382
511
  */
@@ -457,30 +586,51 @@ export class ElementController extends PropertyChangeNotifier {
457
586
  */
458
587
  renderTemplate(template) {
459
588
  var _a;
460
- // When getting the host to render to, we start by looking
461
- // up the shadow root. If there isn't one, then that means
462
- // we're doing a Light DOM render to the element's direct children.
463
589
  const element = this.source;
464
590
  const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
465
591
  if (this.view !== null) {
466
- // If there's already a view, we need to unbind and remove through dispose.
467
592
  this.view.dispose();
468
593
  this.view = null;
469
594
  }
470
595
  else if (!this.needsInitialization || this.hasExistingShadowRoot) {
471
- this.hasExistingShadowRoot = false;
472
- // If there was previous custom rendering, we need to clear out the host.
473
- for (let child = host.firstChild; child !== null; child = host.firstChild) {
474
- host.removeChild(child);
596
+ if (!this.hasExistingShadowRoot || !this.needsInitialization) {
597
+ for (let child = host.firstChild; child !== null; child = host.firstChild) {
598
+ host.removeChild(child);
599
+ }
475
600
  }
476
601
  }
477
602
  if (template) {
478
- // If a new template was provided, render it.
479
- this.view = template.render(element, host, element);
480
- this.view.sourceLifetime =
481
- SourceLifetime.coupled;
603
+ const hasPrerenderedContent = this.hasExistingShadowRoot && this.needsInitialization;
604
+ let didHydrate = false;
605
+ if (hasPrerenderedContent && ElementController.hydrationHook) {
606
+ didHydrate = ElementController.hydrationHook(this, template, element, host);
607
+ }
608
+ if (!didHydrate) {
609
+ this.renderClientSide(template, element, host);
610
+ }
611
+ this._resolvePrerendered(hasPrerenderedContent);
612
+ this._resolveHydrated(didHydrate);
613
+ }
614
+ else if (this.needsInitialization) {
615
+ this._resolvePrerendered(false);
616
+ this._resolveHydrated(false);
482
617
  }
483
618
  }
619
+ /**
620
+ * Standard client-side render: clears any stale content, clones the
621
+ * compiled fragment, binds, and appends to the host.
622
+ */
623
+ renderClientSide(template, element, host) {
624
+ if (this.hasExistingShadowRoot) {
625
+ for (let child = host.firstChild; child !== null; child = host.firstChild) {
626
+ host.removeChild(child);
627
+ }
628
+ this.hasExistingShadowRoot = false;
629
+ }
630
+ this.view = template.render(element, host, element);
631
+ this.view.sourceLifetime =
632
+ SourceLifetime.coupled;
633
+ }
484
634
  /**
485
635
  * Locates or creates a controller for the specified element.
486
636
  * @param element - The element to return the controller for.
@@ -521,7 +671,22 @@ export class ElementController extends PropertyChangeNotifier {
521
671
  static setStrategy(strategy) {
522
672
  elementControllerStrategy = strategy;
523
673
  }
674
+ /**
675
+ * Installs the hydration hook. Called by enableHydration().
676
+ * @internal
677
+ */
678
+ static installHydrationHook(hook) {
679
+ ElementController.hydrationHook = hook;
680
+ }
524
681
  }
682
+ // --- Static hydration hook ---
683
+ /**
684
+ * A hook that, when set, handles prerendered content hydration.
685
+ * Called by renderTemplate when an existing shadow root is detected.
686
+ * Returns true if hydration was performed, false to fall back to client-side.
687
+ * @internal
688
+ */
689
+ ElementController.hydrationHook = null;
525
690
  makeSerializationNoop(ElementController);
526
691
  // Set default strategy for ElementController
527
692
  ElementController.setStrategy(ElementController);
@@ -633,7 +798,7 @@ if (ElementStyles.supportsAdoptedStyleSheets) {
633
798
  }
634
799
  };
635
800
  }
636
- catch (e) {
801
+ catch (_e) {
637
802
  // Do nothing if an error is thrown, the default
638
803
  // case handles FrozenArray.
639
804
  }
@@ -642,232 +807,3 @@ if (ElementStyles.supportsAdoptedStyleSheets) {
642
807
  else {
643
808
  ElementStyles.setDefaultStrategy(StyleElementStrategy);
644
809
  }
645
- /**
646
- * The attribute used to indicate that an element needs hydration.
647
- * @public
648
- */
649
- export const needsHydrationAttribute = "needs-hydration";
650
- /**
651
- * An ElementController capable of hydrating FAST elements from
652
- * Declarative Shadow DOM.
653
- *
654
- * @beta
655
- */
656
- export class HydratableElementController extends ElementController {
657
- /**
658
- * {@inheritdoc ElementController.shadowOptions}
659
- */
660
- get shadowOptions() {
661
- return super.shadowOptions;
662
- }
663
- set shadowOptions(value) {
664
- super.shadowOptions = value;
665
- if ((this.hasExistingShadowRoot || (value !== void 0 && !this.template)) &&
666
- this.definition.templateOptions === TemplateOptions.deferAndHydrate) {
667
- this.source.toggleAttribute(deferHydrationAttribute, true);
668
- this.source.toggleAttribute(needsHydrationAttribute, true);
669
- }
670
- }
671
- /**
672
- * Adds the current element instance to the hydrating instances map
673
- */
674
- addHydratingInstance() {
675
- if (!HydratableElementController.hydratingInstances) {
676
- return;
677
- }
678
- const name = this.definition.name;
679
- let instances = HydratableElementController.hydratingInstances.get(name);
680
- if (!instances) {
681
- instances = new Set();
682
- HydratableElementController.hydratingInstances.set(name, instances);
683
- }
684
- instances.add(this.source);
685
- }
686
- /**
687
- * Configure lifecycle callbacks for hydration events
688
- */
689
- static config(callbacks) {
690
- HydratableElementController.lifecycleCallbacks = callbacks;
691
- return this;
692
- }
693
- static hydrationObserverHandler(records) {
694
- for (const record of records) {
695
- if (!record.target.hasAttribute(deferHydrationAttribute)) {
696
- HydratableElementController.hydrationObserver.unobserve(record.target);
697
- record.target.$fastController.connect();
698
- }
699
- }
700
- }
701
- /**
702
- * Checks to see if hydration is complete and if so, invokes the hydrationComplete callback.
703
- * Then resets the ElementController strategy to the default so that future elements
704
- * don't use the HydratableElementController.
705
- *
706
- * @param deadline - the idle deadline object
707
- */
708
- static checkHydrationComplete(deadline) {
709
- var _a, _b, _c;
710
- if (deadline.didTimeout) {
711
- HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
712
- return;
713
- }
714
- // If there are no more hydrating instances, invoke the hydrationComplete callback
715
- if (((_a = HydratableElementController.hydratingInstances) === null || _a === void 0 ? void 0 : _a.size) === 0) {
716
- try {
717
- (_c = (_b = HydratableElementController.lifecycleCallbacks).hydrationComplete) === null || _c === void 0 ? void 0 : _c.call(_b);
718
- }
719
- catch (_d) {
720
- // A lifecycle callback must never prevent post-hydration cleanup.
721
- }
722
- // Reset to the default strategy after hydration is complete
723
- ElementController.setStrategy(ElementController);
724
- }
725
- }
726
- /**
727
- * Runs connected lifecycle behavior on the associated element.
728
- */
729
- connect() {
730
- var _a, _b, _c, _d, _e, _f, _g;
731
- // Initialize needsHydration on first connect
732
- this.needsHydration =
733
- (_a = this.needsHydration) !== null && _a !== void 0 ? _a : this.source.hasAttribute(needsHydrationAttribute);
734
- if (this.needsHydration) {
735
- this.addHydratingInstance();
736
- }
737
- // If the `defer-hydration` attribute exists on the source,
738
- // wait for it to be removed before continuing connection behavior.
739
- if (this.source.hasAttribute(deferHydrationAttribute)) {
740
- this.addHydratingInstance();
741
- HydratableElementController.hydrationObserver.observe(this.source, {
742
- attributeFilter: [deferHydrationAttribute],
743
- });
744
- return;
745
- }
746
- // If the controller does not need to be hydrated, defer connection behavior
747
- // to the base-class. This case handles element re-connection and initial connection
748
- // of elements that did not get declarative shadow-dom emitted, as well as if an extending
749
- // class
750
- if (!this.needsHydration) {
751
- super.connect();
752
- this.removeHydratingInstance();
753
- return;
754
- }
755
- if (this.stage !== Stages.disconnected) {
756
- return;
757
- }
758
- if (!HydratableElementController.hydrationStarted) {
759
- HydratableElementController.hydrationStarted = true;
760
- try {
761
- (_c = (_b = HydratableElementController.lifecycleCallbacks).hydrationStarted) === null || _c === void 0 ? void 0 : _c.call(_b);
762
- }
763
- catch (_h) {
764
- // A lifecycle callback must never prevent hydration.
765
- }
766
- }
767
- try {
768
- (_e = (_d = HydratableElementController.lifecycleCallbacks).elementWillHydrate) === null || _e === void 0 ? void 0 : _e.call(_d, this.source);
769
- }
770
- catch (_j) {
771
- // A lifecycle callback must never prevent hydration.
772
- }
773
- this.stage = Stages.connecting;
774
- this.bindObservables();
775
- this.connectBehaviors();
776
- if (this.template) {
777
- if (isHydratable(this.template)) {
778
- const element = this.source;
779
- const host = (_f = getShadowRoot(element)) !== null && _f !== void 0 ? _f : element;
780
- let firstChild = host.firstChild;
781
- let lastChild = host.lastChild;
782
- if (element.shadowRoot === null) {
783
- // handle element boundary markers when shadowRoot is not present
784
- if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
785
- firstChild.data = "";
786
- firstChild = firstChild.nextSibling;
787
- }
788
- if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
789
- lastChild.data = "";
790
- lastChild = lastChild.previousSibling;
791
- }
792
- }
793
- this.view = this.template.hydrate(firstChild, lastChild, element);
794
- (_g = this.view) === null || _g === void 0 ? void 0 : _g.bind(this.source);
795
- }
796
- else {
797
- this.renderTemplate(this.template);
798
- }
799
- }
800
- this.addStyles(this.mainStyles);
801
- this.stage = Stages.connected;
802
- this.source.removeAttribute(needsHydrationAttribute);
803
- this.needsInitialization = this.needsHydration = false;
804
- this.removeHydratingInstance();
805
- Observable.notify(this, isConnectedPropertyName);
806
- }
807
- /**
808
- * Removes the current element instance from the hydrating instances map
809
- */
810
- removeHydratingInstance() {
811
- var _a, _b;
812
- if (!HydratableElementController.hydratingInstances) {
813
- return;
814
- }
815
- try {
816
- (_b = (_a = HydratableElementController.lifecycleCallbacks).elementDidHydrate) === null || _b === void 0 ? void 0 : _b.call(_a, this.source);
817
- }
818
- catch (_c) {
819
- // A lifecycle callback must never prevent hydration.
820
- }
821
- const name = this.definition.name;
822
- const instances = HydratableElementController.hydratingInstances.get(name);
823
- if (instances) {
824
- instances.delete(this.source);
825
- if (!instances.size) {
826
- HydratableElementController.hydratingInstances.delete(name);
827
- }
828
- if (HydratableElementController.idleCallbackId) {
829
- cancelIdleCallback(HydratableElementController.idleCallbackId);
830
- }
831
- HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
832
- }
833
- }
834
- /**
835
- * Unregisters the hydration observer when the element is disconnected.
836
- */
837
- disconnect() {
838
- super.disconnect();
839
- HydratableElementController.hydrationObserver.unobserve(this.source);
840
- }
841
- /**
842
- * Sets the ElementController strategy to HydratableElementController.
843
- * @remarks
844
- * This method is typically called during application startup to enable
845
- * hydration support for FAST elements.
846
- */
847
- static install() {
848
- ElementController.setStrategy(HydratableElementController);
849
- }
850
- }
851
- HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
852
- /**
853
- * Lifecycle callbacks for hydration events
854
- */
855
- HydratableElementController.lifecycleCallbacks = {};
856
- /**
857
- * Whether the hydrationStarted callback has already been invoked.
858
- */
859
- HydratableElementController.hydrationStarted = false;
860
- /**
861
- * An idle callback ID used to track hydration completion
862
- */
863
- HydratableElementController.idleCallbackId = null;
864
- /**
865
- * A map of element instances by the name of the custom element they are
866
- * associated with. The key is the custom element name, and the value is the
867
- * instances of hydratable elements which currently need to be hydrated.
868
- *
869
- * When all of the instances in the set have been hydrated, the set is
870
- * cleared and removed from the map. If the map is empty, the
871
- * hydrationComplete callback is invoked.
872
- */
873
- HydratableElementController.hydratingInstances = new Map();
@@ -1,2 +1,2 @@
1
- export { HydratableElementController } from "./element-controller.js";
1
+ export { ElementController } from "./element-controller.js";
2
2
  export * from "./hydration.js";