animejs 4.0.1 → 4.1.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/lib/anime.esm.js CHANGED
@@ -1,26 +1,28 @@
1
1
  /**
2
2
  * anime.js - ESM
3
- * @version v4.0.1
3
+ * @version v4.1.0
4
4
  * @author Julian Garnier
5
5
  * @license MIT
6
6
  * @copyright (c) 2025 Julian Garnier
7
7
  * @see https://animejs.com
8
8
  */
9
9
 
10
+ // Global types ///////////////////////////////////////////////////////////////
11
+
10
12
  /**
11
13
  * @typedef {Object} DefaultsParams
12
- * @property {number|string} [id]
14
+ * @property {Number|String} [id]
13
15
  * @property {PercentageKeyframes|DurationKeyframes} [keyframes]
14
16
  * @property {EasingParam} [playbackEase]
15
- * @property {number} [playbackRate]
16
- * @property {number} [frameRate]
17
- * @property {number|boolean} [loop]
18
- * @property {boolean} [reversed]
19
- * @property {boolean} [alternate]
20
- * @property {boolean|ScrollObserver} [autoplay]
21
- * @property {number|FunctionValue} [duration]
22
- * @property {number|FunctionValue} [delay]
23
- * @property {number} [loopDelay]
17
+ * @property {Number} [playbackRate]
18
+ * @property {Number} [frameRate]
19
+ * @property {Number|Boolean} [loop]
20
+ * @property {Boolean} [reversed]
21
+ * @property {Boolean} [alternate]
22
+ * @property {Boolean|ScrollObserver} [autoplay]
23
+ * @property {Number|FunctionValue} [duration]
24
+ * @property {Number|FunctionValue} [delay]
25
+ * @property {Number} [loopDelay]
24
26
  * @property {EasingParam} [ease]
25
27
  * @property {'none'|'replace'|'blend'|compositionTypes} [composition]
26
28
  * @property {(v: any) => any} [modifier]
@@ -36,7 +38,33 @@
36
38
  /** @typedef {JSAnimation|Timeline} Renderable */
37
39
  /** @typedef {Timer|Renderable} Tickable */
38
40
  /** @typedef {Timer&JSAnimation&Timeline} CallbackArgument */
39
- /** @typedef {Animatable|Tickable|Draggable|ScrollObserver|Scope} Revertible */
41
+ /** @typedef {Animatable|Tickable|Draggable|ScrollObserver|TextSplitter|Scope} Revertible */
42
+
43
+ // Stagger types //////////////////////////////////////////////////////////////
44
+
45
+ /**
46
+ * @callback StaggerFunction
47
+ * @param {Target} [target]
48
+ * @param {Number} [index]
49
+ * @param {Number} [length]
50
+ * @param {Timeline} [tl]
51
+ * @return {Number|String}
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} StaggerParams
56
+ * @property {Number|String} [start]
57
+ * @property {Number|'first'|'center'|'last'|'random'} [from]
58
+ * @property {Boolean} [reversed]
59
+ * @property {Array.<Number>} [grid]
60
+ * @property {('x'|'y')} [axis]
61
+ * @property {String|StaggerFunction} [use]
62
+ * @property {Number} [total]
63
+ * @property {EasingParam} [ease]
64
+ * @property {TweenModifier} [modifier]
65
+ */
66
+
67
+ // Eases types ////////////////////////////////////////////////////////////////
40
68
 
41
69
  /**
42
70
  * @callback EasingFunction
@@ -64,21 +92,7 @@
64
92
  /** @typedef {Array.<TargetSelector>|TargetSelector} TargetsParam */
65
93
  /** @typedef {Array.<Target>} TargetsArray */
66
94
 
67
- /**
68
- * @callback FunctionValue
69
- * @param {Target} target - The animated target
70
- * @param {Number} index - The target index
71
- * @param {Number} length - The total number of animated targets
72
- * @return {Number|String|TweenObjectValue|Array.<Number|String|TweenObjectValue>}
73
- */
74
-
75
- /**
76
- * @callback TweenModifier
77
- * @param {Number} value - The animated value
78
- * @return {Number|String}
79
- */
80
-
81
- /** @typedef {[Number, Number, Number, Number]} ColorArray */
95
+ // Callback types ////////////////////////////////////////////////////////////
82
96
 
83
97
  /**
84
98
  * @template T
@@ -105,6 +119,46 @@
105
119
  * @property {Callback<T>} [onRender]
106
120
  */
107
121
 
122
+ // Timer types ////////////////////////////////////////////////////////////////
123
+
124
+ /**
125
+ * @typedef {Object} TimerOptions
126
+ * @property {Number|String} [id]
127
+ * @property {TweenParamValue} [duration]
128
+ * @property {TweenParamValue} [delay]
129
+ * @property {Number} [loopDelay]
130
+ * @property {Boolean} [reversed]
131
+ * @property {Boolean} [alternate]
132
+ * @property {Boolean|Number} [loop]
133
+ * @property {Boolean|ScrollObserver} [autoplay]
134
+ * @property {Number} [frameRate]
135
+ * @property {Number} [playbackRate]
136
+ */
137
+
138
+ /**
139
+
140
+ /**
141
+ * @typedef {TimerOptions & TickableCallbacks<Timer>} TimerParams
142
+ */
143
+
144
+ // Tween types ////////////////////////////////////////////////////////////////
145
+
146
+ /**
147
+ * @callback FunctionValue
148
+ * @param {Target} target - The animated target
149
+ * @param {Number} index - The target index
150
+ * @param {Number} length - The total number of animated targets
151
+ * @return {Number|String|TweenObjectValue|Array.<Number|String|TweenObjectValue>}
152
+ */
153
+
154
+ /**
155
+ * @callback TweenModifier
156
+ * @param {Number} value - The animated value
157
+ * @return {Number|String}
158
+ */
159
+
160
+ /** @typedef {[Number, Number, Number, Number]} ColorArray */
161
+
108
162
  /**
109
163
  * @typedef {Object} Tween
110
164
  * @property {Number} id
@@ -158,25 +212,7 @@
158
212
  /** @typedef {WeakMap.<Target, TweenLookups>} TweenReplaceLookups */
159
213
  /** @typedef {Map.<Target, TweenLookups>} TweenAdditiveLookups */
160
214
 
161
- /**
162
- * @typedef {Object} TimerOptions
163
- * @property {Number|String} [id]
164
- * @property {TweenParamValue} [duration]
165
- * @property {TweenParamValue} [delay]
166
- * @property {Number} [loopDelay]
167
- * @property {Boolean} [reversed]
168
- * @property {Boolean} [alternate]
169
- * @property {Boolean|Number} [loop]
170
- * @property {Boolean|ScrollObserver} [autoplay]
171
- * @property {Number} [frameRate]
172
- * @property {Number} [playbackRate]
173
- */
174
-
175
- /**
176
-
177
- /**
178
- * @typedef {TimerOptions & TickableCallbacks<Timer>} TimerParams
179
- */
215
+ // Animation types ////////////////////////////////////////////////////////////
180
216
 
181
217
  /**
182
218
  * @typedef {Number|String|FunctionValue} TweenParamValue
@@ -250,6 +286,8 @@
250
286
  * @typedef {Record<String, TweenOptions | Callback<JSAnimation> | TweenModifier | boolean | PercentageKeyframes | DurationKeyframes | ScrollObserver> & TimerOptions & AnimationOptions & TweenParamsOptions & TickableCallbacks<JSAnimation> & RenderableCallbacks<JSAnimation>} AnimationParams
251
287
  */
252
288
 
289
+ // Timeline types /////////////////////////////////////////////////////////////
290
+
253
291
  /**
254
292
  * @typedef {Object} TimelineOptions
255
293
  * @property {DefaultsParams} [defaults]
@@ -260,6 +298,8 @@
260
298
  * @typedef {TimerOptions & TimelineOptions & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} TimelineParams
261
299
  */
262
300
 
301
+ // Animatable types ///////////////////////////////////////////////////////////
302
+
263
303
  /**
264
304
  * @callback AnimatablePropertySetter
265
305
  * @param {Number|Array.<Number>} to
@@ -294,6 +334,136 @@
294
334
  * @typedef {Record<String, TweenParamValue | EasingParam | TweenModifier | TweenComposition | AnimatablePropertyParamsOptions> & AnimatablePropertyParamsOptions} AnimatableParams
295
335
  */
296
336
 
