animejs 4.4.0 → 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 +4 -5
  2. package/dist/bundles/anime.esm.js +491 -272
  3. package/dist/bundles/anime.esm.min.js +2 -2
  4. package/dist/bundles/anime.umd.js +491 -272
  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 +93 -73
  61. package/dist/modules/core/render.js +96 -76
  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 - ESM bundle
3
- * @version v4.4.0
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
@@ -725,6 +744,11 @@ const maxFps = 240;
725
744
  const emptyString = '';
726
745
  const cssVarPrefix = 'var(';
727
746
 
747
+ // Arrays
748
+
749
+ // 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.
750
+ const emptyArray = [];
751
+
728
752
  const shortTransforms = /*#__PURE__*/ (() => {
729
753
  const map = new Map();
730
754
  map.set('x', 'translateX');
@@ -758,6 +782,13 @@ const transformsFragmentStrings = /*#__PURE__*/ validTransforms.reduce((a, v) =>
758
782
  /** @return {void} */
759
783
  const noop = () => {};
760
784
 
785
+ /**
786
+ * @template T
787
+ * @param {T} v
788
+ * @return {T}
789
+ */
790
+ const noopModifier = v => v;
791
+
761
792
  // Regex
762
793
 
763
794
  const validRgbHslRgx = /\)\s*[-.\d]/;
@@ -779,10 +810,13 @@ const cssVariableMatchRgx = /var\(\s*(--[\w-]+)(?:\s*,\s*([^)]+))?\s*\)/;
779
810
  /**
780
811
  * @typedef {Object} EditorGlobals
781
812
  * @property {boolean} showPanel
782
- * @property {boolean} synced
783
813
  * @property {Function} addAnimation
814
+ * @property {Function} addSet
784
815
  * @property {Function} addTimeline
785
816
  * @property {Function} addTimelineChild
817
+ * @property {Function} addTimelineLabel
818
+ * @property {Function} addTimelineCall
819
+ * @property {Function} addTimelineSync
786
820
  * @property {Function} resolveStagger
787
821
  * @property {Object|null} _head
788
822
  * @property {Object|null} _tail
@@ -805,7 +839,7 @@ const defaults = {
805
839
  loopDelay: 0,
806
840
  ease: 'out(2)',
807
841
  composition: compositionTypes.replace,
808
- modifier: v => v,
842
+ modifier: noopModifier,
809
843
  onBegin: noop,
810
844
  onBeforeUpdate: noop,
811
845
  onUpdate: noop,
@@ -835,7 +869,7 @@ const globals = {
835
869
  editor: null,
836
870
  };
837
871
 
838
- const globalVersions = { version: '4.4.0', engine: null };
872
+ const globalVersions = { version: '4.5.0', engine: null };
839
873
 
840
874
  if (isBrowser) {
841
875
  if (!win.AnimeJS) win.AnimeJS = [];
@@ -982,7 +1016,7 @@ const snap$1 = (v, increment) => isArr(increment) ? increment.reduce((closest, c
982
1016
  * @param {Number} factor - Interpolation factor in the range [0, 1]
983
1017
  * @return {Number} The interpolated value
984
1018
  */
985
- const lerp$1 = (start, end, factor) => start + (end - start) * factor;
1019
+ const lerp$1 = (start, end, factor) => factor === 1 ? end : factor === 0 ? start : start + (end - start) * factor;
986
1020
 
987
1021
  /**
988
1022
  * Replaces infinity with maximum safe value
@@ -1231,6 +1265,61 @@ const buildTransformString = (props) => {
1231
1265
  return str;
1232
1266
  };
1233
1267
 
1268
+ /**
1269
+ * 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.
1270
+ *
1271
+ * import { registerAdapter } from 'animejs/adapters';
1272
+ *
1273
+ * const myAdapter = registerAdapter();
1274
+ * const widget = myAdapter.registerTargetAdapter((t) => t instanceof MyWidget);
1275
+ * widget.registerProperty('value',
1276
+ * (t) => t.getValue(),
1277
+ * (target, value) => target.setValue(value),
1278
+ * );
1279
+ *
1280
+ * 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.
1281
+ *
1282
+ * 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.
1283
+ */
1284
+
1285
+
1286
+ const adapters = /** @type {Adapter[]} */([]);
1287
+
1288
+ /**
1289
+ * Internal resolution. Tries every Adapter's target adapters first (in registration order, first match wins), then every Adapter's property resolvers.
1290
+ *
1291
+ * @param {any} target
1292
+ * @param {string} name
1293
+ * @return {TargetAdapterEntry | null}
1294
+ */
1295
+ function resolveAdapterEntry(target, name) {
1296
+ if (!target) return null;
1297
+ const al = adapters.length;
1298
+ outer: for (let i = 0; i < al; i++) {
1299
+ const a = adapters[i];
1300
+ if (a.detect && !a.detect(target)) continue;
1301
+ const tas = a.targetAdapters;
1302
+ for (let j = 0, m = tas.length; j < m; j++) {
1303
+ const ta = tas[j];
1304
+ if (ta.detect(target)) {
1305
+ const entry = ta.props[name];
1306
+ if (entry && (!entry.gate || entry.gate(target))) return entry;
1307
+ break outer;
1308
+ }
1309
+ }
1310
+ }
1311
+ for (let i = 0; i < al; i++) {
1312
+ const a = adapters[i];
1313
+ if (a.detect && !a.detect(target)) continue;
1314
+ const rs = a.propertyResolvers;
1315
+ for (let j = 0, m = rs.length; j < m; j++) {
1316
+ const entry = rs[j](target, name);
1317
+ if (entry) return entry;
1318
+ }
1319
+ }
1320
+ return null;
1321
+ }
1322
+
1234
1323
 
1235
1324
 
1236
1325
  /**
@@ -1328,6 +1417,21 @@ const setValue = (targetValue, defaultValue) => {
1328
1417
  return isUnd(targetValue) ? defaultValue : targetValue;
1329
1418
  };
1330
1419
 
1420
+ /**
1421
+ * 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.
1422
+ *
1423
+ * @param {String} value
1424
+ * @param {Target} target
1425
+ * @return {String|Number}
1426
+ */
1427
+ const resolveCssVar = (value, target) => {
1428
+ const match = value.match(cssVariableMatchRgx);
1429
+ const el = target[isDomSymbol] ? target : document.documentElement;
1430
+ let computed = getComputedStyle(/** @type {HTMLElement} */(el))?.getPropertyValue(match[1]);
1431
+ if ((!computed || computed.trim() === emptyString) && match[2]) computed = match[2].trim();
1432
+ return computed || 0;
1433
+ };
1434
+
1331
1435
  /**
1332
1436
  * @param {TweenPropValue} value
1333
1437
  * @param {Target} target
@@ -1338,30 +1442,26 @@ const setValue = (targetValue, defaultValue) => {
1338
1442
  * @return {any}
1339
1443
  */
1340
1444
  const getFunctionValue = (value, target, index, targets, store, prevTween) => {
1341
- let func;
1342
1445
  if (isFnc(value)) {
1343
- func = () => {
1446
+ if (!store) {
1447
+ const computed = /** @type {Function} */(value)(target, index, targets, prevTween);
1448
+ // Fallback to 0 if the function returns undefined, NaN, null, false or 0
1449
+ return !isNaN(+computed) ? +computed : computed || 0;
1450
+ }
1451
+ const func = () => {
1344
1452
  const computed = /** @type {Function} */(value)(target, index, targets, prevTween);
1345
- // Fallback to 0 if the function returns undefined / NaN / null / false / 0
1346
1453
  return !isNaN(+computed) ? +computed : computed || 0;
1347
1454
  };
1348
- } else if (isStr(value) && stringStartsWith(value, cssVarPrefix)) {
1349
- func = () => {
1350
- const match = value.match(cssVariableMatchRgx);
1351
- const cssVarName = match[1];
1352
- const fallbackValue = match[2];
1353
- let computed = getComputedStyle(/** @type {HTMLElement} */(target))?.getPropertyValue(cssVarName);
1354
- // Use fallback if CSS variable is not set or empty
1355
- if ((!computed || computed.trim() === emptyString) && fallbackValue) {
1356
- computed = fallbackValue.trim();
1357
- }
1358
- return computed || 0;
1359
- };
1360
- } else {
1361
- return value;
1455
+ store.func = func;
1456
+ return func();
1362
1457
  }
1363
- if (store) store.func = func;
1364
- return func();
1458
+ if (isStr(value) && stringStartsWith(value, cssVarPrefix)) {
1459
+ if (!store) return resolveCssVar(/** @type {String} */(value), target);
1460
+ const func = () => resolveCssVar(/** @type {String} */(value), target);
1461
+ store.func = func;
1462
+ return func();
1463
+ }
1464
+ return value;
1365
1465
  };
1366
1466
 
1367
1467
  /**
@@ -1408,6 +1508,12 @@ const getCSSValue = (target, propName, animationInlineStyles) => {
1408
1508
  */
1409
1509
  const getOriginalAnimatableValue = (target, propName, tweenType, animationInlineStyles) => {
1410
1510
  const type = !isUnd(tweenType) ? tweenType : getTweenType(target, propName);
1511
+ const adapterProp = resolveAdapterEntry(target, propName);
1512
+ if (adapterProp) {
1513
+ const value = adapterProp.get(target);
1514
+ if (value && animationInlineStyles) animationInlineStyles[propName] = value;
1515
+ return value == null ? 0 : value;
1516
+ }
1411
1517
  if (type === tweenTypes.OBJECT) {
1412
1518
  const value = target[propName];
1413
1519
  if (value && animationInlineStyles) animationInlineStyles[propName] = value;
@@ -1449,7 +1555,7 @@ const createDecomposedValueTargetObject = () => {
1449
1555
  };
1450
1556
 
1451
1557
  /**
1452
- * @param {String|Number} rawValue
1558
+ * @param {String|Number|Object} rawValue
1453
1559
  * @param {TweenDecomposedValue} targetObject
1454
1560
  * @return {TweenDecomposedValue}
1455
1561
  */
@@ -1467,39 +1573,38 @@ const decomposeRawValue = (rawValue, targetObject) => {
1467
1573
  // It's a number
1468
1574
  targetObject.n = num;
1469
1575
  return targetObject;
1576
+ }
1577
+ // let str = /** @type {String} */(rawValue).trim();
1578
+ let str = /** @type {String} */(rawValue);
1579
+ // Parsing operators (+=, -=, *=) manually is much faster than using regex here
1580
+ if (str[1] === '=') {
1581
+ targetObject.o = str[0];
1582
+ str = str.slice(2);
1583
+ }
1584
+ // Skip exec regex if the value type is complex or color to avoid long regex backtracking
1585
+ const unitMatch = str.includes(' ') ? false : unitsExecRgx.exec(str);
1586
+ if (unitMatch) {
1587
+ // Has a number and a unit
1588
+ targetObject.t = valueTypes.UNIT;
1589
+ targetObject.n = +unitMatch[1];
1590
+ targetObject.u = unitMatch[2];
1591
+ return targetObject;
1592
+ } else if (targetObject.o) {
1593
+ // Has an operator (+=, -=, *=)
1594
+ targetObject.n = +str;
1595
+ return targetObject;
1596
+ } else if (isCol(str)) {
1597
+ // Color string
1598
+ targetObject.t = valueTypes.COLOR;
1599
+ targetObject.d = convertColorStringValuesToRgbaArray(str);
1600
+ return targetObject;
1470
1601
  } else {
1471
- // let str = /** @type {String} */(rawValue).trim();
1472
- let str = /** @type {String} */(rawValue);
1473
- // Parsing operators (+=, -=, *=) manually is much faster than using regex here
1474
- if (str[1] === '=') {
1475
- targetObject.o = str[0];
1476
- str = str.slice(2);
1477
- }
1478
- // Skip exec regex if the value type is complex or color to avoid long regex backtracking
1479
- const unitMatch = str.includes(' ') ? false : unitsExecRgx.exec(str);
1480
- if (unitMatch) {
1481
- // Has a number and a unit
1482
- targetObject.t = valueTypes.UNIT;
1483
- targetObject.n = +unitMatch[1];
1484
- targetObject.u = unitMatch[2];
1485
- return targetObject;
1486
- } else if (targetObject.o) {
1487
- // Has an operator (+=, -=, *=)
1488
- targetObject.n = +str;
1489
- return targetObject;
1490
- } else if (isCol(str)) {
1491
- // Is a color
1492
- targetObject.t = valueTypes.COLOR;
1493
- targetObject.d = convertColorStringValuesToRgbaArray(str);
1494
- return targetObject;
1495
- } else {
1496
- // Is a more complex string (generally svg coords, calc() or filters CSS values)
1497
- const matchedNumbers = str.match(digitWithExponentRgx);
1498
- targetObject.t = valueTypes.COMPLEX;
1499
- targetObject.d = matchedNumbers ? matchedNumbers.map(Number) : [];
1500
- targetObject.s = str.split(digitWithExponentRgx) || [];
1501
- return targetObject;
1502
- }
1602
+ // Is a more complex string (generally svg coords, calc() or filters CSS values)
1603
+ const matchedNumbers = str.match(digitWithExponentRgx);
1604
+ targetObject.t = valueTypes.COMPLEX;
1605
+ targetObject.d = matchedNumbers ? matchedNumbers.map(Number) : [];
1606
+ targetObject.s = str.split(digitWithExponentRgx) || [];
1607
+ return targetObject;
1503
1608
  }
1504
1609
  };
1505
1610
 
@@ -1520,30 +1625,6 @@ const decomposeTweenValue = (tween, targetObject) => {
1520
1625
 
1521
1626
  const decomposedOriginalValue = createDecomposedValueTargetObject();
1522
1627
 
1523
- /**
1524
- * @param {Tween} tween
1525
- * @param {Number} progress
1526
- * @param {Number} precision
1527
- * @return {String}
1528
- */
1529
- const composeColorValue = (tween, progress, precision) => {
1530
- const mod = tween._modifier;
1531
- const fn = tween._fromNumbers;
1532
- const tn = tween._toNumbers;
1533
- const r = round$1(clamp$1(/** @type {Number} */(mod(lerp$1(fn[0], tn[0], progress))), 0, 255), 0);
1534
- const g = round$1(clamp$1(/** @type {Number} */(mod(lerp$1(fn[1], tn[1], progress))), 0, 255), 0);
1535
- const b = round$1(clamp$1(/** @type {Number} */(mod(lerp$1(fn[2], tn[2], progress))), 0, 255), 0);
1536
- const a = clamp$1(/** @type {Number} */(mod(round$1(lerp$1(fn[3], tn[3], progress), precision))), 0, 1);
1537
- if (tween._composition !== compositionTypes.none) {
1538
- const ns = tween._numbers;
1539
- ns[0] = r;
1540
- ns[1] = g;
1541
- ns[2] = b;
1542
- ns[3] = a;
1543
- }
1544
- return `rgba(${r},${g},${b},${a})`;
1545
- };
1546
-
1547
1628
  /**
1548
1629
  * @param {Tween} tween
1549
1630
  * @param {Number} progress
@@ -1555,15 +1636,14 @@ const composeComplexValue = (tween, progress, precision) => {
1555
1636
  const fn = tween._fromNumbers;
1556
1637
  const tn = tween._toNumbers;
1557
1638
  const ts = tween._strings;
1558
- const hasComposition = tween._composition !== compositionTypes.none;
1559
1639
  let v = ts[0];
1560
1640
  for (let j = 0, l = tn.length; j < l; j++) {
1561
1641
  const n = /** @type {Number} */(mod(round$1(lerp$1(fn[j], tn[j], progress), precision)));
1562
1642
  const s = ts[j + 1];
1563
1643
  v += `${s ? n + s : n}`;
1564
- if (hasComposition) {
1565
- tween._numbers[j] = n;
1566
- }
1644
+ // 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.
1645
+ // Potential optimization, skip the write when nothing reads it: if (hasComposition || tween._setter) tween._numbers[j] = n;
1646
+ tween._numbers[j] = n;
1567
1647
  }
1568
1648
  return v;
1569
1649
  };
@@ -1615,12 +1695,14 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1615
1695
  // Execute the "expensive" iterations calculations only when necessary
1616
1696
  if (iterationCount > 1) {
1617
1697
  // bitwise NOT operator seems to be generally faster than Math.floor() across browsers
1618
- const currentIteration = ~~(tickableCurrentTime / (iterationDuration + (isCurrentTimeEqualOrAboveDuration ? 0 : _loopDelay)));
1698
+ const period = iterationDuration + (isCurrentTimeEqualOrAboveDuration ? 0 : _loopDelay);
1699
+ const currentIteration = ~~(tickableCurrentTime / period);
1619
1700
  tickable._currentIteration = clamp$1(currentIteration, 0, iterationCount);
1620
1701
  // Prevent the iteration count to go above the max iterations when reaching the end of the animation
1621
1702
  if (isCurrentTimeEqualOrAboveDuration) tickable._currentIteration--;
1622
1703
  isOdd = tickable._currentIteration % 2;
1623
- iterationElapsedTime = tickableCurrentTime % (iterationDuration + _loopDelay) || 0;
1704
+ // 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.
1705
+ iterationElapsedTime = tickableCurrentTime - currentIteration * period || 0;
1624
1706
  }
1625
1707
 
1626
1708
  // Checks if exactly one of _reversed and (_alternate && isOdd) is true
@@ -1652,12 +1734,14 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1652
1734
  if (
1653
1735
  forcedTick ||
1654
1736
  tickMode === tickModes.AUTO && (
1655
- time >= tickableDelay && time <= tickableEndTime || // Normal render
1737
+ // Timeline children render from their offset instead of their delay so the gap left by a truncated sibling is covered on seek.
1738
+ time >= (parent && tickableDelay > 0 ? 0 : tickableDelay) && time <= tickableEndTime || // Normal render
1656
1739
  time <= tickableDelay && tickablePrevTime > tickableDelay || // Playhead is before the animation start time so make sure the animation is at its initial state
1657
1740
  time >= tickableEndTime && tickablePrevTime !== duration // Playhead is after the animation end time so make sure the animation is at its end state
1658
1741
  ) ||
1659
1742
  iterationTime >= tickableEndTime && tickablePrevTime !== duration ||
1660
- iterationTime <= tickableDelay && tickablePrevTime > 0 ||
1743
+ // 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.
1744
+ iterationTime <= tickableDelay && tickablePrevTime > 0 && !isCurrentTimeEqualOrAboveDuration ||
1661
1745
  time <= tickablePrevTime && tickablePrevTime === duration && completed || // Force a render if a seek occurs on an completed animation
1662
1746
  isCurrentTimeEqualOrAboveDuration && !completed && isSetter // This prevents 0 duration tickables to be skipped
1663
1747
  ) {
@@ -1673,7 +1757,8 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1673
1757
 
1674
1758
  // Time has jumped more than globals.tickThreshold so consider this tick manual
1675
1759
  const forcedRender = forcedTick || (isRunningBackwards ? deltaTime * -1 : deltaTime) >= globals.tickThreshold;
1676
- const absoluteTime = tickable._offset + (parent ? parent._offset : 0) + tickableDelay + iterationTime;
1760
+ // Round to match the precision of tween._absoluteStartTime so equal-time boundary checks compare cleanly without floating point drift from the unrounded _offset.
1761
+ const absoluteTime = round$1(tickable._offset + (parent ? parent._offset : 0) + tickableDelay + iterationTime, 12);
1677
1762
 
1678
1763
  // Only Animation can have tweens, Timer returns undefined
1679
1764
  let tween = /** @type {Tween} */(/** @type {JSAnimation} */(tickable)._head);
@@ -1692,15 +1777,38 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1692
1777
  const tweenNextRep = tween._nextRep;
1693
1778
  const tweenPrevRep = tween._prevRep;
1694
1779
  const tweenHasComposition = tweenComposition !== compositionTypes.none;
1780
+ // The previous sibling stops writing at its truncated end, so this tween takes over the hold from that point.
1781
+ const tweenPrevRepEndTime = tweenPrevRep ? tweenPrevRep._absoluteStartTime + tweenPrevRep._changeDuration : 0;
1782
+ const tweenPrevRepIsCrossParent = tweenPrevRep && tweenPrevRep.parent !== tween.parent;
1783
+ // Same parent keyframes take over at their own start, end plus delay equals the next start by construction.
1784
+ // Cross parent siblings take over at their update start.
1785
+ // Negative delay siblings take over at their own start instead.
1786
+ const tweenNextRepTakeover = !tweenNextRep || tweenNextRep._isOverridden ? tweenAbsEndTime :
1787
+ tweenNextRep.parent === tween.parent ? tweenAbsEndTime + tweenNextRep._delay :
1788
+ tweenNextRep._absoluteStartTime < tweenNextRep._absoluteUpdateStartTime ? tweenNextRep._absoluteStartTime : tweenNextRep._absoluteUpdateStartTime;
1695
1789
 
1696
1790
  if ((forcedRender || (
1697
- (tweenCurrentTime !== tweenChangeDuration || absoluteTime <= tweenAbsEndTime + (tweenNextRep ? tweenNextRep._delay : 0)) &&
1698
- (tweenCurrentTime !== 0 || absoluteTime >= tween._absoluteStartTime)
1699
- )) && (!tweenHasComposition || (
1791
+ // 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.
1792
+ (tweenCurrentTime !== tweenChangeDuration || absoluteTime <= tweenNextRepTakeover ||
1793
+ (tweenPrevRep && !tweenPrevRepIsCrossParent && (!tweenNextRep || tweenNextRep.parent !== tween.parent))) &&
1794
+ // A cross parent tween re-renders its from value from the previous sibling truncated end so the handoff gap holds.
1795
+ // 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.
1796
+ (tweenCurrentTime !== 0 || absoluteTime >= tween._absoluteStartTime ||
1797
+ (tweenPrevRepIsCrossParent && !tween._hasFromValue && !tweenPrevRep._isOverridden && absoluteTime >= tweenPrevRepEndTime) ||
1798
+ (tweenNextRep && !tweenNextRep._isOverridden && tweenNextRep.parent === tween.parent && tweenNextRep._currentTime !== 0 && iterationTime < tweenNextRep._startTime))
1799
+ )) &&
1800
+ // 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.
1801
+ (!tweenPrevRep || tweenPrevRepIsCrossParent || iterationTime >= tween._startTime) &&
1802
+ (!tweenHasComposition || (
1700
1803
  !tween._isOverridden &&
1701
1804
  (!tween._isOverlapped || absoluteTime <= tweenAbsEndTime) &&
1702
- (!tweenNextRep || (tweenNextRep._isOverridden || absoluteTime <= tweenNextRep._absoluteStartTime)) &&
1703
- (!tweenPrevRep || (tweenPrevRep._isOverridden || (absoluteTime >= (tweenPrevRep._absoluteStartTime + tweenPrevRep._changeDuration) + tween._delay)))
1805
+ // The next sibling owns the value past its takeover point, so yielding there keeps writes single owner in both directions.
1806
+ (!tweenNextRep || tweenNextRep._isOverridden || absoluteTime <= tweenNextRepTakeover) &&
1807
+ // The previous sibling owns the value up to its truncated end.
1808
+ // Cross parent tweens take over the hold from that point, explicit from values wait for their own start.
1809
+ (!tweenPrevRep || (tweenPrevRep._isOverridden || (!tweenPrevRepIsCrossParent ?
1810
+ absoluteTime >= tweenPrevRepEndTime + tween._delay :
1811
+ absoluteTime >= tween._absoluteStartTime || (!tween._hasFromValue && absoluteTime >= tweenPrevRepEndTime))))
1704
1812
  ))
1705
1813
  ) {
1706
1814
 
@@ -1711,7 +1819,7 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1711
1819
  const tweenType = tween._tweenType;
1712
1820
  const tweenIsObject = tweenType === tweenTypes.OBJECT;
1713
1821
  const tweenIsNumber = tweenValueType === valueTypes.NUMBER;
1714
- // Only round the in-between frames values if the final value is a string
1822
+ // 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.
1715
1823
  const tweenPrecision = (tweenIsNumber && tweenIsObject) || tweenProgress === 0 || tweenProgress === 1 ? -1 : globals.precision;
1716
1824
 
1717
1825
  // Recompose tween value
@@ -1727,7 +1835,22 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1727
1835
  number = /** @type {Number} */(tweenModifier(round$1(lerp$1(tween._fromNumber, tween._toNumber, tweenProgress), tweenPrecision)));
1728
1836
  value = `${number}${tween._unit}`;
1729
1837
  } else if (tweenValueType === valueTypes.COLOR) {
1730
- value = composeColorValue(tween, tweenProgress, tweenPrecision);
1838
+ const ns = tween._numbers;
1839
+ const fn = tween._fromNumbers;
1840
+ const tn = tween._toNumbers;
1841
+ const omt = 1 - tweenProgress;
1842
+ const fr = fn[0], fg = fn[1], fb = fn[2];
1843
+ const tr = tn[0], tg = tn[1], tb = tn[2];
1844
+ // RGB channels lerp in pseudo-linear space (square inputs, sqrt result) to approximate gamma-correct blending.
1845
+ // See https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-24-importance-being-linear.
1846
+ ns[0] = /** @type {Number} */(tweenModifier(Math.sqrt(fr * fr * omt + tr * tr * tweenProgress)));
1847
+ ns[1] = /** @type {Number} */(tweenModifier(Math.sqrt(fg * fg * omt + tg * tg * tweenProgress)));
1848
+ ns[2] = /** @type {Number} */(tweenModifier(Math.sqrt(fb * fb * omt + tb * tb * tweenProgress)));
1849
+ ns[3] = /** @type {Number} */(tweenModifier(lerp$1(fn[3], tn[3], tweenProgress)));
1850
+ // The rgba string is built only for the dispatch path or the internalRender composition tick (setters handles the color comp)
1851
+ if (!tween._setter || internalRender) {
1852
+ value = `rgba(${round$1(ns[0], 0)},${round$1(ns[1], 0)},${round$1(ns[2], 0)},${ns[3]})`;
1853
+ }
1731
1854
  } else if (tweenValueType === valueTypes.COMPLEX) {
1732
1855
  value = composeComplexValue(tween, tweenProgress, tweenPrecision);
1733
1856
  }
@@ -1742,7 +1865,9 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1742
1865
  const tweenProperty = tween.property;
1743
1866
  tweenTarget = tween.target;
1744
1867
 
1745
- if (tweenIsObject) {
1868
+ if (tween._setter) {
1869
+ tween._setter(tweenTarget, number, tween);
1870
+ } else if (tweenIsObject) {
1746
1871
  tweenTarget[tweenProperty] = value;
1747
1872
  } else if (tweenType === tweenTypes.ATTRIBUTE) {
1748
1873
  /** @type {DOMTarget} */(tweenTarget).setAttribute(tweenProperty, /** @type {String} */(value));
@@ -1770,6 +1895,9 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1770
1895
  tween._value = value;
1771
1896
  }
1772
1897
 
1898
+ } else if (tweenCurrentTime && tweenPrevRep && !tweenPrevRepIsCrossParent && iterationTime < tween._startTime) {
1899
+ // Mark the keyframe as reverted when the playhead moves before its start, the previous keyframe owns the from revert and writes it once.
1900
+ tween._currentTime = 0;
1773
1901
  }
1774
1902
 
1775
1903
  if (tweenTransformsNeedUpdate && tween._renderTransforms) {
@@ -1830,50 +1958,6 @@ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1830
1958
  return hasRendered;
1831
1959
  };
1832
1960
 
1833
- // Shared context for extracted forEachChildren callbacks in tick()
1834
- // Avoids closure allocation every frame
1835
-
1836
- let renderCtxChildrenTime = 0;
1837
- let renderCtxTlFps = 0;
1838
- let renderCtxTickTime = 0;
1839
- let renderCtxTickMode = 0;
1840
- let renderCtxMuteCallbacks = 0;
1841
- let renderCtxInternalRender = 0;
1842
- let renderCtxChildrenHasRendered = 0;
1843
- let renderCtxChildrenHaveCompleted = true;
1844
- let loopCtxIsRunningBackwards = false;
1845
- let loopCtxIterationDuration = 0;
1846
- let loopCtxMuteCallbacks = 0;
1847
-
1848
- /** @param {JSAnimation} child */
1849
- const tickLoopChild = (child) => {
1850
- if (!loopCtxIsRunningBackwards) {
1851
- // Force an internal render to trigger the callbacks if the child has not completed on loop
1852
- if (!child.completed && !child.backwards && child._currentTime < child.iterationDuration) {
1853
- render(child, loopCtxIterationDuration, loopCtxMuteCallbacks, 1, tickModes.FORCE);
1854
- }
1855
- // Reset their began and completed flags to allow retrigering callbacks on the next iteration
1856
- child.began = false;
1857
- child.completed = false;
1858
- } else {
1859
- const childDuration = child.duration;
1860
- const childStartTime = child._offset + child._delay;
1861
- const childEndTime = childStartTime + childDuration;
1862
- // Triggers the onComplete callback on reverse for children on the edges of the timeline
1863
- if (!loopCtxMuteCallbacks && childDuration <= minValue && (!childStartTime || childEndTime === loopCtxIterationDuration)) {
1864
- child.onComplete(child);
1865
- }
1866
- }
1867
- };
1868
-
1869
- /** @param {JSAnimation} child */
1870
- const tickRenderChild = (child) => {
1871
- const childTime = round$1((renderCtxChildrenTime - child._offset) * child._speed, 12); // Rounding is needed when using seconds
1872
- const childTickMode = child._fps < renderCtxTlFps ? child.requestTick(renderCtxTickTime) : renderCtxTickMode;
1873
- renderCtxChildrenHasRendered += render(child, childTime, renderCtxMuteCallbacks, renderCtxInternalRender, childTickMode);
1874
- if (!child.completed && renderCtxChildrenHaveCompleted) renderCtxChildrenHaveCompleted = false;
1875
- };
1876
-
1877
1961
  /**
1878
1962
  * @param {Tickable} tickable
1879
1963
  * @param {Number} time
@@ -1890,28 +1974,44 @@ const tick = (tickable, time, muteCallbacks, internalRender, tickMode) => {
1890
1974
  const tlIsRunningBackwards = tl.backwards;
1891
1975
  const tlChildrenTime = internalRender ? time : tl._iterationTime;
1892
1976
  const tlCildrenTickTime = now();
1977
+
1893
1978
  let tlChildrenHasRendered = 0;
1894
1979
  let tlChildrenHaveCompleted = true;
1980
+
1895
1981
  // If the timeline has looped forward, we need to manually triggers children skipped callbacks
1896
1982
  if (!internalRender && tl._currentIteration !== _currentIteration) {
1897
1983
  const tlIterationDuration = tl.iterationDuration;
1898
- loopCtxIsRunningBackwards = tlIsRunningBackwards;
1899
- loopCtxIterationDuration = tlIterationDuration;
1900
- loopCtxMuteCallbacks = muteCallbacks;
1901
- forEachChildren(tl, tickLoopChild);
1984
+ forEachChildren(tl, (/** @type {JSAnimation} */child) => {
1985
+ if (!tlIsRunningBackwards) {
1986
+ // Force an internal render to trigger the callbacks if the child has not completed on loop
1987
+ if (!child.completed && !child.backwards && child._currentTime < child.iterationDuration) {
1988
+ render(child, tlIterationDuration, muteCallbacks, 1, tickModes.FORCE);
1989
+ }
1990
+ // Reset their began and completed flags to allow retrigering callbacks on the next iteration
1991
+ child.began = false;
1992
+ child.completed = false;
1993
+ } else {
1994
+ const childDuration = child.duration;
1995
+ const childStartTime = child._offset + child._delay;
1996
+ const childEndTime = childStartTime + childDuration;
1997
+ // Triggers the onComplete callback on reverse for children on the edges of the timeline
1998
+ if (!muteCallbacks && childDuration <= minValue && (!childStartTime || childEndTime === tlIterationDuration)) {
1999
+ child.onComplete(child);
2000
+ }
2001
+ }
2002
+ });
1902
2003
  if (!muteCallbacks) tl.onLoop(/** @type {CallbackArgument} */(tl));
1903
2004
  }
