animejs 4.4.1 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +2 -3
  2. package/dist/bundles/anime.esm.js +462 -213
  3. package/dist/bundles/anime.esm.min.js +2 -2
  4. package/dist/bundles/anime.umd.js +462 -213
  5. package/dist/bundles/anime.umd.min.js +2 -2
  6. package/dist/modules/adapters/index.cjs +14 -0
  7. package/dist/modules/adapters/index.d.ts +1 -0
  8. package/dist/modules/adapters/index.js +8 -0
  9. package/dist/modules/adapters/registry.cjs +149 -0
  10. package/dist/modules/adapters/registry.d.ts +65 -0
  11. package/dist/modules/adapters/registry.js +146 -0
  12. package/dist/modules/adapters/three/adapter.cjs +26 -0
  13. package/dist/modules/adapters/three/adapter.d.ts +15 -0
  14. package/dist/modules/adapters/three/adapter.js +24 -0
  15. package/dist/modules/adapters/three/helpers.cjs +297 -0
  16. package/dist/modules/adapters/three/helpers.d.ts +89 -0
  17. package/dist/modules/adapters/three/helpers.js +280 -0
  18. package/dist/modules/adapters/three/index.cjs +20 -0
  19. package/dist/modules/adapters/three/index.d.ts +2 -0
  20. package/dist/modules/adapters/three/index.js +12 -0
  21. package/dist/modules/adapters/three/instance.cjs +368 -0
  22. package/dist/modules/adapters/three/instance.d.ts +133 -0
  23. package/dist/modules/adapters/three/instance.js +365 -0
  24. package/dist/modules/adapters/three/object3d.cjs +214 -0
  25. package/dist/modules/adapters/three/object3d.d.ts +1 -0
  26. package/dist/modules/adapters/three/object3d.js +212 -0
  27. package/dist/modules/adapters/three/resolvers.cjs +105 -0
  28. package/dist/modules/adapters/three/resolvers.d.ts +1 -0
  29. package/dist/modules/adapters/three/resolvers.js +103 -0
  30. package/dist/modules/adapters/three/uniform.cjs +41 -0
  31. package/dist/modules/adapters/three/uniform.d.ts +1 -0
  32. package/dist/modules/adapters/three/uniform.js +39 -0
  33. package/dist/modules/animatable/animatable.cjs +2 -1
  34. package/dist/modules/animatable/animatable.d.ts +2 -1
  35. package/dist/modules/animatable/animatable.js +2 -1
  36. package/dist/modules/animatable/index.cjs +1 -1
  37. package/dist/modules/animatable/index.js +1 -1
  38. package/dist/modules/animation/additive.cjs +1 -1
  39. package/dist/modules/animation/additive.js +1 -1
  40. package/dist/modules/animation/animation.cjs +43 -16
  41. package/dist/modules/animation/animation.d.ts +5 -0
  42. package/dist/modules/animation/animation.js +45 -18
  43. package/dist/modules/animation/composition.cjs +38 -35
  44. package/dist/modules/animation/composition.js +38 -35
  45. package/dist/modules/animation/index.cjs +1 -1
  46. package/dist/modules/animation/index.js +1 -1
  47. package/dist/modules/core/clock.cjs +11 -15
  48. package/dist/modules/core/clock.d.ts +0 -2
  49. package/dist/modules/core/clock.js +11 -15
  50. package/dist/modules/core/colors.cjs +1 -1
  51. package/dist/modules/core/colors.js +1 -1
  52. package/dist/modules/core/consts.cjs +15 -1
  53. package/dist/modules/core/consts.d.ts +2 -0
  54. package/dist/modules/core/consts.js +14 -2
  55. package/dist/modules/core/globals.cjs +7 -4
  56. package/dist/modules/core/globals.d.ts +8 -2
  57. package/dist/modules/core/globals.js +8 -5
  58. package/dist/modules/core/helpers.cjs +2 -2
  59. package/dist/modules/core/helpers.js +3 -3
  60. package/dist/modules/core/render.cjs +64 -14
  61. package/dist/modules/core/render.js +65 -15
  62. package/dist/modules/core/styles.cjs +16 -2
  63. package/dist/modules/core/styles.js +16 -2
  64. package/dist/modules/core/targets.cjs +11 -13
  65. package/dist/modules/core/targets.js +11 -13
  66. package/dist/modules/core/transforms.cjs +1 -1
  67. package/dist/modules/core/transforms.js +1 -1
  68. package/dist/modules/core/units.cjs +1 -1
  69. package/dist/modules/core/units.js +1 -1
  70. package/dist/modules/core/values.cjs +73 -82
  71. package/dist/modules/core/values.d.ts +1 -2
  72. package/dist/modules/core/values.js +76 -84
  73. package/dist/modules/draggable/draggable.cjs +1 -1
  74. package/dist/modules/draggable/draggable.js +1 -1
  75. package/dist/modules/draggable/index.cjs +1 -1
  76. package/dist/modules/draggable/index.js +1 -1
  77. package/dist/modules/easings/cubic-bezier/index.cjs +1 -1
  78. package/dist/modules/easings/cubic-bezier/index.js +1 -1
  79. package/dist/modules/easings/eases/index.cjs +1 -1
  80. package/dist/modules/easings/eases/index.js +1 -1
  81. package/dist/modules/easings/eases/parser.cjs +3 -3
  82. package/dist/modules/easings/eases/parser.d.ts +4 -5
  83. package/dist/modules/easings/eases/parser.js +3 -3
  84. package/dist/modules/easings/index.cjs +1 -1
  85. package/dist/modules/easings/index.js +1 -1
  86. package/dist/modules/easings/irregular/index.cjs +1 -1
  87. package/dist/modules/easings/irregular/index.js +1 -1
  88. package/dist/modules/easings/linear/index.cjs +1 -1
  89. package/dist/modules/easings/linear/index.js +1 -1
  90. package/dist/modules/easings/none.cjs +1 -1
  91. package/dist/modules/easings/none.js +1 -1
  92. package/dist/modules/easings/spring/index.cjs +1 -1
  93. package/dist/modules/easings/spring/index.js +1 -1
  94. package/dist/modules/easings/steps/index.cjs +1 -1
  95. package/dist/modules/easings/steps/index.js +1 -1
  96. package/dist/modules/engine/engine.cjs +4 -2
  97. package/dist/modules/engine/engine.js +4 -2
  98. package/dist/modules/engine/index.cjs +1 -1
  99. package/dist/modules/engine/index.js +1 -1
  100. package/dist/modules/events/index.cjs +1 -1
  101. package/dist/modules/events/index.js +1 -1
  102. package/dist/modules/events/scroll.cjs +3 -1
  103. package/dist/modules/events/scroll.js +3 -1
  104. package/dist/modules/index.cjs +1 -1
  105. package/dist/modules/index.js +1 -1
  106. package/dist/modules/layout/index.cjs +1 -1
  107. package/dist/modules/layout/index.js +1 -1
  108. package/dist/modules/layout/layout.cjs +1 -1
  109. package/dist/modules/layout/layout.js +1 -1
  110. package/dist/modules/scope/index.cjs +1 -1
  111. package/dist/modules/scope/index.js +1 -1
  112. package/dist/modules/scope/scope.cjs +1 -1
  113. package/dist/modules/scope/scope.js +1 -1
  114. package/dist/modules/svg/drawable.cjs +1 -1
  115. package/dist/modules/svg/drawable.js +1 -1
  116. package/dist/modules/svg/helpers.cjs +1 -1
  117. package/dist/modules/svg/helpers.js +1 -1
  118. package/dist/modules/svg/index.cjs +1 -1
  119. package/dist/modules/svg/index.js +1 -1
  120. package/dist/modules/svg/morphto.cjs +1 -1
  121. package/dist/modules/svg/morphto.js +1 -1
  122. package/dist/modules/svg/motionpath.cjs +1 -1
  123. package/dist/modules/svg/motionpath.js +1 -1
  124. package/dist/modules/text/index.cjs +1 -1
  125. package/dist/modules/text/index.js +1 -1
  126. package/dist/modules/text/scramble.cjs +12 -2
  127. package/dist/modules/text/scramble.d.ts +9 -1
  128. package/dist/modules/text/scramble.js +12 -2
  129. package/dist/modules/text/split.cjs +2 -1
  130. package/dist/modules/text/split.js +2 -1
  131. package/dist/modules/timeline/index.cjs +1 -1
  132. package/dist/modules/timeline/index.js +1 -1
  133. package/dist/modules/timeline/position.cjs +1 -1
  134. package/dist/modules/timeline/position.js +1 -1
  135. package/dist/modules/timeline/timeline.cjs +14 -5
  136. package/dist/modules/timeline/timeline.d.ts +3 -3
  137. package/dist/modules/timeline/timeline.js +14 -5
  138. package/dist/modules/timer/index.cjs +1 -1
  139. package/dist/modules/timer/index.js +1 -1
  140. package/dist/modules/timer/timer.cjs +1 -1
  141. package/dist/modules/timer/timer.js +1 -1
  142. package/dist/modules/types/index.d.ts +36 -11
  143. package/dist/modules/utils/chainable.cjs +1 -1
  144. package/dist/modules/utils/chainable.js +1 -1
  145. package/dist/modules/utils/index.cjs +1 -1
  146. package/dist/modules/utils/index.js +1 -1
  147. package/dist/modules/utils/number.cjs +1 -1
  148. package/dist/modules/utils/number.js +1 -1
  149. package/dist/modules/utils/random.cjs +4 -3
  150. package/dist/modules/utils/random.d.ts +1 -1
  151. package/dist/modules/utils/random.js +4 -3
  152. package/dist/modules/utils/stagger.cjs +67 -13
  153. package/dist/modules/utils/stagger.js +69 -15
  154. package/dist/modules/utils/target.cjs +4 -1
  155. package/dist/modules/utils/target.js +4 -1
  156. package/dist/modules/utils/time.cjs +6 -5
  157. package/dist/modules/utils/time.d.ts +1 -1
  158. package/dist/modules/utils/time.js +6 -5
  159. package/dist/modules/waapi/composition.cjs +1 -1
  160. package/dist/modules/waapi/composition.js +1 -1
  161. package/dist/modules/waapi/index.cjs +1 -1
  162. package/dist/modules/waapi/index.js +1 -1
  163. package/dist/modules/waapi/waapi.cjs +1 -1
  164. package/dist/modules/waapi/waapi.js +1 -1
  165. package/package.json +38 -5
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Anime.js - UMD bundle
3
- * @version v4.4.1
3
+ * @version v4.5.0
4
4
  * @license MIT
