tiny-essentials 1.17.0 → 1.17.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.
@@ -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
@@ -2419,7 +2432,7 @@ class TinyHtml {
2419
2432
  */
2420
2433
  static setWinScrollTop(value) {
2421
2434
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
2422
- window.scrollTo({ top: value });
2435
+ TinyHtml.setScrollTop(window, value);
2423
2436
  }
2424
2437
 
2425
2438
  /**
@@ -2428,7 +2441,7 @@ class TinyHtml {
2428
2441
  */
2429
2442
  static setWinScrollLeft(value) {
2430
2443
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
2431
- window.scrollTo({ left: value });
2444
+ TinyHtml.setScrollLeft(window, value);
2432
2445
  }
2433
2446
 
2434
2447
  /**
@@ -2745,6 +2758,30 @@ class TinyHtml {
2745
2758
 
2746
2759
  //////////////////////////////////////////////////
2747
2760
 
2761
+ /**
2762
+ * Applies an animation to one or multiple TinyElement instances.
2763
+ *
2764
+ * @param {TinyElement|TinyElement[]} el - A single TinyElement or an array of TinyElements to animate.
2765
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2766
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2767
+ * @returns {TinyElement|TinyElement[]}
2768
+ */
2769
+ static animate(el, keyframes, ops) {
2770
+ TinyHtml._preElems(el, 'animate').forEach((elem) => elem.animate(keyframes, ops));
2771
+ return el;
2772
+ }
2773
+
2774
+ /**
2775
+ * Applies an animation to one or multiple TinyElement instances.
2776
+ *
2777
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2778
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2779
+ * @returns {TinyElement|TinyElement[]}
2780
+ */
2781
+ animate(keyframes, ops) {
2782
+ return TinyHtml.animate(this, keyframes, ops);
2783
+ }
2784
+
2748
2785
  /**
2749
2786
  * Gets the offset of the element relative to the document.
2750
2787
  * @param {TinyElement} el - Target element.
@@ -2900,26 +2937,147 @@ class TinyHtml {
2900
2937
  }
2901
2938
 
2902
2939
  /**
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[]}
2940
+ * Collection of easing functions used for scroll and animation calculations.
2941
+ * Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
2942
+ *
2943
+ * @type {Record<string, (t: number) => number>}
2907
2944
  */
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);
2945
+ static easings = {
2946
+ linear: (t) => t,
2947
+ easeInQuad: (t) => t * t,
2948
+ easeOutQuad: (t) => t * (2 - t),
2949
+ easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
2950
+ easeInCubic: (t) => t * t * t,
2951
+ easeOutCubic: (t) => --t * t * t + 1,
2952
+ easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
2953
+ };
2954
+
2955
+ /**
2956
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
2957
+ * using a custom duration and easing function.
2958
+ *
2959
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
2960
+ *
2961
+ * @param {TinyElementAndWindow | TinyElementAndWindow[]} el - A single element, array of elements, or the window to scroll.
2962
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
2963
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
2964
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
2965
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
2966
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
2967
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
2968
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
2969
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2970
+ * @throws {TypeError} If `el` is not a valid element, array, or window.
2971
+ * @throws {TypeError} If `targetX` or `targetY` is defined but not a number.
2972
+ * @throws {TypeError} If `duration` is defined but not a number.
2973
+ * @throws {TypeError} If `easing` is defined but not a valid easing function name.
2974
+ * @throws {TypeError} If `onAnimation` is defined but not a function.
2975
+ */
2976
+ static scrollToXY(el, { targetX, targetY, duration, easing, onAnimation } = {}) {
2977
+ if (targetX !== undefined && typeof targetX !== 'number')
2978
+ throw new TypeError('`targetX` must be a number if provided.');
2979
+ if (targetY !== undefined && typeof targetY !== 'number')
2980
+ throw new TypeError('`targetY` must be a number if provided.');
2981
+ if (duration !== undefined && typeof duration !== 'number')
2982
+ throw new TypeError('`duration` must be a number if provided.');
2983
+ if (easing !== undefined && typeof easing !== 'string')
2984
+ throw new TypeError('`easing` must be a string if provided.');
2985
+ if (easing !== undefined && typeof TinyHtml.easings[easing] !== 'function')
2986
+ throw new TypeError(`Unknown easing function: "${easing}".`);
2987
+ if (onAnimation !== undefined && typeof onAnimation !== 'function')
2988
+ throw new TypeError('`onAnimation` must be a function if provided.');
2989
+
2990
+ /**
2991
+ * Performs an instant scroll to the given coordinates.
2992
+ *
2993
+ * @param {ElementAndWindow} elem - The element or window to scroll.
2994
+ * @param {number} newX - The final horizontal scroll position.
2995
+ * @param {number} newY - The final vertical scroll position.
2996
+ * @param {number} time - Normalized progress value.
2997
+ */
2998
+ const executeScroll = (elem, newX, newY, time) => {
2999
+ if (elem instanceof Window) {
3000
+ window.scrollTo(newX, newY);
2913
3001
  } else if (elem.nodeType === 9) {
2914
3002
  // @ts-ignore
2915
- elem.defaultView.scrollTo(elem.defaultView.pageXOffset, value);
3003
+ elem.defaultView.scrollTo(newX, newY);
2916
3004
  } else {
2917
- elem.scrollTop = value;
3005
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
3006
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
3007
+ if (startX !== newX) elem.scrollLeft = newX;
3008
+ if (startY !== newY) elem.scrollTop = newY;
3009
+ }
3010
+ if (typeof onAnimation === 'function')
3011
+ onAnimation({ x: newX, y: newY, isComplete: time >= 1, time });
3012
+ };
3013
+
3014
+ TinyHtml._preElemsAndWindow(el, 'scrollToXY').forEach((elem) => {
3015
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
3016
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
3017
+ const targX = targetX ?? startX;
3018
+ const targY = targetY ?? startY;
3019
+
3020
+ const changeX = targX - startX;
3021
+ const changeY = targY - startY;
3022
+
3023
+ const ease = (typeof easing === 'string' && TinyHtml.easings[easing]) || null;
3024
+ if (typeof duration !== 'number' || typeof ease !== 'function')
3025
+ return executeScroll(elem, targX, targY, 1);
3026
+ const startTime = performance.now();
3027
+ const dur = duration ?? 0;
3028
+
3029
+ /**
3030
+ * Animates the scroll position based on easing and time.
3031
+ *
3032
+ * @param {number} currentTime - Timestamp provided by requestAnimationFrame.
3033
+ */
3034
+ function animateScroll(currentTime) {
3035
+ if (typeof ease !== 'function') return;
3036
+ const time = Math.min(1, (currentTime - startTime) / dur);
3037
+ const easedTime = ease(time);
3038
+
3039
+ const newX = startX + changeX * easedTime;
3040
+ const newY = startY + changeY * easedTime;
3041
+ executeScroll(elem, newX, newY, time);
3042
+
3043
+ if (time < 1) requestAnimationFrame(animateScroll);
2918
3044
  }
3045
+
3046
+ requestAnimationFrame(animateScroll);
2919
3047
  });
2920
3048
  return el;
2921
3049
  }
