@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.
@@ -152,6 +152,39 @@ const noop = () => void 0;
152
152
  result.globalThis = result;
153
153
  }
154
154
  })();
155
+ (function requestIdleCallbackPolyfill() {
156
+ if ("requestIdleCallback" in globalThis) {
157
+ return;
158
+ }
159
+ /**
160
+ * A polyfill for requestIdleCallback that falls back to setTimeout.
161
+ *
162
+ * @param callback - The function to call when the browser is idle.
163
+ * @param options - Options object that may contain a timeout property.
164
+ * @returns An ID that can be used to cancel the callback.
165
+ * @public
166
+ */
167
+ globalThis.requestIdleCallback = function requestIdleCallback(callback, options) {
168
+ const start = Date.now();
169
+ return setTimeout(() => {
170
+ callback({
171
+ didTimeout: (options === null || options === void 0 ? void 0 : options.timeout)
172
+ ? Date.now() - start >= options.timeout
173
+ : false,
174
+ timeRemaining: () => 0,
175
+ });
176
+ }, 1);
177
+ };
178
+ /**
179
+ * A polyfill for cancelIdleCallback that falls back to clearTimeout.
180
+ *
181
+ * @param id - The ID of the callback to cancel.
182
+ * @public
183
+ */
184
+ globalThis.cancelIdleCallback = function cancelIdleCallback(id) {
185
+ clearTimeout(id);
186
+ };
187
+ })();
155
188
 
156
189
  // ensure FAST global - duplicated debug.ts