337
+ // Scope types ////////////////////////////////////////////////////////////////
338
+
339
+ /**
340
+ * @typedef {Object} ReactRef
341
+ * @property {HTMLElement|SVGElement|null} [current]
342
+ */
343
+
344
+ /**
345
+ * @typedef {Object} AngularRef
346
+ * @property {HTMLElement|SVGElement} [nativeElement]
347
+ */
348
+
349
+ /**
350
+ * @typedef {Object} ScopeParams
351
+ * @property {DOMTargetSelector|ReactRef|AngularRef} [root]
352
+ * @property {DefaultsParams} [defaults]
353
+ * @property {Record<String, String>} [mediaQueries]
354
+ */
355
+
356
+ /**
357
+ * @template T
358
+ * @callback ScopedCallback
359
+ * @param {Scope} scope
360
+ * @return {T}
361
+ */
362
+
363
+ /**
364
+ * @callback ScopeCleanupCallback
365
+ * @param {Scope} [scope]
366
+ */
367
+
368
+ /**
369
+ * @callback ScopeConstructorCallback
370
+ * @param {Scope} [scope]
371
+ * @return {ScopeCleanupCallback|void}
372
+ */
373
+
374
+ /**
375
+ * @callback ScopeMethod
376
+ * @param {...*} args
377
+ * @return {ScopeCleanupCallback|void}
378
+ */
379
+
380
+ // Draggable types ////////////////////////////////////////////////////////////
381
+
382
+ /**
383
+ * @typedef {Object} DraggableAxisParam
384
+ * @property {String} [mapTo]
385
+ * @property {TweenModifier} [modifier]
386
+ * @property {TweenComposition} [composition]
387
+ * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap]
388
+ */
389
+
390
+ /**
391
+ * @typedef {Object} DraggableCursorParams
392
+ * @property {String} [onHover]
393
+ * @property {String} [onGrab]
394
+ */
395
+
396
+ /**
397
+ * @typedef {Object} DraggableParams
398
+ * @property {DOMTargetSelector} [trigger]
399
+ * @property {DOMTargetSelector|Array<Number>|((draggable: Draggable) => DOMTargetSelector|Array<Number>)} [container]
400
+ * @property {Boolean|DraggableAxisParam} [x]
401
+ * @property {Boolean|DraggableAxisParam} [y]
402
+ * @property {TweenModifier} [modifier]
403
+ * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap]
404
+ * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [containerPadding]
405
+ * @property {Number|((draggable: Draggable) => Number)} [containerFriction]
406
+ * @property {Number|((draggable: Draggable) => Number)} [releaseContainerFriction]
407
+ * @property {Number|((draggable: Draggable) => Number)} [dragSpeed]
408
+ * @property {Number|((draggable: Draggable) => Number)} [scrollSpeed]
409
+ * @property {Number|((draggable: Draggable) => Number)} [scrollThreshold]
410
+ * @property {Number|((draggable: Draggable) => Number)} [minVelocity]
411
+ * @property {Number|((draggable: Draggable) => Number)} [maxVelocity]
412
+ * @property {Number|((draggable: Draggable) => Number)} [velocityMultiplier]
413
+ * @property {Number} [releaseMass]
414
+ * @property {Number} [releaseStiffness]
415
+ * @property {Number} [releaseDamping]
416
+ * @property {Boolean} [releaseDamping]
417
+ * @property {EasingParam} [releaseEase]
418
+ * @property {Boolean|DraggableCursorParams|((draggable: Draggable) => Boolean|DraggableCursorParams)} [cursor]
419
+ * @property {Callback<Draggable>} [onGrab]
420
+ * @property {Callback<Draggable>} [onDrag]
421
+ * @property {Callback<Draggable>} [onRelease]
422
+ * @property {Callback<Draggable>} [onUpdate]
423
+ * @property {Callback<Draggable>} [onSettle]
424
+ * @property {Callback<Draggable>} [onSnap]
425
+ * @property {Callback<Draggable>} [onResize]
426
+ * @property {Callback<Draggable>} [onAfterResize]
427
+ */
428
+
429
+ // Text types /////////////////////////////////////////////////////////////////
430
+
431
+ /**
432
+ * @typedef {Object} splitTemplateParams
433
+ * @property {false|String} [class]
434
+ * @property {Boolean|'hidden'|'clip'|'visible'|'scroll'|'auto'} [wrap]
435
+ * @property {Boolean|'top'|'right'|'bottom'|'left'|'center'} [clone]
436
+ */
437
+
438
+ /**
439
+ * @typedef {Boolean|String} SplitValue
440
+ */
441
+
442
+ /**
443
+ * @callback SplitFunctionValue
444
+ * @param {Node|HTMLElement} [value]
445
+ * @return String
446
+ */
447
+
448
+ /**
449
+ * @typedef {Object} TextSplitterParams
450
+ * @property {SplitValue|splitTemplateParams|SplitFunctionValue} [lines]
451
+ * @property {SplitValue|splitTemplateParams|SplitFunctionValue} [words]
452
+ * @property {SplitValue|splitTemplateParams|SplitFunctionValue} [chars]
453
+ * @property {Boolean} [accessible]
454
+ * @property {Boolean} [includeSpaces]
455
+ * @property {Boolean} [debug]
456
+ */
457
+
458
+ // SVG types //////////////////////////////////////////////////////////////////
459
+
460
+ /**
461
+ * @typedef {SVGGeometryElement & {
462
+ * setAttribute(name: 'draw', value: `${number} ${number}`): void;
463
+ * draw: `${number} ${number}`;
464
+ * }} DrawableSVGGeometry
465
+ */
466
+
297
467
 
298
468
  // Environments
299
469
 
@@ -358,11 +528,13 @@ const maxFps = 120;
358
528
  // Strings
359
529
 
360
530
  const emptyString = '';
361
- const shortTransforms = new Map();
362
-
363
- shortTransforms.set('x', 'translateX');
364
- shortTransforms.set('y', 'translateY');
365
- shortTransforms.set('z', 'translateZ');
531
+ const shortTransforms = /*#__PURE__*/ (() => {
532
+ const map = new Map();
533
+ map.set('x', 'translateX');
534
+ map.set('y', 'translateY');
535
+ map.set('z', 'translateZ');
536
+ return map;
537
+ })();
366
538
 