5
5
  * @copyright 2026 - Julian Garnier
6
6
  */
@@ -39,6 +39,12 @@
39
39
  /** @typedef {Timer&JSAnimation&Timeline} CallbackArgument */
40
40
  /** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope|AutoLayout} Revertible */
41
41
 
42
+ /**
43
+ * @typedef {Object} TweakRegister
44
+ * @property {String} type
45
+ * @property {*} defaultValue
46
+ */
47
+
42
48
  // Stagger types
43
49
 
44
50
  /**
@@ -58,11 +64,18 @@
58
64
  * @property {Number|'first'|'center'|'last'|'random'|Array.<Number>} [from]
59
65
  * @property {Boolean} [reversed]
60
66
  * @property {Array.<Number>|Boolean} [grid]
61
- * @property {('x'|'y')} [axis]
62
- * @property {String|((target: Target, i: Number, length: Number) => Number)} [use]
67
+ * @property {('x'|'y'|'z')} [axis]
68
+ * @property {String | { method(target: Target, i: Number, length: Number): Number }['method']} [use]
63
69
  * @property {Number} [total]
64
70
  * @property {EasingParam} [ease]
65
71
  * @property {TweenModifier} [modifier]
72
+ * @property {Number|[Number, Number]} [jitter] Additive uniform noise on the
73
+ * computed stagger value. Number form gives flat `+/-jitter`; tuple form
74
+ * ramps the magnitude `start -> end` across the from/axis/grid ordering
75
+ * and respects `ease`.
76
+ * @property {Boolean|Number} [seed] Seed for jitter draws and `from: 'random'`
77
+ * shuffling. `false` (default) uses Math.random. `true` seeds with `0`. A
78
+ * number is used directly as the seed.
66
79
  */
67
80
 
68
81
  // Targets types
@@ -138,10 +151,7 @@
138
151
 
139
152
  /**
140
153
  * @template T
141
- * @callback Callback
142
- * @param {T} self - Returns itself
143
- * @param {PointerEvent} [e]
144
- * @return {*}
154
+ * @typedef {{ method(self: T): * }['method']} Callback
145
155
  */
146
156
 
147
157
  /**
@@ -185,12 +195,17 @@
185
195
  // Tween types
186
196
 
187
197
  /**
198
+ * @typedef {Number|String|TweenKeyValue|EasingParam|Array.<Number|String|TweenKeyValue>} FunctionValueReturn
199
+ */
200
+
201
+ /**
202
+ * @template [T=FunctionValueReturn]
188
203
  * @callback FunctionValue
189
204
  * @param {Target} [target] - The animated target
190
205
  * @param {Number} [index] - The target index
191
206
  * @param {TargetsArray} [targets] - The array of all animated targets
192
207
  * @param {Tween|null} [prevTween] - The previous sibling tween for the same target and property
193
- * @return {Number|String|TweenObjectValue|EasingParam|Array.<Number|String|TweenObjectValue>}
208
+ * @return {T}
194
209
  */
195
210
 
196
211
  /**
@@ -207,7 +222,7 @@
207
222
  * @property {JSAnimation} parent
208
223
  * @property {String} property
209
224
  * @property {Target} target
210
- * @property {String|Number} _value
225
+ * @property {String|Number|Object} _value
211
226
  * @property {Function|null} _toFunc
212
227
  * @property {Function|null} _fromFunc
213
228
  * @property {EasingFunction} _ease
@@ -226,7 +241,11 @@
226
241
  * @property {Number} _startTime
227
242
  * @property {Number} _changeDuration
228
243
  * @property {Number} _absoluteStartTime
244
+ * @property {Number} _absoluteUpdateStartTime
245
+ * @property {Number} _absoluteEndTime
246
+ * @property {Number} _hasFromValue
229
247
  * @property {tweenTypes} _tweenType
248
+ * @property {((target: any, value: number, tween: Tween) => void) | null} _setter
230
249
  * @property {valueTypes} _valueType
231
250
  * @property {Number} _composition
232
251
  * @property {Number} _isOverlapped
@@ -247,8 +266,8 @@
247
266
  * @property {Number} n - Single number value
248
267
  * @property {String} u - Value unit
249
268
  * @property {String} o - Value operator
250
- * @property {Array.<Number>} d - Array of Numbers (in case of complex value type)
251
- * @property {Array.<String>} s - Strings (in case of complex value type)
269
+ * @property {Array.<Number>} d - Array of Numbers (complex / color value type)
270
+ * @property {Array.<String>} s - Strings (complex value type)
252
271
  */
253
272
 
254
273
  /** @typedef {{_head: null|Tween, _tail: null|Tween}} TweenPropertySiblings */
@@ -259,7 +278,7 @@
259
278
  // JSAnimation types
260
279
 
261
280
  /**
262
- * @typedef {Number|String|FunctionValue|EasingParam} TweenParamValue
281
+ * @typedef {Number|String|FunctionValue|EasingParam|TweakRegister} TweenParamValue
263
282
  */
264
283
 
265
284
  /**
@@ -452,7 +471,7 @@
452
471
  */
453
472
 
454
473
  /**
455
- * @typedef {Record<String, TweenParamValue | EasingParam | TweenModifier | TweenComposition | AnimatablePropertyParamsOptions> & AnimatablePropertyParamsOptions} AnimatableParams
474
+ * @typedef {Record<String, TweenParamValue | EasingParam | TweenModifier | TweenComposition | AnimatablePropertyParamsOptions | Callback<JSAnimation>> & AnimatablePropertyParamsOptions & TickableCallbacks<JSAnimation> & RenderableCallbacks<JSAnimation>} AnimatableParams
456
475
  */
457
476
 
458
477
  // Scope types
@@ -495,7 +514,7 @@
495
514
  /**
496
515
  * @callback ScopeMethod
497
516
  * @param {...*} args
498
- * @return {ScopeCleanupCallback|void}
517
+ * @return {*}
499
518
  */
500
519
 
501
520
  // Scroll types
@@ -731,6 +750,11 @@
731
750
  const emptyString = '';
732
751
  const cssVarPrefix = 'var(';
733
752
 
