animejs 4.0.2 → 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,12 +1,14 @@
1
1
  /**
2
2
  * anime.js - ESM
3
- * @version v4.0.2
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
14
  * @property {Number|String} [id]
@@ -36,61 +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 */
40
42
 
41
- /**
42
- * @typedef {Object} DraggableAxisParam
43
- * @property {String} [mapTo]
44
- * @property {TweenModifier} [modifier]
45
- * @property {TweenComposition} [composition]
46
- * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap]
47
- */
43
+ // Stagger types //////////////////////////////////////////////////////////////
48
44
 
49
45
  /**
50
- * @typedef {Object} DraggableCursorParams
51
- * @property {String} [onHover]
52
- * @property {String} [onGrab]
46
+ * @callback StaggerFunction
47
+ * @param {Target} [target]
48
+ * @param {Number} [index]
49
+ * @param {Number} [length]
50
+ * @param {Timeline} [tl]
51
+ * @return {Number|String}
53
52
  */
54
53
 
55
54
  /**
56
- * @typedef {Object} DraggableParams
57
- * @property {DOMTargetSelector} [trigger]
58
- * @property {DOMTargetSelector|Array<Number>|((draggable: Draggable) => DOMTargetSelector|Array<Number>)} [container]
59
- * @property {Boolean|DraggableAxisParam} [x]
60
- * @property {Boolean|DraggableAxisParam} [y]
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]
61
64
  * @property {TweenModifier} [modifier]
62
- * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap]
63
- * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [containerPadding]
64
- * @property {Number|((draggable: Draggable) => Number)} [containerFriction]
65
- * @property {Number|((draggable: Draggable) => Number)} [releaseContainerFriction]
66
- * @property {Number|((draggable: Draggable) => Number)} [dragSpeed]
67
- * @property {Number|((draggable: Draggable) => Number)} [scrollSpeed]
68
- * @property {Number|((draggable: Draggable) => Number)} [scrollThreshold]
69
- * @property {Number|((draggable: Draggable) => Number)} [minVelocity]
70
- * @property {Number|((draggable: Draggable) => Number)} [maxVelocity]
71
- * @property {Number|((draggable: Draggable) => Number)} [velocityMultiplier]
72
- * @property {Number} [releaseMass]
73
- * @property {Number} [releaseStiffness]
74
- * @property {Number} [releaseDamping]
75
- * @property {Boolean} [releaseDamping]
76
- * @property {EasingParam} [releaseEase]
77
- * @property {Boolean|DraggableCursorParams|((draggable: Draggable) => Boolean|DraggableCursorParams)} [cursor]
78
- * @property {Callback<Draggable>} [onGrab]
79
- * @property {Callback<Draggable>} [onDrag]
80
- * @property {Callback<Draggable>} [onRelease]
81
- * @property {Callback<Draggable>} [onUpdate]
82
- * @property {Callback<Draggable>} [onSettle]
83
- * @property {Callback<Draggable>} [onSnap]
84
- * @property {Callback<Draggable>} [onResize]
85
- * @property {Callback<Draggable>} [onAfterResize]
86
65
  */
87
66
 
88
- /**
89
- * @typedef {SVGGeometryElement & {
90
- * setAttribute(name: 'draw', value: `${number} ${number}`): void;
91
- * draw: `${number} ${number}`;
92
- * }} DrawableSVGGeometry
93
- */
67
+ // Eases types ////////////////////////////////////////////////////////////////
94
68
 
95
69
  /**
96
70
  * @callback EasingFunction
@@ -118,21 +92,7 @@
118
92
  /** @typedef {Array.<TargetSelector>|TargetSelector} TargetsParam */
119
93
  /** @typedef {Array.<Target>} TargetsArray */
120
94
 
