@optionfactory/ful 6.0.8 → 7.0.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/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
  }
@@ -2158,11 +2206,12 @@ var ful = (function (exports, ftl) {
2158
2206
  this.#menu = fragment.querySelector("menu");
2159
2207
  this.#menu.addEventListener('click', evt => {
2160
2208
  evt.stopPropagation();
2161
- if (!evt.target.matches('li')) {
2209
+ const li = evt.target.closest('li');
2210
+ if (!li) {
2162
2211
  this.hide();
2163
2212
  return;
2164
2213
  }
2165
- this.#change(evt.target);
2214
+ this.#change(li);
2166
2215
  });
2167
2216
  this.replaceChildren(fragment);
2168
2217
  }
@@ -2461,7 +2510,6 @@ var ful = (function (exports, ftl) {
2461
2510
  }
2462
2511
  return [...this.#values.entries()][0] ?? null;
2463
2512
  }
2464
- //@ts-ignore
2465
2513
  get disabled() {
2466
2514
  return this.#input.hasAttribute('disabled');
2467
2515
  }
@@ -2609,7 +2657,6 @@ var ful = (function (exports, ftl) {
2609
2657
  ftl.Attributes.toggle(this, 'readonly', v);
2610
2658
  });
2611
2659
  }
2612
- //@ts-ignore
2613
2660
  get disabled(){
2614
2661
  return this.#fieldset.hasAttribute('disabled');
2615
2662
  }
@@ -2719,7 +2766,6 @@ var ful = (function (exports, ftl) {
2719
2766
  ftl.Attributes.toggle(this, 'readonly', v);
2720
2767
  });
2721
2768
  }
2722
- //@ts-ignore
2723
2769
  get disabled() {
2724
2770
  return this.#input.hasAttribute('disabled');
2725
2771
  }