753
+ // Arrays
754
+
755
+ // Shared sentinel for tween slots that don't hold array data. Never mutated, only read; COMPLEX and COLOR tweens always replace the slot before writing.
756
+ const emptyArray = [];
757
+
734
758
  const shortTransforms = /*#__PURE__*/ (() => {
735
759
  const map = new Map();
736
760
  map.set('x', 'translateX');
@@ -764,6 +788,13 @@
764
788
  /** @return {void} */
765
789
  const noop = () => {};
766
790
 
791
+ /**
792
+ * @template T
793
+ * @param {T} v
794
+ * @return {T}
795
+ */
796
+ const noopModifier = v => v;
797
+
767
798
  // Regex
768
799
 
769
800
  const validRgbHslRgx = /\)\s*[-.\d]/;
@@ -785,10 +816,13 @@
785
816
  /**
786
817
  * @typedef {Object} EditorGlobals
787
818
  * @property {boolean} showPanel
788
- * @property {boolean} synced
789
819
  * @property {Function} addAnimation
820
+ * @property {Function} addSet
790
821
  * @property {Function} addTimeline
791
822
  * @property {Function} addTimelineChild
823
+ * @property {Function} addTimelineLabel
824
+ * @property {Function} addTimelineCall
825
+ * @property {Function} addTimelineSync
792
826
  * @property {Function} resolveStagger
793
827
  * @property {Object|null} _head
794
828
  * @property {Object|null} _tail
@@ -811,7 +845,7 @@
811
845
  loopDelay: 0,
812
846
  ease: 'out(2)',
813
847
  composition: compositionTypes.replace,
814
- modifier: v => v,
848
+ modifier: noopModifier,
815
849
  onBegin: noop,
816
850
  onBeforeUpdate: noop,
817
851
  onUpdate: noop,
@@ -841,7 +875,7 @@
841
875
  editor: null,
842
876
  };
843
877
 
844
- const globalVersions = { version: '4.4.1', engine: null };
878
+ const globalVersions = { version: '4.5.0', engine: null };
845
879
 
846
880
  if (isBrowser) {
847
881
  if (!win.AnimeJS) win.AnimeJS = [];
@@ -988,7 +1022,7 @@
988
1022
  * @param {Number} factor - Interpolation factor in the range [0, 1]
989
1023
  * @return {Number} The interpolated value
990
1024
  */
991
- const lerp$1 = (start, end, factor) => start + (end - start) * factor;
1025
+ const lerp$1 = (start, end, factor) => factor === 1 ? end : factor === 0 ? start : start + (end - start) * factor;
992
1026
 
993
1027
  /**
994
1028
  * Replaces infinity with maximum safe value
@@ -1237,6 +1271,61 @@
1237
1271
  return str;
1238
1272
  };
1239
1273
 
1274
+ /**
1275
+ * Anime.js adapter API. Each library or class group that wants to extend `animate()` and `utils.set()` calls `registerAdapter()` to create its own `Adapter`. The returned `Adapter` exposes `registerTargetAdapter(detect)` for per-class detection and `registerPropertyResolver(fn)` for global Color / Vector / pattern-based fallbacks.
1276
+ *
1277
+ * import { registerAdapter } from 'animejs/adapters';
1278
+ *
1279
+ * const myAdapter = registerAdapter();
1280
+ * const widget = myAdapter.registerTargetAdapter((t) => t instanceof MyWidget);
1281
+ * widget.registerProperty('value',
1282
+ * (t) => t.getValue(),
1283
+ * (target, value) => target.setValue(value),
1284
+ * );
1285
+ *
1286
+ * For scalar tweens, `value` is the interpolated number. For color and complex tweens it is `undefined`; read `tween._numbers` instead. `gate(target)` scopes the prop to a subset of matching targets.
1287
+ *
1288
+ * Resolution order: every Adapter's target adapters in registration order (first match wins) then every Adapter's property resolvers (first non-null wins) then engine direct property path.
1289
+ */
1290
+
1291
+
1292
+ const adapters = /** @type {Adapter[]} */([]);
1293
+
1294
+ /**
1295
+ * Internal resolution. Tries every Adapter's target adapters first (in registration order, first match wins), then every Adapter's property resolvers.
1296
+ *
1297
+ * @param {any} target
1298
+ * @param {string} name
1299
+ * @return {TargetAdapterEntry | null}
1300
+ */
1301
+ function resolveAdapterEntry(target, name) {
1302
+ if (!target) return null;
1303
+ const al = adapters.length;
1304
+ outer: for (let i = 0; i < al; i++) {
1305
+ const a = adapters[i];
1306
+ if (a.detect && !a.detect(target)) continue;
1307
+ const tas = a.targetAdapters;
1308
+ for (let j = 0, m = tas.length; j < m; j++) {
1309
+ const ta = tas[j];
1310
+ if (ta.detect(target)) {
1311
+ const entry = ta.props[name];
1312
+ if (entry && (!entry.gate || entry.gate(target))) return entry;
1313
+ break outer;
1314
+ }
1315
+ }
1316
+ }
1317
+ for (let i = 0; i < al; i++) {
1318
+ const a = adapters[i];
1319
+ if (a.detect && !a.detect(target)) continue;
1320
+ const rs = a.propertyResolvers;
1321
+ for (let j = 0, m = rs.length; j < m; j++) {
1322
+ const entry = rs[j](target, name);
1323
+ if (entry) return entry;
1324
+ }
1325
+ }
1326
+ return null;
1327
+ }
1328
+
1240
1329
 
1241
1330
 
1242
1331
  /**
@@ -1334,6 +1423,21 @@
1334
1423
  return isUnd(targetValue) ? defaultValue : targetValue;
1335
1424
  };
1336
1425
 
1426
+ /**
1427
+ * Resolve against the target when it's a DOM element, otherwise fall back to :root so non-DOM targets like three.js meshes and custom adapters still pick up CSS variables defined on the document.
1428
+ *
1429
+ * @param {String} value
1430
+ * @param {Target} target
1431
+ * @return {String|Number}
1432
+ */
1433
+ const resolveCssVar = (value, target) => {
1434
+ const match = value.match(cssVariableMatchRgx);
1435
+ const el = target[isDomSymbol] ? target : document.documentElement;
1436
+ let computed = getComputedStyle(/** @type {HTMLElement} */(el))?.getPropertyValue(match[1]);
1437
+ if ((!computed || computed.trim() === emptyString) && match[2]) computed = match[2].trim();
1438
+ return computed || 0;
1439
+ };
1440
+
1337
1441
  /**
1338
1442
  * @param {TweenPropValue} value
1339
1443
  * @param {Target} target
@@ -1344,30 +1448,26 @@
1344
1448
  * @return {any}
1345
1449
  */
1346
1450
  const getFunctionValue = (value, target, index, targets, store, prevTween) => {
1347
- let func;
1348
1451
  if (isFnc(value)) {
1349
- func = () => {
1452
+ if (!store) {
1453
+ const computed = /** @type {Function} */(value)(target, index, targets, prevTween);
1454
+ // Fallback to 0 if the function returns undefined, NaN, null, false or 0
1455
+ return !isNaN(+computed) ? +computed : computed || 0;
1456
+ }
1457
+ const func = () => {
1350
1458
  const computed = /** @type {Function} */(value)(target, index, targets, prevTween);
1351
- // Fallback to 0 if the function returns undefined / NaN / null / false / 0
1352
1459
  return !isNaN(+computed) ? +computed : computed || 0;
1353
1460
  };
1354
- } else if (isStr(value) && stringStartsWith(value, cssVarPrefix)) {
1355
- func = () => {
1356
- const match = value.match(cssVariableMatchRgx);
1357
- const cssVarName = match[1];
1358
- const fallbackValue = match[2];
1359
- let computed = getComputedStyle(/** @type {HTMLElement} */(target))?.getPropertyValue(cssVarName);
1360
- // Use fallback if CSS variable is not set or empty
1361
- if ((!computed || computed.trim() === emptyString) && fallbackValue) {
1362
- computed = fallbackValue.trim();
1363
- }
1364
- return computed || 0;
1365
- };
1366
- } else {
1367
- return value;
1461
+ store.func = func;
1462
+ return func();
1368
1463
  }
1369
- if (store) store.func = func;
1370
- return func();
1464
+ if (isStr(value) && stringStartsWith(value, cssVarPrefix)) {
1465
+ if (!store) return resolveCssVar(/** @type {String} */(value), target);
1466
+ const func = () => resolveCssVar(/** @type {String} */(value), target);
1467
+ store.func = func;
1468
+ return func();
1469
+ }
1470
+ return value;
1371
1471
  };
1372
1472
 
1373
1473
  /**
@@ -1414,6 +1514,12 @@
1414
1514
  */
1415
1515
  const getOriginalAnimatableValue = (target, propName, tweenType, animationInlineStyles) => {
1416
1516
  const type = !isUnd(tweenType) ? tweenType : getTweenType(target, propName);
1517
+ const adapterProp = resolveAdapterEntry(target, propName);
1518
+ if (adapterProp) {
1519
+ const value = adapterProp.get(target);
1520
+ if (value && animationInlineStyles) animationInlineStyles[propName] = value;
1521
+ return value == null ? 0 : value;
1522
+ }
1417
1523
  if (type === tweenTypes.OBJECT) {
1418
1524
  const value = target[propName];
1419
1525
  if (value && animationInlineStyles) animationInlineStyles[propName] = value;
@@ -1455,7 +1561,7 @@
1455
1561
  };
1456
1562
 
1457
1563
  /**
1458
- * @param {String|Number} rawValue
1564
+ * @param {String|Number|Object} rawValue
1459
1565
  * @param {TweenDecomposedValue} targetObject
1460
1566
  * @return {TweenDecomposedValue}
1461
1567
  */
@@ -1473,39 +1579,38 @@
1473
1579
  // It's a number
1474
1580
  targetObject.n = num;
1475
1581
  return targetObject;
1582
+ }
1583
+ // let str = /** @type {String} */(rawValue).trim();
1584
+ let str = /** @type {String} */(rawValue);
1585
+ // Parsing operators (+=, -=, *=) manually is much faster than using regex here
1586
+ if (str[1] === '=') {
1587
+ targetObject.o = str[0];
1588
+ str = str.slice(2);
1589
+ }
1590
+ // Skip exec regex if the value type is complex or color to avoid long regex backtracking
1591
+ const unitMatch = str.includes(' ') ? false : unitsExecRgx.exec(str);
1592
+ if (unitMatch) {
1593
+ // Has a number and a unit
1594
+ targetObject.t = valueTypes.UNIT;
1595
+ targetObject.n = +unitMatch[1];
1596
+ targetObject.u = unitMatch[2];
1597
+ return targetObject;
1598
+ } else if (targetObject.o) {
1599
+ // Has an operator (+=, -=, *=)
1600
+ targetObject.n = +str;
1601
+ return targetObject;
1602
+ } else if (isCol(str)) {
1603
+ // Color string
1604
+ targetObject.t = valueTypes.COLOR;
1605
+ targetObject.d = convertColorStringValuesToRgbaArray(str);
1606
+ return targetObject;
1476
1607
  } else {
1477
- // let str = /** @type {String} */(rawValue).trim();
1478
- let str = /** @type {String} */(rawValue);
1479
- // Parsing operators (+=, -=, *=) manually is much faster than using regex here
1480
- if (str[1] === '=') {
1481
- targetObject.o = str[0];
1482
- str = str.slice(2);
1483
- }
1484
- // Skip exec regex if the value type is complex or color to avoid long regex backtracking
1485
- const unitMatch = str.includes(' ') ? false : unitsExecRgx.exec(str);
1486
- if (unitMatch) {
1487
- // Has a number and a unit
1488
- targetObject.t = valueTypes.UNIT;
1489
- targetObject.n = +unitMatch[1];
1490
- targetObject.u = unitMatch[2];
1491
- return targetObject;
1492
- } else if (targetObject.o) {
1493
- // Has an operator (+=, -=, *=)
1494
- targetObject.n = +str;
1495
- return targetObject;
1496
- } else if (isCol(str)) {
1497
- // Is a color
1498
- targetObject.t = valueTypes.COLOR;
1499
- targetObject.d = convertColorStringValuesToRgbaArray(str);
1500
- return targetObject;
1501
- } else {
1502
- // Is a more complex string (generally svg coords, calc() or filters CSS values)
1503
- const matchedNumbers = str.match(digitWithExponentRgx);
1504
- targetObject.t = valueTypes.COMPLEX;
1505
- targetObject.d = matchedNumbers ? matchedNumbers.map(Number) : [];
1506
- targetObject.s = str.split(digitWithExponentRgx) || [];
1507
- return targetObject;
1508
- }
1608
+ // Is a more complex string (generally svg coords, calc() or filters CSS values)
1609
+ const matchedNumbers = str.match(digitWithExponentRgx);
1610
+ targetObject.t = valueTypes.COMPLEX;
1611
+ targetObject.d = matchedNumbers ? matchedNumbers.map(Number) : [];
1612
+ targetObject.s = str.split(digitWithExponentRgx) || [];
1613
+ return targetObject;
1509
1614
  }
1510
1615
  };
1511
1616
 
@@ -1526,30 +1631,6 @@
1526
1631
 
1527
1632
  const decomposedOriginalValue = createDecomposedValueTargetObject();
1528
1633
 
1529
- /**
1530
- * @param {Tween} tween
1531
- * @param {Number} progress
1532
- * @param {Number} precision
1533
- * @return {String}
1534
- */
1535
- const composeColorValue = (tween, progress, precision) => {
1536
- const mod = tween._modifier;
1537
- const fn = tween._fromNumbers;
1538
- const tn = tween._toNumbers;
1539
- const r = round$1(clamp$1(/** @type {Number} */(mod(lerp$1(fn[0], tn[0], progress))), 0, 255), 0);
1540
- const g = round$1(clamp$1(/** @type {Number} */(mod(lerp$1(fn[1], tn[1], progress))), 0, 255), 0);
1541
- const b = round$1(clamp$1(/** @type {Number} */(mod(lerp$1(fn[2], tn[2], progress))), 0, 255), 0);
1542
- const a = clamp$1(/** @type {Number} */(mod(round$1(lerp$1(fn[3], tn[3], progress), precision))), 0, 1);
1543
- if (tween._composition !== compositionTypes.none) {
1544
- const ns = tween._numbers;
1545
- ns[0] = r;
1546
- ns[1] = g;
1547
- ns[2] = b;
1548
- ns[3] = a;
1549
- }
1550
- return `rgba(${r},${g},${b},${a})`;
1551
- };
1552
-
1553
1634
  /**
1554
1635
  * @param {Tween} tween
1555
1636
  * @param {Number} progress
@@ -1561,15 +1642,14 @@
1561
1642
  const fn = tween._fromNumbers;
1562
1643
  const tn = tween._toNumbers;
1563
1644
  const ts = tween._strings;
1564
- const hasComposition = tween._composition !== compositionTypes.none;
1565
1645
  let v = ts[0];
1566
1646
  for (let j = 0, l = tn.length; j < l; j++) {
1567
1647
  const n = /** @type {Number} */(mod(round$1(lerp$1(fn[j], tn[j], progress), precision)));
1568
1648
  const s = ts[j + 1];
1569
1649
  v += `${s ? n + s : n}`;
1570
- if (hasComposition) {
1571
- tween._numbers[j] = n;
1572
- }
1650
+ // Keep _numbers fresh for every tween, not only composed ones, so a non-composition setter that reads the lerped triplet such as three transformOrigin still animates.
1651
+ // Potential optimization, skip the write when nothing reads it: if (hasComposition || tween._setter) tween._numbers[j] = n;
1652
+ tween._numbers[j] = n;
1573
1653
  }