1904
- renderCtxChildrenTime = tlChildrenTime;
1905
- renderCtxTlFps = tl._fps;
1906
- renderCtxTickTime = tlCildrenTickTime;
1907
- renderCtxTickMode = tickMode;
1908
- renderCtxMuteCallbacks = muteCallbacks;
1909
- renderCtxInternalRender = internalRender;
1910
- renderCtxChildrenHasRendered = 0;
1911
- renderCtxChildrenHaveCompleted = true;
1912
- forEachChildren(tl, tickRenderChild, tlIsRunningBackwards);
1913
- tlChildrenHasRendered = renderCtxChildrenHasRendered;
1914
- tlChildrenHaveCompleted = renderCtxChildrenHaveCompleted;
2005
+
2006
+ forEachChildren(tl, (/** @type {JSAnimation} */child) => {
2007
+ const childTime = round$1((tlChildrenTime - child._offset) * child._speed, 12); // Rounding is needed when using seconds
2008
+ // 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.
2009
+ if (tlIsRunningBackwards && childTime > child._delay + child.duration) return;
2010
+ const childTickMode = child._fps < tl._fps ? child.requestTick(tlCildrenTickTime) : tickMode;
2011
+ tlChildrenHasRendered += render(child, childTime, muteCallbacks, internalRender, childTickMode);
2012
+ if (!child.completed && tlChildrenHaveCompleted) tlChildrenHaveCompleted = false;
2013
+ }, tlIsRunningBackwards);
2014
+
1915
2015
  // Renders on timeline are triggered by its children so it needs to be set after rendering the children