157
190
  const propConfig = {
@@ -3128,7 +3161,7 @@ class HydrationView extends DefaultExecutionContext {
3128
3161
  fragment.appendChild(end);
3129
3162
  }
3130
3163
  bind(source, context = this) {
3131
- var _b, _c;
3164
+ var _b;
3132
3165
  if (this.hydrationStage !== HydrationStage.hydrated) {
3133
3166
  this._hydrationStage = HydrationStage.hydrating;
3134
3167
  }
@@ -3172,7 +3205,28 @@ class HydrationView extends DefaultExecutionContext {
3172
3205
  if (typeof templateString !== "string") {
3173
3206
  templateString = templateString.innerHTML;
3174
3207
  }
3175
- 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);
3208
+ const hostElement = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode())
3209
+ .host;
3210
+ const hostName = (hostElement === null || hostElement === void 0 ? void 0 : hostElement.nodeName) || "unknown";
3211
+ const factoryInfo = factory;
3212
+ // Build detailed error message
3213
+ const details = [
3214
+ `HydrationView was unable to successfully target bindings inside "<${hostName.toLowerCase()}>".`,
3215
+ `\nMismatch Details:`,
3216
+ ` - Expected target node ID: "${factory.targetNodeId}"`,
3217
+ ` - Available target IDs: [${Object.keys(this.targets).join(", ") || "none"}]`,
3218
+ ];
3219
+ if (factory.targetTagName) {
3220
+ details.push(` - Expected tag name: "${factory.targetTagName}"`);
3221
+ }
3222
+ if (factoryInfo.sourceAspect) {
3223
+ details.push(` - Source aspect: "${factoryInfo.sourceAspect}"`);
3224
+ }
3225
+ if (factoryInfo.aspectType !== undefined) {
3226
+ details.push(` - Aspect type: ${factoryInfo.aspectType}`);
3227
+ }
3228
+ 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 ? "..." : ""}`);
3229
+ throw new HydrationBindingError(details.join("\n"), factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
3176
3230
  }
3177
3231
  }
3178
3232
  }
@@ -4756,6 +4810,13 @@ const fastElementBaseTypes = new Set();
4756
4810
  * @internal
4757
4811
  */
4758
4812
  const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
4813
+ /**
4814
+ * Values for the `templateOptions` property.
4815
+ * @alpha
4816
+ */
4817
+ const TemplateOptions = {
4818
+ deferAndHydrate: "defer-and-hydrate",
4819
+ };
4759
4820
  /**
4760
4821
  * Defines metadata for a FASTElement.
4761
4822
  * @public
@@ -4819,10 +4880,12 @@ class FASTElementDefinition {
4819
4880
  * This operation is idempotent per registry.
4820
4881
  */
4821
4882
  define(registry = this.registry) {
4883
+ var _b, _c;
4822
4884
  const type = this.type;
4823
4885
  if (!registry.get(this.name)) {
4824
4886
  this.platformDefined = true;
4825
4887
  registry.define(this.name, type, this.elementOptions);
4888
+ (_c = (_b = this.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.elementDidDefine) === null || _c === void 0 ? void 0 : _c.call(_b, this.name);
4826
4889
  }
4827
4890
  return this;
4828
4891
  }
@@ -4863,15 +4926,13 @@ class FASTElementDefinition {
4863
4926
  }, nameOrDef));
4864
4927
  }
4865
4928
  const definition = new FASTElementDefinition(type, nameOrDef);
4866
- Promise.all([
4867
- new Promise(resolve => {
4868
- Observable.getNotifier(definition).subscribe({
4869
- handleChange: () => resolve(),
4870
- }, "template");
4871
- }),
4872
- ]).then(() => {
4873
- resolve(definition);
4874
- });
4929
+ Observable.getNotifier(definition).subscribe({
4930
+ handleChange: () => {
4931
+ var _b, _c;
4932
+ (_c = (_b = definition.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.templateDidUpdate) === null || _c === void 0 ? void 0 : _c.call(_b, definition.name);
4933
+ resolve(definition);
4934
+ },
4935
+ }, "template");
4875
4936
  });
4876
4937
  }
4877
4938
  }
@@ -4899,9 +4960,7 @@ FASTElementDefinition.registerAsync = (name) => __awaiter(void 0, void 0, void 0
4899
4960
  if (FASTElementDefinition.isRegistered[name]) {
4900
4961
  resolve(FASTElementDefinition.isRegistered[name]);
4901
4962
  }
4902
- Observable.getNotifier(FASTElementDefinition.isRegistered).subscribe({
4903
- handleChange: () => resolve(FASTElementDefinition.isRegistered[name]),
4904
- }, name);
4963
+ Observable.getNotifier(FASTElementDefinition.isRegistered).subscribe({ handleChange: () => resolve(FASTElementDefinition.isRegistered[name]) }, name);
4905
4964
  });
4906
4965
  });
4907
4966
  Observable.defineProperty(FASTElementDefinition.prototype, "template");
@@ -5997,32 +6056,77 @@ const needsHydrationAttribute = "needs-hydration";
5997
6056
  * @beta
5998
6057
  */
5999
6058
  class HydratableElementController extends ElementController {
6059
+ /**
6060
+ * Adds the current element instance to the hydrating instances map
6061
+ */
6062
+ addHydratingInstance() {
6063
+ if (!HydratableElementController.hydratingInstances) {
6064
+ return;
6065
+ }
6066
+ const name = this.definition.name;
6067
+ let instances = HydratableElementController.hydratingInstances.get(name);
6068
+ if (!instances) {
6069
+ instances = new Set();
6070
+ HydratableElementController.hydratingInstances.set(name, instances);
6071
+ }
6072
+ instances.add(this.source);
6073
+ }
6074
+ /**
6075
+ * Configure lifecycle callbacks for hydration events
6076
+ */
6077
+ static config(callbacks) {
6078
+ HydratableElementController.lifecycleCallbacks = callbacks;
6079
+ return this;
6080
+ }
6000
6081
  static hydrationObserverHandler(records) {
6001
6082
  for (const record of records) {
6002
- HydratableElementController.hydrationObserver.unobserve(record.target);
6003
- record.target.$fastController.connect();
6083
+ if (!record.target.hasAttribute(deferHydrationAttribute)) {
6084
+ HydratableElementController.hydrationObserver.unobserve(record.target);
6085
+ record.target.$fastController.connect();
6086
+ }
6087
+ }
6088
+ }
6089
+ /**
6090
+ * Checks to see if hydration is complete and if so, invokes the hydrationComplete callback.
6091
+ * Then resets the ElementController strategy to the default so that future elements
6092
+ * don't use the HydratableElementController.
6093
+ *
6094
+ * @param deadline - the idle deadline object
6095
+ */
6096
+ static checkHydrationComplete(deadline) {
6097
+ var _a, _b, _c;
6098
+ if (deadline.didTimeout) {
6099
+ HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
6100
+ return;
6101
+ }
6102
+ // If there are no more hydrating instances, invoke the hydrationComplete callback
6103
+ if (((_a = HydratableElementController.hydratingInstances) === null || _a === void 0 ? void 0 : _a.size) === 0) {
6104
+ (_c = (_b = HydratableElementController.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.hydrationComplete) === null || _c === void 0 ? void 0 : _c.call(_b);
6105
+ // Reset to the default strategy after hydration is complete
6106
+ ElementController.setStrategy(ElementController);
6004
6107
  }
6005
6108
  }
6006
6109
  static forCustomElement(element, override) {
6007
6110
  const definition = FASTElementDefinition.getForInstance(element);
6008
- if (definition !== undefined &&
6009
- definition.templateOptions === "defer-and-hydrate" &&
6111
+ if ((definition === null || definition === void 0 ? void 0 : definition.templateOptions) === TemplateOptions.deferAndHydrate &&
6010
6112
  !definition.template) {
6011
- element.setAttribute(deferHydrationAttribute, "");
6012
- element.setAttribute(needsHydrationAttribute, "");
6113
+ element.toggleAttribute(deferHydrationAttribute, true);
6114
+ element.toggleAttribute(needsHydrationAttribute, true);
6013
6115
  }
6014
6116
  return super.forCustomElement(element, override);
6015
6117
  }
6016
6118
  connect() {
6017
- var _a, _b;
6119
+ var _a, _b, _c, _d, _e;
6018
6120
  // Initialize needsHydration on first connect
6019
- if (this.needsHydration === undefined) {
6020
- this.needsHydration =
6021
- this.source.getAttribute(needsHydrationAttribute) !== null;
6121
+ this.needsHydration =
6122
+ (_a = this.needsHydration) !== null && _a !== void 0 ? _a : this.source.getAttribute(needsHydrationAttribute) !== null;
6123
+ if (this.needsHydration) {
6124
+ (_c = (_b = HydratableElementController.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.elementWillHydrate) === null || _c === void 0 ? void 0 : _c.call(_b, this.definition.name);
6022
6125
  }
6023
6126
  // If the `defer-hydration` attribute exists on the source,
6024
6127
  // wait for it to be removed before continuing connection behavior.
6025
6128
  if (this.source.hasAttribute(deferHydrationAttribute)) {
6129
+ this.addHydratingInstance();
6026
6130
  HydratableElementController.hydrationObserver.observe(this.source, {
6027
6131
  attributeFilter: [deferHydrationAttribute],
6028
6132
  });
@@ -6034,6 +6138,7 @@ class HydratableElementController extends ElementController {
6034
6138
  // class
6035
6139
  if (!this.needsHydration) {
6036
6140
  super.connect();
6141
+ this.removeHydratingInstance();
6037
6142
  return;
6038
6143
  }
6039
6144
  if (this.stage !== 3 /* Stages.disconnected */) {
@@ -6042,10 +6147,10 @@ class HydratableElementController extends ElementController {
6042
6147
  this.stage = 0 /* Stages.connecting */;
6043
6148
  this.bindObservables();
6044
6149
  this.connectBehaviors();
6045
- const element = this.source;
6046
- const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
6047
6150
  if (this.template) {
6048
6151
  if (isHydratable(this.template)) {
6152
+ const element = this.source;
6153
+ const host = (_d = getShadowRoot(element)) !== null && _d !== void 0 ? _d : element;
6049
6154
  let firstChild = host.firstChild;
6050
6155
  let lastChild = host.lastChild;
6051
6156
  if (element.shadowRoot === null) {
@@ -6060,7 +6165,7 @@ class HydratableElementController extends ElementController {
6060
6165
  }
6061
6166
  }
6062
6167
  this.view = this.template.hydrate(firstChild, lastChild, element);
6063
- (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
6168
+ (_e = this.view) === null || _e === void 0 ? void 0 : _e.bind(this.source);
6064
6169
  }
6065
6170
  else {
6066
6171
  this.renderTemplate(this.template);
@@ -6070,8 +6175,32 @@ class HydratableElementController extends ElementController {
6070
6175
  this.stage = 1 /* Stages.connected */;
6071
6176
  this.source.removeAttribute(needsHydrationAttribute);
6072
6177
  this.needsInitialization = this.needsHydration = false;
6178
+ this.removeHydratingInstance();
6073
6179
  Observable.notify(this, isConnectedPropertyName);
6074
6180
  }
6181
+ /**
6182
+ * Removes the current element instance from the hydrating instances map
6183
+ */
6184
+ removeHydratingInstance() {
6185
+ var _a, _b;
6186
+ if (!HydratableElementController.hydratingInstances) {
6187
+ return;
6188
+ }
6189
+ const name = this.definition.name;
6190
+ const instances = HydratableElementController.hydratingInstances.get(name);
6191
+ // Callback: After hydration has finished
6192
+ (_b = (_a = HydratableElementController.lifecycleCallbacks) === null || _a === void 0 ? void 0 : _a.elementDidHydrate) === null || _b === void 0 ? void 0 : _b.call(_a, this.definition.name);
6193
+ if (instances) {
6194
+ instances.delete(this.source);
6195
+ if (!instances.size) {
6196
+ HydratableElementController.hydratingInstances.delete(name);
6197
+ }
6198
+ if (HydratableElementController.idleCallbackId) {
6199
+ cancelIdleCallback(HydratableElementController.idleCallbackId);
6200
+ }
6201
+ HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
6202
+ }
6203
+ }
6075
6204
  disconnect() {
6076
6205
  super.disconnect();
6077
6206
  HydratableElementController.hydrationObserver.unobserve(this.source);
@@ -6081,6 +6210,20 @@ class HydratableElementController extends ElementController {
6081
6210
  }
6082
6211
  }
6083
6212
  HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
6213
+ /**
6214
+ * An idle callback ID used to track hydration completion
6215
+ */
6216
+ HydratableElementController.idleCallbackId = null;
6217
+ /**
6218
+ * A map of element instances by the name of the custom element they are
6219
+ * associated with. The key is the custom element name, and the value is the
6220
+ * instances of hydratable elements which currently need to be hydrated.
6221
+ *
6222
+ * When all of the instances in the set have been hydrated, the set is
6223
+ * cleared and removed from the map. If the map is empty, the
6224
+ * hydrationComplete callback is invoked.
6225
+ */
6226
+ HydratableElementController.hydratingInstances = new Map();
6084
6227
 
6085
6228
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
6086
6229
  function createFASTElement(BaseType) {
@@ -6184,4 +6327,4 @@ function customElement(nameOrDef) {
6184
6327
 
6185
6328
  DOM.setPolicy(DOMPolicy.create());
6186
6329
 
6187
- export { ArrayObserver, AttributeConfiguration, AttributeDefinition, Binding, CSSBindingDirective, CSSDirective, ChildrenDirective, Compiler, DOM, DOMAspect, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, HydratableElementController, HydrationBindingError, InlineTemplateDirective, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RenderBehavior, RenderDirective, RepeatBehavior, RepeatDirective, SlottedDirective, Sort, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewTemplate, attr, booleanConverter, children, css, cssDirective, customElement, elements, emptyArray, fastElementRegistry, html, htmlDirective, lengthOf, listener, normalizeBinding$1 as normalizeBinding, nullableBooleanConverter, nullableNumberConverter, observable, oneTime, oneWay, ref, render, repeat, slotted, sortedCount, volatile, when };
6330
+ export { ArrayObserver, AttributeConfiguration, AttributeDefinition, Binding, CSSBindingDirective, CSSDirective, ChildrenDirective, Compiler, DOM, DOMAspect, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, HydratableElementController, HydrationBindingError, InlineTemplateDirective, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RenderBehavior, RenderDirective, RepeatBehavior, RepeatDirective, SlottedDirective, Sort, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, TemplateOptions, Updates, ViewTemplate, attr, booleanConverter, children, css, cssDirective, customElement, elements, emptyArray, fastElementRegistry, html, htmlDirective, lengthOf, listener, normalizeBinding$1 as normalizeBinding, nullableBooleanConverter, nullableNumberConverter, observable, oneTime, oneWay, ref, render, repeat, slotted, sortedCount, volatile, when };