tiny-essentials 1.17.0 → 1.18.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.
Files changed (39) hide show
  1. package/dist/v1/TinyBasicsEs.js +336 -45
  2. package/dist/v1/TinyBasicsEs.min.js +1 -1
  3. package/dist/v1/TinyClipboard.js +459 -0
  4. package/dist/v1/TinyClipboard.min.js +1 -0
  5. package/dist/v1/TinyDragger.js +336 -45
  6. package/dist/v1/TinyDragger.min.js +1 -1
  7. package/dist/v1/TinyEssentials.js +1218 -45
  8. package/dist/v1/TinyEssentials.min.js +1 -1
  9. package/dist/v1/TinyHtml.js +336 -45
  10. package/dist/v1/TinyHtml.min.js +1 -1
  11. package/dist/v1/TinySmartScroller.js +336 -45
  12. package/dist/v1/TinySmartScroller.min.js +1 -1
  13. package/dist/v1/TinyTextRangeEditor.js +497 -0
  14. package/dist/v1/TinyTextRangeEditor.min.js +1 -0
  15. package/dist/v1/TinyUploadClicker.js +338 -45
  16. package/dist/v1/TinyUploadClicker.min.js +1 -1
  17. package/dist/v1/build/TinyClipboard.cjs +7 -0
  18. package/dist/v1/build/TinyClipboard.d.mts +3 -0
  19. package/dist/v1/build/TinyClipboard.mjs +2 -0
  20. package/dist/v1/build/TinyTextRangeEditor.cjs +7 -0
  21. package/dist/v1/build/TinyTextRangeEditor.d.mts +3 -0
  22. package/dist/v1/build/TinyTextRangeEditor.mjs +2 -0
  23. package/dist/v1/index.cjs +4 -0
  24. package/dist/v1/index.d.mts +3 -1
  25. package/dist/v1/index.mjs +3 -1
  26. package/dist/v1/libs/TinyClipboard.cjs +420 -0
  27. package/dist/v1/libs/TinyClipboard.d.mts +155 -0
  28. package/dist/v1/libs/TinyClipboard.mjs +398 -0
  29. package/dist/v1/libs/TinyHtml.cjs +336 -45
  30. package/dist/v1/libs/TinyHtml.d.mts +238 -27
  31. package/dist/v1/libs/TinyHtml.mjs +320 -47
  32. package/dist/v1/libs/TinyTextRangeEditor.cjs +458 -0
  33. package/dist/v1/libs/TinyTextRangeEditor.d.mts +200 -0
  34. package/dist/v1/libs/TinyTextRangeEditor.mjs +424 -0
  35. package/docs/v1/README.md +2 -0
  36. package/docs/v1/libs/TinyClipboard.md +213 -0
  37. package/docs/v1/libs/TinyHtml.md +211 -15
  38. package/docs/v1/libs/TinyTextRangeEditor.md +208 -0
  39. package/package.json +1 -1
@@ -11,6 +11,19 @@ const {
11
11
  areElsCollRight,
12
12
  } = collision;
13
13
 
14
+ /**
15
+ * Callback invoked on each animation frame with the current scroll position,
16
+ * normalized animation time (`0` to `1`), and a completion flag.
17
+ *
18
+ * @typedef {(progress: { x: number, y: number, isComplete: boolean, time: number }) => void} OnScrollAnimation
19
+ */
20
+
21
+ /**
22
+ * A list of supported easing function names for smooth animations.
23
+ *
24
+ * @typedef {'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'} Easings
25
+ */
26
+
14
27
  /**
15
28
  * Represents a raw Node element or an instance of TinyHtml.
16
29
  * This type is used to abstract interactions with both plain elements
@@ -81,6 +94,21 @@ const {
81
94
  * @typedef {Element|Window|Document} ElementAndWinAndDoc
82
95
  */
83
96
 