2922
3050
 
3051
+ /**
3052
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
3053
+ * using a custom duration and easing function.
3054
+ *
3055
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
3056
+ *
3057
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
3058
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
3059
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
3060
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
3061
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
3062
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
3063
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
3064
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
3065
+ */
3066
+ scrollToXY({ targetX, targetY, duration, easing, onAnimation } = {}) {
3067
+ return TinyHtml.scrollToXY(this, { targetX, targetY, duration, easing, onAnimation });
3068
+ }
3069
+
3070
+ /**
3071
+ * Sets the vertical scroll position.
3072
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
3073
+ * @param {number} value - Scroll top value.
3074
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
3075
+ */
3076
+ static setScrollTop(el, value) {
3077
+ if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
3078
+ return TinyHtml.scrollToXY(el, { targetY: value });
3079
+ }
3080
+
2923
3081
  /**
2924
3082
  * Sets the vertical scroll position.
2925
3083
  * @param {number} value - Scroll top value.
@@ -2937,17 +3095,7 @@ class TinyHtml {
2937
3095
  */
2938
3096
  static setScrollLeft(el, value) {
2939
3097
  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;
3098
+ return TinyHtml.scrollToXY(el, { targetX: value });
2951
3099
  }
2952
3100
 
2953
3101
  /**
@@ -1,4 +1,18 @@
1
1
  export default TinyHtml;
2
+ /**
3
+ * Callback invoked on each animation frame with the current scroll position,
4
+ * normalized animation time (`0` to `1`), and a completion flag.
5
+ */
6
+ export type OnScrollAnimation = (progress: {
7
+ x: number;
8
+ y: number;
9
+ isComplete: boolean;
10
+ time: number;
11
+ }) => void;
12
+ /**
13
+ * A list of supported easing function names for smooth animations.
14
+ */
15
+ export type Easings = "linear" | "easeInQuad" | "easeOutQuad" | "easeInOutQuad" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic";
2
16
  /**
3
17
  * Represents a raw Node element or an instance of TinyHtml.
4
18
  * This type is used to abstract interactions with both plain elements
@@ -1084,6 +1098,15 @@ declare class TinyHtml {
1084
1098
  * @returns {number}
1085
1099
  */
