@microsoft/fast-element 2.7.0 → 2.8.1

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/CHANGELOG.json CHANGED
@@ -2,7 +2,37 @@
2
2
  "name": "@microsoft/fast-element",
3
3
  "entries": [
4
4
  {
5
- "date": "Wed, 20 Aug 2025 20:56:28 GMT",
5
+ "date": "Tue, 21 Oct 2025 16:17:03 GMT",
6
+ "version": "2.8.1",
7
+ "tag": "@microsoft/fast-element_v2.8.1",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "author": "863023+radium-v@users.noreply.github.com",
12
+ "package": "@microsoft/fast-element",
13
+ "commit": "d66ae6571c97341890e5320048fc1a3ef38dc5be",
14
+ "comment": "feat: add methods to track hydrating instances"
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Mon, 13 Oct 2025 00:37:08 GMT",
21
+ "version": "2.8.0",
22
+ "tag": "@microsoft/fast-element_v2.8.0",
23
+ "comments": {
24
+ "minor": [
25
+ {
26
+ "author": "863023+radium-v@users.noreply.github.com",
27
+ "package": "@microsoft/fast-element",
28
+ "commit": "28a1d5dcae41f04e2ff8cb9114c312efb88fb1e1",
29
+ "comment": "[feat]: implement lifecycle callbacks for hydration and template events"
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ {
35
+ "date": "Wed, 20 Aug 2025 20:57:02 GMT",
6
36
  "version": "2.7.0",
7
37
  "tag": "@microsoft/fast-element_v2.7.0",
8
38
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,28 @@
1
1
  # Change Log - @microsoft/fast-element
2
2
 
3
- <!-- This log was last generated on Wed, 20 Aug 2025 20:56:28 GMT and should not be manually modified. -->
3
+ <!-- This log was last generated on Tue, 21 Oct 2025 16:17:03 GMT and should not be manually modified. -->
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 2.8.1
8
+
9
+ Tue, 21 Oct 2025 16:17:03 GMT
10
+
11
+ ### Patches
12
+
13
+ - feat: add methods to track hydrating instances (863023+radium-v@users.noreply.github.com)
14
+
15
+ ## 2.8.0
16
+
17
+ Mon, 13 Oct 2025 00:37:08 GMT
18
+
19
+ ### Minor changes
20
+
21
+ - [feat]: implement lifecycle callbacks for hydration and template events (863023+radium-v@users.noreply.github.com)
22
+
7
23
  ## 2.7.0
8
24
 
9
- Wed, 20 Aug 2025 20:56:28 GMT
25
+ Wed, 20 Aug 2025 20:57:02 GMT
10
26
 
11
27
  ### Minor changes
12
28
 
@@ -206,6 +206,24 @@ export declare class StyleElementStrategy implements StyleStrategy {
206
206
  }
207
207
  export declare const deferHydrationAttribute = "defer-hydration";
208
208
  export declare const needsHydrationAttribute = "needs-hydration";
209
+ /**
210
+ * Lifecycle callbacks for element hydration events
211
+ * @public
212
+ */
213
+ export interface HydrationControllerCallbacks {
214
+ /**
215
+ * Called before hydration has started
216
+ */
217
+ elementWillHydrate?(name: string): void;
218
+ /**
219
+ * Called after hydration has finished
220
+ */
221
+ elementDidHydrate?(name: string): void;
222
+ /**
223
+ * Called after all elements have completed hydration
224
+ */
225
+ hydrationComplete?(): void;
226
+ }
209
227
  /**
210
228
  * An ElementController capable of hydrating FAST elements from
211
229
  * Declarative Shadow DOM.
@@ -220,9 +238,47 @@ export declare class HydratableElementController<TElement extends HTMLElement =
220
238
  */
221
239
  protected needsHydration?: boolean;
222
240
  private static hydrationObserver;
241
+ /**
242
+ * Lifecycle callbacks for hydration events
243
+ */
244
+ static lifecycleCallbacks?: HydrationControllerCallbacks;
245
+ /**
246
+ * An idle callback ID used to track hydration completion
247
+ */
248
+ private static idleCallbackId;
249
+ /**
250
+ * Adds the current element instance to the hydrating instances map
251
+ */
252
+ private addHydratingInstance;
253
+ /**
254
+ * Configure lifecycle callbacks for hydration events
255
+ */
256
+ static config(callbacks: HydrationControllerCallbacks): typeof HydratableElementController;
223
257
  private static hydrationObserverHandler;
258
+ /**
259
+ * Checks to see if hydration is complete and if so, invokes the hydrationComplete callback.
260
+ * Then resets the ElementController strategy to the default so that future elements
261
+ * don't use the HydratableElementController.
262
+ *
263
+ * @param deadline - the idle deadline object
264
+ */
265
+ private static checkHydrationComplete;
224
266
  static forCustomElement(element: HTMLElement, override?: boolean): ElementController<HTMLElement>;
225
267
  connect(): void;
268
+ /**
269
+ * A map of element instances by the name of the custom element they are
270
+ * associated with. The key is the custom element name, and the value is the
271
+ * instances of hydratable elements which currently need to be hydrated.
272
+ *
273
+ * When all of the instances in the set have been hydrated, the set is
274
+ * cleared and removed from the map. If the map is empty, the
275
+ * hydrationComplete callback is invoked.
276
+ */
277
+ private static hydratingInstances?;
278
+ /**
279
+ * Removes the current element instance from the hydrating instances map
280
+ */
281
+ private removeHydratingInstance;
226
282
  disconnect(): void;
227
283
  static install(): void;
228
284
  }
@@ -22,10 +22,31 @@ export interface ShadowRootOptions extends ShadowRootInit {
22
22
  registry?: CustomElementRegistry;
23
23
  }
24
24
  /**
25
- * Template options.
25
+ * Values for the `templateOptions` property.
26
26
  * @alpha
27
27
  */
28
- export declare type TemplateOptions = "defer-and-hydrate";
28
+ export declare const TemplateOptions: {
29
+ readonly deferAndHydrate: "defer-and-hydrate";
30
+ };
31
+ /**
32
+ * Type for the `TemplateOptions` const enum.
33
+ * @alpha
34
+ */
35
+ export declare type TemplateOptions = (typeof TemplateOptions)[keyof typeof TemplateOptions];
36
+ /**
37
+ * Lifecycle callbacks for template events.
38
+ * @public
39
+ */
40
+ export interface TemplateLifecycleCallbacks {
41
+ /**
42
+ * Called after the template has been assigned to the definition
43
+ */
44
+ templateDidUpdate?(name: string): void;
45
+ /**
46
+ * Called after the custom element has been defined
47
+ */
48
+ elementDidDefine?(name: string): void;
49
+ }
29
50
  /**
30
51
  * Represents metadata configuration for a custom element.
31
52
  * @public
@@ -69,6 +90,10 @@ export interface PartialFASTElementDefinition {
69
90
  * If not provided, defaults to the global registry.
70
91
  */
71
92
  readonly registry?: CustomElementRegistry;
93
+ /**
94
+ * Lifecycle callbacks for template events.
95
+ */
96
+ readonly lifecycleCallbacks?: TemplateLifecycleCallbacks;
72
97
  }
73
98
  /**
74
99
  * Defines metadata for a FASTElement.
@@ -125,6 +150,10 @@ export declare class FASTElementDefinition<TType extends Constructable<HTMLEleme
125
150
  * The registry to register this component in by default.
126
151
  */
127
152
  readonly registry: CustomElementRegistry;
153
+ /**
154
+ * Lifecycle callbacks for template events.
155
+ */
156
+ readonly lifecycleCallbacks?: TemplateLifecycleCallbacks;
128
157
  /**
129
158
  * The definition has been registered to the FAST element registry.
130
159
  */
@@ -29,6 +29,6 @@ export { ElementView, HTMLView, SyntheticView, View, HydratableView, HydrationBi
29
29
  export { elements, ElementsFilter, NodeBehaviorOptions, NodeObservationDirective, } from "./templating/node-observation.js";
30
30
  export { render, RenderBehavior, RenderDirective } from "./templating/render.js";
31
31
  export { customElement, FASTElement } from "./components/fast-element.js";
32
- export { FASTElementDefinition, PartialFASTElementDefinition, ShadowRootOptions, fastElementRegistry, TemplateOptions, TypeRegistry, } from "./components/fast-definitions.js";
32
+ export { FASTElementDefinition, PartialFASTElementDefinition, ShadowRootOptions, fastElementRegistry, TemplateOptions, TypeRegistry, type TemplateLifecycleCallbacks, } from "./components/fast-definitions.js";
33
33
  export { attr, AttributeConfiguration, AttributeDefinition, AttributeMode, booleanConverter, DecoratorAttributeConfiguration, nullableBooleanConverter, nullableNumberConverter, ValueConverter, } from "./components/attributes.js";
34
- export { ElementController, ElementControllerStrategy, HydratableElementController, } from "./components/element-controller.js";
34
+ export { ElementController, ElementControllerStrategy, HydratableElementController, type HydrationControllerCallbacks, } from "./components/element-controller.js";
@@ -4,7 +4,7 @@ import { ExecutionContext, Observable, SourceLifetime, } from "../observation/ob
4
4
  import { FAST, makeSerializationNoop } from "../platform.js";
5
5
  import { ElementStyles } from "../styles/element-styles.js";
6
6
  import { UnobservableMutationObserver } from "../utilities.js";
7
- import { FASTElementDefinition } from "./fast-definitions.js";
7
+ import { FASTElementDefinition, TemplateOptions, } from "./fast-definitions.js";
8
8
  import { HydrationMarkup, isHydratable } from "./hydration.js";
9
9
  const defaultEventOptions = {
10
10
  bubbles: true,
@@ -591,32 +591,77 @@ export const needsHydrationAttribute = "needs-hydration";
591
591
  * @beta
592
592
  */
593
593
  export class HydratableElementController extends ElementController {
594
+ /**
595
+ * Adds the current element instance to the hydrating instances map
596
+ */
597
+ addHydratingInstance() {
598
+ if (!HydratableElementController.hydratingInstances) {
599
+ return;
600
+ }
601
+ const name = this.definition.name;
602
+ let instances = HydratableElementController.hydratingInstances.get(name);
603
+ if (!instances) {
604
+ instances = new Set();
605
+ HydratableElementController.hydratingInstances.set(name, instances);
606
+ }
607
+ instances.add(this.source);
608
+ }
609
+ /**
610
+ * Configure lifecycle callbacks for hydration events
611
+ */
612
+ static config(callbacks) {
613
+ HydratableElementController.lifecycleCallbacks = callbacks;
614
+ return this;
615
+ }
594
616
  static hydrationObserverHandler(records) {
595
617
  for (const record of records) {
596
- HydratableElementController.hydrationObserver.unobserve(record.target);
597
- record.target.$fastController.connect();
618
+ if (!record.target.hasAttribute(deferHydrationAttribute)) {
619
+ HydratableElementController.hydrationObserver.unobserve(record.target);
620
+ record.target.$fastController.connect();
621
+ }
622
+ }
623
+ }
624
+ /**
625
+ * Checks to see if hydration is complete and if so, invokes the hydrationComplete callback.
626
+ * Then resets the ElementController strategy to the default so that future elements
627
+ * don't use the HydratableElementController.
628
+ *
629
+ * @param deadline - the idle deadline object
630
+ */
631
+ static checkHydrationComplete(deadline) {
632
+ var _a, _b, _c;
633
+ if (deadline.didTimeout) {
634
+ HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
635
+ return;
636
+ }
637
+ // If there are no more hydrating instances, invoke the hydrationComplete callback
638
+ if (((_a = HydratableElementController.hydratingInstances) === null || _a === void 0 ? void 0 : _a.size) === 0) {
639
+ (_c = (_b = HydratableElementController.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.hydrationComplete) === null || _c === void 0 ? void 0 : _c.call(_b);
640
+ // Reset to the default strategy after hydration is complete
641
+ ElementController.setStrategy(ElementController);
598
642
  }
599
643
  }
600
644
  static forCustomElement(element, override) {
601
645
  const definition = FASTElementDefinition.getForInstance(element);
602
- if (definition !== undefined &&
603
- definition.templateOptions === "defer-and-hydrate" &&
646
+ if ((definition === null || definition === void 0 ? void 0 : definition.templateOptions) === TemplateOptions.deferAndHydrate &&
604
647
  !definition.template) {
605
- element.setAttribute(deferHydrationAttribute, "");
606
- element.setAttribute(needsHydrationAttribute, "");
648
+ element.toggleAttribute(deferHydrationAttribute, true);
649
+ element.toggleAttribute(needsHydrationAttribute, true);
607
650
  }
608
651
  return super.forCustomElement(element, override);
609
652
  }
610
653
  connect() {
611
- var _a, _b;
654
+ var _a, _b, _c, _d, _e;
612
655
  // Initialize needsHydration on first connect
613
- if (this.needsHydration === undefined) {
614
- this.needsHydration =
615
- this.source.getAttribute(needsHydrationAttribute) !== null;
656
+ this.needsHydration =
657
+ (_a = this.needsHydration) !== null && _a !== void 0 ? _a : this.source.getAttribute(needsHydrationAttribute) !== null;
658
+ if (this.needsHydration) {
659
+ (_c = (_b = HydratableElementController.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.elementWillHydrate) === null || _c === void 0 ? void 0 : _c.call(_b, this.definition.name);
616
660
  }
617
661
  // If the `defer-hydration` attribute exists on the source,
618
662
  // wait for it to be removed before continuing connection behavior.
619
663
  if (this.source.hasAttribute(deferHydrationAttribute)) {
664
+ this.addHydratingInstance();
620
665
  HydratableElementController.hydrationObserver.observe(this.source, {
621
666
  attributeFilter: [deferHydrationAttribute],
622
667
  });
@@ -628,6 +673,7 @@ export class HydratableElementController extends ElementController {
628
673
  // class
629
674
  if (!this.needsHydration) {
630
675
  super.connect();
676
+ this.removeHydratingInstance();
631
677
  return;
632
678
  }
633
679
  if (this.stage !== 3 /* Stages.disconnected */) {
@@ -636,10 +682,10 @@ export class HydratableElementController extends ElementController {
636
682
  this.stage = 0 /* Stages.connecting */;
637
683
  this.bindObservables();
638
684
  this.connectBehaviors();
639
- const element = this.source;
640
- const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
641
685
  if (this.template) {
642
686
  if (isHydratable(this.template)) {
687
+ const element = this.source;
688
+ const host = (_d = getShadowRoot(element)) !== null && _d !== void 0 ? _d : element;
643
689
  let firstChild = host.firstChild;
644
690
  let lastChild = host.lastChild;
645
691
  if (element.shadowRoot === null) {
@@ -654,7 +700,7 @@ export class HydratableElementController extends ElementController {
654
700
  }
655
701
  }
656
702
  this.view = this.template.hydrate(firstChild, lastChild, element);
657
- (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
703
+ (_e = this.view) === null || _e === void 0 ? void 0 : _e.bind(this.source);
658
704
  }
659
705
  else {
660
706
  this.renderTemplate(this.template);
@@ -664,8 +710,32 @@ export class HydratableElementController extends ElementController {
664
710
  this.stage = 1 /* Stages.connected */;
665
711
  this.source.removeAttribute(needsHydrationAttribute);
666
712
  this.needsInitialization = this.needsHydration = false;
713
+ this.removeHydratingInstance();
667
714
  Observable.notify(this, isConnectedPropertyName);
668
715
  }
716
+ /**
717
+ * Removes the current element instance from the hydrating instances map
718
+ */
719
+ removeHydratingInstance() {
720
+ var _a, _b;
721
+ if (!HydratableElementController.hydratingInstances) {
722
+ return;
723
+ }
724
+ const name = this.definition.name;
725
+ const instances = HydratableElementController.hydratingInstances.get(name);
726
+ // Callback: After hydration has finished
727
+ (_b = (_a = HydratableElementController.lifecycleCallbacks) === null || _a === void 0 ? void 0 : _a.elementDidHydrate) === null || _b === void 0 ? void 0 : _b.call(_a, this.definition.name);
728
+ if (instances) {
729
+ instances.delete(this.source);
730
+ if (!instances.size) {
731
+ HydratableElementController.hydratingInstances.delete(name);
732
+ }
733
+ if (HydratableElementController.idleCallbackId) {
734
+ cancelIdleCallback(HydratableElementController.idleCallbackId);
735
+ }
736
+ HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
737
+ }
738
+ }
669
739
  disconnect() {
670
740
  super.disconnect();
671
741
  HydratableElementController.hydrationObserver.unobserve(this.source);
@@ -675,3 +745,17 @@ export class HydratableElementController extends ElementController {
675
745
  }
676
746
  }
677
747
  HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
748
+ /**
749
+ * An idle callback ID used to track hydration completion
750
+ */
751
+ HydratableElementController.idleCallbackId = null;
752
+ /**
753
+ * A map of element instances by the name of the custom element they are
754
+ * associated with. The key is the custom element name, and the value is the
755
+ * instances of hydratable elements which currently need to be hydrated.
756
+ *
757
+ * When all of the instances in the set have been hydrated, the set is
758
+ * cleared and removed from the map. If the map is empty, the
759
+ * hydrationComplete callback is invoked.
760
+ */
761
+ HydratableElementController.hydratingInstances = new Map();
@@ -21,6 +21,13 @@ const fastElementBaseTypes = new Set();
21
21
  * @internal
22
22
  */
23
23
  export const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
24
+ /**
25
+ * Values for the `templateOptions` property.
26
+ * @alpha
27
+ */
28
+ export const TemplateOptions = {
29
+ deferAndHydrate: "defer-and-hydrate",
30
+ };
24
31
  /**
25
32
  * Defines metadata for a FASTElement.
26
33
  * @public
@@ -84,10 +91,12 @@ export class FASTElementDefinition {
84
91
  * This operation is idempotent per registry.
85
92
  */
86
93
  define(registry = this.registry) {
94
+ var _b, _c;
87
95
  const type = this.type;
88
96
  if (!registry.get(this.name)) {
89
97
  this.platformDefined = true;
90
98
  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);
91
100
  }
92
101
  return this;
93
102
  }
@@ -128,15 +137,13 @@ export class FASTElementDefinition {
128
137
  }, nameOrDef));
129
138
  }
130
139
  const definition = new FASTElementDefinition(type, nameOrDef);
131
- Promise.all([
132
- new Promise(resolve => {
133
- Observable.getNotifier(definition).subscribe({
134
- handleChange: () => resolve(),
135
- }, "template");
136
- }),
137
- ]).then(() => {
138
- resolve(definition);
139
- });
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");
140
147
  });
141
148
  }
142
149
  }
@@ -165,9 +172,7 @@ FASTElementDefinition.registerAsync = (name) => __awaiter(void 0, void 0, void 0
165
172
  if (FASTElementDefinition.isRegistered[name]) {
166
173
  resolve(FASTElementDefinition.isRegistered[name]);
167
174
  }
168
- Observable.getNotifier(FASTElementDefinition.isRegistered).subscribe({
169
- handleChange: () => resolve(FASTElementDefinition.isRegistered[name]),
170
- }, name);
175
+ Observable.getNotifier(FASTElementDefinition.isRegistered).subscribe({ handleChange: () => resolve(FASTElementDefinition.isRegistered[name]) }, name);
171
176
  });
172
177
  });
173
178
  Observable.defineProperty(FASTElementDefinition.prototype, "template");
package/dist/esm/index.js CHANGED
@@ -34,6 +34,6 @@ export { elements, NodeObservationDirective, } from "./templating/node-observati
34
34
  export { render, RenderBehavior, RenderDirective } from "./templating/render.js";
35
35
  // Components
36
36
  export { customElement, FASTElement } from "./components/fast-element.js";
37
- export { FASTElementDefinition, fastElementRegistry, } from "./components/fast-definitions.js";
37
+ export { FASTElementDefinition, fastElementRegistry, TemplateOptions, } from "./components/fast-definitions.js";
38
38
  export { attr, AttributeConfiguration, AttributeDefinition, booleanConverter, nullableBooleanConverter, nullableNumberConverter, } from "./components/attributes.js";
39
39
  export { ElementController, HydratableElementController, } from "./components/element-controller.js";
@@ -25,3 +25,36 @@
25
25
  result.globalThis = result;
26
26
  }
27
27
  })();
28
+ (function requestIdleCallbackPolyfill() {
29
+ if ("requestIdleCallback" in globalThis) {
30
+ return;
31
+ }
32
+ /**
33
+ * A polyfill for requestIdleCallback that falls back to setTimeout.
34
+ *
35
+ * @param callback - The function to call when the browser is idle.
36
+ * @param options - Options object that may contain a timeout property.
37
+ * @returns An ID that can be used to cancel the callback.
38
+ * @public
39
+ */
40
+ globalThis.requestIdleCallback = function requestIdleCallback(callback, options) {
41
+ const start = Date.now();
42
+ return setTimeout(() => {
43
+ callback({
44
+ didTimeout: (options === null || options === void 0 ? void 0 : options.timeout)
45
+ ? Date.now() - start >= options.timeout
46
+ : false,
47
+ timeRemaining: () => 0,
48
+ });
49
+ }, 1);
50
+ };
51
+ /**
52
+ * A polyfill for cancelIdleCallback that falls back to clearTimeout.
53
+ *
54
+ * @param id - The ID of the callback to cancel.
55
+ * @public
56
+ */
57
+ globalThis.cancelIdleCallback = function cancelIdleCallback(id) {
58
+ clearTimeout(id);
59
+ };
60
+ })();
@@ -360,7 +360,7 @@ export class HydrationView extends DefaultExecutionContext {
360
360
  fragment.appendChild(end);
361
361
  }
362
362
  bind(source, context = this) {
363
- var _b, _c;
363
+ var _b;
364
364
  if (this.hydrationStage !== HydrationStage.hydrated) {
365
365
  this._hydrationStage = HydrationStage.hydrating;
366
366
  }
@@ -404,7 +404,28 @@ export class HydrationView extends DefaultExecutionContext {
404
404
  if (typeof templateString !== "string") {
405
405
  templateString = templateString.innerHTML;
406
406
  }
407
- throw new HydrationBindingError(`HydrationView was unable to successfully target bindings inside "${(_c = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode()).host) === null || _c === void 0 ? void 0 : _c.nodeName}".`, factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
407
+ const hostElement = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode())
408
+ .host;
409
+ const hostName = (hostElement === null || hostElement === void 0 ? void 0 : hostElement.nodeName) || "unknown";
410
+ const factoryInfo = factory;
411
+ // Build detailed error message
412
+ const details = [
413
+ `HydrationView was unable to successfully target bindings inside "<${hostName.toLowerCase()}>".`,
414
+ `\nMismatch Details:`,
415
+ ` - Expected target node ID: "${factory.targetNodeId}"`,
416
+ ` - Available target IDs: [${Object.keys(this.targets).join(", ") || "none"}]`,
417
+ ];
418
+ if (factory.targetTagName) {
419
+ details.push(` - Expected tag name: "${factory.targetTagName}"`);
420
+ }
421
+ if (factoryInfo.sourceAspect) {
422
+ details.push(` - Source aspect: "${factoryInfo.sourceAspect}"`);
423
+ }
424
+ if (factoryInfo.aspectType !== undefined) {
425
+ details.push(` - Aspect type: ${factoryInfo.aspectType}`);
426
+ }
427
+ details.push(`\nThis usually means:`, ` 1. The server-rendered HTML doesn't match the client template`, ` 2. The hydration markers are missing or corrupted`, ` 3. The DOM structure was modified before hydration`, `\nTemplate: ${templateString.slice(0, 200)}${templateString.length > 200 ? "..." : ""}`);
428
+ throw new HydrationBindingError(details.join("\n"), factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
408
429
  }
409
430
  }
410
431
  }