@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.
@@ -85,6 +85,39 @@ const noop = () => void 0;
85
85
  result.globalThis = result;
86
86
  }
87
87
  })();
88
+ (function requestIdleCallbackPolyfill() {
89
+ if ("requestIdleCallback" in globalThis) {
90
+ return;
91
+ }
92
+ /**
93
+ * A polyfill for requestIdleCallback that falls back to setTimeout.
94
+ *
95
+ * @param callback - The function to call when the browser is idle.
96
+ * @param options - Options object that may contain a timeout property.
97
+ * @returns An ID that can be used to cancel the callback.
98
+ * @public
99
+ */
100
+ globalThis.requestIdleCallback = function requestIdleCallback(callback, options) {
101
+ const start = Date.now();
102
+ return setTimeout(() => {
103
+ callback({
104
+ didTimeout: (options === null || options === void 0 ? void 0 : options.timeout)
105
+ ? Date.now() - start >= options.timeout
106
+ : false,
107
+ timeRemaining: () => 0,
108
+ });
109
+ }, 1);
110
+ };
111
+ /**
112
+ * A polyfill for cancelIdleCallback that falls back to clearTimeout.
113
+ *
114
+ * @param id - The ID of the callback to cancel.
115
+ * @public
116
+ */
117
+ globalThis.cancelIdleCallback = function cancelIdleCallback(id) {
118
+ clearTimeout(id);
119
+ };
120
+ })();
88
121
 
89
122
  // ensure FAST global - duplicated debug.ts
