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