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.
@@ -2573,6 +2573,19 @@ const {
2573
2573
  areElsCollRight: TinyHtml_areElsCollRight,
2574
2574
  } = collision_namespaceObject;
2575
2575
 
2576
+ /**
2577
+ * Callback invoked on each animation frame with the current scroll position,
2578
+ * normalized animation time (`0` to `1`), and a completion flag.
2579
+ *
2580
+ * @typedef {(progress: { x: number, y: number, isComplete: boolean, time: number }) => void} OnScrollAnimation
2581
+ */
2582
+
2583
+ /**
2584
+ * A list of supported easing function names for smooth animations.
2585
+ *
2586
+ * @typedef {'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'} Easings
2587
+ */
2588
+
2576
2589
  /**
2577
2590
  * Represents a raw Node element or an instance of TinyHtml.
2578
2591
  * This type is used to abstract interactions with both plain elements
@@ -4981,7 +4994,7 @@ class TinyHtml {
4981
4994
  */
4982
4995
  static setWinScrollTop(value) {
4983
4996
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
4984
- window.scrollTo({ top: value });
4997
+ TinyHtml.setScrollTop(window, value);
4985
4998
  }
4986
4999
 
4987
5000
  /**
@@ -4990,7 +5003,7 @@ class TinyHtml {
4990
5003
  */
4991
5004
  static setWinScrollLeft(value) {
4992
5005
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
4993
- window.scrollTo({ left: value });
5006
+ TinyHtml.setScrollLeft(window, value);
4994
5007
  }
4995
5008
 
4996
5009
  /**
@@ -5307,6 +5320,30 @@ class TinyHtml {
5307
5320
 
5308
5321
  //////////////////////////////////////////////////
5309
5322
 
5323
+ /**
5324
+ * Applies an animation to one or multiple TinyElement instances.
5325
+ *
5326
+ * @param {TinyElement|TinyElement[]} el - A single TinyElement or an array of TinyElements to animate.
5327
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
5328
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
5329
+ * @returns {TinyElement|TinyElement[]}
5330
+ */
5331
+ static animate(el, keyframes, ops) {
5332
+ TinyHtml._preElems(el, 'animate').forEach((elem) => elem.animate(keyframes, ops));
5333
+ return el;
5334
+ }
5335
+
5336
+ /**
5337
+ * Applies an animation to one or multiple TinyElement instances.
5338
+ *
5339
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
5340
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
5341
+ * @returns {TinyElement|TinyElement[]}
5342
+ */
5343
+ animate(keyframes, ops) {
5344
+ return TinyHtml.animate(this, keyframes, ops);
5345
+ }
5346
+
5310
5347
  /**
5311
5348
  * Gets the offset of the element relative to the document.
5312
5349
  * @param {TinyElement} el - Target element.
@@ -5462,26 +5499,147 @@ class TinyHtml {
5462
5499
  }
5463
5500
 
5464
5501
  /**
5465
- * Sets the vertical scroll position.
5466
- * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
5467
- * @param {number} value - Scroll top value.
5468
- * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
5502
+ * Collection of easing functions used for scroll and animation calculations.
5503
+ * Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
5504
+ *
5505
+ * @type {Record<string, (t: number) => number>}
5469
5506
  */