90
123
  const propConfig = {
@@ -3061,7 +3094,7 @@ class HydrationView extends DefaultExecutionContext {
3061
3094
  fragment.appendChild(end);
3062
3095
  }
3063
3096
  bind(source, context = this) {
3064
- var _b, _c;
3097
+ var _b;
3065
3098
  if (this.hydrationStage !== HydrationStage.hydrated) {
3066
3099
  this._hydrationStage = HydrationStage.hydrating;
3067
3100
  }
@@ -3105,7 +3138,28 @@ class HydrationView extends DefaultExecutionContext {
3105
3138
  if (typeof templateString !== "string") {
3106
3139
  templateString = templateString.innerHTML;
3107
3140
  }
3108
- 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);
3141
+ const hostElement = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode())
3142
+ .host;
3143
+ const hostName = (hostElement === null || hostElement === void 0 ? void 0 : hostElement.nodeName) || "unknown";
3144
+ const factoryInfo = factory;
3145
+ // Build detailed error message
3146
+ const details = [
3147
+ `HydrationView was unable to successfully target bindings inside "<${hostName.toLowerCase()}>".`,
3148
+ `\nMismatch Details:`,
3149
+ ` - Expected target node ID: "${factory.targetNodeId}"`,
3150
+ ` - Available target IDs: [${Object.keys(this.targets).join(", ") || "none"}]`,
3151
+ ];
3152
+ if (factory.targetTagName) {
3153
+ details.push(` - Expected tag name: "${factory.targetTagName}"`);
3154
+ }
3155
+ if (factoryInfo.sourceAspect) {
3156
+ details.push(` - Source aspect: "${factoryInfo.sourceAspect}"`);
3157
+ }
3158
+ if (factoryInfo.aspectType !== undefined) {
3159
+ details.push(` - Aspect type: ${factoryInfo.aspectType}`);
3160
+ }
3161
+ 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 ? "..." : ""}`);
3162
+ throw new HydrationBindingError(details.join("\n"), factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
3109
3163
  }
3110
3164
  }
3111
3165
  }
@@ -4689,6 +4743,13 @@ const fastElementBaseTypes = new Set();
4689
4743
  * @internal
4690
4744
  */
4691
4745
  const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
4746
+ /**
4747
+ * Values for the `templateOptions` property.
4748
+ * @alpha
4749
+ */
4750
+ const TemplateOptions = {
4751
+ deferAndHydrate: "defer-and-hydrate",
4752
+ };
4692
4753
  /**
4693
4754
  * Defines metadata for a FASTElement.
4694
4755
  * @public
@@ -4752,10 +4813,12 @@ class FASTElementDefinition {
4752
4813
  * This operation is idempotent per registry.
4753
4814
  */
4754
4815
  define(registry = this.registry) {
4816
+ var _b, _c;
4755
4817
  const type = this.type;
4756
4818
  if (!registry.get(this.name)) {
4757
4819
  this.platformDefined = true;
4758
4820
  registry.define(this.name, type, this.elementOptions);
4821
+ (_c = (_b = this.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.elementDidDefine) === null || _c === void 0 ? void 0 : _c.call(_b, this.name);
4759
4822
  }
4760
4823
  return this;
4761
4824
  }
@@ -4796,15 +4859,13 @@ class FASTElementDefinition {
4796
4859
  }, nameOrDef));
4797
4860
  }
4798
4861
  const definition = new FASTElementDefinition(type, nameOrDef);
4799
- Promise.all([
4800
- new Promise(resolve => {
4801
- Observable.getNotifier(definition).subscribe({
4802
- handleChange: () => resolve(),
4803
- }, "template");
4804
- }),
4805
- ]).then(() => {
4806
- resolve(definition);
4807
- });
4862
+ Observable.getNotifier(definition).subscribe({
4863
+ handleChange: () => {
4864
+ var _b, _c;
4865
+ (_c = (_b = definition.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.templateDidUpdate) === null || _c === void 0 ? void 0 : _c.call(_b, definition.name);
4866
+ resolve(definition);
4867
+ },
4868
+ }, "template");
4808
4869
  });
4809
4870
  }
4810
4871
  }
@@ -4832,9 +4893,7 @@ FASTElementDefinition.registerAsync = (name) => __awaiter(void 0, void 0, void 0
4832
4893
  if (FASTElementDefinition.isRegistered[name]) {
4833
4894
  resolve(FASTElementDefinition.isRegistered[name]);
4834
4895
  }
4835
- Observable.getNotifier(FASTElementDefinition.isRegistered).subscribe({
4836
- handleChange: () => resolve(FASTElementDefinition.isRegistered[name]),
4837
- }, name);
4896
+ Observable.getNotifier(FASTElementDefinition.isRegistered).subscribe({ handleChange: () => resolve(FASTElementDefinition.isRegistered[name]) }, name);
4838
4897
  });
4839
4898
  });
4840
4899
  Observable.defineProperty(FASTElementDefinition.prototype, "template");
@@ -5930,32 +5989,77 @@ const needsHydrationAttribute = "needs-hydration";
5930
5989
  * @beta
5931
5990
  */
5932
5991
  class HydratableElementController extends ElementController {
5992
+ /**
5993
+ * Adds the current element instance to the hydrating instances map
5994
+ */
5995
+ addHydratingInstance() {
5996
+ if (!HydratableElementController.hydratingInstances) {
5997
+ return;
5998
+ }
5999
+ const name = this.definition.name;
6000
+ let instances = HydratableElementController.hydratingInstances.get(name);
6001
+ if (!instances) {
6002
+ instances = new Set();
6003
+ HydratableElementController.hydratingInstances.set(name, instances);
6004
+ }
6005
+ instances.add(this.source);
6006
+ }
6007
+ /**
6008
+ * Configure lifecycle callbacks for hydration events
6009
+ */
6010
+ static config(callbacks) {
6011
+ HydratableElementController.lifecycleCallbacks = callbacks;
6012
+ return this;
6013
+ }
5933
6014
  static hydrationObserverHandler(records) {
5934
6015
  for (const record of records) {
5935
- HydratableElementController.hydrationObserver.unobserve(record.target);
5936
- record.target.$fastController.connect();
6016
+ if (!record.target.hasAttribute(deferHydrationAttribute)) {
6017
+ HydratableElementController.hydrationObserver.unobserve(record.target);
6018
+ record.target.$fastController.connect();
6019
+ }
6020
+ }
6021
+ }
6022
+ /**
6023
+ * Checks to see if hydration is complete and if so, invokes the hydrationComplete callback.
6024
+ * Then resets the ElementController strategy to the default so that future elements
6025
+ * don't use the HydratableElementController.
6026
+ *
6027
+ * @param deadline - the idle deadline object
6028
+ */
6029
+ static checkHydrationComplete(deadline) {
6030
+ var _a, _b, _c;
6031
+ if (deadline.didTimeout) {
6032
+ HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
6033
+ return;
6034
+ }
6035
+ // If there are no more hydrating instances, invoke the hydrationComplete callback
6036
+ if (((_a = HydratableElementController.hydratingInstances) === null || _a === void 0 ? void 0 : _a.size) === 0) {
6037
+ (_c = (_b = HydratableElementController.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.hydrationComplete) === null || _c === void 0 ? void 0 : _c.call(_b);
6038
+ // Reset to the default strategy after hydration is complete
6039
+ ElementController.setStrategy(ElementController);
5937
6040
  }
5938
6041
  }
5939
6042
  static forCustomElement(element, override) {
5940
6043
  const definition = FASTElementDefinition.getForInstance(element);
5941
- if (definition !== undefined &&
5942
- definition.templateOptions === "defer-and-hydrate" &&
6044
+ if ((definition === null || definition === void 0 ? void 0 : definition.templateOptions) === TemplateOptions.deferAndHydrate &&
5943
6045
  !definition.template) {
5944
- element.setAttribute(deferHydrationAttribute, "");
5945
- element.setAttribute(needsHydrationAttribute, "");
6046
+ element.toggleAttribute(deferHydrationAttribute, true);
6047
+ element.toggleAttribute(needsHydrationAttribute, true);
5946
6048
  }
5947
6049
  return super.forCustomElement(element, override);
5948
6050
  }
5949
6051
  connect() {
5950
- var _a, _b;
6052
+ var _a, _b, _c, _d, _e;
5951
6053
  // Initialize needsHydration on first connect
5952
- if (this.needsHydration === undefined) {
5953
- this.needsHydration =
5954
- this.source.getAttribute(needsHydrationAttribute) !== null;
6054
+ this.needsHydration =
6055
+ (_a = this.needsHydration) !== null && _a !== void 0 ? _a : this.source.getAttribute(needsHydrationAttribute) !== null;
6056
+ if (this.needsHydration) {
6057
+ (_c = (_b = HydratableElementController.lifecycleCallbacks) === null || _b === void 0 ? void 0 : _b.elementWillHydrate) === null || _c === void 0 ? void 0 : _c.call(_b, this.definition.name);
5955
6058
  }
5956
6059
  // If the `defer-hydration` attribute exists on the source,
5957
6060
  // wait for it to be removed before continuing connection behavior.
5958
6061
  if (this.source.hasAttribute(deferHydrationAttribute)) {
6062
+ this.addHydratingInstance();
5959
6063
  HydratableElementController.hydrationObserver.observe(this.source, {
5960
6064
  attributeFilter: [deferHydrationAttribute],
5961
6065
  });
@@ -5967,6 +6071,7 @@ class HydratableElementController extends ElementController {
5967
6071
  // class
5968
6072
  if (!this.needsHydration) {
5969
6073
  super.connect();
6074
+ this.removeHydratingInstance();
5970
6075
  return;
5971
6076
  }
5972
6077
  if (this.stage !== 3 /* Stages.disconnected */) {
@@ -5975,10 +6080,10 @@ class HydratableElementController extends ElementController {
5975
6080
  this.stage = 0 /* Stages.connecting */;
5976
6081
  this.bindObservables();
5977
6082
  this.connectBehaviors();
5978
- const element = this.source;
5979
- const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
5980
6083
  if (this.template) {
5981
6084
  if (isHydratable(this.template)) {
6085
+ const element = this.source;
6086
+ const host = (_d = getShadowRoot(element)) !== null && _d !== void 0 ? _d : element;
5982
6087
  let firstChild = host.firstChild;
5983
6088
  let lastChild = host.lastChild;
5984
6089
  if (element.shadowRoot === null) {
@@ -5993,7 +6098,7 @@ class HydratableElementController extends ElementController {
5993
6098
  }
5994
6099
  }
5995
6100
  this.view = this.template.hydrate(firstChild, lastChild, element);
5996
- (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
6101
+ (_e = this.view) === null || _e === void 0 ? void 0 : _e.bind(this.source);
5997
6102
  }
5998
6103
  else {
5999
6104
  this.renderTemplate(this.template);
@@ -6003,8 +6108,32 @@ class HydratableElementController extends ElementController {
6003
6108
  this.stage = 1 /* Stages.connected */;
6004
6109
  this.source.removeAttribute(needsHydrationAttribute);
6005
6110
  this.needsInitialization = this.needsHydration = false;
6111
+ this.removeHydratingInstance();
6006
6112
  Observable.notify(this, isConnectedPropertyName);
6007
6113
  }
6114
+ /**
6115
+ * Removes the current element instance from the hydrating instances map
6116
+ */
6117
+ removeHydratingInstance() {
6118
+ var _a, _b;
6119
+ if (!HydratableElementController.hydratingInstances) {
6120
+ return;
6121
+ }
6122
+ const name = this.definition.name;
6123
+ const instances = HydratableElementController.hydratingInstances.get(name);
6124
+ // Callback: After hydration has finished
6125
+ (_b = (_a = HydratableElementController.lifecycleCallbacks) === null || _a === void 0 ? void 0 : _a.elementDidHydrate) === null || _b === void 0 ? void 0 : _b.call(_a, this.definition.name);
6126
+ if (instances) {
6127
+ instances.delete(this.source);
6128
+ if (!instances.size) {
6129
+ HydratableElementController.hydratingInstances.delete(name);
6130
+ }
6131
+ if (HydratableElementController.idleCallbackId) {
6132
+ cancelIdleCallback(HydratableElementController.idleCallbackId);
6133
+ }
6134
+ HydratableElementController.idleCallbackId = requestIdleCallback(HydratableElementController.checkHydrationComplete, { timeout: 50 });
6135
+ }
6136
+ }
6008
6137
  disconnect() {
6009
6138
  super.disconnect();
6010
6139
  HydratableElementController.hydrationObserver.unobserve(this.source);
@@ -6014,6 +6143,20 @@ class HydratableElementController extends ElementController {
6014
6143
  }
6015
6144
  }
6016
6145
  HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
6146
+ /**
6147
+ * An idle callback ID used to track hydration completion
6148
+ */
6149
+ HydratableElementController.idleCallbackId = null;
6150
+ /**
6151
+ * A map of element instances by the name of the custom element they are
6152
+ * associated with. The key is the custom element name, and the value is the
6153
+ * instances of hydratable elements which currently need to be hydrated.
6154
+ *
6155
+ * When all of the instances in the set have been hydrated, the set is
6156
+ * cleared and removed from the map. If the map is empty, the
6157
+ * hydrationComplete callback is invoked.
6158
+ */
6159
+ HydratableElementController.hydratingInstances = new Map();
6017
6160
 
6018
6161
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
6019
6162
  function createFASTElement(BaseType) {
@@ -6117,4 +6260,4 @@ function customElement(nameOrDef) {
6117
6260
 
6118
6261
  DOM.setPolicy(DOMPolicy.create());
6119
6262
 
6120
- 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 };
6263
+ 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 };