1574
1654
  return v;
1575
1655
  };
@@ -1621,12 +1701,14 @@
1621
1701
  // Execute the "expensive" iterations calculations only when necessary
1622
1702
  if (iterationCount > 1) {
1623
1703
  // bitwise NOT operator seems to be generally faster than Math.floor() across browsers
1624
- const currentIteration = ~~(tickableCurrentTime / (iterationDuration + (isCurrentTimeEqualOrAboveDuration ? 0 : _loopDelay)));
1704
+ const period = iterationDuration + (isCurrentTimeEqualOrAboveDuration ? 0 : _loopDelay);
1705
+ const currentIteration = ~~(tickableCurrentTime / period);
1625
1706
  tickable._currentIteration = clamp$1(currentIteration, 0, iterationCount);
1626
1707
  // Prevent the iteration count to go above the max iterations when reaching the end of the animation
1627
1708
  if (isCurrentTimeEqualOrAboveDuration) tickable._currentIteration--;
1628
1709
  isOdd = tickable._currentIteration % 2;
1629
- iterationElapsedTime = tickableCurrentTime % (iterationDuration + _loopDelay) || 0;
1710
+ // Derive elapsed from the same `~~` truncation that gave currentIteration. Using `% period` here can disagree with `~~(/period)` under float drift at iteration boundaries and write the wrong end of the tween for one frame.
1711
+ iterationElapsedTime = tickableCurrentTime - currentIteration * period || 0;
1630
1712
  }
1631
1713
 
1632
1714
  // Checks if exactly one of _reversed and (_alternate && isOdd) is true