5470
- static setScrollTop(el, value) {
5471
- if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
5472
- TinyHtml._preElemsAndWindow(el, 'setScrollTop').forEach((elem) => {
5473
- if (TinyHtml.isWindow(elem)) {
5474
- elem.scrollTo(elem.pageXOffset, value);
5507
+ static easings = {
5508
+ linear: (t) => t,
5509
+ easeInQuad: (t) => t * t,
5510
+ easeOutQuad: (t) => t * (2 - t),
5511
+ easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
5512
+ easeInCubic: (t) => t * t * t,
5513
+ easeOutCubic: (t) => --t * t * t + 1,
5514
+ easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
5515
+ };
5516
+
5517
+ /**
5518
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
5519
+ * using a custom duration and easing function.
5520
+ *
5521
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
5522
+ *
5523
+ * @param {TinyElementAndWindow | TinyElementAndWindow[]} el - A single element, array of elements, or the window to scroll.
5524
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
5525
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
5526
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
5527
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
5528
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
5529
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
5530
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
5531
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
5532
+ * @throws {TypeError} If `el` is not a valid element, array, or window.
5533
+ * @throws {TypeError} If `targetX` or `targetY` is defined but not a number.
5534
+ * @throws {TypeError} If `duration` is defined but not a number.
5535
+ * @throws {TypeError} If `easing` is defined but not a valid easing function name.
5536
+ * @throws {TypeError} If `onAnimation` is defined but not a function.
5537
+ */
5538
+ static scrollToXY(el, { targetX, targetY, duration, easing, onAnimation } = {}) {
5539
+ if (targetX !== undefined && typeof targetX !== 'number')
5540
+ throw new TypeError('`targetX` must be a number if provided.');
5541
+ if (targetY !== undefined && typeof targetY !== 'number')
5542
+ throw new TypeError('`targetY` must be a number if provided.');
5543
+ if (duration !== undefined && typeof duration !== 'number')
5544
+ throw new TypeError('`duration` must be a number if provided.');
5545
+ if (easing !== undefined && typeof easing !== 'string')
5546
+ throw new TypeError('`easing` must be a string if provided.');
5547
+ if (easing !== undefined && typeof TinyHtml.easings[easing] !== 'function')
5548
+ throw new TypeError(`Unknown easing function: "${easing}".`);
5549
+ if (onAnimation !== undefined && typeof onAnimation !== 'function')
5550
+ throw new TypeError('`onAnimation` must be a function if provided.');
5551
+
5552
+ /**
5553
+ * Performs an instant scroll to the given coordinates.
5554
+ *
5555
+ * @param {ElementAndWindow} elem - The element or window to scroll.
5556
+ * @param {number} newX - The final horizontal scroll position.
5557
+ * @param {number} newY - The final vertical scroll position.
5558
+ * @param {number} time - Normalized progress value.
5559
+ */
5560
+ const executeScroll = (elem, newX, newY, time) => {
5561
+ if (elem instanceof Window) {
5562
+ window.scrollTo(newX, newY);
5475
5563
  } else if (elem.nodeType === 9) {
5476
5564
  // @ts-ignore
5477
- elem.defaultView.scrollTo(elem.defaultView.pageXOffset, value);
5565
+ elem.defaultView.scrollTo(newX, newY);
5478
5566
  } else {
5479
- elem.scrollTop = value;
5567
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
5568
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
5569
+ if (startX !== newX) elem.scrollLeft = newX;
5570
+ if (startY !== newY) elem.scrollTop = newY;
5571
+ }
5572
+ if (typeof onAnimation === 'function')
5573
+ onAnimation({ x: newX, y: newY, isComplete: time >= 1, time });
5574
+ };
5575
+
5576
+ TinyHtml._preElemsAndWindow(el, 'scrollToXY').forEach((elem) => {
5577
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
5578
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
5579
+ const targX = targetX ?? startX;
5580
+ const targY = targetY ?? startY;
5581
+
5582
+ const changeX = targX - startX;
5583
+ const changeY = targY - startY;
5584
+
5585
+ const ease = (typeof easing === 'string' && TinyHtml.easings[easing]) || null;
5586
+ if (typeof duration !== 'number' || typeof ease !== 'function')
5587
+ return executeScroll(elem, targX, targY, 1);
5588
+ const startTime = performance.now();
5589
+ const dur = duration ?? 0;
5590
+
5591
+ /**
5592
+ * Animates the scroll position based on easing and time.
5593
+ *
5594
+ * @param {number} currentTime - Timestamp provided by requestAnimationFrame.
5595
+ */
5596
+ function animateScroll(currentTime) {
5597
+ if (typeof ease !== 'function') return;
5598
+ const time = Math.min(1, (currentTime - startTime) / dur);
5599
+ const easedTime = ease(time);
5600
+
5601
+ const newX = startX + changeX * easedTime;
5602
+ const newY = startY + changeY * easedTime;
5603
+ executeScroll(elem, newX, newY, time);
5604
+
5605
+ if (time < 1) requestAnimationFrame(animateScroll);
5480
5606
  }
5607
+
5608
+ requestAnimationFrame(animateScroll);
5481
5609
  });
5482
5610
  return el;
5483
5611
  }
5484
5612
 
5613
+ /**
5614
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
5615
+ * using a custom duration and easing function.
5616
+ *
5617
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
5618
+ *
5619
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
5620
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
5621
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
5622
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
5623
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
5624
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
5625
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
5626
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
5627
+ */
5628
+ scrollToXY({ targetX, targetY, duration, easing, onAnimation } = {}) {
5629
+ return TinyHtml.scrollToXY(this, { targetX, targetY, duration, easing, onAnimation });
5630
+ }
5631
+
5632
+ /**
5633
+ * Sets the vertical scroll position.
5634
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
5635
+ * @param {number} value - Scroll top value.
5636
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
5637
+ */
5638
+ static setScrollTop(el, value) {
5639
+ if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
5640
+ return TinyHtml.scrollToXY(el, { targetY: value });
5641
+ }
5642
+
5485
5643
  /**
5486
5644
  * Sets the vertical scroll position.
5487
5645
  * @param {number} value - Scroll top value.
@@ -5499,17 +5657,7 @@ class TinyHtml {
5499
5657
  */
5500
5658
  static setScrollLeft(el, value) {
5501
5659
  if (typeof value !== 'number') throw new TypeError('ScrollLeft value must be a number.');
5502
- TinyHtml._preElemsAndWindow(el, 'setScrollLeft').forEach((elem) => {
5503
- if (TinyHtml.isWindow(elem)) {
5504
- elem.scrollTo(value, elem.pageYOffset);
5505
- } else if (elem.nodeType === 9) {
5506
- // @ts-ignore
5507
- elem.defaultView.scrollTo(value, elem.defaultView.pageYOffset);
5508
- } else {
5509
- elem.scrollLeft = value;
5510
- }
5511
- });
5512
- return el;
5660
+ return TinyHtml.scrollToXY(el, { targetX: value });
5513
5661
  }
5514
5662
 
5515
5663
  /**