121
- /**
122
- * @callback FunctionValue
123
- * @param {Target} target - The animated target
124
- * @param {Number} index - The target index
125
- * @param {Number} length - The total number of animated targets
126
- * @return {Number|String|TweenObjectValue|Array.<Number|String|TweenObjectValue>}
127
- */
128
-
129
- /**
130
- * @callback TweenModifier
131
- * @param {Number} value - The animated value
132
- * @return {Number|String}
133
- */
134
-
135
- /** @typedef {[Number, Number, Number, Number]} ColorArray */
95
+ // Callback types ////////////////////////////////////////////////////////////
136
96
 
137
97
  /**
138
98
  * @template T
@@ -159,6 +119,46 @@
159
119
  * @property {Callback<T>} [onRender]
160
120
  */
161
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
+
162
162
  /**
163
163
  * @typedef {Object} Tween
164
164
  * @property {Number} id
@@ -212,25 +212,7 @@
212
212
  /** @typedef {WeakMap.<Target, TweenLookups>} TweenReplaceLookups */
213
213
  /** @typedef {Map.<Target, TweenLookups>} TweenAdditiveLookups */
214
214
 
215
- /**
216
- * @typedef {Object} TimerOptions
217
- * @property {Number|String} [id]
218
- * @property {TweenParamValue} [duration]
219
- * @property {TweenParamValue} [delay]
220
- * @property {Number} [loopDelay]
221
- * @property {Boolean} [reversed]
222
- * @property {Boolean} [alternate]
223
- * @property {Boolean|Number} [loop]
224
- * @property {Boolean|ScrollObserver} [autoplay]
225
- * @property {Number} [frameRate]
226
- * @property {Number} [playbackRate]
227
- */
228
-
229
- /**
230
-
231
- /**
232
- * @typedef {TimerOptions & TickableCallbacks<Timer>} TimerParams
233
- */
215
+ // Animation types ////////////////////////////////////////////////////////////
234
216
 
235
217
  /**
236
218
  * @typedef {Number|String|FunctionValue} TweenParamValue
@@ -304,6 +286,8 @@
304
286
  * @typedef {Record<String, TweenOptions | Callback<JSAnimation> | TweenModifier | boolean | PercentageKeyframes | DurationKeyframes | ScrollObserver> & TimerOptions & AnimationOptions & TweenParamsOptions & TickableCallbacks<JSAnimation> & RenderableCallbacks<JSAnimation>} AnimationParams
305
287
  */
306
288
 
289
+ // Timeline types /////////////////////////////////////////////////////////////
290
+
307
291
  /**
308
292
  * @typedef {Object} TimelineOptions
309
293
  * @property {DefaultsParams} [defaults]
@@ -314,6 +298,8 @@
314
298
  * @typedef {TimerOptions & TimelineOptions & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} TimelineParams
315
299
  */
316
300
 
301
+ // Animatable types ///////////////////////////////////////////////////////////
302
+
317
303
  /**
318
304
  * @callback AnimatablePropertySetter
319
305
  * @param {Number|Array.<Number>} to
@@ -348,6 +334,136 @@
348
334
  * @typedef {Record<String, TweenParamValue | EasingParam | TweenModifier | TweenComposition | AnimatablePropertyParamsOptions> & AnimatablePropertyParamsOptions} AnimatableParams
349
335
  */
350
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
+
351
467
 
352
468
  // Environments
353
469
 
@@ -412,11 +528,13 @@ const maxFps = 120;
412
528
  // Strings
413
529
 
414
530
  const emptyString = '';
415
- const shortTransforms = new Map();
416
-
417
- shortTransforms.set('x', 'translateX');
418
- shortTransforms.set('y', 'translateY');
419
- 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
+ })();
420
538
 