1086
1100
  static outerWidth(el: TinyElementAndWinAndDoc, includeMargin?: boolean): number;
1101
+ /**
1102
+ * Applies an animation to one or multiple TinyElement instances.
1103
+ *
1104
+ * @param {TinyElement|TinyElement[]} el - A single TinyElement or an array of TinyElements to animate.
1105
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
1106
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
1107
+ * @returns {TinyElement|TinyElement[]}
1108
+ */
1109
+ static animate(el: TinyElement | TinyElement[], keyframes: Keyframe[] | PropertyIndexedKeyframes | null, ops?: number | KeyframeAnimationOptions): TinyElement | TinyElement[];
1087
1110
  /**
1088
1111
  * Gets the offset of the element relative to the document.
1089
1112
  * @param {TinyElement} el - Target element.
@@ -1120,6 +1143,41 @@ declare class TinyHtml {
1120
1143
  * @returns {number}
1121
1144
  */
1122
1145
  static scrollLeft(el: TinyElementAndWindow): number;
1146
+ /**
1147
+ * Collection of easing functions used for scroll and animation calculations.
1148
+ * Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
1149
+ *
1150
+ * @type {Record<string, (t: number) => number>}
1151
+ */
1152
+ static easings: Record<string, (t: number) => number>;
1153
+ /**
1154
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
1155
+ * using a custom duration and easing function.
1156
+ *
1157
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
1158
+ *
1159
+ * @param {TinyElementAndWindow | TinyElementAndWindow[]} el - A single element, array of elements, or the window to scroll.
1160
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
1161
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
1162
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
1163
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
1164
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
1165
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
1166
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
1167
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
1168
+ * @throws {TypeError} If `el` is not a valid element, array, or window.
1169
+ * @throws {TypeError} If `targetX` or `targetY` is defined but not a number.
1170
+ * @throws {TypeError} If `duration` is defined but not a number.
1171
+ * @throws {TypeError} If `easing` is defined but not a valid easing function name.
1172
+ * @throws {TypeError} If `onAnimation` is defined but not a function.
1173
+ */
1174
+ static scrollToXY(el: TinyElementAndWindow | TinyElementAndWindow[], { targetX, targetY, duration, easing, onAnimation }?: {
1175
+ targetX?: number | undefined;
1176
+ targetY?: number | undefined;
1177
+ duration?: number | undefined;
1178
+ easing?: Easings | undefined;
1179
+ onAnimation?: OnScrollAnimation | undefined;
1180
+ }): TinyElementAndWindow | TinyElementAndWindow[];
1123
1181
  /**
1124
1182
  * Sets the vertical scroll position.
1125
1183
  * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
@@ -2093,6 +2151,14 @@ declare class TinyHtml {
2093
2151
  * @returns {number}
2094
2152
  */