97
+ /**
98
+ * Represents a raw DOM element with document or an instance of TinyHtml.
99
+ * This type is used to abstract interactions with both plain elements
100
+ * and wrapped elements via the TinyHtml class.
101
+ *
102
+ * @typedef {ElementWithDoc|TinyHtml} TinyElementWithDoc
103
+ */
104
+
105
+ /**
106
+ * Represents a value that can be either a DOM Element, or the document object.
107
+ * Useful for functions that operate generically on measurable targets.
108
+ *
109
+ * @typedef {Element|Document} ElementWithDoc
110
+ */
111
+
84
112
  /**
85
113
  * A parameter type used for filtering or matching elements.
86
114
  * It can be:
@@ -100,12 +128,6 @@ const {
100
128
  * @typedef {Window|Element|Document|Text} ConstructorElValues
101
129
  */
102
130
 
103
- /**
104
- * The handler function used in event listeners.
105
- *
106
- * @typedef {(e: Event) => any} EventRegistryHandle
107
- */
108
-
109
131
  /**
110
132
  * Options passed to `addEventListener` or `removeEventListener`.
111
133
  * Can be a boolean or an object of type `AddEventListenerOptions`.
@@ -117,7 +139,7 @@ const {
117
139
  * Structure describing a registered event callback and its options.
118
140
  *
119
141
  * @typedef {Object} EventRegistryItem
120
- * @property {EventRegistryHandle} handler - The function to be executed when the event is triggered.
142
+ * @property {EventListenerOrEventListenerObject|null} handler - The function to be executed when the event is triggered.
121
143
  * @property {EventRegistryOptions} [options] - Optional configuration passed to the listener.
122
144
  */
123
145
 