421
539
  const validTransforms = [
422
540
  'translateX',
@@ -438,7 +556,7 @@ const validTransforms = [
438
556
  'matrix3d',
439
557
  ];
440
558
 
441
- const transformsFragmentStrings = validTransforms.reduce((a, v) => ({...a, [v]: v + '('}), {});
559
+ const transformsFragmentStrings = /*#__PURE__*/ validTransforms.reduce((a, v) => ({...a, [v]: v + '('}), {});
442
560
 
443
561
  // Functions
444
562
 
@@ -489,13 +607,16 @@ const defaults = {
489
607
  onRender: noop,
490
608
  };
491
609
 
610
+ const scope = {
611
+ /** @type {Scope} */
612
+ current: null,
613
+ /** @type {Document|DOMTarget} */
614
+ root: doc,
615
+ };
616
+
492
617
  const globals = {
493
618
  /** @type {DefaultsParams} */
494
619
  defaults,
495
- /** @type {Document|DOMTarget} */
496
- root: doc,
497
- /** @type {Scope} */
498
- scope: null,
499
620
  /** @type {Number} */
500
621
  precision: 4,
501
622
  /** @type {Number} */
@@ -504,7 +625,7 @@ const globals = {
504
625
  tickThreshold: 200,
505
626
  };
506
627
 
507
- const globalVersions = { version: '4.0.2', engine: null };
628
+ const globalVersions = { version: '4.1.0', engine: null };
508
629
 
509
630
  if (isBrowser) {
510
631
  if (!win.AnimeJS) win.AnimeJS = [];
@@ -623,6 +744,25 @@ const snap = (v, increment) => isArr(increment) ? increment.reduce((closest, cv)
623
744
  */
624
745
  const interpolate = (start, end, progress) => start + (end - start) * progress;
625
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
+
626
766
  /**
627
767
  * @param {Number} v
628
768
  * @return {Number}
@@ -719,6 +859,32 @@ const addChild = (parent, child, sortMethod, prevProp = '_prev', nextProp = '_ne
719
859
  child[nextProp] = next;
720
860
  };
721
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
+
722
888
  /*
723
889
  * Base class to control framerate and playback rate.
724
890
  * Inherited by Engine, Timer, Animation and Timeline.
@@ -1227,8 +1393,8 @@ const addAdditiveAnimation = lookups => {
1227
1393
  return animation;
1228
1394
  };
1229
1395
 
1230
- const engineTickMethod = isBrowser ? requestAnimationFrame : setImmediate;
1231
- const engineCancelMethod = isBrowser ? cancelAnimationFrame : clearImmediate;
1396
+ const engineTickMethod = /*#__PURE__*/ (() => isBrowser ? requestAnimationFrame : setImmediate)();
1397
+ const engineCancelMethod = /*#__PURE__*/ (() => isBrowser ? cancelAnimationFrame : clearImmediate)();
1232
1398
 
1233
1399
  class Engine extends Clock {
1234
1400
 
@@ -1402,7 +1568,7 @@ const parseInlineTransforms = (target, propName, animationInlineStyles) => {
1402
1568
  * @return {NodeList|HTMLCollection}
1403
1569
  */
1404
1570
  function getNodeList(v) {
1405
- const n = isStr(v) ? globals.root.querySelectorAll(v) : v;
1571
+ const n = isStr(v) ? scope.root.querySelectorAll(v) : v;
1406
1572
  if (n instanceof NodeList || n instanceof HTMLCollection) return n;
1407
1573
  }
1408
1574
 
@@ -2335,7 +2501,7 @@ class Timer extends Clock {
2335
2501
  onUpdate,
2336
2502
  } = parameters;
2337
2503
 
2338
- if (globals.scope) globals.scope.revertibles.push(this);
2504
+ if (scope.current) scope.current.register(this);
2339
2505
 
2340
2506
  const timerInitTime = parent ? 0 : engine._elapsedTime;
2341
2507
  const timerDefaults = parent ? parent.defaults : globals.defaults;
@@ -2465,7 +2631,7 @@ class Timer extends Clock {
2465
2631
  }
2466
2632
 
2467
2633
  get progress() {
2468
- return clamp(round(this._currentTime / this.duration, 5), 0, 1);
2634
+ return clamp(round(this._currentTime / this.duration, 10), 0, 1);
2469
2635
  }
2470
2636
 
2471
2637
  /** @param {Number} progress */
@@ -2474,7 +2640,7 @@ class Timer extends Clock {
2474
2640
  }
2475
2641
 
2476
2642
  get iterationProgress() {
2477
- return clamp(round(this._iterationTime / this.iterationDuration, 5), 0, 1);
2643
+ return clamp(round(this._iterationTime / this.iterationDuration, 10), 0, 1);
2478
2644
  }
2479
2645
 
2480
2646
  /** @param {Number} progress */
@@ -3734,12 +3900,13 @@ class JSAnimation extends Timer {
3734
3900
  */
3735
3901
  refresh() {
3736
3902
  forEachChildren(this, (/** @type {Tween} */tween) => {
3737
- const ogValue = getOriginalAnimatableValue(tween.target, tween.property, tween._tweenType);
3738
- decomposeRawValue(ogValue, decomposedOriginalValue);
3739
- tween._fromNumbers = cloneArray(decomposedOriginalValue.d);
3740
- tween._fromNumber = decomposedOriginalValue.n;
3741
- if (tween._func) {
3742
- 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;
3743
3910
  tween._toNumbers = cloneArray(toTargetObject.d);
3744
3911
  tween._strings = cloneArray(toTargetObject.s);
3745
3912
  tween._toNumber = toTargetObject.n;
@@ -3898,30 +4065,9 @@ const commonDefaultPXProperties = [
3898
4065
  ...transformsShorthands
3899
4066
  ];
3900
4067
 
3901
- const validIndividualTransforms = [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))];
3902
-
3903
- // Setting it to true in case CSS.registerProperty is not supported will automatically skip the registration and fallback to no animation
3904
- let transformsPropertiesRegistered = isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'));
3905
-
3906
- const registerTransformsProperties = () => {
3907
- if (transformsPropertiesRegistered) return;
3908
- validTransforms.forEach(t => {
3909
- const isSkew = stringStartsWith(t, 'skew');
3910
- const isScale = stringStartsWith(t, 'scale');
3911
- const isRotate = stringStartsWith(t, 'rotate');
3912
- const isTranslate = stringStartsWith(t, 'translate');
3913
- const isAngle = isRotate || isSkew;
3914
- const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
3915
- try {
3916
- CSS.registerProperty({
3917
- name: '--' + t,
3918
- syntax,
3919
- inherits: false,
3920
- initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
3921
- });
3922
- } catch {} });
3923
- transformsPropertiesRegistered = true;
3924
- };
4068
+ const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])();
4069
+
4070
+ let transformsPropertiesRegistered = null;
3925
4071
 
3926
4072
  const WAAPIAnimationsLookups = {
3927
4073
  _head: null,
@@ -4032,9 +4178,31 @@ class WAAPIAnimation {
4032
4178
  */
4033
4179
  constructor(targets, params) {
4034
4180
 
4035
- if (globals.scope) globals.scope.revertibles.push(this);
4181
+ if (scope.current) scope.current.register(this);
4036
4182
 
4037
- 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
+ }
4038
4206
 
4039
4207
  const parsedTargets = registerTargets(targets);
4040
4208
  const targetsLength = parsedTargets.length;
@@ -4488,12 +4656,10 @@ const remove = (targets, renderable, propertyName) => {
4488
4656
  };
4489
4657
 
4490
4658
  /**
4491
- * @param {Number} min
4492
- * @param {Number} max
4493
- * @param {Number} [decimalLength]
4494
- * @return {Number}
4659
+ * @param {(...args: any[]) => Tickable} constructor
4660
+ * @return {(...args: any[]) => Tickable}
4495
4661
  */
4496
- 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;
4497
4663
 
4498
4664
  /**
4499
4665
  * @param {String|Array} items
@@ -4501,17 +4667,6 @@ const random = (min, max, decimalLength) => { const m = 10 ** (decimalLength ||
4501
4667
  */
4502
4668
  const randomPick = items => items[random(0, items.length - 1)];
4503
4669
 
4504
- /**
4505
- * Adapted from https://bost.ocks.org/mike/shuffle/
4506
- * @param {Array} items
4507
- * @return {Array}
4508
- */
4509
- const shuffle = items => {
4510
- let m = items.length, t, i;
4511
- while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
4512
- return items;
4513
- };
4514
-
4515
4670
  /**
4516
4671
  * @param {Number|String} v
4517
4672
  * @param {Number} decimalLength
@@ -4706,6 +4861,7 @@ const utils = {
4706
4861
  shuffle,
4707
4862
  lerp,
4708
4863
  sync,
4864
+ keepTime,
4709
4865
  clamp: /** @type {typeof clamp & ChainedClamp} */(makeChainable(clamp)),
4710
4866
  round: /** @type {typeof round & ChainedRound} */(makeChainable(round)),
4711
4867
  snap: /** @type {typeof snap & ChainedSnap} */(makeChainable(snap)),
@@ -5045,7 +5201,7 @@ class Animatable {
5045
5201
  * @param {AnimatableParams} parameters
5046
5202
  */
5047
5203
  constructor(targets, parameters) {
5048
- if (globals.scope) globals.scope.revertibles.push(this);
5204
+ if (scope.current) scope.current.register(this);
5049
5205
  /** @type {AnimationParams} */
5050
5206
  const globalParams = {};
5051
5207
  const properties = {};
@@ -5381,7 +5537,7 @@ class Draggable {
5381
5537
  */
5382
5538
  constructor(target, parameters = {}) {
5383
5539
  if (!target) return;
5384
- if (globals.scope) globals.scope.revertibles.push(this);
5540
+ if (scope.current) scope.current.register(this);
5385
5541
  const paramX = parameters.x;
5386
5542
  const paramY = parameters.y;
5387
5543
  const trigger = parameters.trigger;
@@ -6442,44 +6598,10 @@ const createDraggable = (target, parameters) => new Draggable(target, parameters
6442
6598
 
6443
6599
 
6444
6600
 
6445
- /**
6446
- * @typedef {Object} ReactRef
6447
- * @property {HTMLElement|SVGElement|null} [current]
6448
- */
6449
-
6450
- /**
6451
- * @typedef {Object} AngularRef
6452
- * @property {HTMLElement|SVGElement} [nativeElement]
6453
- */
6454
-
6455
- /**
6456
- * @typedef {Object} ScopeParams
6457
- * @property {DOMTargetSelector|ReactRef|AngularRef} [root]
6458
- * @property {DefaultsParams} [defaults]
6459
- * @property {Record<String, String>} [mediaQueries]
6460
- */
6461
-
6462
- /**
6463
- * @callback ScopeCleanup
6464
- * @param {Scope} [scope]
6465
- */
6466
-
6467
- /**
6468
- * @callback ScopeConstructor
6469
- * @param {Scope} [scope]
6470
- * @return {ScopeCleanup|void}
6471
- */
6472
-
6473
- /**
6474
- * @callback ScopeMethod
6475
- * @param {...*} args
6476
- * @return {ScopeCleanup|void}
6477
- */
6478
-
6479
6601
  class Scope {
6480
6602
  /** @param {ScopeParams} [parameters] */
6481
6603
  constructor(parameters = {}) {
6482
- if (globals.scope) globals.scope.revertibles.push(this);
6604
+ if (scope.current) scope.current.register(this);
6483
6605
  const rootParam = parameters.root;
6484
6606
  /** @type {Document|DOMTarget} */
6485
6607
  let root = doc;
@@ -6496,13 +6618,23 @@ class Scope {
6496
6618
  this.defaults = scopeDefaults ? mergeObjects(scopeDefaults, globalDefault) : globalDefault;
6497
6619
  /** @type {Document|DOMTarget} */
6498
6620
  this.root = root;
6499
- /** @type {Array<ScopeConstructor>} */
6621
+ /** @type {Array<ScopeConstructorCallback>} */
6500
6622
  this.constructors = [];
6501
- /** @type {Array<Function>} */
6623
+ /** @type {Array<ScopeCleanupCallback>} */
6502
6624
  this.revertConstructors = [];
6503
6625
  /** @type {Array<Revertible>} */
6504
6626
  this.revertibles = [];
6505
- /** @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>} */
6506
6638
  this.methods = {};
6507
6639
  /** @type {Record<String, Boolean>} */
6508
6640
  this.matches = {};
@@ -6520,25 +6652,30 @@ class Scope {
6520
6652
  }
6521
6653
 
6522
6654
  /**
6523
- * @callback ScoppedCallback
6524
- * @param {this} scope
6525
- * @return {any}
6526
- *
6527
- * @param {ScoppedCallback} cb
6528
- * @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}
6529
6666
  */
6530
6667
  execute(cb) {
6531
- let activeScope = globals.scope;
6532
- let activeRoot = globals.root;
6668
+ let activeScope = scope.current;
6669
+ let activeRoot = scope.root;
6533
6670
  let activeDefaults = globals.defaults;
6534
- globals.scope = this;
6535
- globals.root = this.root;
6671
+ scope.current = this;
6672
+ scope.root = this.root;
6536
6673
  globals.defaults = this.defaults;
6537
6674
  const mqs = this.mediaQueryLists;
6538
6675
  for (let mq in mqs) this.matches[mq] = mqs[mq].matches;
6539
6676
  const returned = cb(this);
6540
- globals.scope = activeScope;
6541
- globals.root = activeRoot;
6677
+ scope.current = activeScope;
6678
+ scope.root = activeRoot;
6542
6679
  globals.defaults = activeDefaults;
6543
6680
  return returned;
6544
6681
  }
@@ -6547,6 +6684,7 @@ class Scope {
6547
6684
  * @return {this}
6548
6685
  */
6549
6686
  refresh() {
6687
+ this.onceIndex = 0;
6550
6688
  this.execute(() => {
6551
6689
  let i = this.revertibles.length;
6552
6690
  let y = this.revertConstructors.length;
@@ -6554,9 +6692,9 @@ class Scope {
6554
6692
  while (y--) this.revertConstructors[y](this);
6555
6693
  this.revertibles.length = 0;
6556
6694
  this.revertConstructors.length = 0;
6557
- this.constructors.forEach( constructor => {
6695
+ this.constructors.forEach((/** @type {ScopeConstructorCallback} */constructor) => {
6558
6696
  const revertConstructor = constructor(this);
6559
- if (revertConstructor) {
6697
+ if (isFnc(revertConstructor)) {
6560
6698
  this.revertConstructors.push(revertConstructor);
6561
6699
  }
6562
6700
  });
@@ -6565,28 +6703,26 @@ class Scope {
6565
6703
  }
6566
6704
 
6567
6705
  /**
6568
- * @callback contructorCallback
6569
- * @param {this} self
6570
- *
6571
6706
  * @overload
6572
6707
  * @param {String} a1
6573
6708
  * @param {ScopeMethod} a2
6574
6709
  * @return {this}
6575
6710
  *
6576
6711
  * @overload
6577
- * @param {contructorCallback} a1
6712
+ * @param {ScopeConstructorCallback} a1
6578
6713
  * @return {this}
6579
6714
  *
6580
- * @param {String|contructorCallback} a1
6715
+ * @param {String|ScopeConstructorCallback} a1
6581
6716
  * @param {ScopeMethod} [a2]
6582
6717
  */
6583
6718
  add(a1, a2) {
6719
+ this.once = false;
6584
6720
  if (isFnc(a1)) {
6585
- const constructor = /** @type {contructorCallback} */(a1);
6721
+ const constructor = /** @type {ScopeConstructorCallback} */(a1);
6586
6722
  this.constructors.push(constructor);
6587
6723
  this.execute(() => {
6588
6724
  const revertConstructor = constructor(this);
6589
- if (revertConstructor) {
6725
+ if (isFnc(revertConstructor)) {
6590
6726
  this.revertConstructors.push(revertConstructor);
6591
6727
  }
6592
6728
  });
@@ -6596,6 +6732,46 @@ class Scope {
6596
6732
  return this;
6597
6733
  }
6598
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
+
6599
6775
  /**
6600
6776
  * @param {Event} e
6601
6777
  */
@@ -6610,15 +6786,25 @@ class Scope {
6610
6786
  revert() {
6611
6787
  const revertibles = this.revertibles;
6612
6788
  const revertConstructors = this.revertConstructors;
6789
+ const revertiblesOnce = this.revertiblesOnce;
6790
+ const revertConstructorsOnce = this.revertConstructorsOnce;
6613
6791
  const mqs = this.mediaQueryLists;
6614
6792
  let i = revertibles.length;
6615
- let y = revertConstructors.length;
6793
+ let j = revertConstructors.length;
6794
+ let k = revertiblesOnce.length;
6795
+ let l = revertConstructorsOnce.length;
6616
6796
  while (i--) revertibles[i].revert();
6617
- while (y--) revertConstructors[y](this);
6797
+ while (j--) revertConstructors[j](this);
6798
+ while (k--) revertiblesOnce[k].revert();
6799
+ while (l--) revertConstructorsOnce[l](this);
6618
6800
  for (let mq in mqs) mqs[mq].removeEventListener('change', this);
6619
6801
  revertibles.length = 0;
6620
6802
  revertConstructors.length = 0;
6621
6803
  this.constructors.length = 0;
6804
+ revertiblesOnce.length = 0;
6805
+ revertConstructorsOnce.length = 0;
6806
+ this.constructorsOnce.length = 0;
6807
+ this.onceIndex = 0;
6622
6808
  this.matches = {};
6623
6809
  this.methods = {};
6624
6810
  this.mediaQueryLists = {};
@@ -6944,7 +7130,7 @@ const getAnimationDomTarget = linked => {
6944
7130
 
6945
7131
  let scrollerIndex = 0;
6946
7132
 
6947
- 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'];
6948
7134
 
6949
7135
  /**
6950
7136
  * @typedef {Object} ScrollThresholdParam
@@ -6990,7 +7176,7 @@ class ScrollObserver {
6990
7176
  * @param {ScrollObserverParams} parameters
6991
7177
  */
6992
7178
  constructor(parameters = {}) {
6993
- if (globals.scope) globals.scope.revertibles.push(this);
7179
+ if (scope.current) scope.current.register(this);
6994
7180
  const syncMode = setValue(parameters.sync, 'play pause');
6995
7181
  const ease = syncMode ? parseEasings(/** @type {EasingParam} */(syncMode)) : null;
6996
7182
  const isLinear = syncMode && (syncMode === 'linear' || syncMode === none);
@@ -7187,7 +7373,7 @@ class ScrollObserver {
7187
7373
  const $debug = doc.createElement('div');
7188
7374
  const $thresholds = doc.createElement('div');
7189
7375
  const $triggers = doc.createElement('div');
7190
- const color = debugColors[this.index % debugColors.length];
7376
+ const color = debugColors$1[this.index % debugColors$1.length];
7191
7377
  const useWin = container.useWin;
7192
7378
  const containerWidth = useWin ? container.winWidth : container.width;
7193
7379
  const containerHeight = useWin ? container.winHeight : container.height;
@@ -7545,29 +7731,462 @@ const onScroll = (parameters = {}) => new ScrollObserver(parameters);
7545
7731
 
7546
7732
 
7547
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
+
7548
7744
  /**
7549
- * @typedef {Object} StaggerParameters
7550
- * @property {Number|String} [start]
7551
- * @property {Number|'first'|'center'|'last'} [from]
7552
- * @property {Boolean} [reversed]
7553
- * @property {Array.<Number>} [grid]
7554
- * @property {('x'|'y')} [axis]
7555
- * @property {EasingParam} [ease]
7556
- * @property {TweenModifier} [modifier]
7745
+ * @typedef {Object} Segment
7746
+ * @property {String} segment
7747
+ * @property {Boolean} [isWordLike]
7557
7748
  */
7558
7749
 
7559
7750
  /**
7560
- * @callback StaggerFunction
7561
- * @param {Target} [target]
7562
- * @param {Number} [index]
7563
- * @param {Number} [length]
7564
- * @param {Timeline} [tl]
7565
- * @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}
7566
8177
  */
8178
+ const split = (target, parameters) => new TextSplitter(target, parameters);
8179
+
8180
+ const text = {
8181
+ split,
8182
+ };
8183
+
8184
+
8185
+
7567
8186
 
7568
8187
  /**
7569
8188
  * @param {Number|String|[Number|String,Number|String]} val
7570
- * @param {StaggerParameters} params
8189
+ * @param {StaggerParams} params
7571
8190
  * @return {StaggerFunction}
7572
8191
  */
7573
8192
  const stagger = (val, params = {}) => {
@@ -7581,20 +8200,27 @@ const stagger = (val, params = {}) => {
7581
8200
  const staggerEase = hasSpring ? /** @type {Spring} */(ease).ease : hasEasing ? parseEasings(ease) : null;
7582
8201
  const grid = params.grid;
7583
8202
  const axis = params.axis;
8203
+ const customTotal = params.total;
7584
8204
  const fromFirst = isUnd(from) || from === 0 || from === 'first';
7585
8205
  const fromCenter = from === 'center';
7586
8206
  const fromLast = from === 'last';
8207
+ const fromRandom = from === 'random';
7587
8208
  const isRange = isArr(val);
8209
+ const useProp = params.use;
7588
8210
  const val1 = isRange ? parseNumber(val[0]) : parseNumber(val);
7589
8211
  const val2 = isRange ? parseNumber(val[1]) : 0;
7590
8212
  const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
7591
8213
  const start = params.start || 0 + (isRange ? val1 : 0);
7592
8214
  let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
7593
- return (_, i, t, tl) => {
7594
- if (fromCenter) fromIndex = (t - 1) / 2;
7595
- 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;
7596
8222
  if (!values.length) {
7597
- for (let index = 0; index < t; index++) {
8223
+ for (let index = 0; index < total; index++) {
7598
8224
  if (!grid) {
7599
8225
  values.push(abs(fromIndex - index));
7600
8226
  } else {
@@ -7613,15 +8239,16 @@ const stagger = (val, params = {}) => {
7613
8239
  }
7614
8240
  if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue);
7615
8241
  if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
8242
+ if (fromRandom) values = shuffle(values);
7616
8243
  }
7617
8244
  const spacing = isRange ? (val2 - val1) / maxValue : val1;
7618
8245
  const offset = tl ? parseTimelinePosition(tl, isUnd(params.start) ? tl.iterationDuration : start) : /** @type {Number} */(start);
7619
8246
  /** @type {String|Number} */
7620
- let output = offset + ((spacing * round(values[i], 2)) || 0);
8247
+ let output = offset + ((spacing * round(values[staggerIndex], 2)) || 0);
7621
8248
  if (params.modifier) output = params.modifier(output);
7622
8249
  if (unitMatch) output = `${output}${unitMatch[2]}`;
7623
8250
  return output;
7624
8251
  }
7625
8252
  };
7626
8253
 
7627
- 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 };