367
539
  const validTransforms = [
368
540
  'translateX',
@@ -384,7 +556,7 @@ const validTransforms = [
384
556
  'matrix3d',
385
557
  ];
386
558
 
387
- const transformsFragmentStrings = validTransforms.reduce((a, v) => ({...a, [v]: v + '('}), {});
559
+ const transformsFragmentStrings = /*#__PURE__*/ validTransforms.reduce((a, v) => ({...a, [v]: v + '('}), {});
388
560
 
389
561
  // Functions
390
562
 
@@ -435,13 +607,16 @@ const defaults = {
435
607
  onRender: noop,
436
608
  };
437
609
 
610
+ const scope = {
611
+ /** @type {Scope} */
612
+ current: null,
613
+ /** @type {Document|DOMTarget} */
614
+ root: doc,
615
+ };
616
+
438
617
  const globals = {
439
618
  /** @type {DefaultsParams} */
440
619
  defaults,
441
- /** @type {Document|DOMTarget} */
442
- root: doc,
443
- /** @type {Scope} */
444
- scope: null,
445
620
  /** @type {Number} */
446
621
  precision: 4,
447
622
  /** @type {Number} */
@@ -450,7 +625,7 @@ const globals = {
450
625
  tickThreshold: 200,
451
626
  };
452
627
 
453
- const globalVersions = { version: '4.0.1', engine: null };
628
+ const globalVersions = { version: '4.1.0', engine: null };
454
629
 
455
630
  if (isBrowser) {
456
631
  if (!win.AnimeJS) win.AnimeJS = [];
@@ -531,7 +706,6 @@ const atan2 = Math.atan2;
531
706
  const PI = Math.PI;
532
707
  const _round = Math.round;
533
708
 
534
-
535
709
  /**
536
710
  * @param {Number} v
537
711
  * @param {Number} min
@@ -570,6 +744,25 @@ const snap = (v, increment) => isArr(increment) ? increment.reduce((closest, cv)
570
744
  */
571
745
  const interpolate = (start, end, progress) => start + (end - start) * progress;
572
746
 
747
+ /**
748
+ * @param {Number} min
749
+ * @param {Number} max
750
+ * @param {Number} [decimalLength]
751
+ * @return {Number}
752
+ */
753
+ const random = (min, max, decimalLength) => { const m = 10 ** (decimalLength || 0); return floor((Math.random() * (max - min + (1 / m)) + min) * m) / m };
754
+
755
+ /**
756
+ * Adapted from https://bost.ocks.org/mike/shuffle/
757
+ * @param {Array} items
758
+ * @return {Array}
759
+ */
760
+ const shuffle = items => {
761
+ let m = items.length, t, i;
762
+ while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
763
+ return items;
764
+ };
765
+
573
766
  /**
574
767
  * @param {Number} v
575
768
  * @return {Number}
@@ -580,7 +773,7 @@ const clampInfinity = v => v === Infinity ? maxValue : v === -Infinity ? -1e12 :
580
773
  * @param {Number} v
581
774
  * @return {Number}
582
775
  */
583
- const clampZero = v => v < minValue ? minValue : v;
776
+ const normalizeTime = v => v <= minValue ? minValue : clampInfinity(round(v, 11));
584
777
 
585
778
  // Arrays
586
779
 
@@ -666,6 +859,32 @@ const addChild = (parent, child, sortMethod, prevProp = '_prev', nextProp = '_ne
666
859
  child[nextProp] = next;
667
860
  };
668
861
 
862
+ /**
863
+ * @param {(...args: any[]) => Tickable | ((...args: any[]) => void)} constructor
864
+ * @return {(...args: any[]) => Tickable | ((...args: any[]) => void)}
865
+ */
866
+ const createRefreshable = constructor => {
867
+ /** @type {Tickable} */
868
+ let tracked;
869
+ return (...args) => {
870
+ let currentIteration, currentIterationProgress, reversed, alternate;
871
+ if (tracked) {
872
+ currentIteration = tracked.currentIteration;
873
+ currentIterationProgress = tracked.iterationProgress;
874
+ reversed = tracked.reversed;
875
+ alternate = tracked._alternate;
876
+ tracked.revert();
877
+ }
878
+ const cleanup = constructor(...args);
879
+ if (cleanup && !isFnc(cleanup) && cleanup.revert) tracked = cleanup;
880
+ if (!isUnd(currentIterationProgress)) {
881
+ /** @type {Tickable} */(tracked).currentIteration = currentIteration;
882
+ /** @type {Tickable} */(tracked).iterationProgress = (alternate ? !(currentIteration % 2) ? reversed : !reversed : reversed) ? 1 - currentIterationProgress : currentIterationProgress;
883
+ }
884
+ return cleanup || noop;
885
+ }
886
+ };
887
+
669
888
  /*
670
889
  * Base class to control framerate and playback rate.
671
890
  * Inherited by Engine, Timer, Animation and Timeline.
@@ -1174,8 +1393,8 @@ const addAdditiveAnimation = lookups => {
1174
1393
  return animation;
1175
1394
  };
1176
1395
 
1177
- const engineTickMethod = isBrowser ? requestAnimationFrame : setImmediate;
1178
- const engineCancelMethod = isBrowser ? cancelAnimationFrame : clearImmediate;
1396
+ const engineTickMethod = /*#__PURE__*/ (() => isBrowser ? requestAnimationFrame : setImmediate)();
1397
+ const engineCancelMethod = /*#__PURE__*/ (() => isBrowser ? cancelAnimationFrame : clearImmediate)();
1179
1398
 
1180
1399
  class Engine extends Clock {
1181
1400
 
@@ -1349,7 +1568,7 @@ const parseInlineTransforms = (target, propName, animationInlineStyles) => {
1349
1568
  * @return {NodeList|HTMLCollection}
1350
1569
  */
1351
1570
  function getNodeList(v) {
1352
- const n = isStr(v) ? globals.root.querySelectorAll(v) : v;
1571
+ const n = isStr(v) ? scope.root.querySelectorAll(v) : v;
1353
1572
  if (n instanceof NodeList || n instanceof HTMLCollection) return n;
1354
1573
  }
1355
1574
 
@@ -1504,43 +1723,61 @@ const morphTo = (path2, precision = .33) => ($path1) => {
1504
1723
  };
1505
1724
 
1506
1725
  /**
1507
- * @param {SVGGeometryElement} $el
1508
- * @param {Number} start
1509
- * @param {Number} end
1510
- * @return {Proxy}
1726
+ * @param {SVGGeometryElement} [$el]
1727
+ * @return {Number}
1728
+ */
1729
+ const getScaleFactor = $el => {
1730
+ let scaleFactor = 1;
1731
+ if ($el && $el.getCTM) {
1732
+ const ctm = $el.getCTM();
1733
+ if (ctm) {
1734
+ const scaleX = sqrt(ctm.a * ctm.a + ctm.b * ctm.b);
1735
+ const scaleY = sqrt(ctm.c * ctm.c + ctm.d * ctm.d);
1736
+ scaleFactor = (scaleX + scaleY) / 2;
1737
+ }
1738
+ }
1739
+ return scaleFactor;
1740
+ };
1741
+
1742
+ /**
1743
+ * Creates a proxy that wraps an SVGGeometryElement and adds drawing functionality.
1744
+ * @param {SVGGeometryElement} $el - The SVG element to transform into a drawable
1745
+ * @param {number} start - Starting position (0-1)
1746
+ * @param {number} end - Ending position (0-1)
1747
+ * @return {DrawableSVGGeometry} - Returns a proxy that preserves the original element's type with additional 'draw' attribute functionality
1511
1748
  */
1512
- function createDrawableProxy($el, start, end) {
1513
- const strokeLineCap = getComputedStyle($el).strokeLinecap;
1749
+ const createDrawableProxy = ($el, start, end) => {
1514
1750
  const pathLength = K;
1751
+ const computedStyles = getComputedStyle($el);
1752
+ const strokeLineCap = computedStyles.strokeLinecap;
1753
+ // @ts-ignore
1754
+ const $scalled = computedStyles.vectorEffect === 'non-scaling-stroke' ? $el : null;
1515
1755
  let currentCap = strokeLineCap;
1756
+
1516
1757
  const proxy = new Proxy($el, {
1517
1758
  get(target, property) {
1518
1759
  const value = target[property];
1519
1760
  if (property === proxyTargetSymbol) return target;
1520
1761
  if (property === 'setAttribute') {
1521
- /** @param {any[]} args */
1522
1762
  return (...args) => {
1523
1763
  if (args[0] === 'draw') {
1524
1764
  const value = args[1];
1525
1765
  const values = value.split(' ');
1526
1766
  const v1 = +values[0];
1527
1767
  const v2 = +values[1];
1528
-
1529
1768
  // TOTO: Benchmark if performing two slices is more performant than one split
1530
-
1531
1769
  // const spaceIndex = value.indexOf(' ');
1532
1770
  // const v1 = round(+value.slice(0, spaceIndex), precision);
1533
1771
  // const v2 = round(+value.slice(spaceIndex + 1), precision);
1534
-
1535
- const os = v1 * -1e3;
1536
- const d1 = (v2 * pathLength) + os;
1537
- // Prevents linecap to smear by offsetting the dasharray length by 0.01% when v2 is not at max
1538
- const d2 = (pathLength + ((v1 === 0 && v2 === 1) || (v1 === 1 && v2 === 0) ? 0 : 10) - d1);
1539
- // Handle cases where the cap is still visible when the line is completly hidden
1772
+ const scaleFactor = getScaleFactor($scalled);
1773
+ const os = v1 * -1e3 * scaleFactor;
1774
+ const d1 = (v2 * pathLength * scaleFactor) + os;
1775
+ const d2 = (pathLength * scaleFactor +
1776
+ ((v1 === 0 && v2 === 1) || (v1 === 1 && v2 === 0) ? 0 : 10 * scaleFactor) - d1);
1540
1777
  if (strokeLineCap !== 'butt') {
1541
1778
  const newCap = v1 === v2 ? 'butt' : strokeLineCap;
1542
1779
  if (currentCap !== newCap) {
1543
- target.setAttribute('stroke-linecap', `${newCap}`);
1780
+ target.style.strokeLinecap = `${newCap}`;
1544
1781
  currentCap = newCap;
1545
1782
  }
1546
1783
  }
@@ -1550,31 +1787,37 @@ function createDrawableProxy($el, start, end) {
1550
1787
  return Reflect.apply(value, target, args);
1551
1788
  };
1552
1789
  }
1790
+
1553
1791
  if (isFnc(value)) {
1554
- /** @param {any[]} args */
1555
1792
  return (...args) => Reflect.apply(value, target, args);
1556
1793
  } else {
1557
1794
  return value;
1558
1795
  }
1559
1796
  }
1560
1797
  });
1798
+
1561
1799
  if ($el.getAttribute('pathLength') !== `${pathLength}`) {
1562
1800
  $el.setAttribute('pathLength', `${pathLength}`);
1563
1801
  proxy.setAttribute('draw', `${start} ${end}`);
1564
1802
  }
1565
- return /** @type {typeof Proxy} */(/** @type {unknown} */(proxy));
1566
- }
1803
+
1804
+ return /** @type {DrawableSVGGeometry} */(proxy);
1805
+ };
1567
1806
 
1568
1807
  /**
1569
- * @param {TargetsParam} selector
1570
- * @param {Number} [start=0]
1571
- * @param {Number} [end=0]
1572
- * @return {Array.<Proxy>}
1808
+ * Creates drawable proxies for multiple SVG elements.
1809
+ * @param {TargetsParam} selector - CSS selector, SVG element, or array of elements and selectors
1810
+ * @param {number} [start=0] - Starting position (0-1)
1811
+ * @param {number} [end=0] - Ending position (0-1)
1812
+ * @return {Array<DrawableSVGGeometry>} - Array of proxied elements with drawing functionality
1573
1813
  */
1574
1814
  const createDrawable = (selector, start = 0, end = 0) => {
1575
- const els = /** @type {Array.<Proxy>} */((/** @type {unknown} */(parseTargets(selector))));
1576
- els.forEach(($el, i) => els[i] = createDrawableProxy(/** @type {SVGGeometryElement} */(/** @type {unknown} */($el)), start, end));
1577
- return els;
1815
+ const els = parseTargets(selector);
1816
+ return els.map($el => createDrawableProxy(
1817
+ /** @type {SVGGeometryElement} */($el),
1818
+ start,
1819
+ end
1820
+ ));
1578
1821
  };
1579
1822
 
1580
1823
  // Motion path animation
@@ -2258,7 +2501,7 @@ class Timer extends Clock {
2258
2501
  onUpdate,
2259
2502
  } = parameters;
2260
2503
 
2261
- if (globals.scope) globals.scope.revertibles.push(this);
2504
+ if (scope.current) scope.current.register(this);
2262
2505
 
2263
2506
  const timerInitTime = parent ? 0 : engine._elapsedTime;
2264
2507
  const timerDefaults = parent ? parent.defaults : globals.defaults;
@@ -2388,7 +2631,7 @@ class Timer extends Clock {
2388
2631
  }
2389
2632
 
2390
2633
  get progress() {
2391
- return clamp(round(this._currentTime / this.duration, 5), 0, 1);
2634
+ return clamp(round(this._currentTime / this.duration, 10), 0, 1);
2392
2635
  }
2393
2636
 
2394
2637
  /** @param {Number} progress */
@@ -2397,7 +2640,7 @@ class Timer extends Clock {
2397
2640
  }
2398
2641
 
2399
2642
  get iterationProgress() {
2400
- return clamp(round(this._iterationTime / this.iterationDuration, 5), 0, 1);
2643
+ return clamp(round(this._iterationTime / this.iterationDuration, 10), 0, 1);
2401
2644
  }
2402
2645
 
2403
2646
  /** @param {Number} progress */
@@ -2589,11 +2832,12 @@ class Timer extends Clock {
2589
2832
  */
2590
2833
  stretch(newDuration) {
2591
2834
  const currentDuration = this.duration;
2592
- if (currentDuration === clampZero(newDuration)) return this;
2835
+ const normlizedDuration = normalizeTime(newDuration);
2836
+ if (currentDuration === normlizedDuration) return this;
2593
2837
  const timeScale = newDuration / currentDuration;
2594
2838
  const isSetter = newDuration <= minValue;
2595
- this.duration = isSetter ? minValue : clampZero(clampInfinity(round(currentDuration * timeScale, 12)));
2596
- this.iterationDuration = isSetter ? minValue : clampZero(clampInfinity(round(this.iterationDuration * timeScale, 12)));
2839
+ this.duration = isSetter ? minValue : normlizedDuration;
2840
+ this.iterationDuration = isSetter ? minValue : normalizeTime(this.iterationDuration * timeScale);
2597
2841
  this._offset *= timeScale;
2598
2842
  this._delay *= timeScale;
2599
2843
  this._loopDelay *= timeScale;
@@ -3637,13 +3881,13 @@ class JSAnimation extends Timer {
3637
3881
  */
3638
3882
  stretch(newDuration) {
3639
3883
  const currentDuration = this.duration;
3640
- if (currentDuration === clampZero(newDuration)) return this;
3884
+ if (currentDuration === normalizeTime(newDuration)) return this;
3641
3885
  const timeScale = newDuration / currentDuration;
3642
3886
  // NOTE: Find a better way to handle the stretch of an animation after stretch = 0
3643
3887
  forEachChildren(this, (/** @type {Tween} */tween) => {
3644
3888
  // Rounding is necessary here to minimize floating point errors
3645
- tween._updateDuration = clampZero(round(tween._updateDuration * timeScale, 12));
3646
- tween._changeDuration = clampZero(round(tween._changeDuration * timeScale, 12));
3889
+ tween._updateDuration = normalizeTime(tween._updateDuration * timeScale);
3890
+ tween._changeDuration = normalizeTime(tween._changeDuration * timeScale);
3647
3891
  tween._currentTime *= timeScale;
3648
3892
  tween._startTime *= timeScale;
3649
3893
  tween._absoluteStartTime *= timeScale;
@@ -3656,12 +3900,13 @@ class JSAnimation extends Timer {
3656
3900
  */
3657
3901
  refresh() {
3658
3902
  forEachChildren(this, (/** @type {Tween} */tween) => {
3659
- const ogValue = getOriginalAnimatableValue(tween.target, tween.property, tween._tweenType);
3660
- decomposeRawValue(ogValue, decomposedOriginalValue);
3661
- tween._fromNumbers = cloneArray(decomposedOriginalValue.d);
3662
- tween._fromNumber = decomposedOriginalValue.n;
3663
- if (tween._func) {
3664
- decomposeRawValue(tween._func(), toTargetObject);
3903
+ const tweenFunc = tween._func;
3904
+ if (tweenFunc) {
3905
+ const ogValue = getOriginalAnimatableValue(tween.target, tween.property, tween._tweenType);
3906
+ decomposeRawValue(ogValue, decomposedOriginalValue);
3907
+ decomposeRawValue(tweenFunc(), toTargetObject);
3908
+ tween._fromNumbers = cloneArray(decomposedOriginalValue.d);
3909
+ tween._fromNumber = decomposedOriginalValue.n;
3665
3910
  tween._toNumbers = cloneArray(toTargetObject.d);
3666
3911
  tween._strings = cloneArray(toTargetObject.s);
3667
3912
  tween._toNumber = toTargetObject.n;
@@ -3745,13 +3990,14 @@ const parseWAAPIEasing = (ease) => {
3745
3990
  const parsed = parseEaseString(ease, WAAPIeases, WAAPIEasesLookups);
3746
3991
  if (isFnc(parsed)) parsedEase = parsed === none ? 'linear' : easingToLinear(parsed);
3747
3992
  }
3993
+ WAAPIEasesLookups[ease] = parsedEase;
3748
3994
  } else if (isFnc(ease)) {
3749
3995
  const easing = easingToLinear(ease);
3750
3996
  if (easing) parsedEase = easing;
3751
3997
  } else if (/** @type {Spring} */(ease).ease) {
3752
3998
  parsedEase = easingToLinear(/** @type {Spring} */(ease).ease);
3753
3999
  }
3754
- return WAAPIEasesLookups[ease] = parsedEase;
4000
+ return parsedEase;
3755
4001
  };
3756
4002
 
3757
4003
  /**
@@ -3819,28 +4065,9 @@ const commonDefaultPXProperties = [
3819
4065
  ...transformsShorthands
3820
4066
  ];
3821
4067
 
3822
- const validIndividualTransforms = [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))];
3823
-
3824
- // Setting it to true in case CSS.registerProperty is not supported will automatically skip the registration and fallback to no animation
3825
- let transformsPropertiesRegistered = isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'));
3826
-
3827
- const registerTransformsProperties = () => {
3828
- validTransforms.forEach(t => {
3829
- const isSkew = stringStartsWith(t, 'skew');
3830
- const isScale = stringStartsWith(t, 'scale');
3831
- const isRotate = stringStartsWith(t, 'rotate');
3832
- const isTranslate = stringStartsWith(t, 'translate');
3833
- const isAngle = isRotate || isSkew;
3834
- const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
3835
- CSS.registerProperty({
3836
- name: '--' + t,
3837
- syntax,
3838
- inherits: false,
3839
- initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
3840
- });
3841
- });
3842
- transformsPropertiesRegistered = true;
3843
- };
4068
+ const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])();
4069
+
4070
+ let transformsPropertiesRegistered = null;
3844
4071
 
3845
4072
  const WAAPIAnimationsLookups = {
3846
4073
  _head: null,
@@ -3951,9 +4178,31 @@ class WAAPIAnimation {
3951
4178
  */
3952
4179
  constructor(targets, params) {
3953
4180
 
3954
- if (globals.scope) globals.scope.revertibles.push(this);
4181
+ if (scope.current) scope.current.register(this);
3955
4182
 
3956
- if (!transformsPropertiesRegistered) registerTransformsProperties();
4183
+ // Skip the registration and fallback to no animation in case CSS.registerProperty is not supported
4184
+ if (isNil(transformsPropertiesRegistered)) {
4185
+ if (isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'))) {
4186
+ transformsPropertiesRegistered = false;
4187
+ } else {
4188
+ validTransforms.forEach(t => {
4189
+ const isSkew = stringStartsWith(t, 'skew');
4190
+ const isScale = stringStartsWith(t, 'scale');
4191
+ const isRotate = stringStartsWith(t, 'rotate');
4192
+ const isTranslate = stringStartsWith(t, 'translate');
4193
+ const isAngle = isRotate || isSkew;
4194
+ const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
4195
+ try {
4196
+ CSS.registerProperty({
4197
+ name: '--' + t,
4198
+ syntax,
4199
+ inherits: false,
4200
+ initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
4201
+ });
4202
+ } catch {} });
4203
+ transformsPropertiesRegistered = true;
4204
+ }
4205
+ }
3957
4206
 
3958
4207
  const parsedTargets = registerTargets(targets);
3959
4208
  const targetsLength = parsedTargets.length;
@@ -4120,7 +4369,13 @@ class WAAPIAnimation {
4120
4369
  /** @param {Number} time */
4121
4370
  set currentTime(time) {
4122
4371
  const t = time * (globals.timeScale === 1 ? 1 : K);
4123
- this.forEach(anim => anim.currentTime = t);
4372
+ this.forEach(anim => {
4373
+ // Make sure the animation playState is not 'paused' in order to properly trigger an onfinish callback.
4374
+ // The "paused" play state supersedes the "finished" play state; if the animation is both paused and finished, the "paused" state is the one that will be reported.
4375
+ // https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish_event
4376
+ if (t >= this.duration) anim.play();
4377
+ anim.currentTime = t;
4378
+ });
4124
4379
  }
4125
4380
 
4126
4381
  get progress() {
@@ -4401,12 +4656,10 @@ const remove = (targets, renderable, propertyName) => {
4401
4656
  };
4402
4657
 
4403
4658
  /**
4404
- * @param {Number} min
4405
- * @param {Number} max
4406
- * @param {Number} [decimalLength]
4407
- * @return {Number}
4659
+ * @param {(...args: any[]) => Tickable} constructor
4660
+ * @return {(...args: any[]) => Tickable}
4408
4661
  */
4409
- const random = (min, max, decimalLength) => { const m = 10 ** (decimalLength || 0); return floor((Math.random() * (max - min + (1 / m)) + min) * m) / m };
4662
+ const keepTime = createRefreshable;
4410
4663
 
4411
4664
  /**
4412
4665
  * @param {String|Array} items
@@ -4414,17 +4667,6 @@ const random = (min, max, decimalLength) => { const m = 10 ** (decimalLength ||
4414
4667
  */
4415
4668
  const randomPick = items => items[random(0, items.length - 1)];
4416
4669
 
4417
- /**
4418
- * Adapted from https://bost.ocks.org/mike/shuffle/
4419
- * @param {Array} items
4420
- * @return {Array}
4421
- */
4422
- const shuffle = items => {
4423
- let m = items.length, t, i;
4424
- while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
4425
- return items;
4426
- };
4427
-
4428
4670
  /**
4429
4671
  * @param {Number|String} v
4430
4672
  * @param {Number} decimalLength
@@ -4619,6 +4861,7 @@ const utils = {
4619
4861
  shuffle,
4620
4862
  lerp,
4621
4863
  sync,
4864
+ keepTime,
4622
4865
  clamp: /** @type {typeof clamp & ChainedClamp} */(makeChainable(clamp)),
4623
4866
  round: /** @type {typeof round & ChainedRound} */(makeChainable(round)),
4624
4867
  snap: /** @type {typeof snap & ChainedSnap} */(makeChainable(snap)),
@@ -4907,15 +5150,11 @@ class Timeline extends Timer {
4907
5150
  */
4908
5151
  stretch(newDuration) {
4909
5152
  const currentDuration = this.duration;
4910
- if (currentDuration === clampZero(newDuration)) return this;
5153
+ if (currentDuration === normalizeTime(newDuration)) return this;
4911
5154
  const timeScale = newDuration / currentDuration;
4912
5155
  const labels = this.labels;
4913
- forEachChildren(this, (/** @type {JSAnimation} */child) => {
4914
- child.stretch(child.duration * timeScale);
4915
- });
4916
- for (let labelName in labels) {
4917
- labels[labelName] *= timeScale;
4918
- }
5156
+ forEachChildren(this, (/** @type {JSAnimation} */child) => child.stretch(child.duration * timeScale));
5157
+ for (let labelName in labels) labels[labelName] *= timeScale;
4919
5158
  return super.stretch(newDuration);
4920
5159
  }
4921
5160
 
@@ -4962,7 +5201,7 @@ class Animatable {
4962
5201
  * @param {AnimatableParams} parameters
4963
5202
  */
4964
5203
  constructor(targets, parameters) {
4965
- if (globals.scope) globals.scope.revertibles.push(this);
5204
+ if (scope.current) scope.current.register(this);
4966
5205
  /** @type {AnimationParams} */
4967
5206
  const globalParams = {};
4968
5207
  const properties = {};
@@ -5281,12 +5520,6 @@ class Transforms {
5281
5520
  }
5282
5521
  }
5283
5522
 
5284
- /**
5285
- * @typedef {Object} DraggableCursorParams
5286
- * @property {String} [onHover]
5287
- * @property {String} [onGrab]
5288
- */
5289
-
5290
5523
  /**
5291
5524
  * @template {Array<Number>|DOMTargetSelector|String|Number|Boolean|Function|DraggableCursorParams} T
5292
5525
  * @param {T | ((draggable: Draggable) => T)} value
@@ -5297,47 +5530,6 @@ const parseDraggableFunctionParameter = (value, draggable) => value && isFnc(val
5297
5530
 
5298
5531
  let zIndex = 0;
5299
5532
 
5300
- /**
5301
- * @typedef {Object} DraggableAxisParam
5302
- * @property {String} [mapTo]
5303
- * @property {TweenModifier} [modifier]
5304
- * @property {TweenComposition} [composition]
5305
- * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap]
5306
- */
5307
-
5308
- /**
5309
- * @typedef {Object} DraggableParams
5310
- * @property {DOMTargetSelector} [trigger]
5311
- * @property {DOMTargetSelector|Array<Number>|((draggable: Draggable) => DOMTargetSelector|Array<Number>)} [container]
5312
- * @property {Boolean|DraggableAxisParam} [x]
5313
- * @property {Boolean|DraggableAxisParam} [y]
5314
- * @property {TweenModifier} [modifier]
5315
- * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap]
5316
- * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [containerPadding]
5317
- * @property {Number|((draggable: Draggable) => Number)} [containerFriction]
5318
- * @property {Number|((draggable: Draggable) => Number)} [releaseContainerFriction]
5319
- * @property {Number|((draggable: Draggable) => Number)} [dragSpeed]
5320
- * @property {Number|((draggable: Draggable) => Number)} [scrollSpeed]
5321
- * @property {Number|((draggable: Draggable) => Number)} [scrollThreshold]
5322
- * @property {Number|((draggable: Draggable) => Number)} [minVelocity]
5323
- * @property {Number|((draggable: Draggable) => Number)} [maxVelocity]
5324
- * @property {Number|((draggable: Draggable) => Number)} [velocityMultiplier]
5325
- * @property {Number} [releaseMass]
5326
- * @property {Number} [releaseStiffness]
5327
- * @property {Number} [releaseDamping]
5328
- * @property {Boolean} [releaseDamping]
5329
- * @property {EasingParam} [releaseEase]
5330
- * @property {Boolean|DraggableCursorParams|((draggable: Draggable) => Boolean|DraggableCursorParams)} [cursor]
5331
- * @property {Callback<Draggable>} [onGrab]
5332
- * @property {Callback<Draggable>} [onDrag]
5333
- * @property {Callback<Draggable>} [onRelease]
5334
- * @property {Callback<Draggable>} [onUpdate]
5335
- * @property {Callback<Draggable>} [onSettle]
5336
- * @property {Callback<Draggable>} [onSnap]
5337
- * @property {Callback<Draggable>} [onResize]
5338
- * @property {Callback<Draggable>} [onAfterResize]
5339
- */
5340
-
5341
5533
  class Draggable {
5342
5534
  /**
5343
5535
  * @param {TargetsParam} target
@@ -5345,7 +5537,7 @@ class Draggable {
5345
5537
  */
5346
5538
  constructor(target, parameters = {}) {
5347
5539
  if (!target) return;
5348
- if (globals.scope) globals.scope.revertibles.push(this);
5540
+ if (scope.current) scope.current.register(this);
5349
5541
  const paramX = parameters.x;
5350
5542
  const paramY = parameters.y;
5351
5543
  const trigger = parameters.trigger;
@@ -5536,7 +5728,7 @@ class Draggable {
5536
5728
  this.canScroll = false;
5537
5729
  this.enabled = false;
5538
5730
  this.initialized = false;
5539
- this.activeProp = this.disabled[0] ? yProp : xProp;
5731
+ this.activeProp = this.disabled[1] ? xProp : yProp;
5540
5732
  this.animate.animations[this.activeProp].onRender = () => {
5541
5733
  const hasUpdated = this.updated;
5542
5734
  const hasMoved = this.grabbed && hasUpdated;
@@ -6356,6 +6548,7 @@ class Draggable {
6356
6548
  this.overshootXTicker.revert();
6357
6549
  this.overshootYTicker.revert();
6358
6550
  this.resizeTicker.revert();
6551
+ this.animate.revert();
6359
6552
  return this;
6360
6553
  }
6361
6554
 
@@ -6405,44 +6598,10 @@ const createDraggable = (target, parameters) => new Draggable(target, parameters
6405
6598
 
6406
6599
 
6407
6600
 
6408
- /**
6409
- * @typedef {Object} ReactRef
6410
- * @property {HTMLElement|SVGElement|null} [current]
6411
- */
6412
-
6413
- /**
6414
- * @typedef {Object} AngularRef
6415
- * @property {HTMLElement|SVGElement} [nativeElement]
6416
- */
6417
-
6418
- /**
6419
- * @typedef {Object} ScopeParams
6420
- * @property {DOMTargetSelector|ReactRef|AngularRef} [root]
6421
- * @property {DefaultsParams} [defaults]
6422
- * @property {Record<String, String>} [mediaQueries]
6423
- */
6424
-
6425
- /**
6426
- * @callback ScopeCleanup
6427
- * @param {Scope} [scope]
6428
- */
6429
-
6430
- /**
6431
- * @callback ScopeConstructor
6432
- * @param {Scope} [scope]
6433
- * @return {ScopeCleanup|void}
6434
- */
6435
-
6436
- /**
6437
- * @callback ScopeMethod
6438
- * @param {...*} args
6439
- * @return {ScopeCleanup|void}
6440
- */
6441
-
6442
6601
  class Scope {
6443
6602
  /** @param {ScopeParams} [parameters] */
6444
6603
  constructor(parameters = {}) {
6445
- if (globals.scope) globals.scope.revertibles.push(this);
6604
+ if (scope.current) scope.current.register(this);
6446
6605
  const rootParam = parameters.root;
6447
6606
  /** @type {Document|DOMTarget} */
6448
6607
  let root = doc;
@@ -6459,13 +6618,23 @@ class Scope {
6459
6618
  this.defaults = scopeDefaults ? mergeObjects(scopeDefaults, globalDefault) : globalDefault;
6460
6619
  /** @type {Document|DOMTarget} */
6461
6620
  this.root = root;
6462
- /** @type {Array<ScopeConstructor>} */
6621
+ /** @type {Array<ScopeConstructorCallback>} */
6463
6622
  this.constructors = [];
6464
- /** @type {Array<Function>} */
6623
+ /** @type {Array<ScopeCleanupCallback>} */
6465
6624
  this.revertConstructors = [];
6466
6625
  /** @type {Array<Revertible>} */
6467
6626
  this.revertibles = [];
6468
- /** @type {Record<String, Function>} */
6627
+ /** @type {Array<ScopeConstructorCallback | ((scope: this) => Tickable)>} */
6628
+ this.constructorsOnce = [];
6629
+ /** @type {Array<ScopeCleanupCallback>} */
6630
+ this.revertConstructorsOnce = [];
6631
+ /** @type {Array<Revertible>} */
6632
+ this.revertiblesOnce = [];
6633
+ /** @type {Boolean} */
6634
+ this.once = false;
6635
+ /** @type {Number} */
6636
+ this.onceIndex = 0;
6637
+ /** @type {Record<String, ScopeMethod>} */
6469
6638
  this.methods = {};
6470
6639
  /** @type {Record<String, Boolean>} */
6471
6640
  this.matches = {};
@@ -6483,25 +6652,30 @@ class Scope {
6483
6652
  }
6484
6653
 
6485
6654
  /**
6486
- * @callback ScoppedCallback
6487
- * @param {this} scope
6488
- * @return {any}
6489
- *
6490
- * @param {ScoppedCallback} cb
6491
- * @return {this}
6655
+ * @param {Revertible} revertible
6656
+ */
6657
+ register(revertible) {
6658
+ const store = this.once ? this.revertiblesOnce : this.revertibles;
6659
+ store.push(revertible);
6660
+ }
6661
+
6662
+ /**
6663
+ * @template T
6664
+ * @param {ScopedCallback<T>} cb
6665
+ * @return {T}
6492
6666
  */
6493
6667
  execute(cb) {
6494
- let activeScope = globals.scope;
6495
- let activeRoot = globals.root;
6668
+ let activeScope = scope.current;
6669
+ let activeRoot = scope.root;
6496
6670
  let activeDefaults = globals.defaults;
6497
- globals.scope = this;
6498
- globals.root = this.root;
6671
+ scope.current = this;
6672
+ scope.root = this.root;
6499
6673
  globals.defaults = this.defaults;
6500
6674
  const mqs = this.mediaQueryLists;
6501
6675
  for (let mq in mqs) this.matches[mq] = mqs[mq].matches;
6502
6676
  const returned = cb(this);
6503
- globals.scope = activeScope;
6504
- globals.root = activeRoot;
6677
+ scope.current = activeScope;
6678
+ scope.root = activeRoot;
6505
6679
  globals.defaults = activeDefaults;
6506
6680
  return returned;
6507
6681
  }
@@ -6510,6 +6684,7 @@ class Scope {
6510
6684
  * @return {this}
6511
6685
  */
6512
6686
  refresh() {
6687
+ this.onceIndex = 0;
6513
6688
  this.execute(() => {
6514
6689
  let i = this.revertibles.length;
6515
6690
  let y = this.revertConstructors.length;
@@ -6517,9 +6692,9 @@ class Scope {
6517
6692
  while (y--) this.revertConstructors[y](this);
6518
6693
  this.revertibles.length = 0;
6519
6694
  this.revertConstructors.length = 0;
6520
- this.constructors.forEach( constructor => {
6695
+ this.constructors.forEach((/** @type {ScopeConstructorCallback} */constructor) => {
6521
6696
  const revertConstructor = constructor(this);
6522
- if (revertConstructor) {
6697
+ if (isFnc(revertConstructor)) {
6523
6698
  this.revertConstructors.push(revertConstructor);
6524
6699
  }
6525
6700
  });
@@ -6528,28 +6703,26 @@ class Scope {
6528
6703
  }
6529
6704
 
6530
6705
  /**
6531
- * @callback contructorCallback
6532
- * @param {this} self
6533
- *
6534
6706
  * @overload
6535
6707
  * @param {String} a1
6536
6708
  * @param {ScopeMethod} a2
6537
6709
  * @return {this}
6538
6710
  *
6539
6711
  * @overload
6540
- * @param {contructorCallback} a1
6712
+ * @param {ScopeConstructorCallback} a1
6541
6713
  * @return {this}
6542
6714
  *
6543
- * @param {String|contructorCallback} a1
6715
+ * @param {String|ScopeConstructorCallback} a1
6544
6716
  * @param {ScopeMethod} [a2]
6545
6717
  */
6546
6718
  add(a1, a2) {
6719
+ this.once = false;
6547
6720
  if (isFnc(a1)) {
6548
- const constructor = /** @type {contructorCallback} */(a1);
6721
+ const constructor = /** @type {ScopeConstructorCallback} */(a1);
6549
6722
  this.constructors.push(constructor);
6550
6723
  this.execute(() => {
6551
6724
  const revertConstructor = constructor(this);
6552
- if (revertConstructor) {
6725
+ if (isFnc(revertConstructor)) {
6553
6726
  this.revertConstructors.push(revertConstructor);
6554
6727
  }
6555
6728
  });
@@ -6559,6 +6732,46 @@ class Scope {
6559
6732
  return this;
6560
6733
  }
6561
6734
 
6735
+ /**
6736
+ * @param {ScopeConstructorCallback} scopeConstructorCallback
6737
+ * @return {this}
6738
+ */
6739
+ addOnce(scopeConstructorCallback) {
6740
+ this.once = true;
6741
+ if (isFnc(scopeConstructorCallback)) {
6742
+ const currentIndex = this.onceIndex++;
6743
+ const tracked = this.constructorsOnce[currentIndex];
6744
+ if (tracked) return this;
6745
+ const constructor = /** @type {ScopeConstructorCallback} */(scopeConstructorCallback);
6746
+ this.constructorsOnce[currentIndex] = constructor;
6747
+ this.execute(() => {
6748
+ const revertConstructor = constructor(this);
6749
+ if (isFnc(revertConstructor)) {
6750
+ this.revertConstructorsOnce.push(revertConstructor);
6751
+ }
6752
+ });
6753
+ }
6754
+ return this;
6755
+ }
6756
+
6757
+ /**
6758
+ * @param {(scope: this) => Tickable} cb
6759
+ * @return {Tickable}
6760
+ */
6761
+ keepTime(cb) {
6762
+ this.once = true;
6763
+ const currentIndex = this.onceIndex++;
6764
+ const tracked = /** @type {(scope: this) => Tickable} */(this.constructorsOnce[currentIndex]);
6765
+ if (isFnc(tracked)) return tracked(this);
6766
+ const constructor = /** @type {(scope: this) => Tickable} */(createRefreshable(cb));
6767
+ this.constructorsOnce[currentIndex] = constructor;
6768
+ let trackedTickable;
6769
+ this.execute(() => {
6770
+ trackedTickable = constructor(this);
6771
+ });
6772
+ return trackedTickable;
6773
+ }
6774
+
6562
6775
  /**
6563
6776
  * @param {Event} e
6564
6777
  */
@@ -6573,15 +6786,25 @@ class Scope {
6573
6786
  revert() {
6574
6787
  const revertibles = this.revertibles;
6575
6788
  const revertConstructors = this.revertConstructors;
6789
+ const revertiblesOnce = this.revertiblesOnce;
6790
+ const revertConstructorsOnce = this.revertConstructorsOnce;
6576
6791
  const mqs = this.mediaQueryLists;
6577
6792
  let i = revertibles.length;
6578
- let y = revertConstructors.length;
6793
+ let j = revertConstructors.length;
6794
+ let k = revertiblesOnce.length;
6795
+ let l = revertConstructorsOnce.length;
6579
6796
  while (i--) revertibles[i].revert();
6580
- while (y--) revertConstructors[y](this);
6797
+ while (j--) revertConstructors[j](this);
6798
+ while (k--) revertiblesOnce[k].revert();
6799
+ while (l--) revertConstructorsOnce[l](this);
6581
6800
  for (let mq in mqs) mqs[mq].removeEventListener('change', this);
6582
6801
  revertibles.length = 0;
6583
6802
  revertConstructors.length = 0;
6584
6803
  this.constructors.length = 0;
6804
+ revertiblesOnce.length = 0;
6805
+ revertConstructorsOnce.length = 0;
6806
+ this.constructorsOnce.length = 0;
6807
+ this.onceIndex = 0;
6585
6808
  this.matches = {};
6586
6809
  this.methods = {};
6587
6810
  this.mediaQueryLists = {};
@@ -6907,7 +7130,7 @@ const getAnimationDomTarget = linked => {
6907
7130
 
6908
7131
  let scrollerIndex = 0;
6909
7132
 
6910
- const debugColors = ['#FF4B4B','#FF971B','#FFC730','#F9F640','#7AFF5A','#18FF74','#17E09B','#3CFFEC','#05DBE9','#33B3F1','#638CF9','#C563FE','#FF4FCF','#F93F8A'];
7133
+ const debugColors$1 = ['#FF4B4B','#FF971B','#FFC730','#F9F640','#7AFF5A','#18FF74','#17E09B','#3CFFEC','#05DBE9','#33B3F1','#638CF9','#C563FE','#FF4FCF','#F93F8A'];
6911
7134
 
6912
7135
  /**
6913
7136
  * @typedef {Object} ScrollThresholdParam
@@ -6953,7 +7176,7 @@ class ScrollObserver {
6953
7176
  * @param {ScrollObserverParams} parameters
6954
7177
  */
6955
7178
  constructor(parameters = {}) {
6956
- if (globals.scope) globals.scope.revertibles.push(this);
7179
+ if (scope.current) scope.current.register(this);
6957
7180
  const syncMode = setValue(parameters.sync, 'play pause');
6958
7181
  const ease = syncMode ? parseEasings(/** @type {EasingParam} */(syncMode)) : null;
6959
7182
  const isLinear = syncMode && (syncMode === 'linear' || syncMode === none);
@@ -7150,7 +7373,7 @@ class ScrollObserver {
7150
7373
  const $debug = doc.createElement('div');
7151
7374
  const $thresholds = doc.createElement('div');
7152
7375
  const $triggers = doc.createElement('div');
7153
- const color = debugColors[this.index % debugColors.length];
7376
+ const color = debugColors$1[this.index % debugColors$1.length];
7154
7377
  const useWin = container.useWin;
7155
7378
  const containerWidth = useWin ? container.winWidth : container.width;
7156
7379
  const containerHeight = useWin ? container.winHeight : container.height;
@@ -7508,29 +7731,462 @@ const onScroll = (parameters = {}) => new ScrollObserver(parameters);
7508
7731
 
7509
7732
 
7510
7733
 
7734
+ const segmenter = !isUnd(Intl) && Intl.Segmenter;
7735
+ const valueRgx = /\{value\}/g;
7736
+ const indexRgx = /\{i\}/g;
7737
+ const whiteSpaceGroupRgx = /(\s+)/;
7738
+ const whiteSpaceRgx = /^\s+$/;
7739
+ const lineType = 'line';
7740
+ const wordType = 'word';
7741
+ const charType = 'char';
7742
+ const dataLine = `data-line`;
7743
+
7511
7744
  /**
7512
- * @typedef {Object} StaggerParameters
7513
- * @property {Number|String} [start]
7514
- * @property {Number|'first'|'center'|'last'} [from]
7515
- * @property {Boolean} [reversed]
7516
- * @property {Array.<Number>} [grid]
7517
- * @property {('x'|'y')} [axis]
7518
- * @property {EasingParam} [ease]
7519
- * @property {TweenModifier} [modifier]
7745
+ * @typedef {Object} Segment
7746
+ * @property {String} segment
7747
+ * @property {Boolean} [isWordLike]
7520
7748
  */
7521
7749
 
7522
7750
  /**
7523
- * @callback StaggerFunction
7524
- * @param {Target} [target]
7525
- * @param {Number} [index]
7526
- * @param {Number} [length]
7527
- * @param {Timeline} [tl]
7528
- * @return {Number|String}
7751
+ * @typedef {Object} Segmenter
7752
+ * @property {function(String): Iterable<Segment>} segment
7753
+ */
7754
+
7755
+ /** @type {Segmenter} */
7756
+ let wordSegmenter = null;
7757
+ /** @type {Segmenter} */
7758
+ let graphemeSegmenter = null;
7759
+ let $splitTemplate = null;
7760
+
7761
+ /**
7762
+ * @param {Segment} seg
7763
+ * @return {Boolean}
7764
+ */
7765
+ const isSegmentWordLike = seg => {
7766
+ return seg.isWordLike ||
7767
+ seg.segment === ' ' || // Consider spaces as words first, then handle them diffrently later
7768
+ isNum(+seg.segment); // Safari doesn't considers numbers as words
7769
+ };
7770
+
7771
+ /**
7772
+ * @param {HTMLElement} $el
7773
+ */
7774
+ const setAriaHidden = $el => $el.setAttribute('aria-hidden', 'true');
7775
+
7776
+ /**
7777
+ * @param {DOMTarget} $el
7778
+ * @param {String} type
7779
+ * @return {Array<HTMLElement>}
7780
+ */
7781
+ const getAllTopLevelElements = ($el, type) => [.../** @type {*} */($el.querySelectorAll(`[data-${type}]:not([data-${type}] [data-${type}])`))];
7782
+
7783
+ const debugColors = { line: '#00D672', word: '#FF4B4B', char: '#5A87FF' };
7784
+
7785
+ /**
7786
+ * @param {HTMLElement} $el
7787
+ */
7788
+ const filterEmptyElements = $el => {
7789
+ if (!$el.childElementCount && !$el.textContent.trim()) {
7790
+ const $parent = $el.parentElement;
7791
+ $el.remove();
7792
+ if ($parent) filterEmptyElements($parent);
7793
+ }
7794
+ };
7795
+
7796
+ /**
7797
+ * @param {HTMLElement} $el
7798
+ * @param {Number} lineIndex
7799
+ * @param {Set<HTMLElement>} bin
7800
+ * @returns {Set<HTMLElement>}
7801
+ */
7802
+ const filterLineElements = ($el, lineIndex, bin) => {
7803
+ const dataLineAttr = $el.getAttribute(dataLine);
7804
+ if (dataLineAttr !== null && +dataLineAttr !== lineIndex || $el.tagName === 'BR') bin.add($el);
7805
+ let i = $el.childElementCount;
7806
+ while (i--) filterLineElements(/** @type {HTMLElement} */($el.children[i]), lineIndex, bin);
7807
+ return bin;
7808
+ };
7809
+
7810
+ /**
7811
+ * @param {'line'|'word'|'char'} type
7812
+ * @param {splitTemplateParams} params
7813
+ * @return {String}
7814
+ */
7815
+ const generateTemplate = (type, params = {}) => {
7816
+ let template = ``;
7817
+ const classString = isStr(params.class) ? ` class="${params.class}"` : '';
7818
+ const cloneType = setValue(params.clone, false);
7819
+ const wrapType = setValue(params.wrap, false);
7820
+ const overflow = wrapType ? wrapType === true ? 'clip' : wrapType : cloneType ? 'clip' : false;
7821
+ if (wrapType) template += `<span${overflow ? ` style="overflow:${overflow};"` : ''}>`;
7822
+ template += `<span${classString}${cloneType ? ` style="position:relative;"` : ''} data-${type}="{i}">`;
7823
+ if (cloneType) {
7824
+ const left = cloneType === 'left' ? '-100%' : cloneType === 'right' ? '100%' : '0';
7825
+ const top = cloneType === 'top' ? '-100%' : cloneType === 'bottom' ? '100%' : '0';
7826
+ template += `<span>{value}</span>`;
7827
+ template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`;
7828
+ } else {
7829
+ template += `{value}`;
7830
+ }
7831
+ template += `</span>`;
7832
+ if (wrapType) template += `</span>`;
7833
+ return template;
7834
+ };
7835
+
7836
+ /**
7837
+ * @param {String|SplitFunctionValue} htmlTemplate
7838
+ * @param {Array<HTMLElement>} store
7839
+ * @param {Node|HTMLElement} node
7840
+ * @param {DocumentFragment} $parentFragment
7841
+ * @param {'line'|'word'|'char'} type
7842
+ * @param {Boolean} debug
7843
+ * @param {Number} lineIndex
7844
+ * @param {Number} [wordIndex]
7845
+ * @param {Number} [charIndex]
7846
+ * @return {HTMLElement}
7847
+ */
7848
+ const processHTMLTemplate = (htmlTemplate, store, node, $parentFragment, type, debug, lineIndex, wordIndex, charIndex) => {
7849
+ const isLine = type === lineType;
7850
+ const isChar = type === charType;
7851
+ const className = `_${type}_`;
7852
+ const template = isFnc(htmlTemplate) ? htmlTemplate(node) : htmlTemplate;
7853
+ const displayStyle = isLine ? 'block' : 'inline-block';
7854
+ $splitTemplate.innerHTML = template
7855
+ .replace(valueRgx, `<i class="${className}"></i>`)
7856
+ .replace(indexRgx, `${isChar ? charIndex : isLine ? lineIndex : wordIndex}`);
7857
+ const $content = $splitTemplate.content;
7858
+ const $highestParent = /** @type {HTMLElement} */($content.firstElementChild);
7859
+ const $split = /** @type {HTMLElement} */($content.querySelector(`[data-${type}]`)) || $highestParent;
7860
+ const $replacables = /** @type {NodeListOf<HTMLElement>} */($content.querySelectorAll(`i.${className}`));
7861
+ const replacablesLength = $replacables.length;
7862
+ if (replacablesLength) {
7863
+ $highestParent.style.display = displayStyle;
7864
+ $split.style.display = displayStyle;
7865
+ $split.setAttribute(dataLine, `${lineIndex}`);
7866
+ if (!isLine) {
7867
+ $split.setAttribute('data-word', `${wordIndex}`);
7868
+ if (isChar) $split.setAttribute('data-char', `${charIndex}`);
7869
+ }
7870
+ let i = replacablesLength;
7871
+ while (i--) {
7872
+ const $replace = $replacables[i];
7873
+ const $closestParent = $replace.parentElement;
7874
+ $closestParent.style.display = displayStyle;
7875
+ if (isLine) {
7876
+ $closestParent.innerHTML = /** @type {HTMLElement} */(node).innerHTML;
7877
+ } else {
7878
+ $closestParent.replaceChild(node.cloneNode(true), $replace);
7879
+ }
7880
+ }
7881
+ store.push($split);
7882
+ $parentFragment.appendChild($content);
7883
+ } else {
7884
+ console.warn(`The expression "{value}" is missing from the provided template.`);
7885
+ }
7886
+ if (debug) $highestParent.style.outline = `1px dotted ${debugColors[type]}`;
7887
+ return $highestParent;
7888
+ };
7889
+
7890
+ /**
7891
+ * A class that splits text into words and wraps them in span elements while preserving the original HTML structure.
7892
+ * @class
7893
+ */
7894
+ class TextSplitter {
7895
+ /**
7896
+ * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
7897
+ * @param {TextSplitterParams} [parameters]
7898
+ */
7899
+ constructor(target, parameters = {}) {
7900
+ // Only init segmenters when needed
7901
+ if (!wordSegmenter) wordSegmenter = segmenter ? new segmenter([], { granularity: wordType }) : {
7902
+ segment: (text) => {
7903
+ const segments = [];
7904
+ const words = text.split(whiteSpaceGroupRgx);
7905
+ for (let i = 0, l = words.length; i < l; i++) {
7906
+ const segment = words[i];
7907
+ segments.push({
7908
+ segment,
7909
+ isWordLike: !whiteSpaceRgx.test(segment), // Consider non-whitespace as word-like
7910
+ });
7911
+ }
7912
+ return segments;
7913
+ }
7914
+ };
7915
+ if (!graphemeSegmenter) graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : {
7916
+ segment: text => [...text].map(char => ({ segment: char }))
7917
+ };
7918
+ if (!$splitTemplate && isBrowser) $splitTemplate = doc.createElement('template');
7919
+ if (scope.current) scope.current.register(this);
7920
+ const { words, chars, lines, accessible, includeSpaces, debug } = parameters;
7921
+ const $target = /** @type {HTMLElement} */((target = isArr(target) ? target[0] : target) && /** @type {Node} */(target).nodeType ? target : (getNodeList(target) || [])[0]);
7922
+ const lineParams = lines === true ? {} : lines;
7923
+ const wordParams = words === true || isUnd(words) ? {} : words;
7924
+ const charParams = chars === true ? {} : chars;
7925
+ this.debug = setValue(debug, false);
7926
+ this.includeSpaces = setValue(includeSpaces, false);
7927
+ this.accessible = setValue(accessible, true);
7928
+ this.linesOnly = lineParams && (!wordParams && !charParams);
7929
+ /** @type {String|false|SplitFunctionValue} */
7930
+ this.lineTemplate = isObj(lineParams) ? generateTemplate(lineType, /** @type {splitTemplateParams} */(lineParams)) : lineParams;
7931
+ /** @type {String|false|SplitFunctionValue} */
7932
+ this.wordTemplate = isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {splitTemplateParams} */(wordParams)) : wordParams;
7933
+ /** @type {String|false|SplitFunctionValue} */
7934
+ this.charTemplate = isObj(charParams) ? generateTemplate(charType, /** @type {splitTemplateParams} */(charParams)) : charParams;
7935
+ this.$target = $target;
7936
+ this.html = $target && $target.innerHTML;
7937
+ this.lines = [];
7938
+ this.words = [];
7939
+ this.chars = [];
7940
+ this.effects = [];
7941
+ this.effectsCleanups = [];
7942
+ this.cache = null;
7943
+ this.ready = false;
7944
+ this.width = 0;
7945
+ this.resizeTimeout = null;
7946
+ const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split();
7947
+ // Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback
7948
+ this.resizeObserver = new ResizeObserver(() => {
7949
+ // Use a setTimeout instead of a Timer for better tree shaking
7950
+ clearTimeout(this.resizeTimeout);
7951
+ this.resizeTimeout = setTimeout(() => {
7952
+ const currentWidth = /** @type {HTMLElement} */($target).offsetWidth;
7953
+ if (currentWidth === this.width) return;
7954
+ this.width = currentWidth;
7955
+ handleSplit();
7956
+ }, 150);
7957
+ });
7958
+ // Only declare the font ready promise when splitting by lines and not alreay split
7959
+ if (this.lineTemplate && !this.ready) {
7960
+ doc.fonts.ready.then(handleSplit);
7961
+ } else {
7962
+ handleSplit();
7963
+ }
7964
+ $target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.');
7965
+ }
7966
+
7967
+ /**
7968
+ * @param {(...args: any[]) => Tickable | (() => void)} effect
7969
+ * @return this
7970
+ */
7971
+ addEffect(effect) {
7972
+ if (!isFnc(effect)) return console.warn('Effect must return a function.');
7973
+ const refreshableEffect = createRefreshable(effect);
7974
+ this.effects.push(refreshableEffect);
7975
+ if (this.ready) this.effectsCleanups[this.effects.length - 1] = refreshableEffect(this);
7976
+ return this;
7977
+ }
7978
+
7979
+ revert() {
7980
+ clearTimeout(this.resizeTimeout);
7981
+ this.lines.length = this.words.length = this.chars.length = 0;
7982
+ this.resizeObserver.disconnect();
7983
+ // Make sure to revert the effects after disconnecting the resizeObserver to avoid triggering it in the process
7984
+ this.effectsCleanups.forEach(cleanup => isFnc(cleanup) ? cleanup(this) : cleanup.revert && cleanup.revert());
7985
+ this.$target.innerHTML = this.html;
7986
+ return this;
7987
+ }
7988
+
7989
+ /**
7990
+ * Recursively processes a node and its children
7991
+ * @param {Node} node
7992
+ */
7993
+ splitNode(node) {
7994
+ const wordTemplate = this.wordTemplate;
7995
+ const charTemplate = this.charTemplate;
7996
+ const includeSpaces = this.includeSpaces;
7997
+ const debug = this.debug;
7998
+ const nodeType = node.nodeType;
7999
+ if (nodeType === 3) {
8000
+ const nodeText = node.nodeValue;
8001
+ // If the nodeText is only whitespace, leave it as is
8002
+ if (nodeText.trim()) {
8003
+ const tempWords = [];
8004
+ const words = this.words;
8005
+ const chars = this.chars;
8006
+ const wordSegments = wordSegmenter.segment(nodeText);
8007
+ const $wordsFragment = doc.createDocumentFragment();
8008
+ let prevSeg = null;
8009
+ for (const wordSegment of wordSegments) {
8010
+ const segment = wordSegment.segment;
8011
+ const isWordLike = isSegmentWordLike(wordSegment);
8012
+ // Determine if this segment should be a new word, first segment always becomes a new word
8013
+ if (!prevSeg || (isWordLike && (prevSeg && (isSegmentWordLike(prevSeg))))) {
8014
+ tempWords.push(segment);
8015
+ } else {
8016
+ // Only concatenate if both current and previous are non-word-like and don't contain spaces
8017
+ const lastWordIndex = tempWords.length - 1;
8018
+ const lastWord = tempWords[lastWordIndex];
8019
+ if (!lastWord.includes(' ') && !segment.includes(' ')) {
8020
+ tempWords[lastWordIndex] += segment;
8021
+ } else {
8022
+ tempWords.push(segment);
8023
+ }
8024
+ }
8025
+ prevSeg = wordSegment;
8026
+ }
8027
+
8028
+ for (let i = 0, l = tempWords.length; i < l; i++) {
8029
+ const word = tempWords[i];
8030
+ if (!word.trim()) {
8031
+ // Preserve whitespace only if includeSpaces is false and if the current space is not the first node
8032
+ if (i && includeSpaces) continue;
8033
+ $wordsFragment.appendChild(doc.createTextNode(word));
8034
+ } else {
8035
+ const nextWord = tempWords[i + 1];
8036
+ const hasWordFollowingSpace = includeSpaces && nextWord && !nextWord.trim();
8037
+ const wordToProcess = word;
8038
+ const charSegments = charTemplate ? graphemeSegmenter.segment(wordToProcess) : null;
8039
+ const $charsFragment = charTemplate ? doc.createDocumentFragment() : doc.createTextNode(hasWordFollowingSpace ? word + '\xa0' : word);
8040
+ if (charTemplate) {
8041
+ const charSegmentsArray = [...charSegments];
8042
+ for (let j = 0, jl = charSegmentsArray.length; j < jl; j++) {
8043
+ const charSegment = charSegmentsArray[j];
8044
+ const isLastChar = j === jl - 1;
8045
+ // If this is the last character and includeSpaces is true with a following space, append the space
8046
+ const charText = isLastChar && hasWordFollowingSpace ? charSegment.segment + '\xa0' : charSegment.segment;
8047
+ const $charNode = doc.createTextNode(charText);
8048
+ processHTMLTemplate(charTemplate, chars, $charNode, /** @type {DocumentFragment} */($charsFragment), charType, debug, -1, words.length, chars.length);
8049
+ }
8050
+ }
8051
+ if (wordTemplate) {
8052
+ processHTMLTemplate(wordTemplate, words, $charsFragment, $wordsFragment, wordType, debug, -1, words.length, chars.length);
8053
+ // Chars elements must be re-parsed in the split() method if both words and chars are parsed
8054
+ } else if (charTemplate) {
8055
+ $wordsFragment.appendChild($charsFragment);
8056
+ } else {
8057
+ $wordsFragment.appendChild(doc.createTextNode(word));
8058
+ }
8059
+ // Skip the next iteration if we included a space
8060
+ if (hasWordFollowingSpace) i++;
8061
+ }
8062
+ }
8063
+ node.parentNode.replaceChild($wordsFragment, node);
8064
+ }
8065
+ } else if (nodeType === 1) {
8066
+ // Converting to an array is necessary to work around childNodes pottential mutation
8067
+ const childNodes = /** @type {Array<Node>} */([.../** @type {*} */(node.childNodes)]);
8068
+ for (let i = 0, l = childNodes.length; i < l; i++) this.splitNode(childNodes[i]);
8069
+ }
8070
+ }
8071
+
8072
+ /**
8073
+ * @param {Boolean} clearCache
8074
+ * @return {this}
8075
+ */
8076
+ split(clearCache = false) {
8077
+ const $el = this.$target;
8078
+ const isCached = !!this.cache && !clearCache;
8079
+ const lineTemplate = this.lineTemplate;
8080
+ const wordTemplate = this.wordTemplate;
8081
+ const charTemplate = this.charTemplate;
8082
+ const fontsReady = doc.fonts.status !== 'loading';
8083
+ const canSplitLines = lineTemplate && fontsReady;
8084
+ this.ready = !lineTemplate || fontsReady;
8085
+ if (!isCached) {
8086
+ if (clearCache) {
8087
+ $el.innerHTML = this.html;
8088
+ this.words.length = this.chars.length = 0;
8089
+ }
8090
+ this.splitNode($el);
8091
+ this.cache = $el.innerHTML;
8092
+ }
8093
+ // Always reset the html when splitting by lines
8094
+ if (canSplitLines) {
8095
+ // No need to revert effects animations here since it's already taken care by the refreshable
8096
+ this.effectsCleanups.forEach(cleanup => isFnc(cleanup) && cleanup(this));
8097
+ if (isCached) $el.innerHTML = this.cache;
8098
+ this.lines.length = 0;
8099
+ if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
8100
+ }
8101
+ // Always reparse characters after a line reset or if both words and chars are activated
8102
+ if (charTemplate && (canSplitLines || wordTemplate)) {
8103
+ this.chars = getAllTopLevelElements($el, charType);
8104
+ }
8105
+ // Words are used when lines only and prioritized over chars
8106
+ const elementsArray = this.words.length ? this.words : this.chars;
8107
+ let y, linesCount = 0;
8108
+ for (let i = 0, l = elementsArray.length; i < l; i++) {
8109
+ const $el = elementsArray[i];
8110
+ const { top, height } = $el.getBoundingClientRect();
8111
+ if (y && top - y > height * .5) linesCount++;
8112
+ $el.setAttribute(dataLine, `${linesCount}`);
8113
+ const nested = $el.querySelectorAll(`[${dataLine}]`);
8114
+ let c = nested.length;
8115
+ while (c--) nested[c].setAttribute(dataLine, `${linesCount}`);
8116
+ y = top;
8117
+ }
8118
+ if (canSplitLines) {
8119
+ const linesFragment = doc.createDocumentFragment();
8120
+ const parents = new Set();
8121
+ const clones = [];
8122
+ for (let lineIndex = 0; lineIndex < linesCount + 1; lineIndex++) {
8123
+ const $clone = /** @type {HTMLElement} */($el.cloneNode(true));
8124
+ filterLineElements($clone, lineIndex, new Set()).forEach($el => {
8125
+ const $parent = $el.parentElement;
8126
+ if ($parent) parents.add($parent);
8127
+ $el.remove();
8128
+ });
8129
+ clones.push($clone);
8130
+ }
8131
+ parents.forEach(filterEmptyElements);
8132
+ for (let cloneIndex = 0, clonesLength = clones.length; cloneIndex < clonesLength; cloneIndex++) {
8133
+ processHTMLTemplate(lineTemplate, this.lines, clones[cloneIndex], linesFragment, lineType, this.debug, cloneIndex);
8134
+ }
8135
+ $el.innerHTML = '';
8136
+ $el.appendChild(linesFragment);
8137
+ if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
8138
+ if (charTemplate) this.chars = getAllTopLevelElements($el, charType);
8139
+ }
8140
+ // Remove the word wrappers and clear the words array if lines split only
8141
+ if (this.linesOnly) {
8142
+ const words = this.words;
8143
+ let w = words.length;
8144
+ while (w--) {
8145
+ const $word = words[w];
8146
+ $word.replaceWith($word.textContent);
8147
+ }
8148
+ words.length = 0;
8149
+ }
8150
+ if (canSplitLines || clearCache) {
8151
+ this.effects.forEach((effect, i) => this.effectsCleanups[i] = effect(this));
8152
+ }
8153
+ if (this.accessible && (canSplitLines || !isCached)) {
8154
+ const $accessible = doc.createElement('span');
8155
+ // Make the accessible element visually-hidden (https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html)
8156
+ $accessible.style.cssText = `position:absolute;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);width:1px;height:1px;white-space:nowrap;`;
8157
+ // $accessible.setAttribute('tabindex', '-1');
8158
+ $accessible.innerHTML = this.html;
8159
+ $el.insertBefore($accessible, $el.firstChild);
8160
+ this.lines.forEach(setAriaHidden);
8161
+ this.words.forEach(setAriaHidden);
8162
+ this.chars.forEach(setAriaHidden);
8163
+ }
8164
+ this.width = /** @type {HTMLElement} */($el).offsetWidth;
8165
+ return this;
8166
+ }
8167
+
8168
+ refresh() {
8169
+ this.split(true);
8170
+ }
8171
+ }
8172
+
8173
+ /**
8174
+ * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
8175
+ * @param {TextSplitterParams} [parameters]
8176
+ * @return {TextSplitter}
7529
8177
  */
8178
+ const split = (target, parameters) => new TextSplitter(target, parameters);
8179
+
8180
+ const text = {
8181
+ split,
8182
+ };
8183
+
8184
+
8185
+
7530
8186
 
7531
8187
  /**
7532
8188
  * @param {Number|String|[Number|String,Number|String]} val
7533
- * @param {StaggerParameters} params
8189
+ * @param {StaggerParams} params
7534
8190
  * @return {StaggerFunction}
7535
8191
  */
7536
8192
  const stagger = (val, params = {}) => {
@@ -7544,20 +8200,27 @@ const stagger = (val, params = {}) => {
7544
8200
  const staggerEase = hasSpring ? /** @type {Spring} */(ease).ease : hasEasing ? parseEasings(ease) : null;
7545
8201
  const grid = params.grid;
7546
8202
  const axis = params.axis;
8203
+ const customTotal = params.total;
7547
8204
  const fromFirst = isUnd(from) || from === 0 || from === 'first';
7548
8205
  const fromCenter = from === 'center';
7549
8206
  const fromLast = from === 'last';
8207
+ const fromRandom = from === 'random';
7550
8208
  const isRange = isArr(val);
8209
+ const useProp = params.use;
7551
8210
  const val1 = isRange ? parseNumber(val[0]) : parseNumber(val);
7552
8211
  const val2 = isRange ? parseNumber(val[1]) : 0;
7553
8212
  const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
7554
8213
  const start = params.start || 0 + (isRange ? val1 : 0);
7555
8214
  let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
7556
- return (_, i, t, tl) => {
7557
- if (fromCenter) fromIndex = (t - 1) / 2;
7558
- if (fromLast) fromIndex = t - 1;
8215
+ return (target, i, t, tl) => {
8216
+ const [ registeredTarget ] = registerTargets(target);
8217
+ const total = isUnd(customTotal) ? t : customTotal;
8218
+ const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
8219
+ const staggerIndex = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
8220
+ if (fromCenter) fromIndex = (total - 1) / 2;
8221
+ if (fromLast) fromIndex = total - 1;
7559
8222
  if (!values.length) {
7560
- for (let index = 0; index < t; index++) {
8223
+ for (let index = 0; index < total; index++) {
7561
8224
  if (!grid) {
7562
8225
  values.push(abs(fromIndex - index));
7563
8226
  } else {
@@ -7576,15 +8239,16 @@ const stagger = (val, params = {}) => {
7576
8239
  }
7577
8240
  if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue);
7578
8241
  if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
8242
+ if (fromRandom) values = shuffle(values);
7579
8243
  }
7580
8244
  const spacing = isRange ? (val2 - val1) / maxValue : val1;
7581
8245
  const offset = tl ? parseTimelinePosition(tl, isUnd(params.start) ? tl.iterationDuration : start) : /** @type {Number} */(start);
7582
8246
  /** @type {String|Number} */
7583
- let output = offset + ((spacing * round(values[i], 2)) || 0);
8247
+ let output = offset + ((spacing * round(values[staggerIndex], 2)) || 0);
7584
8248
  if (params.modifier) output = params.modifier(output);
7585
8249
  if (unitMatch) output = `${output}${unitMatch[2]}`;
7586
8250
  return output;
7587
8251
  }
7588
8252
  };
7589
8253
 
7590
- export { Animatable, Draggable, JSAnimation, Scope, ScrollObserver, Spring, Timeline, Timer, WAAPIAnimation, animate, createAnimatable, createDraggable, createScope, createSpring, createTimeline, createTimer, eases, engine, onScroll, scrollContainers, stagger, svg, utils, waapi };
8254
+ export { Animatable, Draggable, JSAnimation, Scope, ScrollObserver, Spring, TextSplitter, Timeline, Timer, WAAPIAnimation, animate, createAnimatable, createDraggable, createScope, createSpring, createTimeline, createTimer, eases, engine, onScroll, scrollContainers, stagger, svg, text, utils, waapi };