2095
2153
  outerWidth(includeMargin?: boolean): number;
2154
+ /**
2155
+ * Applies an animation to one or multiple TinyElement instances.
2156
+ *
2157
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2158
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2159
+ * @returns {TinyElement|TinyElement[]}
2160
+ */
2161
+ animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, ops?: number | KeyframeAnimationOptions): TinyElement | TinyElement[];
2096
2162
  /**
2097
2163
  * Gets the offset of the element relative to the document.
2098
2164
  * @returns {{top: number, left: number}}
@@ -2124,6 +2190,28 @@ declare class TinyHtml {
2124
2190
  * @returns {number}
2125
2191
  */
2126
2192
  scrollLeft(): number;
2193
+ /**
2194
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
2195
+ * using a custom duration and easing function.
2196
+ *
2197
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
2198
+ *
2199
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
2200
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
2201
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
2202
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
2203
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
2204
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
2205
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
2206
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2207
+ */
2208
+ scrollToXY({ targetX, targetY, duration, easing, onAnimation }?: {
2209
+ targetX?: number | undefined;
2210
+ targetY?: number | undefined;
2211
+ duration?: number | undefined;
2212
+ easing?: Easings | undefined;
2213
+ onAnimation?: OnScrollAnimation | undefined;
2214
+ }): TinyElementAndWindow | TinyElementAndWindow[];
2127
2215
  /**
2128
2216
  * Sets the vertical scroll position.
2129
2217
  * @param {number} value - Scroll top value.
@@ -1,5 +1,16 @@
1
1
  import * as TinyCollision from '../basics/collision.mjs';
2
2
  const { areElsColliding, areElsPerfColliding, areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, } = TinyCollision;
3
+ /**
4
+ * Callback invoked on each animation frame with the current scroll position,
5
+ * normalized animation time (`0` to `1`), and a completion flag.
6
+ *
7
+ * @typedef {(progress: { x: number, y: number, isComplete: boolean, time: number }) => void} OnScrollAnimation
8
+ */
9
+ /**
10
+ * A list of supported easing function names for smooth animations.
11
+ *
12
+ * @typedef {'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'} Easings
13
+ */
3
14
  /**
4
15
  * Represents a raw Node element or an instance of TinyHtml.
5
16
  * This type is used to abstract interactions with both plain elements
@@ -2179,7 +2190,7 @@ class TinyHtml {
2179
2190
  static setWinScrollTop(value) {
2180
2191
  if (typeof value !== 'number')
2181
2192
  throw new TypeError('The value must be a number.');
2182
- window.scrollTo({ top: value });
2193
+ TinyHtml.setScrollTop(window, value);
2183
2194
  }
2184
2195
  /**
2185
2196
  * Sets the horizontal scroll position of the window.
@@ -2188,7 +2199,7 @@ class TinyHtml {
2188
2199
  static setWinScrollLeft(value) {
2189
2200
  if (typeof value !== 'number')
2190
2201
  throw new TypeError('The value must be a number.');
2191
- window.scrollTo({ left: value });
2202
+ TinyHtml.setScrollLeft(window, value);
2192
2203
  }
2193
2204
  /**
2194
2205
  * Gets the vertical scroll position of the window.
@@ -2464,6 +2475,28 @@ class TinyHtml {
2464
2475
  return TinyHtml.outerWidth(this, includeMargin);
2465
2476
  }
2466
2477
  //////////////////////////////////////////////////
2478
+ /**
2479
+ * Applies an animation to one or multiple TinyElement instances.
2480
+ *
2481
+ * @param {TinyElement|TinyElement[]} el - A single TinyElement or an array of TinyElements to animate.
2482
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2483
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2484
+ * @returns {TinyElement|TinyElement[]}
2485
+ */
2486
+ static animate(el, keyframes, ops) {
2487
+ TinyHtml._preElems(el, 'animate').forEach((elem) => elem.animate(keyframes, ops));
2488
+ return el;
2489
+ }
2490
+ /**
2491
+ * Applies an animation to one or multiple TinyElement instances.
2492
+ *
2493
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2494
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2495
+ * @returns {TinyElement|TinyElement[]}
2496
+ */
2497
+ animate(keyframes, ops) {
2498
+ return TinyHtml.animate(this, keyframes, ops);
2499
+ }
2467
2500
  /**
2468
2501
  * Gets the offset of the element relative to the document.
2469
2502
  * @param {TinyElement} el - Target element.
@@ -2598,28 +2631,142 @@ class TinyHtml {
2598
2631
  return TinyHtml.scrollLeft(this);
2599
2632
  }
2600
2633
  /**
2601
- * Sets the vertical scroll position.
2602
- * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
2603
- * @param {number} value - Scroll top value.
2604
- * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2634
+ * Collection of easing functions used for scroll and animation calculations.
2635
+ * Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
2636
+ *
2637
+ * @type {Record<string, (t: number) => number>}
2605
2638
  */