@@ -1658,12 +1740,14 @@
1658
1740
  if (
1659
1741
  forcedTick ||
1660
1742
  tickMode === tickModes.AUTO && (
1661
- time >= tickableDelay && time <= tickableEndTime || // Normal render
1743
+ // Timeline children render from their offset instead of their delay so the gap left by a truncated sibling is covered on seek.
1744
+ time >= (parent && tickableDelay > 0 ? 0 : tickableDelay) && time <= tickableEndTime || // Normal render
1662
1745
  time <= tickableDelay && tickablePrevTime > tickableDelay || // Playhead is before the animation start time so make sure the animation is at its initial state
1663
1746
  time >= tickableEndTime && tickablePrevTime !== duration // Playhead is after the animation end time so make sure the animation is at its end state
1664
1747
  ) ||
1665
1748
  iterationTime >= tickableEndTime && tickablePrevTime !== duration ||
1666
- iterationTime <= tickableDelay && tickablePrevTime > 0 ||
1749
+ // iterationTime is per-iteration, compared to the delay to catch a backward seek into a looped iteration's delay region. Exclude the final settled end, where iterationTime clamps to duration and would falsely match the delay region when the delay exceeds the duration.
1750
+ iterationTime <= tickableDelay && tickablePrevTime > 0 && !isCurrentTimeEqualOrAboveDuration ||
1667
1751
  time <= tickablePrevTime && tickablePrevTime === duration && completed || // Force a render if a seek occurs on an completed animation
1668
1752
  isCurrentTimeEqualOrAboveDuration && !completed && isSetter // This prevents 0 duration tickables to be skipped
1669
1753
  ) {
@@ -1679,7 +1763,8 @@
1679
1763
 
1680
1764
  // Time has jumped more than globals.tickThreshold so consider this tick manual
1681
1765
  const forcedRender = forcedTick || (isRunningBackwards ? deltaTime * -1 : deltaTime) >= globals.tickThreshold;
1682
- const absoluteTime = tickable._offset + (parent ? parent._offset : 0) + tickableDelay + iterationTime;
1766
+ // Round to match the precision of tween._absoluteStartTime so equal-time boundary checks compare cleanly without floating point drift from the unrounded _offset.
1767
+ const absoluteTime = round$1(tickable._offset + (parent ? parent._offset : 0) + tickableDelay + iterationTime, 12);
1683
1768
 
1684
1769
  // Only Animation can have tweens, Timer returns undefined
1685
1770
  let tween = /** @type {Tween} */(/** @type {JSAnimation} */(tickable)._head);
@@ -1698,15 +1783,38 @@
1698
1783
  const tweenNextRep = tween._nextRep;
1699
1784
  const tweenPrevRep = tween._prevRep;
1700
1785
  const tweenHasComposition = tweenComposition !== compositionTypes.none;
1786
+ // The previous sibling stops writing at its truncated end, so this tween takes over the hold from that point.
1787
+ const tweenPrevRepEndTime = tweenPrevRep ? tweenPrevRep._absoluteStartTime + tweenPrevRep._changeDuration : 0;
1788
+ const tweenPrevRepIsCrossParent = tweenPrevRep && tweenPrevRep.parent !== tween.parent;
1789
+ // Same parent keyframes take over at their own start, end plus delay equals the next start by construction.
1790
+ // Cross parent siblings take over at their update start.
1791
+ // Negative delay siblings take over at their own start instead.
1792
+ const tweenNextRepTakeover = !tweenNextRep || tweenNextRep._isOverridden ? tweenAbsEndTime :
1793
+ tweenNextRep.parent === tween.parent ? tweenAbsEndTime + tweenNextRep._delay :
1794
+ tweenNextRep._absoluteStartTime < tweenNextRep._absoluteUpdateStartTime ? tweenNextRep._absoluteStartTime : tweenNextRep._absoluteUpdateStartTime;
1701
1795
 
1702
1796
  if ((forcedRender || (
1703
- (tweenCurrentTime !== tweenChangeDuration || absoluteTime <= tweenAbsEndTime + (tweenNextRep ? tweenNextRep._delay : 0)) &&
1704
- (tweenCurrentTime !== 0 || absoluteTime >= tween._absoluteStartTime)
1705
- )) && (!tweenHasComposition || (
1797
+ // Tail keyframes always re-evaluate the gate so an earlier keyframe cannot leave the target stale by writing past its own range after a backward seek.
1798
+ (tweenCurrentTime !== tweenChangeDuration || absoluteTime <= tweenNextRepTakeover ||
1799
+ (tweenPrevRep && !tweenPrevRepIsCrossParent && (!tweenNextRep || tweenNextRep.parent !== tween.parent))) &&
1800
+ // A cross parent tween re-renders its from value from the previous sibling truncated end so the handoff gap holds.
1801
+ // A keyframe re-renders its from revert while the next keyframe time is stale so a backward jump over its range cannot leave the next value in place.
1802
+ (tweenCurrentTime !== 0 || absoluteTime >= tween._absoluteStartTime ||
1803
+ (tweenPrevRepIsCrossParent && !tween._hasFromValue && !tweenPrevRep._isOverridden && absoluteTime >= tweenPrevRepEndTime) ||
1804
+ (tweenNextRep && !tweenNextRep._isOverridden && tweenNextRep.parent === tween.parent && tweenNextRep._currentTime !== 0 && iterationTime < tweenNextRep._startTime))
1805
+ )) &&
1806
+ // Non-first keyframes wait until the iteration reaches their own start before rendering, so the previous keyframe can handle the from-revert when scrubbed backward past this tween's range.
1807
+ (!tweenPrevRep || tweenPrevRepIsCrossParent || iterationTime >= tween._startTime) &&
1808
+ (!tweenHasComposition || (
1706
1809
  !tween._isOverridden &&
1707
1810
  (!tween._isOverlapped || absoluteTime <= tweenAbsEndTime) &&
1708
- (!tweenNextRep || (tweenNextRep._isOverridden || absoluteTime <= tweenNextRep._absoluteStartTime)) &&
1709
- (!tweenPrevRep || (tweenPrevRep._isOverridden || (absoluteTime >= (tweenPrevRep._absoluteStartTime + tweenPrevRep._changeDuration) + tween._delay)))
1811
+ // The next sibling owns the value past its takeover point, so yielding there keeps writes single owner in both directions.
1812
+ (!tweenNextRep || tweenNextRep._isOverridden || absoluteTime <= tweenNextRepTakeover) &&
1813
+ // The previous sibling owns the value up to its truncated end.
1814
+ // Cross parent tweens take over the hold from that point, explicit from values wait for their own start.
1815
+ (!tweenPrevRep || (tweenPrevRep._isOverridden || (!tweenPrevRepIsCrossParent ?
1816
+ absoluteTime >= tweenPrevRepEndTime + tween._delay :
1817
+ absoluteTime >= tween._absoluteStartTime || (!tween._hasFromValue && absoluteTime >= tweenPrevRepEndTime))))
1710
1818
  ))
1711
1819
  ) {
1712
1820
 
@@ -1717,7 +1825,7 @@
1717
1825
  const tweenType = tween._tweenType;
1718
1826
  const tweenIsObject = tweenType === tweenTypes.OBJECT;
1719
1827
  const tweenIsNumber = tweenValueType === valueTypes.NUMBER;
1720
- // Only round the in-between frames values if the final value is a string
1828
+ // Only round the in-between frames values if the final value is a string. Object targets consume raw numbers, so rounding is dead work there.
1721
1829
  const tweenPrecision = (tweenIsNumber && tweenIsObject) || tweenProgress === 0 || tweenProgress === 1 ? -1 : globals.precision;
1722
1830
 
1723
1831
  // Recompose tween value
@@ -1733,7 +1841,22 @@
1733
1841
  number = /** @type {Number} */(tweenModifier(round$1(lerp$1(tween._fromNumber, tween._toNumber, tweenProgress), tweenPrecision)));
1734
1842
  value = `${number}${tween._unit}`;
1735
1843
  } else if (tweenValueType === valueTypes.COLOR) {
1736
- value = composeColorValue(tween, tweenProgress, tweenPrecision);
1844
+ const ns = tween._numbers;
1845
+ const fn = tween._fromNumbers;
1846
+ const tn = tween._toNumbers;
1847
+ const omt = 1 - tweenProgress;
1848
+ const fr = fn[0], fg = fn[1], fb = fn[2];
1849
+ const tr = tn[0], tg = tn[1], tb = tn[2];
1850
+ // RGB channels lerp in pseudo-linear space (square inputs, sqrt result) to approximate gamma-correct blending.
1851
+ // See https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-24-importance-being-linear.
1852
+ ns[0] = /** @type {Number} */(tweenModifier(Math.sqrt(fr * fr * omt + tr * tr * tweenProgress)));
1853
+ ns[1] = /** @type {Number} */(tweenModifier(Math.sqrt(fg * fg * omt + tg * tg * tweenProgress)));
1854
+ ns[2] = /** @type {Number} */(tweenModifier(Math.sqrt(fb * fb * omt + tb * tb * tweenProgress)));
1855
+ ns[3] = /** @type {Number} */(tweenModifier(lerp$1(fn[3], tn[3], tweenProgress)));
1856
+ // The rgba string is built only for the dispatch path or the internalRender composition tick (setters handles the color comp)
1857
+ if (!tween._setter || internalRender) {
1858
+ value = `rgba(${round$1(ns[0], 0)},${round$1(ns[1], 0)},${round$1(ns[2], 0)},${ns[3]})`;
1859
+ }
1737
1860
  } else if (tweenValueType === valueTypes.COMPLEX) {
1738
1861
  value = composeComplexValue(tween, tweenProgress, tweenPrecision);
1739
1862
  }
@@ -1748,7 +1871,9 @@
1748
1871
  const tweenProperty = tween.property;
1749
1872
  tweenTarget = tween.target;
1750
1873
 
1751
- if (tweenIsObject) {
1874
+ if (tween._setter) {
1875
+ tween._setter(tweenTarget, number, tween);
1876
+ } else if (tweenIsObject) {
1752
1877
  tweenTarget[tweenProperty] = value;
1753
1878
  } else if (tweenType === tweenTypes.ATTRIBUTE) {
1754
1879
  /** @type {DOMTarget} */(tweenTarget).setAttribute(tweenProperty, /** @type {String} */(value));
@@ -1776,6 +1901,9 @@
1776
1901
  tween._value = value;
1777
1902
  }
1778
1903
 
1904
+ } else if (tweenCurrentTime && tweenPrevRep && !tweenPrevRepIsCrossParent && iterationTime < tween._startTime) {
1905
+ // Mark the keyframe as reverted when the playhead moves before its start, the previous keyframe owns the from revert and writes it once.
1906
+ tween._currentTime = 0;
1779
1907
  }
1780
1908
 
1781
1909
  if (tweenTransformsNeedUpdate && tween._renderTransforms) {
@@ -1883,6 +2011,8 @@
1883
2011
 
1884
2012
  forEachChildren(tl, (/** @type {JSAnimation} */child) => {
1885
2013
  const childTime = round$1((tlChildrenTime - child._offset) * child._speed, 12); // Rounding is needed when using seconds
2014
+ // Skip past-end siblings on backward iteration so their progress=1 to-values don't render last and overwrite the active sibling's write. Compare against _delay + duration so children with a normalized delay are not skipped while still inside their active range.
2015
+ if (tlIsRunningBackwards && childTime > child._delay + child.duration) return;
1886
2016
  const childTickMode = child._fps < tl._fps ? child.requestTick(tlCildrenTickTime) : tickMode;
1887
2017
  tlChildrenHasRendered += render(child, childTime, muteCallbacks, internalRender, childTickMode);
1888
2018
  if (!child.completed && tlChildrenHaveCompleted) tlChildrenHaveCompleted = false;
@@ -1959,7 +2089,20 @@
1959
2089
  const tweenType = tween._tweenType;
1960
2090
  const originalInlinedValue = tween._inlineValue;
1961
2091
  const tweenHadNoInlineValue = isNil(originalInlinedValue) || originalInlinedValue === emptyString;
1962
- if (tweenType === tweenTypes.OBJECT) {
2092
+ if (tween._setter) {
2093
+ if (!inlineStylesOnly && !tweenHadNoInlineValue) {
2094
+ // Re-seed the original value to the _number / _numbers props so the setter can write the original state instead of re-applying the current frame.
2095
+ decomposeRawValue(originalInlinedValue, decomposedOriginalValue);
2096
+ if (decomposedOriginalValue.d) {
2097
+ const src = decomposedOriginalValue.d;
2098
+ const dst = tween._numbers;
2099
+ for (let i = 0, l = src.length; i < l; i++) dst[i] = src[i];
2100
+ } else {
2101
+ tween._number = decomposedOriginalValue.n;
2102
+ }
2103
+ tween._setter(tween.target, tween._number, tween);
2104
+ }
2105
+ } else if (tweenType === tweenTypes.OBJECT) {
1963
2106
  if (!inlineStylesOnly && !tweenHadNoInlineValue) {
1964
2107
  tweenTarget[tweenProperty] = originalInlinedValue;
1965
2108
  }
@@ -2036,8 +2179,6 @@
2036
2179
  /** @type {Number} */
2037
2180
  this._lastTime = initTime;
2038
2181
  /** @type {Number} */
2039
- this._scheduledTime = 0;
2040
- /** @type {Number} */
2041
2182
  this._frameDuration = K / maxFps;
2042
2183
  /** @type {Number} */
2043
2184
  this._fps = maxFps;
@@ -2056,14 +2197,12 @@
2056
2197
  }
2057
2198
 
2058
2199
  set fps(frameRate) {
2059
- const previousFrameDuration = this._frameDuration;
2060
2200
  const fr = +frameRate;
2061
2201
  const fps = fr < minValue ? minValue : fr;
2062
2202
  const frameDuration = K / fps;
2063
2203
  if (fps > defaults.frameRate) defaults.frameRate = fps;
2064
2204
  this._fps = fps;
2065
2205
  this._frameDuration = frameDuration;
2066
- this._scheduledTime += frameDuration - previousFrameDuration;
2067
2206
  }
2068
2207
 
2069
2208
  get speed() {
@@ -2080,17 +2219,17 @@
2080
2219
  * @return {tickModes}
2081
2220
  */
2082
2221
  requestTick(time) {
2083
- const scheduledTime = this._scheduledTime;
2084
- this._lastTickTime = time;
2085
- // If the current time is lower than the scheduled time
2086
- // this means not enough time has passed to hit one frameDuration
2087
- // so skip that frame
2088
- if (time < scheduledTime) return tickModes.NONE;
2089
2222
  const frameDuration = this._frameDuration;
2090
- const frameDelta = time - scheduledTime;
2091
- // Ensures that _scheduledTime progresses in steps of at least 1 frameDuration.
2092
- // Skips ahead if the actual elapsed time is higher.
2093
- this._scheduledTime += frameDelta < frameDuration ? frameDuration : frameDelta;
2223
+ const elapsed = time - this._lastTickTime;
2224
+ const scaled = frameDuration * .25;
2225
+ const tolerance = scaled < 4 ? scaled : 4;
2226
+ // Tolerance prevents dropping frames that arrive a bit early due to RAF jitter
2227
+ // typically <= ~25% of frame duration and capped at 4ms so it doesn't dominate at high fps.
2228
+ // e.g. at 60fps (frameDuration=16.667ms) a frame arriving after 15ms:
2229
+ // - without tolerance: 15 < 16.667 -> skip
2230
+ // - with tolerance: 15 + 4 >= 16.667 -> tick
2231
+ if (elapsed + tolerance < frameDuration) return tickModes.NONE;
2232
+ this._lastTickTime = elapsed >= frameDuration ? time - (elapsed % frameDuration) : time;
2094
2233
  return tickModes.AUTO;
2095
2234
  }
2096
2235
 
@@ -2250,7 +2389,9 @@
2250
2389
  }
2251
2390
 
2252
2391
  set speed(playbackRate) {
2253
- this._speed = playbackRate * globals.timeScale;
2392
+ const speed = playbackRate * globals.timeScale;
2393
+ if (this._speed === speed) return;
2394
+ this._speed = speed;
2254
2395
  forEachChildren(this, (/** @type {Tickable} */child) => child.speed = child._speed);
2255
2396
  }
2256
2397
 
@@ -2383,7 +2524,7 @@
2383
2524
  if (prevSibling) {
2384
2525
 
2385
2526
  const prevParent = prevSibling.parent;
2386
- const prevAbsEndTime = prevSibling._absoluteStartTime + prevSibling._changeDuration;
2527
+ const prevAbsEndTime = prevSibling._absoluteEndTime;
2387
2528
 
2388
2529
  // Handle looped animations tween
2389
2530
 
@@ -2409,7 +2550,8 @@
2409
2550
 
2410
2551
  }
2411
2552
 
2412
- const absoluteUpdateStartTime = tweenAbsStartTime - tween._delay;
2553
+ // Read the precision-matched update-start instead of subtracting tween._delay live so sequential keyframes touching at a boundary don't trigger a phantom overlap from float drift.
2554
+ const absoluteUpdateStartTime = tween._absoluteUpdateStartTime;
2413
2555
 
2414
2556
  if (prevAbsEndTime > absoluteUpdateStartTime) {
2415
2557
 
@@ -2429,37 +2571,41 @@
2429
2571
  }
2430
2572
  }
2431
2573
 
2432
- // Pause (and cancel) the parent if it only contains overlapped tweens
2574
+ // Skip the cancel cascade when both tweens share the same parent timeline, a timeline cannot replace itself.
2575
+ const tweenParentTL = tween.parent.parent;
2576
+ if (!tweenParentTL || tweenParentTL !== prevParent.parent) {
2433
2577
 
2434
- let pausePrevParentAnimation = true;
2578
+ let pausePrevParentAnimation = true;
2435
2579
 
2436
- forEachChildren(prevParent, (/** @type Tween */t) => {
2437
- if (!t._isOverlapped) pausePrevParentAnimation = false;
2438
- });
2580
+ forEachChildren(prevParent, (/** @type Tween */t) => {
2581
+ if (!t._isOverlapped) pausePrevParentAnimation = false;
2582
+ });
2439
2583
 
2440
- if (pausePrevParentAnimation) {
2441
- const prevParentTL = prevParent.parent;
2442
- if (prevParentTL) {
2443
- let pausePrevParentTL = true;
2444
- forEachChildren(prevParentTL, (/** @type JSAnimation */a) => {
2445
- if (a !== prevParent) {
2446
- forEachChildren(a, (/** @type Tween */t) => {
2447
- if (!t._isOverlapped) pausePrevParentTL = false;
2448
- });
2584
+ if (pausePrevParentAnimation) {
2585
+ const prevParentTL = prevParent.parent;
2586
+ if (prevParentTL) {
2587
+ let pausePrevParentTL = true;
2588
+ forEachChildren(prevParentTL, (/** @type JSAnimation */a) => {
2589
+ if (a !== prevParent) {
2590
+ forEachChildren(a, (/** @type Tween */t) => {
2591
+ if (!t._isOverlapped) pausePrevParentTL = false;
2592
+ });
2593
+ }
2594
+ });
2595
+ if (pausePrevParentTL) {
2596
+ prevParentTL.cancel();
2449
2597
  }
2450
- });
2451
- if (pausePrevParentTL) {
2452
- prevParentTL.cancel();
2598
+ } else {
2599
+ prevParent.cancel();
2600
+ // Previously, calling .cancel() on a timeline child would affect the render order of other children
2601
+ // Worked around this by marking it as .completed and using .pause() for safe removal in the engine loop
2602
+ // This is no longer needed since timeline tween composition is now handled separately
2603
+ // Keeping this here for reference
2604
+ // prevParent.completed = true;
2605
+ // prevParent.pause();
2453
2606
  }
2454
- } else {
2455
- prevParent.cancel();
2456
- // Previously, calling .cancel() on a timeline child would affect the render order of other children
2457
- // Worked around this by marking it as .completed and using .pause() for safe removal in the engine loop
2458
- // This is no longer needed since timeline tween composition is now handled separately
2459
- // Keeping this here for reference
2460
- // prevParent.completed = true;
2461
- // prevParent.pause();
2462
2607
  }
2608
+
2463
2609
  }
2464
2610
 
2465
2611
  }
@@ -2514,14 +2660,12 @@
2514
2660
  tween._number = 0;
2515
2661
  lookupTween._fromNumber = toNumber;
2516
2662
 
2517
- if (tween._toNumbers) {
2663
+ if (tween._toNumbers.length) {
2518
2664
  const toNumbers = cloneArray(tween._toNumbers);
2519
- if (toNumbers) {
2520
- toNumbers.forEach((value, i) => {
2521
- tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value;
2522
- tween._toNumbers[i] = 0;
2523
- });
2524
- }
2665
+ toNumbers.forEach((value, i) => {
2666
+ tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value;
2667
+ tween._toNumbers[i] = 0;
2668
+ });
2525
2669
  lookupTween._fromNumbers = toNumbers;
2526
2670
  }
2527
2671
 
@@ -3222,18 +3366,16 @@
3222
3366
  function registerTargets(targets) {
3223
3367
  const parsedTargetsArray = parseTargets(targets);
3224
3368
  const parsedTargetsLength = parsedTargetsArray.length;
3225
- if (parsedTargetsLength) {
3226
- for (let i = 0; i < parsedTargetsLength; i++) {
3227
- const target = parsedTargetsArray[i];
3228
- if (!target[isRegisteredTargetSymbol]) {
3229
- target[isRegisteredTargetSymbol] = true;
3230
- const isSvgType = isSvg(target);
3231
- const isDom = /** @type {DOMTarget} */(target).nodeType || isSvgType;
3232
- if (isDom) {
3233
- target[isDomSymbol] = true;
3234
- target[isSvgSymbol] = isSvgType;
3235
- target[transformsSymbol] = {};
3236
- }
3369
+ for (let i = 0; i < parsedTargetsLength; i++) {
3370
+ const target = parsedTargetsArray[i];
3371
+ if (!target[isRegisteredTargetSymbol]) {
3372
+ target[isRegisteredTargetSymbol] = true;
3373
+ const isSvgType = isSvg(target);
3374
+ const isDom = /** @type {DOMTarget} */(target).nodeType || isSvgType;
3375
+ if (isDom) {
3376
+ target[isDomSymbol] = true;
3377
+ target[isSvgSymbol] = isSvgType;
3378
+ target[transformsSymbol] = {};
3237
3379
  }
3238
3380
  }
3239
3381
  }
@@ -3355,8 +3497,8 @@
3355
3497
 
3356
3498
  /**
3357
3499
  * @typedef {Object} EasesFunctions
3358
- * @property {typeof none} linear
3359
- * @property {typeof none} none
3500
+ * @property {EasingFunction} linear
3501
+ * @property {EasingFunction} none
3360
3502
  * @property {PowerEasing} in
3361
3503
  * @property {PowerEasing} out
3362
3504
  * @property {PowerEasing} inOut
@@ -3588,6 +3730,11 @@
3588
3730
 
3589
3731
  super(/** @type {TimerParams & AnimationParams} */(parameters), parent, parentPosition);
3590
3732
 
3733
+ /** @type {Tween} */
3734
+ this._head;
3735
+ /** @type {Tween} */
3736
+ this._tail;
3737
+
3591
3738
  ++JSAnimationId;
3592
3739
 
3593
3740
  const parsedTargets = registerTargets(targets);
@@ -3644,6 +3791,7 @@
3644
3791
  if (isKey(p)) {
3645
3792
 
3646
3793
  const tweenType = getTweenType(target, p);
3794
+ const adapterProp = resolveAdapterEntry(target, p);
3647
3795
 
3648
3796
  const propName = sanitizePropertyName(p, target, tweenType);
3649
3797
 
@@ -3727,7 +3875,7 @@
3727
3875
  } else {
3728
3876
  tweenToValue = computedToValue;
3729
3877
  }
3730
- const tweenFromValue = getFunctionValue(key.from, target, ti, tl, null, prevSiblingTween);
3878
+ const tweenFromValue = getFunctionValue(key.from, target, ti, tl, fromFunctionStore, prevSiblingTween);
3731
3879
  const easeToParse = key.ease || tEasing;
3732
3880
 
3733
3881
  const easeFunctionResult = getFunctionValue(easeToParse, target, ti, tl, null, prevSiblingTween);
@@ -3745,9 +3893,13 @@
3745
3893
  const hasToValue = !isUnd(tweenToValue);
3746
3894
  const isFromToArray = isArr(tweenToValue);
3747
3895
  const isFromToValue = isFromToArray || (hasFromvalue && hasToValue);
3896
+ // Capture the update-start in local time, the previous sibling's end for keyframes after the first, zero for the first keyframe. Used to derive a precision-matched _absoluteUpdateStartTime below.
3897
+ const tweenUpdateStartLocal = prevTween ? lastTweenChangeEndTime : 0;
3748
3898
  const tweenStartTime = prevTween ? lastTweenChangeEndTime + tweenDelay : tweenDelay;
3749
3899
  // Rounding is necessary here to minimize floating point errors when working in seconds
3750
3900
  const absoluteStartTime = round$1(absoluteOffsetTime + tweenStartTime, 12);
3901
+ // Match the rounding pattern of prevSibling._absoluteEndTime so the composition overlap check compares cleanly when keyframes touch at a boundary.
3902
+ const absoluteUpdateStartTime = round$1(absoluteOffsetTime + tweenUpdateStartLocal, 12);
3751
3903
 
3752
3904
  // Force a onRender callback if the animation contains at least one from value and autoplay is set to false
3753
3905
  if (!shouldTriggerRender && (hasFromvalue || isFromToArray)) shouldTriggerRender = 1;
@@ -3756,9 +3908,9 @@
3756
3908
 
3757
3909
  if (tweenComposition !== compositionTypes.none) {
3758
3910
  let nextSibling = siblings._head;
3759
- // Iterate trough all the next siblings until we find a sibling with an equal or inferior start time
3760
- while (nextSibling && !nextSibling._isOverridden && nextSibling._absoluteStartTime <= absoluteStartTime) {
3761
- prevSibling = nextSibling;
3911
+ // Walk prior siblings up to the new tween, skipping overridden ones so the chain resolves to the latest live value instead of stopping at the first override.
3912
+ while (nextSibling && nextSibling._absoluteStartTime <= absoluteStartTime) {
3913
+ if (!nextSibling._isOverridden) prevSibling = nextSibling;
3762
3914
  nextSibling = nextSibling._nextRep;
3763
3915
  // Overrides all the next siblings if the next sibling starts at the same time of after as the new tween start time
3764
3916
  if (nextSibling && nextSibling._absoluteStartTime >= absoluteStartTime) {
@@ -3812,8 +3964,8 @@
3812
3964
  if (prevTween) {
3813
3965
  decomposeTweenValue(prevTween, fromTargetObject);
3814
3966
  } else {
3815
- decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value :
3816
3967
  // No need to get and parse the original value if the tween is part of a timeline and has a previous sibling part of the same timeline
3968
+ decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value :
3817
3969
  getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore), fromTargetObject);
3818
3970
  }
3819
3971
  }
@@ -3852,8 +4004,7 @@
3852
4004
  const colorValue = fromTargetObject.t === valueTypes.COLOR ? fromTargetObject : toTargetObject;
3853
4005
  const notColorValue = fromTargetObject.t === valueTypes.COLOR ? toTargetObject : fromTargetObject;
3854
4006
  notColorValue.t = valueTypes.COLOR;
3855
- notColorValue.s = colorValue.s;
3856
- notColorValue.d = [0, 0, 0, 1];
4007
+ notColorValue.d = colorValue.d.map(() => 0);
3857
4008
  }
3858
4009
  }
3859
4010
 
@@ -3883,6 +4034,16 @@
3883
4034
  let inlineValue = inlineStylesStore[propName];
3884
4035
  if (!isNil(inlineValue)) inlineStylesStore[propName] = null;
3885
4036
 
4037
+ // Resolve the adapter setter once so render skips the lookup per frame.
4038
+ const tweenSetter = adapterProp ? adapterProp.set : null;
4039
+
4040
+ // Rounding is necessary here to minimize floating point errors when working in seconds
4041
+ lastTweenChangeEndTime = round$1(tweenStartTime + tweenUpdateDuration, 12);
4042
+
4043
+ const fromD = fromTargetObject.d;
4044
+ const toD = toTargetObject.d;
4045
+ const toS = toTargetObject.s;
4046
+
3886
4047
  /** @type {Tween} */
3887
4048
  const tween = {
3888
4049
  parent: this,
@@ -3893,12 +4054,12 @@
3893
4054
  _toFunc: toFunctionStore.func,
3894
4055
  _fromFunc: fromFunctionStore.func,
3895
4056
  _ease: parseEase(tweenEasing),
3896
- _fromNumbers: cloneArray(fromTargetObject.d),
3897
- _toNumbers: cloneArray(toTargetObject.d),
3898
- _strings: cloneArray(toTargetObject.s),
4057
+ _fromNumbers: fromD ? cloneArray(fromD) : emptyArray,
4058
+ _toNumbers: toD ? cloneArray(toD) : emptyArray,
4059
+ _strings: toS ? cloneArray(toS) : emptyArray,
3899
4060
  _fromNumber: fromTargetObject.n,
3900
4061
  _toNumber: toTargetObject.n,
3901
- _numbers: cloneArray(fromTargetObject.d), // For additive tween and animatables
4062
+ _numbers: fromD ? cloneArray(fromD) : emptyArray, // For additive tween and animatables
3902
4063
  _number: fromTargetObject.n, // For additive tween and animatables
3903
4064
  _unit: toTargetObject.u,
3904
4065
  _modifier: tweenModifier,
@@ -3908,8 +4069,12 @@
3908
4069
  _updateDuration: tweenUpdateDuration,
3909
4070
  _changeDuration: tweenUpdateDuration,
3910
4071
  _absoluteStartTime: absoluteStartTime,
4072
+ _absoluteUpdateStartTime: absoluteUpdateStartTime,
4073
+ _absoluteEndTime: round$1(absoluteOffsetTime + lastTweenChangeEndTime, 12),
4074
+ _hasFromValue: hasFromvalue || isFromToArray ? 1 : 0,
3911
4075
  // NOTE: Investigate bit packing to stores ENUM / BOOL
3912
4076
  _tweenType: tweenType,
4077
+ _setter: tweenSetter,
3913
4078
  _valueType: toTargetObject.t,
3914
4079
  _composition: tweenComposition,
3915
4080
  _isOverlapped: 0,
@@ -3932,10 +4097,11 @@
3932
4097
  const vt = tween._valueType;
3933
4098
  if (vt === valueTypes.COMPLEX) {
3934
4099
  tween._value = composeComplexValue(tween, 1, -1);
3935
- } else if (vt === valueTypes.COLOR) {
3936
- tween._value = composeColorValue(tween, 1, -1);
3937
4100
  } else if (vt === valueTypes.UNIT) {
3938
4101
  tween._value = `${tweenModifier(tween._toNumber)}${tween._unit}`;
4102
+ } else if (vt === valueTypes.COLOR) {
4103
+ const d = toTargetObject.d;
4104
+ tween._value = `rgba(${round$1(d[0], 0)},${round$1(d[1], 0)},${round$1(d[2], 0)},${d[3]})`;
3939
4105
  } else {
3940
4106
  tween._value = tweenModifier(tween._toNumber);
3941
4107
  }
@@ -3943,8 +4109,7 @@
3943
4109
  if (isNaN(firstTweenChangeStartTime)) {
3944
4110
  firstTweenChangeStartTime = tween._startTime;
3945
4111
  }
3946
- // Rounding is necessary here to minimize floating point errors when working in seconds
3947
- lastTweenChangeEndTime = round$1(tweenStartTime + tweenUpdateDuration, 12);
4112
+
3948
4113
  prevTween = tween;
3949
4114
  animationAnimationLength++;
3950
4115
 
@@ -4050,8 +4215,11 @@
4050
4215
  tween._updateDuration = normalizeTime(tween._updateDuration * timeScale);
4051
4216
  tween._changeDuration = normalizeTime(tween._changeDuration * timeScale);
4052
4217
  tween._currentTime *= timeScale;
4218
+ tween._delay *= timeScale;
4053
4219
  tween._startTime *= timeScale;
4054
4220
  tween._absoluteStartTime *= timeScale;
4221
+ tween._absoluteUpdateStartTime *= timeScale;
4222
+ tween._absoluteEndTime *= timeScale;
4055
4223
  });
4056
4224
  return super.stretch(newDuration);
4057
4225
  }
@@ -4180,8 +4348,6 @@
4180
4348
 
4181
4349
 
4182
4350
 
4183
-
4184
-
4185
4351
  /**
4186
4352
  * @param {Timeline} tl
4187
4353
  * @return {Number}
@@ -4374,13 +4540,21 @@
4374
4540
  if (!isUnd(synced) && !isUnd(/** @type {WAAPIAnimation} */(synced).persist)) {
4375
4541
  /** @type {WAAPIAnimation} */(synced).persist = true;
4376
4542
  }
4377
- return this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position);
4543
+ const editor = globals.editor;
4544
+ const childHook = editor && editor.addTimelineChild;
4545
+ if (editor && editor.addTimelineSync) {
4546
+ position = editor.addTimelineSync(synced, position, this.id);
4547
+ editor.addTimelineChild = null; // Suppress the per-child hook for the internal .add, sync already registered.
4548
+ }
4549
+ const result = this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position);
4550
+ if (editor) editor.addTimelineChild = childHook;
4551
+ return result;
4378
4552
  }
4379
4553
 
4380
4554
  /**
4381
4555
  * @param {TargetsParam} targets
4382
4556
  * @param {AnimationParams} parameters
4383
- * @param {TimelinePosition} [position]
4557
+ * @param {TimelinePosition|StaggerFunction<Number|String>|TweakRegister} [position]
4384
4558
  * @return {this}
4385
4559
  */
4386
4560
  set(targets, parameters, position) {
@@ -4397,6 +4571,7 @@
4397
4571
  */
4398
4572
  call(callback, position) {
4399
4573
  if (isUnd(callback) || callback && !isFnc(callback)) return this;
4574
+ if (globals.editor && globals.editor.addTimelineCall) position = globals.editor.addTimelineCall(callback, position, this.id);
4400
4575
  return this.add({ duration: 0, delay: 0, onComplete: () => callback(this) }, position);
4401
4576
  }
4402
4577
 
@@ -4408,6 +4583,7 @@
4408
4583
  */
4409
4584
  label(labelName, position) {
4410
4585
  if (isUnd(labelName) || labelName && !isStr(labelName)) return this;
4586
+ if (globals.editor && globals.editor.addTimelineLabel) position = globals.editor.addTimelineLabel(labelName, position, this.id);
4411
4587
  this.labels[labelName] = parseTimelinePosition(this, position);
4412
4588
  return this;
4413
4589
  }
@@ -4516,6 +4692,7 @@
4516
4692
  const callbacksAnimationParams = { v: 1, autoplay: false };
4517
4693
  const properties = {};
4518
4694
  this.targets = [];
4695
+ /** @type {Record<String, JSAnimation>} */
4519
4696
  this.animations = {};
4520
4697
  /** @type {JSAnimation|null} */
4521
4698
  this.callbacks = null;
@@ -5054,6 +5231,9 @@
5054
5231
  */
5055
5232
  const set = (targets, parameters) => {
5056
5233
  if (isUnd(parameters)) return;
5234
+ if (globals.editor && globals.editor.addSet) {
5235
+ return globals.editor.addSet(targets, parameters);
5236
+ }
5057
5237
  parameters.duration = minValue;
5058
5238
  // Do not overrides currently active tweens by default
5059
5239
  parameters.composition = setValue(parameters.composition, compositionTypes.none);
@@ -6290,13 +6470,14 @@
6290
6470
  };
6291
6471
 
6292
6472
  /**
6293
- * @param {(...args: any[]) => Tickable | ((...args: any[]) => void) | void} constructor
6294
- * @return {(...args: any[]) => Tickable | ((...args: any[]) => void)}
6473
+ * @template {Tickable | ((...args: any[]) => void) | void} T
6474
+ * @param {(...args: any[]) => T} constructor
6475
+ * @return {(...args: any[]) => T extends void ? () => void : T}
6295
6476
  */
6296
6477
  const keepTime = constructor => {
6297
6478
  /** @type {Tickable} */
6298
6479
  let tracked;
6299
- return (...args) => {
6480
+ return /** @type {(...args: any[]) => T extends void ? () => void : T} */(/** @type {*} */((...args) => {
6300
6481
  let currentIteration, currentIterationProgress, reversed, alternate, startTime;
6301
6482
  if (tracked) {
6302
6483
  currentIteration = tracked.currentIteration;
@@ -6314,7 +6495,7 @@
6314
6495
  /** @type {Tickable} */(tracked)._startTime = startTime;
6315
6496
  }
6316
6497
  return cleanup || noop;
6317
- }
6498
+ }));
6318
6499
  };
6319
6500
 
6320
6501
 
@@ -6712,12 +6893,14 @@
6712
6893
 
6713
6894
  refreshScrollObservers() {
6714
6895
  forEachChildren(this, (/** @type {ScrollObserver} */child) => {
6896
+ if (!child.ready) return;
6715
6897
  if (child._debug) {
6716
6898
  child.removeDebug();
6717
6899
  }
6718
6900
  });
6719
6901
  this.updateBounds();
6720
6902
  forEachChildren(this, (/** @type {ScrollObserver} */child) => {
6903
+ if (!child.ready) return;
6721
6904
  child.refresh();
6722
6905
  child.onResize(child);
6723
6906
  if (child._debug) {
@@ -9815,11 +9998,12 @@
9815
9998
  * Adapted from https://bost.ocks.org/mike/shuffle/
9816
9999
  *
9817
10000
  * @param {Array} items - The array to shuffle (will be modified in-place)
10001
+ * @param {RandomNumberGenerator} [rnd] - Optional RNG matching the random() signature (defaults to random)
9818
10002
  * @return {Array} The same array reference, now shuffled
9819
10003
  */
9820
- const shuffle = items => {
10004
+ const shuffle = (items, rnd = random) => {
9821
10005
  let m = items.length, t, i;
9822
- while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
10006
+ while (m) { i = rnd(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
9823
10007
  return items;
9824
10008
  };
9825
10009
 
@@ -9864,6 +10048,7 @@
9864
10048
  let values = [];
9865
10049
  let maxValue = 0;
9866
10050
  let cachedOffset;
10051
+ let jitterSamples = null;
9867
10052
  const from = params.from;
9868
10053
  const reversed = params.reversed;
9869
10054
  const ease = params.ease;
@@ -9885,27 +10070,42 @@
9885
10070
  const val2 = isRange ? parseNumber(val[1]) : 0;
9886
10071
  const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
9887
10072
  const start = params.start || 0 + (isRange ? val1 : 0);
10073
+ const seed = params.seed;
10074
+ const hasSeed = !isUnd(seed) && seed !== false;
10075
+ const rng = hasSeed ? createSeededRandom(seed === true ? 0 : /** @type {Number} */(seed)) : random;
10076
+ const jitter = params.jitter;
10077
+ const hasJitter = !isUnd(jitter);
10078
+ const jitterIsArr = isArr(jitter);
10079
+ const jitterStart = jitterIsArr ? /** @type {[Number,Number]} */(jitter)[0] : /** @type {Number} */(jitter) || 0;
10080
+ const jitterEnd = jitterIsArr ? /** @type {[Number,Number]} */(jitter)[1] : /** @type {Number} */(jitter) || 0;
9888
10081
  let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
9889
10082
  return (target, i, t, _, tl) => {
9890
10083
  const [ registeredTarget ] = registerTargets(target);
9891
10084
  const total = isUnd(customTotal) ? t.length : customTotal;
9892
10085
  const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
9893
- const staggerIndex = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
10086
+ const customIdx = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
10087
+ // Fall back to the natural index when the resolved value lands outside [0, total) so values[staggerIndex] never reads undefined.
10088
+ const staggerIndex = customIdx >= 0 && customIdx < total ? customIdx : i;
9894
10089
  if (fromCenter) fromIndex = (total - 1) / 2;
9895
10090
  if (fromLast) fromIndex = total - 1;
9896
10091
  if (!values.length) {
9897
10092
  if (autoGrid) {
9898
10093
  let hasPositions = true;
10094
+ let has3D = false;
9899
10095
  let minPosX = Infinity;
9900
10096
  let minPosY = Infinity;
10097
+ let minPosZ = Infinity;
9901
10098
  let maxPosX = -Infinity;
9902
10099
  let maxPosY = -Infinity;
10100
+ let maxPosZ = -Infinity;
9903
10101
  const pxArr = [];
9904
10102
  const pyArr = [];
10103
+ const pzArr = [];
9905
10104
  for (let index = 0; index < total; index++) {
9906
10105
  const el = t[index];
9907
10106
  let px = 0;
9908
10107
  let py = 0;
10108
+ let pz = 0;
9909
10109
  let found = false;
9910
10110
  if (el && isFnc(el.getBoundingClientRect)) {
9911
10111
  const rect = el.getBoundingClientRect();
@@ -9917,6 +10117,10 @@
9917
10117
  if (obj && isNum(obj.x) && isNum(obj.y)) {
9918
10118
  px = obj.x;
9919
10119
  py = obj.y;
10120
+ if (isNum(obj.z)) {
10121
+ pz = obj.z;
10122
+ has3D = true;
10123
+ }
9920
10124
  found = true;
9921
10125
  }
9922
10126
  }
@@ -9926,42 +10130,52 @@
9926
10130
  }
9927
10131
  pxArr.push(px);
9928
10132
  pyArr.push(py);
10133
+ pzArr.push(pz);
9929
10134
  if (px < minPosX) minPosX = px;
9930
10135
  if (py < minPosY) minPosY = py;
10136
+ if (pz < minPosZ) minPosZ = pz;
9931
10137
  if (px > maxPosX) maxPosX = px;
9932
10138
  if (py > maxPosY) maxPosY = py;
10139
+ if (pz > maxPosZ) maxPosZ = pz;
9933
10140
  }
9934
10141
  if (hasPositions) {
9935
10142
  let fX = pxArr[0];
9936
10143
  let fY = pyArr[0];
10144
+ let fZ = pzArr[0];
9937
10145
  if (fromArr) {
9938
10146
  fX = minPosX + from[0] * (maxPosX - minPosX);
9939
10147
  fY = minPosY + from[1] * (maxPosY - minPosY);
10148
+ fZ = has3D ? minPosZ + (from.length >= 3 ? from[2] : 0.5) * (maxPosZ - minPosZ) : 0;
9940
10149
  } else if (fromCenter) {
9941
10150
  fX = (minPosX + maxPosX) / 2;
9942
10151
  fY = (minPosY + maxPosY) / 2;
10152
+ fZ = (minPosZ + maxPosZ) / 2;
9943
10153
  } else if (fromLast) {
9944
10154
  fX = pxArr[total - 1];
9945
10155
  fY = pyArr[total - 1];
10156
+ fZ = pzArr[total - 1];
9946
10157
  } else if (isNum(from)) {
9947
10158
  fX = pxArr[from];
9948
10159
  fY = pyArr[from];
10160
+ fZ = pzArr[from];
9949
10161
  }
9950
10162
  for (let index = 0; index < total; index++) {
9951
10163
  const distanceX = fX - pxArr[index];
9952
10164
  const distanceY = fY - pyArr[index];
9953
- let value = sqrt(distanceX * distanceX + distanceY * distanceY);
10165
+ const distanceZ = fZ - pzArr[index];
10166
+ let value = sqrt(distanceX * distanceX + distanceY * distanceY + (has3D ? distanceZ * distanceZ : 0));
9954
10167
  if (axis === 'x') value = -distanceX;
9955
10168
  if (axis === 'y') value = -distanceY;
10169
+ if (axis === 'z') value = -distanceZ;
9956
10170
  values.push(value);
9957
10171
  }
9958
10172
  let minDist = Infinity;
9959
- for (let index = 0, l = values.length; index < l; index++) {
10173
+ for (let index = 0; index < total; index++) {
9960
10174
  const absVal = abs(values[index]);
9961
10175
  if (absVal > 0 && absVal < minDist) minDist = absVal;
9962
10176
  }
9963
10177
  if (minDist > 0 && minDist < Infinity) {
9964
- for (let index = 0, l = values.length; index < l; index++) {
10178
+ for (let index = 0; index < total; index++) {
9965
10179
  values[index] = values[index] / minDist;
9966
10180
  }
9967
10181
  }
@@ -9975,32 +10189,51 @@
9975
10189
  if (!grid) {
9976
10190
  values.push(abs(fromIndex - index));
9977
10191
  } else {
9978
- let fromX, fromY;
10192
+ const dims = grid.length;
10193
+ const wh = grid[0] * grid[1];
10194
+ let fromX, fromY, fromZ;
9979
10195
  if (fromArr) {
9980
10196
  fromX = from[0] * (grid[0] - 1);
9981
10197
  fromY = from[1] * (grid[1] - 1);
10198
+ fromZ = dims === 3 ? (from.length >= 3 ? from[2] : 0.5) * (grid[2] - 1) : 0;
9982
10199
  } else if (fromCenter) {
9983
10200
  fromX = (grid[0] - 1) / 2;
9984
10201
  fromY = (grid[1] - 1) / 2;
10202
+ fromZ = dims === 3 ? (grid[2] - 1) / 2 : 0;
9985
10203
  } else {
9986
10204
  fromX = fromIndex % grid[0];
9987
- fromY = floor(fromIndex / grid[0]);
10205
+ fromY = floor(fromIndex / grid[0]) % grid[1];
10206
+ fromZ = dims === 3 ? floor(fromIndex / wh) : 0;
9988
10207
  }
9989
10208
  const toX = index % grid[0];
9990
- const toY = floor(index / grid[0]);
10209
+ const toY = floor(index / grid[0]) % grid[1];
10210
+ const toZ = dims === 3 ? floor(index / wh) : 0;
9991
10211
  const distanceX = fromX - toX;
9992
10212
  const distanceY = fromY - toY;
9993
- let value = sqrt(distanceX * distanceX + distanceY * distanceY);
10213
+ const distanceZ = fromZ - toZ;
10214
+ let value = sqrt(distanceX * distanceX + distanceY * distanceY + (dims === 3 ? distanceZ * distanceZ : 0));
9994
10215
  if (axis === 'x') value = -distanceX;
9995
10216
  if (axis === 'y') value = -distanceY;
10217
+ if (axis === 'z') value = -distanceZ;
9996
10218
  values.push(value);
9997
10219
  }
9998
10220
  }
9999
10221
  }
10000
- maxValue = max(...values);
10001
- if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue);
10002
- if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
10003
- if (fromRandom) values = shuffle(values);
10222
+ maxValue = values[0];
10223
+ for (let k = 1; k < total; k++) if (values[k] > maxValue) maxValue = values[k];
10224
+ if (staggerEase || reversed) {
10225
+ for (let k = 0; k < total; k++) {
10226
+ let v = values[k];
10227
+ if (staggerEase) v = staggerEase(v / maxValue) * maxValue;
10228
+ if (reversed) v = axis ? -v : abs(maxValue - v);
10229
+ values[k] = v;
10230
+ }
10231
+ }
10232
+ if (hasJitter) {
10233
+ jitterSamples = new Array(total);
10234
+ for (let k = 0; k < total; k++) jitterSamples[k] = rng(-1, 1, 4);
10235
+ }
10236
+ if (fromRandom) values = shuffle(values, rng);
10004
10237
  }
10005
10238
  const spacing = isRange ? (val2 - val1) / maxValue : val1;
10006
10239
  if (isUnd(cachedOffset)) {
@@ -10008,6 +10241,11 @@
10008
10241
  }
10009
10242
  /** @type {String|Number} */
10010
10243
  let output = cachedOffset + ((spacing * round$1(values[staggerIndex], 2)) || 0);
10244
+ if (hasJitter) {
10245
+ const progress = maxValue ? values[staggerIndex] / maxValue : 0;
10246
+ const mag = jitterStart + (jitterEnd - jitterStart) * progress;
10247
+ output = /** @type {Number} */(output) + jitterSamples[staggerIndex] * mag;
10248
+ }
10011
10249
  if (params.modifier) output = params.modifier(/** @type {Number} */(output));
10012
10250
  if (unitMatch) output = `${output}${unitMatch[2]}`;
10013
10251
  return output;
@@ -10377,6 +10615,7 @@
10377
10615
  */
10378
10616
  const generateTemplate = (type, params = {}) => {
10379
10617
  let template = ``;
10618
+ if (!params) params = {};
10380
10619
  const classString = isStr(params.class) ? ` class="${params.class}"` : '';
10381
10620
  const cloneType = setValue(params.clone, false);
10382
10621
  const wrapType = setValue(params.wrap, false);
@@ -10758,6 +10997,16 @@
10758
10997
 
10759
10998
 
10760
10999
 
11000
+ /**
11001
+ * @typedef {Object} ScrambleTextTween
11002
+ * @property {Number} from
11003
+ * @property {Number} to
11004
+ * @property {Number} duration
11005
+ * @property {Number} delay
11006
+ * @property {String} ease
11007
+ * @property {(v: Number) => String} modifier
11008
+ */
11009
+
10761
11010
  /**
10762
11011
  * '-' is the range operator; place it at the start or end of the string to use it as a literal (e.g. '-abc' or 'abc-')
10763
11012
  * @param {String} str
@@ -10795,7 +11044,7 @@
10795
11044
  * progressively revealing the original text.
10796
11045
  *
10797
11046
  * @param {ScrambleTextParams} [params]
10798
- * @return {FunctionValue}
11047
+ * @return {FunctionValue<ScrambleTextTween>}
10799
11048
  */
10800
11049
  const scrambleText = (params = {}) => {
10801
11050
  if (!params) params = {};