@@ -676,9 +698,9 @@ class TinyHtml {
676
698
  * Ensures the input is returned as an array.
677
699
  * Useful to normalize operations across multiple or single element/window/document elements.
678
700
  *
679
- * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/window element or array of html elements.
701
+ * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/document/window element or array of html elements.
680
702
  * @param {string} where - The method or context name where validation is being called.
681
- * @returns {ElementAndWindow[]} - Always returns an array of element/window elements.
703
+ * @returns {ElementAndWindow[]} - Always returns an array of element/document/window elements.
682
704
  * @readonly
683
705
  */
684
706
  static _preElemsAndWinAndDoc(elems, where) {
@@ -695,9 +717,9 @@ class TinyHtml {
695
717
  * Ensures the input is returned as an single element/window/document element.
696
718
  * Useful to normalize operations across multiple or single element/window/document elements.
697
719
  *
698
- * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/window element or array of html elements.
720
+ * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/document/window element or array of html elements.
699
721
  * @param {string} where - The method or context name where validation is being called.
700
- * @returns {ElementAndWindow} - Always returns an single element/window element.
722
+ * @returns {ElementAndWindow} - Always returns an single element/document/window element.
701
723
  * @readonly
702
724
  */
703
725
  static _preElemAndWinAndDoc(elems, where) {
@@ -711,6 +733,32 @@ class TinyHtml {
711
733
  return result;
712
734
  }
713
735
 
736
+ /**
737
+ * Ensures the input is returned as an array.
738
+ * Useful to normalize operations across multiple or single element with document elements.
739
+ *
740
+ * @param {TinyElementWithDoc|TinyElementWithDoc[]} elems - A single element with document element or array of html elements.
741
+ * @param {string} where - The method or context name where validation is being called.
742
+ * @returns {ElementWithDoc[]} - Always returns an array of element with document elements.
743
+ * @readonly
744
+ */
745
+ static _preElemsWithDoc(elems, where) {
746
+ return TinyHtml._preElemsTemplate(elems, where, [Element, Document], ['Element', 'Document']);
747
+ }
748
+
749
+ /**
750
+ * Ensures the input is returned as an single element with document element.
751
+ * Useful to normalize operations across multiple or single element with document elements.
752
+ *
753
+ * @param {TinyElementWithDoc|TinyElementWithDoc[]} elems - A single element/window element or array of html elements.
754
+ * @param {string} where - The method or context name where validation is being called.
755
+ * @returns {ElementWithDoc} - Always returns an single element/window element.
756
+ * @readonly
757
+ */
758
+ static _preElemWithDoc(elems, where) {
759
+ return TinyHtml._preElemTemplate(elems, where, [Element, Document], ['Element', 'Document']);
760
+ }
761
+
714
762
  /**
715
763
  * Normalizes and converts one or more DOM elements (or TinyHtml instances)
716
764
  * into an array of `TinyHtml` instances.
@@ -2419,7 +2467,7 @@ class TinyHtml {
2419
2467
  */
2420
2468
  static setWinScrollTop(value) {
2421
2469
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
2422
- window.scrollTo({ top: value });
2470
+ TinyHtml.setScrollTop(window, value);
2423
2471
  }
2424
2472
 
2425
2473
  /**
@@ -2428,7 +2476,7 @@ class TinyHtml {
2428
2476
  */
2429
2477
  static setWinScrollLeft(value) {
2430
2478
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
2431
- window.scrollTo({ left: value });
2479
+ TinyHtml.setScrollLeft(window, value);
2432
2480
  }
2433
2481
 
2434
2482
  /**
@@ -2745,6 +2793,30 @@ class TinyHtml {
2745
2793
 
2746
2794
  //////////////////////////////////////////////////
2747
2795
 
2796
+ /**
2797
+ * Applies an animation to one or multiple TinyElement instances.
2798
+ *
2799
+ * @param {TinyElement|TinyElement[]} el - A single TinyElement or an array of TinyElements to animate.
2800
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2801
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2802
+ * @returns {TinyElement|TinyElement[]}
2803
+ */
2804
+ static animate(el, keyframes, ops) {
2805
+ TinyHtml._preElems(el, 'animate').forEach((elem) => elem.animate(keyframes, ops));
2806
+ return el;
2807
+ }
2808
+
2809
+ /**
2810
+ * Applies an animation to one or multiple TinyElement instances.
2811
+ *
2812
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2813
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2814
+ * @returns {TinyElement|TinyElement[]}
2815
+ */
2816
+ animate(keyframes, ops) {
2817
+ return TinyHtml.animate(this, keyframes, ops);
2818
+ }
2819
+
2748
2820
  /**
2749
2821
  * Gets the offset of the element relative to the document.
2750
2822
  * @param {TinyElement} el - Target element.
@@ -2900,26 +2972,147 @@ class TinyHtml {
2900
2972
  }
2901
2973
 
2902
2974
  /**
2903
- * Sets the vertical scroll position.
2904
- * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
2905
- * @param {number} value - Scroll top value.
2906
- * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2975
+ * Collection of easing functions used for scroll and animation calculations.
2976
+ * Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
2977
+ *
2978
+ * @type {Record<string, (t: number) => number>}
2907
2979
  */
2908
- static setScrollTop(el, value) {
2909
- if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
2910
- TinyHtml._preElemsAndWindow(el, 'setScrollTop').forEach((elem) => {
2911
- if (TinyHtml.isWindow(elem)) {
2912
- elem.scrollTo(elem.pageXOffset, value);
2980
+ static easings = {
2981
+ linear: (t) => t,
2982
+ easeInQuad: (t) => t * t,
2983
+ easeOutQuad: (t) => t * (2 - t),
2984
+ easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
2985
+ easeInCubic: (t) => t * t * t,
2986
+ easeOutCubic: (t) => --t * t * t + 1,
2987
+ easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
2988
+ };
2989
+
2990
+ /**
2991
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
2992
+ * using a custom duration and easing function.
2993
+ *
2994
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
2995
+ *
2996
+ * @param {TinyElementAndWindow | TinyElementAndWindow[]} el - A single element, array of elements, or the window to scroll.
2997
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
2998
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
2999
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
3000
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
3001
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
3002
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
3003
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
3004
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
3005
+ * @throws {TypeError} If `el` is not a valid element, array, or window.
3006
+ * @throws {TypeError} If `targetX` or `targetY` is defined but not a number.
3007
+ * @throws {TypeError} If `duration` is defined but not a number.
3008
+ * @throws {TypeError} If `easing` is defined but not a valid easing function name.
3009
+ * @throws {TypeError} If `onAnimation` is defined but not a function.
3010
+ */
3011
+ static scrollToXY(el, { targetX, targetY, duration, easing, onAnimation } = {}) {
3012
+ if (targetX !== undefined && typeof targetX !== 'number')
3013
+ throw new TypeError('`targetX` must be a number if provided.');
3014
+ if (targetY !== undefined && typeof targetY !== 'number')
3015
+ throw new TypeError('`targetY` must be a number if provided.');
3016
+ if (duration !== undefined && typeof duration !== 'number')
3017
+ throw new TypeError('`duration` must be a number if provided.');
3018
+ if (easing !== undefined && typeof easing !== 'string')
3019
+ throw new TypeError('`easing` must be a string if provided.');
3020
+ if (easing !== undefined && typeof TinyHtml.easings[easing] !== 'function')
3021
+ throw new TypeError(`Unknown easing function: "${easing}".`);
3022
+ if (onAnimation !== undefined && typeof onAnimation !== 'function')
3023
+ throw new TypeError('`onAnimation` must be a function if provided.');
3024
+
3025
+ /**
3026
+ * Performs an instant scroll to the given coordinates.
3027
+ *
3028
+ * @param {ElementAndWindow} elem - The element or window to scroll.
3029
+ * @param {number} newX - The final horizontal scroll position.
3030
+ * @param {number} newY - The final vertical scroll position.
3031
+ * @param {number} time - Normalized progress value.
3032
+ */
3033
+ const executeScroll = (elem, newX, newY, time) => {
3034
+ if (elem instanceof Window) {
3035
+ window.scrollTo(newX, newY);
2913
3036
  } else if (elem.nodeType === 9) {
2914
3037
  // @ts-ignore
2915
- elem.defaultView.scrollTo(elem.defaultView.pageXOffset, value);
3038
+ elem.defaultView.scrollTo(newX, newY);
2916
3039
  } else {
2917
- elem.scrollTop = value;
3040
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
3041
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
3042
+ if (startX !== newX) elem.scrollLeft = newX;
3043
+ if (startY !== newY) elem.scrollTop = newY;
2918
3044
  }
3045
+ if (typeof onAnimation === 'function')
3046
+ onAnimation({ x: newX, y: newY, isComplete: time >= 1, time });
3047
+ };
3048
+
3049
+ TinyHtml._preElemsAndWindow(el, 'scrollToXY').forEach((elem) => {
3050
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
3051
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
3052
+ const targX = targetX ?? startX;
3053
+ const targY = targetY ?? startY;
3054
+
3055
+ const changeX = targX - startX;
3056
+ const changeY = targY - startY;
3057
+
3058
+ const ease = (typeof easing === 'string' && TinyHtml.easings[easing]) || null;
3059
+ if (typeof duration !== 'number' || typeof ease !== 'function')
3060
+ return executeScroll(elem, targX, targY, 1);
3061
+ const startTime = performance.now();
3062
+ const dur = duration ?? 0;
3063
+
3064
+ /**
3065
+ * Animates the scroll position based on easing and time.
3066
+ *
3067
+ * @param {number} currentTime - Timestamp provided by requestAnimationFrame.
3068
+ */
3069
+ function animateScroll(currentTime) {
3070
+ if (typeof ease !== 'function') return;
3071
+ const time = Math.min(1, (currentTime - startTime) / dur);
3072
+ const easedTime = ease(time);
3073
+
3074
+ const newX = startX + changeX * easedTime;
3075
+ const newY = startY + changeY * easedTime;
3076
+ executeScroll(elem, newX, newY, time);
3077
+
3078
+ if (time < 1) requestAnimationFrame(animateScroll);
3079
+ }
3080
+
3081
+ requestAnimationFrame(animateScroll);
2919
3082
  });
2920
3083
  return el;
2921
3084
  }
2922
3085
 
3086
+ /**
3087
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
3088
+ * using a custom duration and easing function.
3089
+ *
3090
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
3091
+ *
3092
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
3093
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
3094
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
3095
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
3096
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
3097
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
3098
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
3099
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
3100
+ */
3101
+ scrollToXY({ targetX, targetY, duration, easing, onAnimation } = {}) {
3102
+ return TinyHtml.scrollToXY(this, { targetX, targetY, duration, easing, onAnimation });
3103
+ }
3104
+
3105
+ /**
3106
+ * Sets the vertical scroll position.
3107
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
3108
+ * @param {number} value - Scroll top value.
3109
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
3110
+ */
3111
+ static setScrollTop(el, value) {
3112
+ if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
3113
+ return TinyHtml.scrollToXY(el, { targetY: value });
3114
+ }
3115
+
2923
3116
  /**
2924
3117
  * Sets the vertical scroll position.
2925
3118
  * @param {number} value - Scroll top value.
@@ -2937,17 +3130,7 @@ class TinyHtml {
2937
3130
  */
2938
3131
  static setScrollLeft(el, value) {
2939
3132
  if (typeof value !== 'number') throw new TypeError('ScrollLeft value must be a number.');
2940
- TinyHtml._preElemsAndWindow(el, 'setScrollLeft').forEach((elem) => {
2941
- if (TinyHtml.isWindow(elem)) {
2942
- elem.scrollTo(value, elem.pageYOffset);
2943
- } else if (elem.nodeType === 9) {
2944
- // @ts-ignore
2945
- elem.defaultView.scrollTo(value, elem.defaultView.pageYOffset);
2946
- } else {
2947
- elem.scrollLeft = value;
2948
- }
2949
- });
2950
- return el;
3133
+ return TinyHtml.scrollToXY(el, { targetX: value });
2951
3134
  }
2952
3135
 
2953
3136
  /**
@@ -3835,12 +4018,120 @@ class TinyHtml {
3835
4018
 
3836
4019
  ////////////////////////////////////////////
3837
4020
 
4021
+ /**
4022
+ * Registers a listener for the "paste" event to extract files and text from the clipboard (e.g., when the user presses Ctrl+V).
4023
+ *
4024
+ * This method allows reacting to pasted content by providing separate callbacks for files and plain text.
4025
+ * Useful for building file upload areas, rich-text editors, or input enhancements.
4026
+ *
4027
+ * @param {TinyElementWithDoc|TinyElementWithDoc[]} el - The target element(s) where the "paste" event will be listened.
4028
+ * @param {Object} [settings={}] - Optional callbacks to handle clipboard content.
4029
+ * @param {(data: DataTransferItem, file: File) => void} [settings.onFilePaste] - Called for each file pasted from the clipboard (e.g., images).
4030
+ * @param {(data: DataTransferItem, text: string) => void} [settings.onTextPaste] - Called when plain text is pasted from the clipboard.
4031
+ * @returns {EventListenerOrEventListenerObject} The internal "paste" event handler used.
4032
+ */
4033
+ static listenForPaste(el, { onFilePaste, onTextPaste } = {}) {
4034
+ if (typeof onFilePaste !== 'undefined' && typeof onFilePaste !== 'function')
4035
+ throw new TypeError('onFilePaste must be a function.');
4036
+ if (typeof onTextPaste !== 'undefined' && typeof onTextPaste !== 'function')
4037
+ throw new TypeError('onTextPaste must be a function.');
4038
+
4039
+ /** @type {EventListenerOrEventListenerObject} */
4040
+ const pasteEvent = (event) => {
4041
+ if (!(event instanceof ClipboardEvent)) return;
4042
+ const items = event.clipboardData?.items || [];
4043
+ for (const item of items) {
4044
+ if (item.kind === 'file') {
4045
+ if (typeof onFilePaste === 'function') {
4046
+ const file = item.getAsFile();
4047
+ if (file) onFilePaste(item, file);
4048
+ }
4049
+ } else if (item.kind === 'string') {
4050
+ if (typeof onTextPaste === 'function')
4051
+ item.getAsString((text) => onTextPaste(item, text));
4052
+ }
4053
+ }
4054
+ };
4055
+
4056
+ TinyHtml._preElemsWithDoc(el, 'listenForPaste').forEach((elem) =>
4057
+ TinyHtml.on(elem, 'paste', pasteEvent),
4058
+ );
4059
+ return pasteEvent;
4060
+ }
4061
+
4062
+ /**
4063
+ * Registers a listener for the "paste" event to extract files and text from the clipboard (e.g., when the user presses Ctrl+V).
4064
+ *
4065
+ * This method allows reacting to pasted content by providing separate callbacks for files and plain text.
4066
+ * Useful for building file upload areas, rich-text editors, or input enhancements.
4067
+ *
4068
+ * @param {Object} [settings={}] - Optional callbacks to handle clipboard content.
4069
+ * @param {(data: DataTransferItem, file: File) => void} [settings.onFilePaste] - Called for each file pasted from the clipboard (e.g., images).
4070
+ * @param {(data: DataTransferItem, text: string) => void} [settings.onTextPaste] - Called when plain text is pasted from the clipboard.
4071
+ * @returns {EventListenerOrEventListenerObject} The internal "paste" event handler used.
4072
+ */
4073
+ listenForPaste({ onFilePaste, onTextPaste } = {}) {
4074
+ return TinyHtml.listenForPaste(this, { onFilePaste, onTextPaste });
4075
+ }
4076
+
4077
+ /**
4078
+ * Checks if the element has a listener for a specific event.
4079
+ *
4080
+ * @param {TinyEventTarget} el - The element to check.
4081
+ * @param {string} event - The event name to check.
4082
+ * @returns {boolean}
4083
+ */
4084
+ static hasEventListener(el, event) {
4085
+ const elem = TinyHtml._preEventTargetElem(el, 'hasEventListener');
4086
+ if (!__eventRegistry.has(elem)) return false;
4087
+ const events = __eventRegistry.get(elem);
4088
+ return !!(events && Array.isArray(events[event]) && events[event].length > 0);
4089
+ }
4090
+
4091
+ /**
4092
+ * Checks if the element has a listener for a specific event.
4093
+ *
4094
+ * @param {string} event - The event name to check.
4095
+ * @returns {boolean}
4096
+ */
4097
+ hasEventListener(event) {
4098
+ return TinyHtml.hasEventListener(this, event);
4099
+ }
4100
+
4101
+ /**
4102
+ * Checks if the element has the exact handler registered for a specific event.
4103
+ *
4104
+ * @param {TinyEventTarget} el - The element to check.
4105
+ * @param {string} event - The event name to check.
4106
+ * @param {EventListenerOrEventListenerObject} handler - The handler function to check.
4107
+ * @returns {boolean}
4108
+ */
4109
+ static hasExactEventListener(el, event, handler) {
4110
+ const elem = TinyHtml._preEventTargetElem(el, 'hasExactEventListener');
4111
+ if (typeof handler !== 'function') throw new TypeError('The "handler" must be a function.');
4112
+ if (!__eventRegistry.has(elem)) return false;
4113
+ const events = __eventRegistry.get(elem);
4114
+ if (!events || !Array.isArray(events[event])) return false;
4115
+ return events[event].some((item) => item.handler === handler);
4116
+ }
4117
+
4118
+ /**
4119
+ * Checks if the element has the exact handler registered for a specific event.
4120
+ *
4121
+ * @param {string} event - The event name to check.
4122
+ * @param {EventListenerOrEventListenerObject} handler - The handler function to check.
4123
+ * @returns {boolean}
4124
+ */
4125
+ hasExactEventListener(event, handler) {
4126
+ return TinyHtml.hasExactEventListener(this, event, handler);
4127
+ }
4128
+
3838
4129
  /**
3839
4130
  * Registers an event listener on the specified element.
3840
4131
  *
3841
4132
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target to listen on.
3842
4133
  * @param {string} event - The event type (e.g. 'click', 'keydown').
3843
- * @param {EventRegistryHandle} handler - The callback function to run on event.
4134
+ * @param {EventListenerOrEventListenerObject|null} handler - The callback function to run on event.
3844
4135
  * @param {EventRegistryOptions} [options] - Optional event listener options.
3845
4136
  * @returns {TinyEventTarget|TinyEventTarget[]}
3846
4137
  */
@@ -3862,7 +4153,7 @@ class TinyHtml {
3862
4153
  * Registers an event listener on the specified element.
3863
4154
  *
3864
4155
  * @param {string} event - The event type (e.g. 'click', 'keydown').
3865
- * @param {EventRegistryHandle} handler - The callback function to run on event.
4156
+ * @param {EventListenerOrEventListenerObject|null} handler - The callback function to run on event.
3866
4157
  * @param {EventRegistryOptions} [options] - Optional event listener options.
3867
4158
  * @returns {TinyEventTarget|TinyEventTarget[]}
3868
4159
  */
@@ -3875,17 +4166,17 @@ class TinyHtml {
3875
4166
  *
3876
4167
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target to listen on.
3877
4168
  * @param {string} event - The event type (e.g. 'click', 'keydown').
3878
- * @param {EventRegistryHandle} handler - The callback function to run on event.
4169
+ * @param {EventListenerOrEventListenerObject} handler - The callback function to run on event.
3879
4170
  * @param {EventRegistryOptions} [options={}] - Optional event listener options.
3880
4171
  * @returns {TinyEventTarget|TinyEventTarget[]}
3881
4172
  */
3882
4173
  static once(el, event, handler, options = {}) {
3883
4174
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
3884
4175
  TinyHtml._preEventTargetElems(el, 'once').forEach((elem) => {
3885
- /** @type {EventRegistryHandle} e */
4176
+ /** @type {EventListenerOrEventListenerObject} */
3886
4177
  const wrapped = (e) => {
3887
4178
  TinyHtml.off(elem, event, wrapped);
3888
- handler(e);
4179
+ if (typeof handler === 'function') handler(e);
3889
4180
  };
3890
4181
 
3891
4182
  TinyHtml.on(
@@ -3902,7 +4193,7 @@ class TinyHtml {
3902
4193
  * Registers an event listener that runs only once, then is removed.
3903
4194
  *
3904
4195
  * @param {string} event - The event type (e.g. 'click', 'keydown').
3905
- * @param {EventRegistryHandle} handler - The callback function to run on event.
4196
+ * @param {EventListenerOrEventListenerObject} handler - The callback function to run on event.
3906
4197
  * @param {EventRegistryOptions} [options={}] - Optional event listener options.
3907
4198
  * @returns {TinyEventTarget|TinyEventTarget[]}
3908
4199
  */
@@ -3915,7 +4206,7 @@ class TinyHtml {
3915
4206
  *
3916
4207
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
3917
4208
  * @param {string} event - The event type.
3918
- * @param {EventRegistryHandle} handler - The function originally bound to the event.
4209
+ * @param {EventListenerOrEventListenerObject|null} handler - The function originally bound to the event.
3919
4210
  * @param {boolean|EventListenerOptions} [options] - Optional listener options.
3920
4211
  * @returns {TinyEventTarget|TinyEventTarget[]}
3921
4212
  */
@@ -3937,7 +4228,7 @@ class TinyHtml {
3937
4228
  * Removes a specific event listener from an element.
3938
4229
  *
3939
4230
  * @param {string} event - The event type.
3940
- * @param {EventRegistryHandle} handler - The function originally bound to the event.
4231
+ * @param {EventListenerOrEventListenerObject|null} handler - The function originally bound to the event.
3941
4232
  * @param {boolean|EventListenerOptions} [options] - Optional listener options.
3942
4233
  * @returns {TinyEventTarget|TinyEventTarget[]}
3943
4234
  */
@@ -3980,7 +4271,7 @@ class TinyHtml {
3980
4271
  * Removes all event listeners of all types from the element.
3981
4272
  *
3982
4273
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
3983
- * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
4274
+ * @param {((handler: EventListenerOrEventListenerObject|null, event: string) => boolean)|null} [filterFn=null] -
3984
4275
  * Optional filter function to selectively remove specific handlers.
3985
4276
  * @returns {TinyEventTarget|TinyEventTarget[]}
3986
4277
  */
@@ -4007,7 +4298,7 @@ class TinyHtml {
4007
4298
  /**
4008
4299
  * Removes all event listeners of all types from the element.
4009
4300
  *
4010
- * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
4301
+ * @param {((handler: EventListenerOrEventListenerObject|null, event: string) => boolean)|null} [filterFn=null] -
4011
4302
  * Optional filter function to selectively remove specific handlers.
4012
4303
  * @returns {TinyEventTarget|TinyEventTarget[]}
4013
4304
  */