2606
- static setScrollTop(el, value) {
2607
- if (typeof value !== 'number')
2608
- throw new TypeError('ScrollTop value must be a number.');
2609
- TinyHtml._preElemsAndWindow(el, 'setScrollTop').forEach((elem) => {
2610
- if (TinyHtml.isWindow(elem)) {
2611
- elem.scrollTo(elem.pageXOffset, value);
2639
+ static easings = {
2640
+ linear: (t) => t,
2641
+ easeInQuad: (t) => t * t,
2642
+ easeOutQuad: (t) => t * (2 - t),
2643
+ easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
2644
+ easeInCubic: (t) => t * t * t,
2645
+ easeOutCubic: (t) => --t * t * t + 1,
2646
+ easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
2647
+ };
2648
+ /**
2649
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
2650
+ * using a custom duration and easing function.
2651
+ *
2652
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
2653
+ *
2654
+ * @param {TinyElementAndWindow | TinyElementAndWindow[]} el - A single element, array of elements, or the window to scroll.
2655
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
2656
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
2657
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
2658
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
2659
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
2660
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
2661
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
2662
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2663
+ * @throws {TypeError} If `el` is not a valid element, array, or window.
2664
+ * @throws {TypeError} If `targetX` or `targetY` is defined but not a number.
2665
+ * @throws {TypeError} If `duration` is defined but not a number.
2666
+ * @throws {TypeError} If `easing` is defined but not a valid easing function name.
2667
+ * @throws {TypeError} If `onAnimation` is defined but not a function.
2668
+ */
2669
+ static scrollToXY(el, { targetX, targetY, duration, easing, onAnimation } = {}) {
2670
+ if (targetX !== undefined && typeof targetX !== 'number')
2671
+ throw new TypeError('`targetX` must be a number if provided.');
2672
+ if (targetY !== undefined && typeof targetY !== 'number')
2673
+ throw new TypeError('`targetY` must be a number if provided.');
2674
+ if (duration !== undefined && typeof duration !== 'number')
2675
+ throw new TypeError('`duration` must be a number if provided.');
2676
+ if (easing !== undefined && typeof easing !== 'string')
2677
+ throw new TypeError('`easing` must be a string if provided.');
2678
+ if (easing !== undefined && typeof TinyHtml.easings[easing] !== 'function')
2679
+ throw new TypeError(`Unknown easing function: "${easing}".`);
2680
+ if (onAnimation !== undefined && typeof onAnimation !== 'function')
2681
+ throw new TypeError('`onAnimation` must be a function if provided.');
2682
+ /**
2683
+ * Performs an instant scroll to the given coordinates.
2684
+ *
2685
+ * @param {ElementAndWindow} elem - The element or window to scroll.
2686
+ * @param {number} newX - The final horizontal scroll position.
2687
+ * @param {number} newY - The final vertical scroll position.
2688
+ * @param {number} time - Normalized progress value.
2689
+ */
2690
+ const executeScroll = (elem, newX, newY, time) => {
2691
+ if (elem instanceof Window) {
2692
+ window.scrollTo(newX, newY);
2612
2693
  }
2613
2694
  else if (elem.nodeType === 9) {
2614
2695
  // @ts-ignore
2615
- elem.defaultView.scrollTo(elem.defaultView.pageXOffset, value);
2696
+ elem.defaultView.scrollTo(newX, newY);
2616
2697
  }
2617
2698
  else {
2618
- elem.scrollTop = value;
2699
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
2700
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
2701
+ if (startX !== newX)
2702
+ elem.scrollLeft = newX;
2703
+ if (startY !== newY)
2704
+ elem.scrollTop = newY;
2705
+ }
2706
+ if (typeof onAnimation === 'function')
2707
+ onAnimation({ x: newX, y: newY, isComplete: time >= 1, time });
2708
+ };
2709
+ TinyHtml._preElemsAndWindow(el, 'scrollToXY').forEach((elem) => {
2710
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
2711
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
2712
+ const targX = targetX ?? startX;
2713
+ const targY = targetY ?? startY;
2714
+ const changeX = targX - startX;
2715
+ const changeY = targY - startY;
2716
+ const ease = (typeof easing === 'string' && TinyHtml.easings[easing]) || null;
2717
+ if (typeof duration !== 'number' || typeof ease !== 'function')
2718
+ return executeScroll(elem, targX, targY, 1);
2719
+ const startTime = performance.now();
2720
+ const dur = duration ?? 0;
2721
+ /**
2722
+ * Animates the scroll position based on easing and time.
2723
+ *
2724
+ * @param {number} currentTime - Timestamp provided by requestAnimationFrame.
2725
+ */
2726
+ function animateScroll(currentTime) {
2727
+ if (typeof ease !== 'function')
2728
+ return;
2729
+ const time = Math.min(1, (currentTime - startTime) / dur);
2730
+ const easedTime = ease(time);
2731
+ const newX = startX + changeX * easedTime;
2732
+ const newY = startY + changeY * easedTime;
2733
+ executeScroll(elem, newX, newY, time);
2734
+ if (time < 1)
2735
+ requestAnimationFrame(animateScroll);
2619
2736
  }
2737
+ requestAnimationFrame(animateScroll);
2620
2738
  });
2621
2739
  return el;
2622
2740
  }
2741
+ /**
2742
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
2743
+ * using a custom duration and easing function.
2744
+ *
2745
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
2746
+ *
2747
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
2748
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
2749
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
2750
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
2751
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
2752
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
2753
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
2754
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2755
+ */
2756
+ scrollToXY({ targetX, targetY, duration, easing, onAnimation } = {}) {
2757
+ return TinyHtml.scrollToXY(this, { targetX, targetY, duration, easing, onAnimation });
2758
+ }
2759
+ /**
2760
+ * Sets the vertical scroll position.
2761
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
2762
+ * @param {number} value - Scroll top value.
2763
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
2764
+ */
2765
+ static setScrollTop(el, value) {
2766
+ if (typeof value !== 'number')
2767
+ throw new TypeError('ScrollTop value must be a number.');
2768
+ return TinyHtml.scrollToXY(el, { targetY: value });
2769
+ }
2623
2770
  /**
2624
2771
  * Sets the vertical scroll position.
2625
2772
  * @param {number} value - Scroll top value.
@@ -2637,19 +2784,7 @@ class TinyHtml {
2637
2784
  static setScrollLeft(el, value) {
2638
2785
  if (typeof value !== 'number')
2639
2786
  throw new TypeError('ScrollLeft value must be a number.');
2640
- TinyHtml._preElemsAndWindow(el, 'setScrollLeft').forEach((elem) => {
2641
- if (TinyHtml.isWindow(elem)) {
2642
- elem.scrollTo(value, elem.pageYOffset);
2643
- }
2644
- else if (elem.nodeType === 9) {
2645
- // @ts-ignore
2646
- elem.defaultView.scrollTo(value, elem.defaultView.pageYOffset);
2647
- }
2648
- else {
2649
- elem.scrollLeft = value;
2650
- }
2651
- });
2652
- return el;
2787
+ return TinyHtml.scrollToXY(el, { targetX: value });
2653
2788
  }
2654
2789
  /**
2655
2790
  * Sets the horizontal scroll position.