@optionfactory/ful 6.0.7 → 7.0.0

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/dist/ful.iife.js CHANGED
@@ -978,59 +978,108 @@ var ful = (function (exports, ftl) {
978
978
  }
979
979
  }
980
980
 
981
+ /**
982
+ * @typedef {Object} AsyncExtension
983
+ * @property {Promise<any>[]} promises
984
+ * @typedef {Event & { async?: AsyncExtension }} AsyncEvent
985
+ */
981
986
  class AsyncEvents {
982
- static async fireAsync(el, evt) {
987
+ /**
988
+ * Dispatches an event and handles asynchronous resolution based on the execution mode.
989
+ * @param {HTMLElement} el - The target element dispatching the event.
990
+ * @param {AsyncEvent} evt - The event instance.
991
+ * @param {{mode?: 'broadcast' | 'pipeline' | 'delegate'}} [options] - Configuration options (defaults to 'broadcast').
992
+ * @returns {Promise<any>} Resolves with an array of values for broadcasts, a single value for pipelines/delegates, or undefined.
993
+ */
994
+ static async fireAsync(el, evt, options) {
983
995
  el.dispatchEvent(evt);
984
- return await evt.async?.promise;
996
+ const promises = evt.async?.promises ?? [];
997
+ const mode = options?.mode ?? 'broadcast';
998
+ if (mode === 'pipeline' && promises.length > 1) {
999
+ throw new Error(`[AsyncEvents] Event "${evt.type}" is configured in 'pipeline' mode and expects at most one async listener, but ${promises.length} listeners were triggered on this element.`);
1000
+ }
1001
+ if (mode === 'delegate' && promises.length !== 1) {
1002
+ throw new Error(`[AsyncEvents] Event "${evt.type}" is configured in 'delegate' mode and requires exactly one async listener, but ${promises.length} were registered.`);
1003
+ }
1004
+ return mode === 'broadcast' ? Promise.all(promises) : Promise.resolve(promises[0]);
985
1005
  }
1006
+
986
1007
  /**
987
- *
988
- * @param {*} el
989
- * @param {*} type
990
- * @param {*} fn returning the result
991
- * @param {*} options
992
- * @returns
1008
+ * Registers an asynchronous event listener wrapper.
1009
+ * @param {HTMLElement} el - The target element.
1010
+ * @param {string} type - The event name/type.
1011
+ * @param {Function} fn - The async listener middleware function returning the execution result.
1012
+ * @param {AddEventListenerOptions} [options] - Native addEventListener options.
1013
+ * @returns {EventListener} The underlying proxy listener function needed for cleanup via asyncOff.
993
1014
  */
994
1015
  static asyncOn(el, type, fn, options) {
1016
+ /** @type {(evt: Event) => Promise<void>} */
995
1017
  const listener = async (event) => {
996
- let resolve, reject;
997
- const promise = new Promise((res, rej) => {
998
- resolve = res;
999
- reject = rej;
1000
- });
1001
- event.async = { promise };
1018
+ const ae = /** @type {AsyncEvent} */ (event);
1019
+ if (!ae.async) {
1020
+ ae.async = { promises: [] };
1021
+ }
1022
+ const { promise, resolve, reject } = Promise.withResolvers();
1023
+ ae.async.promises.push(promise);
1002
1024
  try {
1003
- //@ts-ignore
1004
- resolve(await fn(event));
1025
+ resolve(await fn(ae));
1005
1026
  } catch (e) {
1006
- //@ts-ignore
1007
1027
  reject(e);
1008
1028
  }
1009
1029
  };
1030
+
1010
1031
  el.addEventListener(type, listener, options);
1011
1032
  return listener;
1012
1033
  }
1034
+
1013
1035
  /**
1014
- *
1015
- * @param {*} el
1016
- * @param {*} type
1017
- * @param {*} listener the listener returned by asyncOn
1018
- * @param {*} options
1019
- */
1036
+ * Unregisters an asynchronous event listener proxy.
1037
+ * @param {HTMLElement} el - The target element.
1038
+ * @param {string} type - The event name/type.
1039
+ * @param {EventListener} listener - The proxy listener instance previously returned by asyncOn.
1040
+ * @param {EventListenerOptions} [options] - Native removeEventListener options.
1041
+ */
1020
1042
  static asyncOff(el, type, listener, options) {
1021
1043
  el.removeEventListener(type, listener, options);
1022
1044
  }
1045
+
1046
+ /**
1047
+ * Mixes the asynchronous execution engine extensions into target class prototypes.
1048
+ * @param {...Function} classes - The target class constructors to decorate.
1049
+ */
1023
1050
  static mixInto(...classes) {
1024
1051
  for (const k of classes) {
1025
1052
  Object.assign(k.prototype, {
1026
- async fireAsync(evt) {
1027
- return await AsyncEvents.fireAsync(this, evt);
1053
+ /**
1054
+ * @this {HTMLElement}
1055
+ * @param {AsyncEvent} evt
1056
+ * @param {{mode?: 'broadcast' | 'pipeline' | 'delegate'}} [options]
1057
+ * @returns {Promise<any>}
1058
+ */
1059
+ async fireAsync(evt, options) {
1060
+ return await AsyncEvents.fireAsync(this, evt, options);
1028
1061
  },
1062
+
1063
+ /**
1064
+ * @this {HTMLElement}
1065
+ * @param {string} type
1066
+ * @param {Function} fn
1067
+ * @param {AddEventListenerOptions} [options]
1068
+ * @returns {EventListener}
1069
+ */
1029
1070
  asyncOn(type, fn, options) {
1030
1071
  return AsyncEvents.asyncOn(this, type, fn, options);
1031
1072
  },
1073
+
1074
+ /**
1075
+ * @this {HTMLElement}
1076
+ * @param {string} type
1077
+ * @param {EventListener} listener
1078
+ * @param {EventListenerOptions} [options]
1079
+ * @returns {void}
1080
+ */
1032
1081
  asyncOff(type, listener, options) {
1033
- return AsyncEvents.asyncOff(this, type, listener, options);
1082
+ AsyncEvents.asyncOff(this, type, listener, options);
1034
1083
  }
1035
1084
  });
1036
1085
  }
@@ -1384,7 +1433,7 @@ var ful = (function (exports, ftl) {
1384
1433
  }
1385
1434
  this.errors = [];
1386
1435
  const sre = new CustomEvent('submit:requested', { bubbles: true, cancelable: false, detail: { submitter, values: se.detail.values, request: se.detail.request} });
1387
- let response = await AsyncEvents.fireAsync(this, sre);
1436
+ let response = await AsyncEvents.fireAsync(this, sre, {mode: "pipeline"});
1388
1437
  request = sre.detail.request;
1389
1438
 
1390
1439
  response = await loader.submit(request, this, response);
@@ -1533,7 +1582,6 @@ var ful = (function (exports, ftl) {
1533
1582
  ftl.Attributes.toggle(this, 'readonly', v);
1534
1583
  });
1535
1584
  }
1536
- //@ts-ignore
1537
1585
  get disabled() {
1538
1586
  return this._input.hasAttribute('disabled');
1539
1587
  }
@@ -2023,7 +2071,7 @@ var ful = (function (exports, ftl) {
2023
2071
  if (this.#data !== null) {
2024
2072
  return
2025
2073
  }
2026
- const raw = RemoteLoader.#revisionedData(this.#http, this.#method, this.#url, this.#revision);
2074
+ const raw = await RemoteLoader.#revisionedData(this.#http, this.#method, this.#url, this.#revision);
2027
2075
  this.#data = this.#responseMapper(raw);
2028
2076
  }
2029
2077
  static async #revisionedData(http, method, url, revision){
@@ -2461,7 +2509,6 @@ var ful = (function (exports, ftl) {
2461
2509
  }
2462
2510
  return [...this.#values.entries()][0] ?? null;
2463
2511
  }
2464
- //@ts-ignore
2465
2512
  get disabled() {
2466
2513
  return this.#input.hasAttribute('disabled');
2467
2514
  }
@@ -2609,7 +2656,6 @@ var ful = (function (exports, ftl) {
2609
2656
  ftl.Attributes.toggle(this, 'readonly', v);
2610
2657
  });
2611
2658
  }
2612
- //@ts-ignore
2613
2659
  get disabled(){
2614
2660
  return this.#fieldset.hasAttribute('disabled');
2615
2661
  }
@@ -2719,7 +2765,6 @@ var ful = (function (exports, ftl) {
2719
2765
  ftl.Attributes.toggle(this, 'readonly', v);
2720
2766
  });
2721
2767
  }
2722
- //@ts-ignore
2723
2768
  get disabled() {
2724
2769
  return this.#input.hasAttribute('disabled');
2725
2770
  }