1916
2016
  if (!muteCallbacks && tlChildrenHasRendered) tl.onRender(/** @type {CallbackArgument} */(tl));
1917
2017
  // Triggers the timeline onComplete() once all chindren all completed and the current time has reached the end
@@ -1983,7 +2083,20 @@ const revertValues = (renderable, inlineStylesOnly = false) => {
1983
2083
  const tweenType = tween._tweenType;
1984
2084
  const originalInlinedValue = tween._inlineValue;
1985
2085
  const tweenHadNoInlineValue = isNil(originalInlinedValue) || originalInlinedValue === emptyString;
1986
- if (tweenType === tweenTypes.OBJECT) {
2086
+ if (tween._setter) {
2087
+ if (!inlineStylesOnly && !tweenHadNoInlineValue) {
2088
+ // 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.
2089
+ decomposeRawValue(originalInlinedValue, decomposedOriginalValue);
2090
+ if (decomposedOriginalValue.d) {
2091
+ const src = decomposedOriginalValue.d;
2092
+ const dst = tween._numbers;
2093
+ for (let i = 0, l = src.length; i < l; i++) dst[i] = src[i];
2094
+ } else {
2095
+ tween._number = decomposedOriginalValue.n;
2096
+ }
2097
+ tween._setter(tween.target, tween._number, tween);
2098
+ }
2099
+ } else if (tweenType === tweenTypes.OBJECT) {
1987
2100
  if (!inlineStylesOnly && !tweenHadNoInlineValue) {
1988
2101
  tweenTarget[tweenProperty] = originalInlinedValue;
1989
2102
  }
@@ -2060,8 +2173,6 @@ class Clock {
2060
2173
  /** @type {Number} */
2061
2174
  this._lastTime = initTime;
2062
2175
  /** @type {Number} */
2063
- this._scheduledTime = 0;
2064
- /** @type {Number} */
2065
2176
  this._frameDuration = K / maxFps;
2066
2177
  /** @type {Number} */
2067
2178
  this._fps = maxFps;
@@ -2080,14 +2191,12 @@ class Clock {
2080
2191
  }
2081
2192
 
2082
2193
  set fps(frameRate) {
2083
- const previousFrameDuration = this._frameDuration;
2084
2194
  const fr = +frameRate;
2085
2195
  const fps = fr < minValue ? minValue : fr;
2086
2196
  const frameDuration = K / fps;
2087
2197
  if (fps > defaults.frameRate) defaults.frameRate = fps;
2088
2198
  this._fps = fps;
2089
2199
  this._frameDuration = frameDuration;
2090
- this._scheduledTime += frameDuration - previousFrameDuration;
2091
2200
  }
2092
2201
 
2093
2202
  get speed() {
@@ -2104,17 +2213,17 @@ class Clock {
2104
2213
  * @return {tickModes}
2105
2214
  */
2106
2215
  requestTick(time) {
2107
- const scheduledTime = this._scheduledTime;
2108
- this._lastTickTime = time;
2109
- // If the current time is lower than the scheduled time
2110
- // this means not enough time has passed to hit one frameDuration
2111
- // so skip that frame
2112
- if (time < scheduledTime) return tickModes.NONE;
2113
2216
  const frameDuration = this._frameDuration;
2114
- const frameDelta = time - scheduledTime;
2115
- // Ensures that _scheduledTime progresses in steps of at least 1 frameDuration.
2116
- // Skips ahead if the actual elapsed time is higher.
2117
- this._scheduledTime += frameDelta < frameDuration ? frameDuration : frameDelta;
2217
+ const elapsed = time - this._lastTickTime;
2218
+ const scaled = frameDuration * .25;
2219
+ const tolerance = scaled < 4 ? scaled : 4;
2220
+ // Tolerance prevents dropping frames that arrive a bit early due to RAF jitter
2221
+ // typically <= ~25% of frame duration and capped at 4ms so it doesn't dominate at high fps.
2222
+ // e.g. at 60fps (frameDuration=16.667ms) a frame arriving after 15ms:
2223
+ // - without tolerance: 15 < 16.667 -> skip
2224
+ // - with tolerance: 15 + 4 >= 16.667 -> tick
2225
+ if (elapsed + tolerance < frameDuration) return tickModes.NONE;
2226
+ this._lastTickTime = elapsed >= frameDuration ? time - (elapsed % frameDuration) : time;
2118
2227
  return tickModes.AUTO;
2119
2228
  }
2120
2229
 
@@ -2274,7 +2383,9 @@ class Engine extends Clock {
2274
2383
  }
2275
2384
 
2276
2385
  set speed(playbackRate) {
2277
- this._speed = playbackRate * globals.timeScale;
2386
+ const speed = playbackRate * globals.timeScale;
2387
+ if (this._speed === speed) return;
2388
+ this._speed = speed;
2278
2389
  forEachChildren(this, (/** @type {Tickable} */child) => child.speed = child._speed);
2279
2390
  }
2280
2391
 
@@ -2407,7 +2518,7 @@ const composeTween = (tween, siblings) => {
2407
2518
  if (prevSibling) {
2408
2519
 
2409
2520
  const prevParent = prevSibling.parent;
2410
- const prevAbsEndTime = prevSibling._absoluteStartTime + prevSibling._changeDuration;
2521
+ const prevAbsEndTime = prevSibling._absoluteEndTime;
2411
2522
 
2412
2523
  // Handle looped animations tween
2413
2524
 
@@ -2433,7 +2544,8 @@ const composeTween = (tween, siblings) => {
2433
2544
 
2434
2545
  }
2435
2546
 
2436
- const absoluteUpdateStartTime = tweenAbsStartTime - tween._delay;
2547
+ // 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.
2548
+ const absoluteUpdateStartTime = tween._absoluteUpdateStartTime;
2437
2549
 
2438
2550
  if (prevAbsEndTime > absoluteUpdateStartTime) {
2439
2551
 
@@ -2453,37 +2565,41 @@ const composeTween = (tween, siblings) => {
2453
2565
  }
2454
2566
  }
2455
2567
 
2456
- // Pause (and cancel) the parent if it only contains overlapped tweens
2568
+ // Skip the cancel cascade when both tweens share the same parent timeline, a timeline cannot replace itself.
2569
+ const tweenParentTL = tween.parent.parent;
2570
+ if (!tweenParentTL || tweenParentTL !== prevParent.parent) {
2457
2571
 
2458
- let pausePrevParentAnimation = true;
2572
+ let pausePrevParentAnimation = true;
2459
2573
 
2460
- forEachChildren(prevParent, (/** @type Tween */t) => {
2461
- if (!t._isOverlapped) pausePrevParentAnimation = false;
2462
- });
2574
+ forEachChildren(prevParent, (/** @type Tween */t) => {
2575
+ if (!t._isOverlapped) pausePrevParentAnimation = false;
2576
+ });
2463
2577
 
2464
- if (pausePrevParentAnimation) {
2465
- const prevParentTL = prevParent.parent;
2466
- if (prevParentTL) {
2467
- let pausePrevParentTL = true;
2468
- forEachChildren(prevParentTL, (/** @type JSAnimation */a) => {
2469
- if (a !== prevParent) {
2470
- forEachChildren(a, (/** @type Tween */t) => {
2471
- if (!t._isOverlapped) pausePrevParentTL = false;
2472
- });
2578
+ if (pausePrevParentAnimation) {
2579
+ const prevParentTL = prevParent.parent;
2580
+ if (prevParentTL) {
2581
+ let pausePrevParentTL = true;
2582
+ forEachChildren(prevParentTL, (/** @type JSAnimation */a) => {
2583
+ if (a !== prevParent) {
2584
+ forEachChildren(a, (/** @type Tween */t) => {
2585
+ if (!t._isOverlapped) pausePrevParentTL = false;
2586
+ });
2587
+ }
2588
+ });
2589
+ if (pausePrevParentTL) {
2590
+ prevParentTL.cancel();
2473
2591
  }
2474
- });
2475
- if (pausePrevParentTL) {
2476
- prevParentTL.cancel();
2592
+ } else {
2593
+ prevParent.cancel();
2594
+ // Previously, calling .cancel() on a timeline child would affect the render order of other children
2595
+ // Worked around this by marking it as .completed and using .pause() for safe removal in the engine loop
2596
+ // This is no longer needed since timeline tween composition is now handled separately
2597
+ // Keeping this here for reference
2598
+ // prevParent.completed = true;
2599
+ // prevParent.pause();
2477
2600
  }
2478
- } else {
2479
- prevParent.cancel();
2480
- // Previously, calling .cancel() on a timeline child would affect the render order of other children
2481
- // Worked around this by marking it as .completed and using .pause() for safe removal in the engine loop
2482
- // This is no longer needed since timeline tween composition is now handled separately
2483
- // Keeping this here for reference
2484
- // prevParent.completed = true;
2485
- // prevParent.pause();
2486
2601
  }
2602
+
2487
2603
  }
2488
2604
 
2489
2605
  }
@@ -2538,14 +2654,12 @@ const composeTween = (tween, siblings) => {
2538
2654
  tween._number = 0;
2539
2655
  lookupTween._fromNumber = toNumber;
2540
2656
 
2541
- if (tween._toNumbers) {
2657
+ if (tween._toNumbers.length) {
2542
2658
  const toNumbers = cloneArray(tween._toNumbers);
2543
- if (toNumbers) {
2544
- toNumbers.forEach((value, i) => {
2545
- tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value;
2546
- tween._toNumbers[i] = 0;
2547
- });
2548
- }
2659
+ toNumbers.forEach((value, i) => {
2660
+ tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value;
2661
+ tween._toNumbers[i] = 0;
2662
+ });
2549
2663
  lookupTween._fromNumbers = toNumbers;
2550
2664
  }
2551
2665
 
@@ -3246,18 +3360,16 @@ function parseTargets(targets) {
3246
3360
  function registerTargets(targets) {
3247
3361
  const parsedTargetsArray = parseTargets(targets);
3248
3362
  const parsedTargetsLength = parsedTargetsArray.length;
3249
- if (parsedTargetsLength) {
3250
- for (let i = 0; i < parsedTargetsLength; i++) {
3251
- const target = parsedTargetsArray[i];
3252
- if (!target[isRegisteredTargetSymbol]) {
3253
- target[isRegisteredTargetSymbol] = true;
3254
- const isSvgType = isSvg(target);
3255
- const isDom = /** @type {DOMTarget} */(target).nodeType || isSvgType;
3256
- if (isDom) {
3257
- target[isDomSymbol] = true;
3258
- target[isSvgSymbol] = isSvgType;
3259
- target[transformsSymbol] = {};
3260
- }
3363
+ for (let i = 0; i < parsedTargetsLength; i++) {
3364
+ const target = parsedTargetsArray[i];
3365
+ if (!target[isRegisteredTargetSymbol]) {
3366
+ target[isRegisteredTargetSymbol] = true;
3367
+ const isSvgType = isSvg(target);
3368
+ const isDom = /** @type {DOMTarget} */(target).nodeType || isSvgType;
3369
+ if (isDom) {
3370
+ target[isDomSymbol] = true;
3371
+ target[isSvgSymbol] = isSvgType;
3372
+ target[transformsSymbol] = {};
3261
3373
  }
3262
3374
  }
3263
3375
  }
@@ -3379,8 +3491,8 @@ const easeInFunctions = {
3379
3491
 
3380
3492
  /**
3381
3493
  * @typedef {Object} EasesFunctions
3382
- * @property {typeof none} linear
3383
- * @property {typeof none} none
3494
+ * @property {EasingFunction} linear
3495
+ * @property {EasingFunction} none
3384
3496
  * @property {PowerEasing} in
3385
3497
  * @property {PowerEasing} out
3386
3498
  * @property {PowerEasing} inOut
@@ -3612,6 +3724,11 @@ class JSAnimation extends Timer {
3612
3724
 
3613
3725
  super(/** @type {TimerParams & AnimationParams} */(parameters), parent, parentPosition);
3614
3726
 
3727
+ /** @type {Tween} */
3728
+ this._head;
3729
+ /** @type {Tween} */
3730
+ this._tail;
3731
+
3615
3732
  ++JSAnimationId;
3616
3733
 
3617
3734
  const parsedTargets = registerTargets(targets);
@@ -3668,6 +3785,7 @@ class JSAnimation extends Timer {
3668
3785
  if (isKey(p)) {
3669
3786
 
3670
3787
  const tweenType = getTweenType(target, p);
3788
+ const adapterProp = resolveAdapterEntry(target, p);
3671
3789
 
3672
3790
  const propName = sanitizePropertyName(p, target, tweenType);
3673
3791
 
@@ -3751,7 +3869,7 @@ class JSAnimation extends Timer {
3751
3869
  } else {
3752
3870
  tweenToValue = computedToValue;
3753
3871
  }
3754
- const tweenFromValue = getFunctionValue(key.from, target, ti, tl, null, prevSiblingTween);
3872
+ const tweenFromValue = getFunctionValue(key.from, target, ti, tl, fromFunctionStore, prevSiblingTween);
3755
3873
  const easeToParse = key.ease || tEasing;
3756
3874
 
3757
3875
  const easeFunctionResult = getFunctionValue(easeToParse, target, ti, tl, null, prevSiblingTween);
@@ -3769,9 +3887,13 @@ class JSAnimation extends Timer {
3769
3887
  const hasToValue = !isUnd(tweenToValue);
3770
3888
  const isFromToArray = isArr(tweenToValue);
3771
3889
  const isFromToValue = isFromToArray || (hasFromvalue && hasToValue);
3890
+ // 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.
3891
+ const tweenUpdateStartLocal = prevTween ? lastTweenChangeEndTime : 0;
3772
3892
  const tweenStartTime = prevTween ? lastTweenChangeEndTime + tweenDelay : tweenDelay;
3773
3893
  // Rounding is necessary here to minimize floating point errors when working in seconds
3774
3894
  const absoluteStartTime = round$1(absoluteOffsetTime + tweenStartTime, 12);
3895
+ // Match the rounding pattern of prevSibling._absoluteEndTime so the composition overlap check compares cleanly when keyframes touch at a boundary.
3896
+ const absoluteUpdateStartTime = round$1(absoluteOffsetTime + tweenUpdateStartLocal, 12);
3775
3897
 
3776
3898
  // Force a onRender callback if the animation contains at least one from value and autoplay is set to false
3777
3899
  if (!shouldTriggerRender && (hasFromvalue || isFromToArray)) shouldTriggerRender = 1;
@@ -3780,9 +3902,9 @@ class JSAnimation extends Timer {
3780
3902
 
3781
3903
  if (tweenComposition !== compositionTypes.none) {
3782
3904
  let nextSibling = siblings._head;
3783
- // Iterate trough all the next siblings until we find a sibling with an equal or inferior start time
3784
- while (nextSibling && !nextSibling._isOverridden && nextSibling._absoluteStartTime <= absoluteStartTime) {
3785
- prevSibling = nextSibling;
3905
+ // 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.
3906
+ while (nextSibling && nextSibling._absoluteStartTime <= absoluteStartTime) {
3907
+ if (!nextSibling._isOverridden) prevSibling = nextSibling;
3786
3908
  nextSibling = nextSibling._nextRep;
3787
3909
  // Overrides all the next siblings if the next sibling starts at the same time of after as the new tween start time
3788
3910
  if (nextSibling && nextSibling._absoluteStartTime >= absoluteStartTime) {
@@ -3836,8 +3958,8 @@ class JSAnimation extends Timer {
3836
3958
  if (prevTween) {
3837
3959
  decomposeTweenValue(prevTween, fromTargetObject);
3838
3960
  } else {
3839
- decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value :
3840
3961
  // 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
3962
+ decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value :
3841
3963
  getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore), fromTargetObject);
3842
3964
  }
3843
3965
  }
@@ -3876,8 +3998,7 @@ class JSAnimation extends Timer {
3876
3998
  const colorValue = fromTargetObject.t === valueTypes.COLOR ? fromTargetObject : toTargetObject;
3877
3999
  const notColorValue = fromTargetObject.t === valueTypes.COLOR ? toTargetObject : fromTargetObject;
3878
4000
  notColorValue.t = valueTypes.COLOR;
3879
- notColorValue.s = colorValue.s;
3880
- notColorValue.d = [0, 0, 0, 1];
4001
+ notColorValue.d = colorValue.d.map(() => 0);
3881
4002
  }
3882
4003
  }
3883
4004
 
@@ -3907,6 +4028,16 @@ class JSAnimation extends Timer {
3907
4028
  let inlineValue = inlineStylesStore[propName];
3908
4029
  if (!isNil(inlineValue)) inlineStylesStore[propName] = null;
3909
4030
 
4031
+ // Resolve the adapter setter once so render skips the lookup per frame.
4032
+ const tweenSetter = adapterProp ? adapterProp.set : null;
4033
+
4034
+ // Rounding is necessary here to minimize floating point errors when working in seconds
4035
+ lastTweenChangeEndTime = round$1(tweenStartTime + tweenUpdateDuration, 12);
4036
+
4037
+ const fromD = fromTargetObject.d;
4038
+ const toD = toTargetObject.d;
4039
+ const toS = toTargetObject.s;
4040
+
3910
4041
  /** @type {Tween} */
3911
4042
  const tween = {
3912
4043
  parent: this,
@@ -3917,12 +4048,12 @@ class JSAnimation extends Timer {
3917
4048
  _toFunc: toFunctionStore.func,
3918
4049
  _fromFunc: fromFunctionStore.func,
3919
4050
  _ease: parseEase(tweenEasing),
3920
- _fromNumbers: cloneArray(fromTargetObject.d),
3921
- _toNumbers: cloneArray(toTargetObject.d),
3922
- _strings: cloneArray(toTargetObject.s),
4051
+ _fromNumbers: fromD ? cloneArray(fromD) : emptyArray,
4052
+ _toNumbers: toD ? cloneArray(toD) : emptyArray,
4053
+ _strings: toS ? cloneArray(toS) : emptyArray,
3923
4054
  _fromNumber: fromTargetObject.n,
3924
4055
  _toNumber: toTargetObject.n,
3925
- _numbers: cloneArray(fromTargetObject.d), // For additive tween and animatables
4056
+ _numbers: fromD ? cloneArray(fromD) : emptyArray, // For additive tween and animatables
3926
4057
  _number: fromTargetObject.n, // For additive tween and animatables
3927
4058
  _unit: toTargetObject.u,
3928
4059
  _modifier: tweenModifier,
@@ -3932,8 +4063,12 @@ class JSAnimation extends Timer {
3932
4063
  _updateDuration: tweenUpdateDuration,
3933
4064
  _changeDuration: tweenUpdateDuration,
3934
4065
  _absoluteStartTime: absoluteStartTime,
4066
+ _absoluteUpdateStartTime: absoluteUpdateStartTime,
4067
+ _absoluteEndTime: round$1(absoluteOffsetTime + lastTweenChangeEndTime, 12),
4068
+ _hasFromValue: hasFromvalue || isFromToArray ? 1 : 0,
3935
4069
  // NOTE: Investigate bit packing to stores ENUM / BOOL
3936
4070
  _tweenType: tweenType,
4071
+ _setter: tweenSetter,
3937
4072
  _valueType: toTargetObject.t,
3938
4073
  _composition: tweenComposition,
3939
4074
  _isOverlapped: 0,
@@ -3956,10 +4091,11 @@ class JSAnimation extends Timer {
3956
4091
  const vt = tween._valueType;
3957
4092
  if (vt === valueTypes.COMPLEX) {
3958
4093
  tween._value = composeComplexValue(tween, 1, -1);
3959
- } else if (vt === valueTypes.COLOR) {
3960
- tween._value = composeColorValue(tween, 1, -1);
3961
4094
  } else if (vt === valueTypes.UNIT) {
3962
4095
  tween._value = `${tweenModifier(tween._toNumber)}${tween._unit}`;
4096
+ } else if (vt === valueTypes.COLOR) {
4097
+ const d = toTargetObject.d;
4098
+ tween._value = `rgba(${round$1(d[0], 0)},${round$1(d[1], 0)},${round$1(d[2], 0)},${d[3]})`;
3963
4099
  } else {
3964
4100
  tween._value = tweenModifier(tween._toNumber);
3965
4101
  }
@@ -3967,8 +4103,7 @@ class JSAnimation extends Timer {
3967
4103
  if (isNaN(firstTweenChangeStartTime)) {
3968
4104
  firstTweenChangeStartTime = tween._startTime;
3969
4105
  }
3970
- // Rounding is necessary here to minimize floating point errors when working in seconds
3971
- lastTweenChangeEndTime = round$1(tweenStartTime + tweenUpdateDuration, 12);
4106
+
3972
4107
  prevTween = tween;
3973
4108
  animationAnimationLength++;
3974
4109
 
@@ -4074,8 +4209,11 @@ class JSAnimation extends Timer {
4074
4209
  tween._updateDuration = normalizeTime(tween._updateDuration * timeScale);
4075
4210
  tween._changeDuration = normalizeTime(tween._changeDuration * timeScale);
4076
4211
  tween._currentTime *= timeScale;
4212
+ tween._delay *= timeScale;
4077
4213
  tween._startTime *= timeScale;
4078
4214
  tween._absoluteStartTime *= timeScale;
4215
+ tween._absoluteUpdateStartTime *= timeScale;
4216
+ tween._absoluteEndTime *= timeScale;
4079
4217
  });
4080
4218
  return super.stretch(newDuration);
4081
4219
  }
@@ -4204,8 +4342,6 @@ const parseTimelinePosition = (timeline, timePosition) => {
4204
4342
 
4205
4343
 
4206
4344
 
4207
-
4208
-
4209
4345
  /**
4210
4346
  * @param {Timeline} tl
4211
4347
  * @return {Number}
@@ -4398,13 +4534,21 @@ class Timeline extends Timer {
4398
4534
  if (!isUnd(synced) && !isUnd(/** @type {WAAPIAnimation} */(synced).persist)) {
4399
4535
  /** @type {WAAPIAnimation} */(synced).persist = true;
4400
4536
  }
4401
- return this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position);
4537
+ const editor = globals.editor;
4538
+ const childHook = editor && editor.addTimelineChild;
4539
+ if (editor && editor.addTimelineSync) {
4540
+ position = editor.addTimelineSync(synced, position, this.id);
4541
+ editor.addTimelineChild = null; // Suppress the per-child hook for the internal .add, sync already registered.
4542
+ }
4543
+ const result = this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position);
4544
+ if (editor) editor.addTimelineChild = childHook;
4545
+ return result;
4402
4546
  }
4403
4547
 
4404
4548
  /**
4405
4549
  * @param {TargetsParam} targets
4406
4550
  * @param {AnimationParams} parameters
4407
- * @param {TimelinePosition} [position]
4551
+ * @param {TimelinePosition|StaggerFunction<Number|String>|TweakRegister} [position]
4408
4552
  * @return {this}
4409
4553
  */
4410
4554
  set(targets, parameters, position) {
@@ -4421,6 +4565,7 @@ class Timeline extends Timer {
4421
4565
  */
4422
4566
  call(callback, position) {
4423
4567
  if (isUnd(callback) || callback && !isFnc(callback)) return this;
4568
+ if (globals.editor && globals.editor.addTimelineCall) position = globals.editor.addTimelineCall(callback, position, this.id);
4424
4569
  return this.add({ duration: 0, delay: 0, onComplete: () => callback(this) }, position);
4425
4570
  }
4426
4571
 
@@ -4432,6 +4577,7 @@ class Timeline extends Timer {
4432
4577
  */
4433
4578
  label(labelName, position) {
4434
4579
  if (isUnd(labelName) || labelName && !isStr(labelName)) return this;
4580
+ if (globals.editor && globals.editor.addTimelineLabel) position = globals.editor.addTimelineLabel(labelName, position, this.id);
4435
4581
  this.labels[labelName] = parseTimelinePosition(this, position);
4436
4582
  return this;
4437
4583
  }
@@ -4540,6 +4686,7 @@ class Animatable {
4540
4686
  const callbacksAnimationParams = { v: 1, autoplay: false };
4541
4687
  const properties = {};
4542
4688
  this.targets = [];
4689
+ /** @type {Record<String, JSAnimation>} */
4543
4690
  this.animations = {};
4544
4691
  /** @type {JSAnimation|null} */
4545
4692
  this.callbacks = null;
@@ -5078,6 +5225,9 @@ function get(targetSelector, propName, unit) {
5078
5225
  */
5079
5226
  const set = (targets, parameters) => {
5080
5227
  if (isUnd(parameters)) return;
5228
+ if (globals.editor && globals.editor.addSet) {
5229
+ return globals.editor.addSet(targets, parameters);
5230
+ }
5081
5231
  parameters.duration = minValue;
5082
5232
  // Do not overrides currently active tweens by default
5083
5233
  parameters.composition = setValue(parameters.composition, compositionTypes.none);
@@ -6314,13 +6464,14 @@ const sync = (callback = noop) => {
6314
6464
  };
6315
6465
 
6316
6466
  /**
6317
- * @param {(...args: any[]) => Tickable | ((...args: any[]) => void) | void} constructor
6318
- * @return {(...args: any[]) => Tickable | ((...args: any[]) => void)}
6467
+ * @template {Tickable | ((...args: any[]) => void) | void} T
6468
+ * @param {(...args: any[]) => T} constructor
6469
+ * @return {(...args: any[]) => T extends void ? () => void : T}
6319
6470
  */
6320
6471
  const keepTime = constructor => {
6321
6472
  /** @type {Tickable} */
6322
6473
  let tracked;
6323
- return (...args) => {
6474
+ return /** @type {(...args: any[]) => T extends void ? () => void : T} */(/** @type {*} */((...args) => {
6324
6475
  let currentIteration, currentIterationProgress, reversed, alternate, startTime;
6325
6476
  if (tracked) {
6326
6477
  currentIteration = tracked.currentIteration;
@@ -6338,7 +6489,7 @@ const keepTime = constructor => {
6338
6489
  /** @type {Tickable} */(tracked)._startTime = startTime;
6339
6490
  }
6340
6491
  return cleanup || noop;
6341
- }
6492
+ }));
6342
6493
  };
6343
6494
 
6344
6495
 
@@ -6736,12 +6887,14 @@ class ScrollContainer {
6736
6887
 
6737
6888
  refreshScrollObservers() {
6738
6889
  forEachChildren(this, (/** @type {ScrollObserver} */child) => {
6890
+ if (!child.ready) return;
6739
6891
  if (child._debug) {
6740
6892
  child.removeDebug();
6741
6893
  }
6742
6894
  });
6743
6895
  this.updateBounds();
6744
6896
  forEachChildren(this, (/** @type {ScrollObserver} */child) => {
6897
+ if (!child.ready) return;
6745
6898
  child.refresh();
6746
6899
  child.onResize(child);
6747
6900
  if (child._debug) {
@@ -9839,11 +9992,12 @@ const randomPick = items => items[random(0, items.length - 1)];
9839
9992
  * Adapted from https://bost.ocks.org/mike/shuffle/
9840
9993
  *
9841
9994
  * @param {Array} items - The array to shuffle (will be modified in-place)
9995
+ * @param {RandomNumberGenerator} [rnd] - Optional RNG matching the random() signature (defaults to random)
9842
9996
  * @return {Array} The same array reference, now shuffled
9843
9997
  */
9844
- const shuffle = items => {
9998
+ const shuffle = (items, rnd = random) => {
9845
9999
  let m = items.length, t, i;
9846
- while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
10000
+ while (m) { i = rnd(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; }
9847
10001
  return items;
9848
10002
  };
9849
10003
 
@@ -9888,6 +10042,7 @@ const stagger = (val, params = {}) => {
9888
10042
  let values = [];
9889
10043
  let maxValue = 0;
9890
10044
  let cachedOffset;
10045
+ let jitterSamples = null;
9891
10046
  const from = params.from;
9892
10047
  const reversed = params.reversed;
9893
10048
  const ease = params.ease;
@@ -9909,27 +10064,42 @@ const stagger = (val, params = {}) => {
9909
10064
  const val2 = isRange ? parseNumber(val[1]) : 0;
9910
10065
  const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
9911
10066
  const start = params.start || 0 + (isRange ? val1 : 0);
10067
+ const seed = params.seed;
10068
+ const hasSeed = !isUnd(seed) && seed !== false;
10069
+ const rng = hasSeed ? createSeededRandom(seed === true ? 0 : /** @type {Number} */(seed)) : random;
10070
+ const jitter = params.jitter;
10071
+ const hasJitter = !isUnd(jitter);
10072
+ const jitterIsArr = isArr(jitter);
10073
+ const jitterStart = jitterIsArr ? /** @type {[Number,Number]} */(jitter)[0] : /** @type {Number} */(jitter) || 0;
10074
+ const jitterEnd = jitterIsArr ? /** @type {[Number,Number]} */(jitter)[1] : /** @type {Number} */(jitter) || 0;
9912
10075
  let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
9913
10076
  return (target, i, t, _, tl) => {
9914
10077
  const [ registeredTarget ] = registerTargets(target);
9915
10078
  const total = isUnd(customTotal) ? t.length : customTotal;
9916
10079
  const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
9917
- const staggerIndex = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
10080
+ const customIdx = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
10081
+ // Fall back to the natural index when the resolved value lands outside [0, total) so values[staggerIndex] never reads undefined.
10082
+ const staggerIndex = customIdx >= 0 && customIdx < total ? customIdx : i;
9918
10083
  if (fromCenter) fromIndex = (total - 1) / 2;
9919
10084
  if (fromLast) fromIndex = total - 1;
9920
10085
  if (!values.length) {
9921
10086
  if (autoGrid) {
9922
10087
  let hasPositions = true;
10088
+ let has3D = false;
9923
10089
  let minPosX = Infinity;
9924
10090
  let minPosY = Infinity;
10091
+ let minPosZ = Infinity;
9925
10092
  let maxPosX = -Infinity;
9926
10093
  let maxPosY = -Infinity;
10094
+ let maxPosZ = -Infinity;
9927
10095
  const pxArr = [];
9928
10096
  const pyArr = [];
10097
+ const pzArr = [];
9929
10098
  for (let index = 0; index < total; index++) {
9930
10099
  const el = t[index];
9931
10100
  let px = 0;
9932
10101
  let py = 0;
10102
+ let pz = 0;
9933
10103
  let found = false;
9934
10104
  if (el && isFnc(el.getBoundingClientRect)) {
9935
10105
  const rect = el.getBoundingClientRect();
@@ -9941,6 +10111,10 @@ const stagger = (val, params = {}) => {
9941
10111
  if (obj && isNum(obj.x) && isNum(obj.y)) {
9942
10112
  px = obj.x;
9943
10113
  py = obj.y;
10114
+ if (isNum(obj.z)) {
10115
+ pz = obj.z;
10116
+ has3D = true;
10117
+ }
9944
10118
  found = true;
9945
10119
  }
9946
10120
  }
@@ -9950,42 +10124,52 @@ const stagger = (val, params = {}) => {
9950
10124
  }
9951
10125
  pxArr.push(px);
9952
10126
  pyArr.push(py);
10127
+ pzArr.push(pz);
9953
10128
  if (px < minPosX) minPosX = px;
9954
10129
  if (py < minPosY) minPosY = py;
10130
+ if (pz < minPosZ) minPosZ = pz;
9955
10131
  if (px > maxPosX) maxPosX = px;
9956
10132
  if (py > maxPosY) maxPosY = py;
10133
+ if (pz > maxPosZ) maxPosZ = pz;
9957
10134
  }
9958
10135
  if (hasPositions) {
9959
10136
  let fX = pxArr[0];
9960
10137
  let fY = pyArr[0];
10138
+ let fZ = pzArr[0];
9961
10139
  if (fromArr) {
9962
10140
  fX = minPosX + from[0] * (maxPosX - minPosX);
9963
10141
  fY = minPosY + from[1] * (maxPosY - minPosY);
10142
+ fZ = has3D ? minPosZ + (from.length >= 3 ? from[2] : 0.5) * (maxPosZ - minPosZ) : 0;
9964
10143
  } else if (fromCenter) {
9965
10144
  fX = (minPosX + maxPosX) / 2;
9966
10145
  fY = (minPosY + maxPosY) / 2;
10146
+ fZ = (minPosZ + maxPosZ) / 2;
9967
10147
  } else if (fromLast) {
9968
10148
  fX = pxArr[total - 1];
9969
10149
  fY = pyArr[total - 1];
10150
+ fZ = pzArr[total - 1];
9970
10151
  } else if (isNum(from)) {
9971
10152
  fX = pxArr[from];
9972
10153
  fY = pyArr[from];
10154
+ fZ = pzArr[from];
9973
10155
  }
9974
10156
  for (let index = 0; index < total; index++) {
9975
10157
  const distanceX = fX - pxArr[index];
9976
10158
  const distanceY = fY - pyArr[index];
9977
- let value = sqrt(distanceX * distanceX + distanceY * distanceY);
10159
+ const distanceZ = fZ - pzArr[index];
10160
+ let value = sqrt(distanceX * distanceX + distanceY * distanceY + (has3D ? distanceZ * distanceZ : 0));
9978
10161
  if (axis === 'x') value = -distanceX;
9979
10162
  if (axis === 'y') value = -distanceY;
10163
+ if (axis === 'z') value = -distanceZ;
9980
10164
  values.push(value);
9981
10165
  }
9982
10166
  let minDist = Infinity;
9983
- for (let index = 0, l = values.length; index < l; index++) {
10167
+ for (let index = 0; index < total; index++) {
9984
10168
  const absVal = abs(values[index]);
9985
10169
  if (absVal > 0 && absVal < minDist) minDist = absVal;
9986
10170
  }
9987
10171
  if (minDist > 0 && minDist < Infinity) {
9988
- for (let index = 0, l = values.length; index < l; index++) {
10172
+ for (let index = 0; index < total; index++) {
9989
10173
  values[index] = values[index] / minDist;
9990
10174
  }
9991
10175
  }
@@ -9999,32 +10183,51 @@ const stagger = (val, params = {}) => {
9999
10183
  if (!grid) {
10000
10184
  values.push(abs(fromIndex - index));
10001
10185
  } else {
10002
- let fromX, fromY;
10186
+ const dims = grid.length;
10187
+ const wh = grid[0] * grid[1];
10188
+ let fromX, fromY, fromZ;
10003
10189
  if (fromArr) {
10004
10190
  fromX = from[0] * (grid[0] - 1);
10005
10191
  fromY = from[1] * (grid[1] - 1);
10192
+ fromZ = dims === 3 ? (from.length >= 3 ? from[2] : 0.5) * (grid[2] - 1) : 0;
10006
10193
  } else if (fromCenter) {
10007
10194
  fromX = (grid[0] - 1) / 2;
10008
10195
  fromY = (grid[1] - 1) / 2;
10196
+ fromZ = dims === 3 ? (grid[2] - 1) / 2 : 0;
10009
10197
  } else {
10010
10198
  fromX = fromIndex % grid[0];
10011
- fromY = floor(fromIndex / grid[0]);
10199
+ fromY = floor(fromIndex / grid[0]) % grid[1];
10200
+ fromZ = dims === 3 ? floor(fromIndex / wh) : 0;
10012
10201
  }
10013
10202
  const toX = index % grid[0];
10014
- const toY = floor(index / grid[0]);
10203
+ const toY = floor(index / grid[0]) % grid[1];
10204
+ const toZ = dims === 3 ? floor(index / wh) : 0;
10015
10205
  const distanceX = fromX - toX;
10016
10206
  const distanceY = fromY - toY;
10017
- let value = sqrt(distanceX * distanceX + distanceY * distanceY);
10207
+ const distanceZ = fromZ - toZ;
10208
+ let value = sqrt(distanceX * distanceX + distanceY * distanceY + (dims === 3 ? distanceZ * distanceZ : 0));
10018
10209
  if (axis === 'x') value = -distanceX;
10019
10210
  if (axis === 'y') value = -distanceY;
10211
+ if (axis === 'z') value = -distanceZ;
10020
10212
  values.push(value);
10021
10213
  }
10022
10214
  }
10023
10215
  }
10024
- maxValue = max(...values);
10025
- if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue);
10026
- if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
10027
- if (fromRandom) values = shuffle(values);
10216
+ maxValue = values[0];
10217
+ for (let k = 1; k < total; k++) if (values[k] > maxValue) maxValue = values[k];
10218
+ if (staggerEase || reversed) {
10219
+ for (let k = 0; k < total; k++) {
10220
+ let v = values[k];
10221
+ if (staggerEase) v = staggerEase(v / maxValue) * maxValue;
10222
+ if (reversed) v = axis ? -v : abs(maxValue - v);
10223
+ values[k] = v;
10224
+ }
10225
+ }
10226
+ if (hasJitter) {
10227
+ jitterSamples = new Array(total);
10228
+ for (let k = 0; k < total; k++) jitterSamples[k] = rng(-1, 1, 4);
10229
+ }
10230
+ if (fromRandom) values = shuffle(values, rng);
10028
10231
  }
10029
10232
  const spacing = isRange ? (val2 - val1) / maxValue : val1;
10030
10233
  if (isUnd(cachedOffset)) {
@@ -10032,6 +10235,11 @@ const stagger = (val, params = {}) => {
10032
10235
  }
10033
10236
  /** @type {String|Number} */
10034
10237
  let output = cachedOffset + ((spacing * round$1(values[staggerIndex], 2)) || 0);
10238
+ if (hasJitter) {
10239
+ const progress = maxValue ? values[staggerIndex] / maxValue : 0;
10240
+ const mag = jitterStart + (jitterEnd - jitterStart) * progress;
10241
+ output = /** @type {Number} */(output) + jitterSamples[staggerIndex] * mag;
10242
+ }
10035
10243
  if (params.modifier) output = params.modifier(/** @type {Number} */(output));
10036
10244
  if (unitMatch) output = `${output}${unitMatch[2]}`;
10037
10245
  return output;
@@ -10401,6 +10609,7 @@ const filterLineElements = ($el, lineIndex, bin) => {
10401
10609
  */
10402
10610
  const generateTemplate = (type, params = {}) => {
10403
10611
  let template = ``;
10612
+ if (!params) params = {};
10404
10613
  const classString = isStr(params.class) ? ` class="${params.class}"` : '';
10405
10614
  const cloneType = setValue(params.clone, false);
10406
10615
  const wrapType = setValue(params.wrap, false);
@@ -10782,6 +10991,16 @@ const split = (target, parameters) => {
10782
10991
 
10783
10992
 
10784
10993
 
10994
+ /**
10995
+ * @typedef {Object} ScrambleTextTween
10996
+ * @property {Number} from
10997
+ * @property {Number} to
10998
+ * @property {Number} duration
10999
+ * @property {Number} delay
11000
+ * @property {String} ease
11001
+ * @property {(v: Number) => String} modifier
11002
+ */
11003
+
10785
11004
  /**
10786
11005
  * '-' 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-')
10787
11006
  * @param {String} str
@@ -10819,7 +11038,7 @@ const originalTexts = new WeakMap();
10819
11038
  * progressively revealing the original text.
10820
11039
  *
10821
11040
  * @param {ScrambleTextParams} [params]
10822
- * @return {FunctionValue}
11041
+ * @return {FunctionValue<ScrambleTextTween>}
10823
11042
  */
10824
11043
  const scrambleText = (params = {}) => {
10825
11044
  if (!params) params = {};