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 - UMD 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
@@ -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.0', 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) {
@@ -1836,50 +1964,6 @@
1836
1964
  return hasRendered;
1837
1965
  };
1838
1966
 
1839
- // Shared context for extracted forEachChildren callbacks in tick()
1840
- // Avoids closure allocation every frame
1841
-
1842
- let renderCtxChildrenTime = 0;
1843
- let renderCtxTlFps = 0;
1844
- let renderCtxTickTime = 0;
1845
- let renderCtxTickMode = 0;
1846
- let renderCtxMuteCallbacks = 0;
1847
- let renderCtxInternalRender = 0;
1848
- let renderCtxChildrenHasRendered = 0;
1849
- let renderCtxChildrenHaveCompleted = true;
1850
- let loopCtxIsRunningBackwards = false;
1851
- let loopCtxIterationDuration = 0;
1852
- let loopCtxMuteCallbacks = 0;
1853
-
1854
- /** @param {JSAnimation} child */
1855
- const tickLoopChild = (child) => {
1856
- if (!loopCtxIsRunningBackwards) {
1857
- // Force an internal render to trigger the callbacks if the child has not completed on loop
1858
- if (!child.completed && !child.backwards && child._currentTime < child.iterationDuration) {
1859
- render(child, loopCtxIterationDuration, loopCtxMuteCallbacks, 1, tickModes.FORCE);
1860
- }
1861
- // Reset their began and completed flags to allow retrigering callbacks on the next iteration
1862
- child.began = false;
1863
- child.completed = false;
1864
- } else {
1865
- const childDuration = child.duration;
1866
- const childStartTime = child._offset + child._delay;
1867
- const childEndTime = childStartTime + childDuration;
1868
- // Triggers the onComplete callback on reverse for children on the edges of the timeline
1869
- if (!loopCtxMuteCallbacks && childDuration <= minValue && (!childStartTime || childEndTime === loopCtxIterationDuration)) {
1870
- child.onComplete(child);
1871
- }
1872
- }
1873
- };
1874
-
1875
- /** @param {JSAnimation} child */
1876
- const tickRenderChild = (child) => {
1877
- const childTime = round$1((renderCtxChildrenTime - child._offset) * child._speed, 12); // Rounding is needed when using seconds
1878
- const childTickMode = child._fps < renderCtxTlFps ? child.requestTick(renderCtxTickTime) : renderCtxTickMode;
1879
- renderCtxChildrenHasRendered += render(child, childTime, renderCtxMuteCallbacks, renderCtxInternalRender, childTickMode);
1880
- if (!child.completed && renderCtxChildrenHaveCompleted) renderCtxChildrenHaveCompleted = false;
1881
- };
1882
-
1883
1967
  /**
1884
1968
  * @param {Tickable} tickable
1885
1969
  * @param {Number} time
@@ -1896,28 +1980,44 @@
1896
1980
  const tlIsRunningBackwards = tl.backwards;
1897
1981
  const tlChildrenTime = internalRender ? time : tl._iterationTime;
1898
1982
  const tlCildrenTickTime = now();
1983
+
1899
1984
  let tlChildrenHasRendered = 0;
1900
1985
  let tlChildrenHaveCompleted = true;
1986
+
1901
1987
  // If the timeline has looped forward, we need to manually triggers children skipped callbacks
1902
1988
  if (!internalRender && tl._currentIteration !== _currentIteration) {
1903
1989
  const tlIterationDuration = tl.iterationDuration;
1904
- loopCtxIsRunningBackwards = tlIsRunningBackwards;
1905
- loopCtxIterationDuration = tlIterationDuration;
1906
- loopCtxMuteCallbacks = muteCallbacks;
1907
- forEachChildren(tl, tickLoopChild);
1990
+ forEachChildren(tl, (/** @type {JSAnimation} */child) => {
1991
+ if (!tlIsRunningBackwards) {
1992
+ // Force an internal render to trigger the callbacks if the child has not completed on loop
1993
+ if (!child.completed && !child.backwards && child._currentTime < child.iterationDuration) {
1994
+ render(child, tlIterationDuration, muteCallbacks, 1, tickModes.FORCE);
1995
+ }
1996
+ // Reset their began and completed flags to allow retrigering callbacks on the next iteration
1997
+ child.began = false;
1998
+ child.completed = false;
1999
+ } else {
2000
+ const childDuration = child.duration;
2001
+ const childStartTime = child._offset + child._delay;
2002
+ const childEndTime = childStartTime + childDuration;
2003
+ // Triggers the onComplete callback on reverse for children on the edges of the timeline
2004
+ if (!muteCallbacks && childDuration <= minValue && (!childStartTime || childEndTime === tlIterationDuration)) {
2005
+ child.onComplete(child);
2006
+ }
2007
+ }
2008
+ });
1908
2009
  if (!muteCallbacks) tl.onLoop(/** @type {CallbackArgument} */(tl));
1909
2010
  }
1910
- renderCtxChildrenTime = tlChildrenTime;
1911
- renderCtxTlFps = tl._fps;
1912
- renderCtxTickTime = tlCildrenTickTime;
1913
- renderCtxTickMode = tickMode;
1914
- renderCtxMuteCallbacks = muteCallbacks;
1915
- renderCtxInternalRender = internalRender;
1916
- renderCtxChildrenHasRendered = 0;
1917
- renderCtxChildrenHaveCompleted = true;
1918
- forEachChildren(tl, tickRenderChild, tlIsRunningBackwards);
1919
- tlChildrenHasRendered = renderCtxChildrenHasRendered;
1920
- tlChildrenHaveCompleted = renderCtxChildrenHaveCompleted;
2011
+
2012
+ forEachChildren(tl, (/** @type {JSAnimation} */child) => {
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;
2016
+ const childTickMode = child._fps < tl._fps ? child.requestTick(tlCildrenTickTime) : tickMode;
2017
+ tlChildrenHasRendered += render(child, childTime, muteCallbacks, internalRender, childTickMode);
2018
+ if (!child.completed && tlChildrenHaveCompleted) tlChildrenHaveCompleted = false;
2019
+ }, tlIsRunningBackwards);
2020
+
1921
2021
  // Renders on timeline are triggered by its children so it needs to be set after rendering the children
1922
2022
  if (!muteCallbacks && tlChildrenHasRendered) tl.onRender(/** @type {CallbackArgument} */(tl));
1923
2023
  // Triggers the timeline onComplete() once all chindren all completed and the current time has reached the end
@@ -1989,7 +2089,20 @@
1989
2089
  const tweenType = tween._tweenType;
1990
2090
  const originalInlinedValue = tween._inlineValue;
1991
2091
  const tweenHadNoInlineValue = isNil(originalInlinedValue) || originalInlinedValue === emptyString;
1992
- 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) {
1993
2106
  if (!inlineStylesOnly && !tweenHadNoInlineValue) {
1994
2107
  tweenTarget[tweenProperty] = originalInlinedValue;
1995
2108
  }
@@ -2066,8 +2179,6 @@
2066
2179
  /** @type {Number} */
2067
2180
  this._lastTime = initTime;
2068
2181
  /** @type {Number} */
2069
- this._scheduledTime = 0;
2070
- /** @type {Number} */
2071
2182
  this._frameDuration = K / maxFps;
2072
2183
  /** @type {Number} */
2073
2184
  this._fps = maxFps;
@@ -2086,14 +2197,12 @@
2086
2197
  }
2087
2198
 
2088
2199
  set fps(frameRate) {
2089
- const previousFrameDuration = this._frameDuration;
2090
2200
  const fr = +frameRate;
2091
2201
  const fps = fr < minValue ? minValue : fr;
2092
2202
  const frameDuration = K / fps;
2093
2203
  if (fps > defaults.frameRate) defaults.frameRate = fps;
2094
2204
  this._fps = fps;
2095
2205
  this._frameDuration = frameDuration;
2096
- this._scheduledTime += frameDuration - previousFrameDuration;
2097
2206
  }
2098
2207
 
2099
2208
  get speed() {
@@ -2110,17 +2219,17 @@
2110
2219
  * @return {tickModes}
2111
2220
  */
2112
2221
  requestTick(time) {
2113
- const scheduledTime = this._scheduledTime;
2114
- this._lastTickTime = time;
2115
- // If the current time is lower than the scheduled time
2116
- // this means not enough time has passed to hit one frameDuration
2117
- // so skip that frame
2118
- if (time < scheduledTime) return tickModes.NONE;
2119
2222
  const frameDuration = this._frameDuration;
2120
- const frameDelta = time - scheduledTime;
2121
- // Ensures that _scheduledTime progresses in steps of at least 1 frameDuration.
2122
- // Skips ahead if the actual elapsed time is higher.
2123
- 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;
2124
2233
  return tickModes.AUTO;
2125
2234
  }
2126
2235
 
@@ -2280,7 +2389,9 @@
2280
2389
  }
2281
2390
 
2282
2391
  set speed(playbackRate) {
2283
- this._speed = playbackRate * globals.timeScale;
2392
+ const speed = playbackRate * globals.timeScale;
2393
+ if (this._speed === speed) return;
2394
+ this._speed = speed;
2284
2395
  forEachChildren(this, (/** @type {Tickable} */child) => child.speed = child._speed);
2285
2396
  }
2286
2397
 
@@ -2413,7 +2524,7 @@
2413
2524
  if (prevSibling) {
2414
2525
 
2415
2526
  const prevParent = prevSibling.parent;
2416
- const prevAbsEndTime = prevSibling._absoluteStartTime + prevSibling._changeDuration;
2527
+ const prevAbsEndTime = prevSibling._absoluteEndTime;
2417
2528
 
2418
2529
  // Handle looped animations tween
2419
2530
 
@@ -2439,7 +2550,8 @@
2439
2550
 
2440
2551
  }
2441
2552
 
2442
- 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;
2443
2555
 
2444
2556
  if (prevAbsEndTime > absoluteUpdateStartTime) {
2445
2557
 
@@ -2459,37 +2571,41 @@
2459
2571
  }
2460
2572
  }
2461
2573
 
2462
- // 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) {
2463
2577
 
2464
- let pausePrevParentAnimation = true;
2578
+ let pausePrevParentAnimation = true;
2465
2579
 
2466
- forEachChildren(prevParent, (/** @type Tween */t) => {
2467
- if (!t._isOverlapped) pausePrevParentAnimation = false;
2468
- });
2580
+ forEachChildren(prevParent, (/** @type Tween */t) => {
2581
+ if (!t._isOverlapped) pausePrevParentAnimation = false;
2582
+ });
2469
2583
 
2470
- if (pausePrevParentAnimation) {
2471
- const prevParentTL = prevParent.parent;
2472
- if (prevParentTL) {
2473
- let pausePrevParentTL = true;
2474
- forEachChildren(prevParentTL, (/** @type JSAnimation */a) => {
2475
- if (a !== prevParent) {
2476
- forEachChildren(a, (/** @type Tween */t) => {
2477
- if (!t._isOverlapped) pausePrevParentTL = false;
2478
- });
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();
2479
2597
  }
2480
- });
2481
- if (pausePrevParentTL) {
2482
- 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();
2483
2606
  }
2484
- } else {
2485
- prevParent.cancel();
2486
- // Previously, calling .cancel() on a timeline child would affect the render order of other children
2487
- // Worked around this by marking it as .completed and using .pause() for safe removal in the engine loop
2488
- // This is no longer needed since timeline tween composition is now handled separately
2489
- // Keeping this here for reference
2490
- // prevParent.completed = true;
2491
- // prevParent.pause();
2492
2607
  }
2608
+
2493
2609
  }
2494
2610
 
2495
2611
  }
@@ -2544,14 +2660,12 @@
2544
2660
  tween._number = 0;
2545
2661
  lookupTween._fromNumber = toNumber;
2546
2662
 
2547
- if (tween._toNumbers) {
2663
+ if (tween._toNumbers.length) {
2548
2664
  const toNumbers = cloneArray(tween._toNumbers);
2549
- if (toNumbers) {
2550
- toNumbers.forEach((value, i) => {
2551
- tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value;
2552
- tween._toNumbers[i] = 0;
2553
- });
2554
- }
2665
+ toNumbers.forEach((value, i) => {
2666
+ tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value;
2667
+ tween._toNumbers[i] = 0;
2668
+ });
2555
2669
  lookupTween._fromNumbers = toNumbers;
2556
2670
  }
2557
2671
 
@@ -3252,18 +3366,16 @@
3252
3366
  function registerTargets(targets) {
3253
3367
  const parsedTargetsArray = parseTargets(targets);
3254
3368
  const parsedTargetsLength = parsedTargetsArray.length;
3255
- if (parsedTargetsLength) {
3256
- for (let i = 0; i < parsedTargetsLength; i++) {
3257
- const target = parsedTargetsArray[i];
3258
- if (!target[isRegisteredTargetSymbol]) {
3259
- target[isRegisteredTargetSymbol] = true;
3260
- const isSvgType = isSvg(target);
3261
- const isDom = /** @type {DOMTarget} */(target).nodeType || isSvgType;
3262
- if (isDom) {
3263
- target[isDomSymbol] = true;
3264
- target[isSvgSymbol] = isSvgType;
3265
- target[transformsSymbol] = {};
3266
- }
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] = {};
3267
3379
  }
3268
3380
  }
3269
3381
  }
@@ -3385,8 +3497,8 @@
3385
3497
 
3386
3498
  /**
3387
3499
  * @typedef {Object} EasesFunctions
3388
- * @property {typeof none} linear
3389
- * @property {typeof none} none
3500
+ * @property {EasingFunction} linear
3501
+ * @property {EasingFunction} none
3390
3502
  * @property {PowerEasing} in
3391
3503
  * @property {PowerEasing} out
3392
3504
  * @property {PowerEasing} inOut
@@ -3618,6 +3730,11 @@
3618
3730
 
3619
3731
  super(/** @type {TimerParams & AnimationParams} */(parameters), parent, parentPosition);
3620
3732
 
3733
+ /** @type {Tween} */
3734
+ this._head;
3735
+ /** @type {Tween} */
3736
+ this._tail;
3737
+
3621
3738
  ++JSAnimationId;
3622
3739
 
3623
3740
  const parsedTargets = registerTargets(targets);
@@ -3674,6 +3791,7 @@
3674
3791
  if (isKey(p)) {
3675
3792
 
3676
3793
  const tweenType = getTweenType(target, p);
3794
+ const adapterProp = resolveAdapterEntry(target, p);
3677
3795
 
3678
3796
  const propName = sanitizePropertyName(p, target, tweenType);
3679
3797
 
@@ -3757,7 +3875,7 @@
3757
3875
  } else {
3758
3876
  tweenToValue = computedToValue;
3759
3877
  }
3760
- const tweenFromValue = getFunctionValue(key.from, target, ti, tl, null, prevSiblingTween);
3878
+ const tweenFromValue = getFunctionValue(key.from, target, ti, tl, fromFunctionStore, prevSiblingTween);
3761
3879
  const easeToParse = key.ease || tEasing;
3762
3880
 
3763
3881
  const easeFunctionResult = getFunctionValue(easeToParse, target, ti, tl, null, prevSiblingTween);
@@ -3775,9 +3893,13 @@
3775
3893
  const hasToValue = !isUnd(tweenToValue);
3776
3894
  const isFromToArray = isArr(tweenToValue);
3777
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;
3778
3898
  const tweenStartTime = prevTween ? lastTweenChangeEndTime + tweenDelay : tweenDelay;
3779
3899
  // Rounding is necessary here to minimize floating point errors when working in seconds
3780
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);
3781
3903
 
3782
3904
  // Force a onRender callback if the animation contains at least one from value and autoplay is set to false
3783
3905
  if (!shouldTriggerRender && (hasFromvalue || isFromToArray)) shouldTriggerRender = 1;
@@ -3786,9 +3908,9 @@
3786
3908
 
3787
3909
  if (tweenComposition !== compositionTypes.none) {
3788
3910
  let nextSibling = siblings._head;
3789
- // Iterate trough all the next siblings until we find a sibling with an equal or inferior start time
3790
- while (nextSibling && !nextSibling._isOverridden && nextSibling._absoluteStartTime <= absoluteStartTime) {
3791
- 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;
3792
3914
  nextSibling = nextSibling._nextRep;
3793
3915
  // Overrides all the next siblings if the next sibling starts at the same time of after as the new tween start time
3794
3916
  if (nextSibling && nextSibling._absoluteStartTime >= absoluteStartTime) {
@@ -3842,8 +3964,8 @@
3842
3964
  if (prevTween) {
3843
3965
  decomposeTweenValue(prevTween, fromTargetObject);
3844
3966
  } else {
3845
- decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value :
3846
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 :
3847
3969
  getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore), fromTargetObject);
3848
3970
  }
3849
3971
  }
@@ -3882,8 +4004,7 @@
3882
4004
  const colorValue = fromTargetObject.t === valueTypes.COLOR ? fromTargetObject : toTargetObject;
3883
4005
  const notColorValue = fromTargetObject.t === valueTypes.COLOR ? toTargetObject : fromTargetObject;
3884
4006
  notColorValue.t = valueTypes.COLOR;
3885
- notColorValue.s = colorValue.s;
3886
- notColorValue.d = [0, 0, 0, 1];
4007
+ notColorValue.d = colorValue.d.map(() => 0);
3887
4008
  }
3888
4009
  }
3889
4010
 
@@ -3913,6 +4034,16 @@
3913
4034
  let inlineValue = inlineStylesStore[propName];
3914
4035
  if (!isNil(inlineValue)) inlineStylesStore[propName] = null;
3915
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
+
3916
4047
  /** @type {Tween} */
3917
4048
  const tween = {
3918
4049
  parent: this,
@@ -3923,12 +4054,12 @@
3923
4054
  _toFunc: toFunctionStore.func,
3924
4055
  _fromFunc: fromFunctionStore.func,
3925
4056
  _ease: parseEase(tweenEasing),
3926
- _fromNumbers: cloneArray(fromTargetObject.d),
3927
- _toNumbers: cloneArray(toTargetObject.d),
3928
- _strings: cloneArray(toTargetObject.s),
4057
+ _fromNumbers: fromD ? cloneArray(fromD) : emptyArray,
4058
+ _toNumbers: toD ? cloneArray(toD) : emptyArray,
4059
+ _strings: toS ? cloneArray(toS) : emptyArray,
3929
4060
  _fromNumber: fromTargetObject.n,
3930
4061
  _toNumber: toTargetObject.n,
3931
- _numbers: cloneArray(fromTargetObject.d), // For additive tween and animatables
4062
+ _numbers: fromD ? cloneArray(fromD) : emptyArray, // For additive tween and animatables
3932
4063
  _number: fromTargetObject.n, // For additive tween and animatables
3933
4064
  _unit: toTargetObject.u,
3934
4065
  _modifier: tweenModifier,
@@ -3938,8 +4069,12 @@
3938
4069
  _updateDuration: tweenUpdateDuration,
3939
4070
  _changeDuration: tweenUpdateDuration,
3940
4071
  _absoluteStartTime: absoluteStartTime,
4072
+ _absoluteUpdateStartTime: absoluteUpdateStartTime,
4073
+ _absoluteEndTime: round$1(absoluteOffsetTime + lastTweenChangeEndTime, 12),
4074
+ _hasFromValue: hasFromvalue || isFromToArray ? 1 : 0,
3941
4075
  // NOTE: Investigate bit packing to stores ENUM / BOOL
3942
4076
  _tweenType: tweenType,
4077
+ _setter: tweenSetter,
3943
4078
  _valueType: toTargetObject.t,
3944
4079
  _composition: tweenComposition,
3945
4080
  _isOverlapped: 0,
@@ -3962,10 +4097,11 @@
3962
4097
  const vt = tween._valueType;
3963
4098
  if (vt === valueTypes.COMPLEX) {
3964
4099
  tween._value = composeComplexValue(tween, 1, -1);
3965
- } else if (vt === valueTypes.COLOR) {
3966
- tween._value = composeColorValue(tween, 1, -1);
3967
4100
  } else if (vt === valueTypes.UNIT) {
3968
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]})`;
3969
4105
  } else {
3970
4106
  tween._value = tweenModifier(tween._toNumber);
3971
4107
  }
@@ -3973,8 +4109,7 @@
3973
4109
  if (isNaN(firstTweenChangeStartTime)) {
3974
4110
  firstTweenChangeStartTime = tween._startTime;
3975
4111
  }
3976
- // Rounding is necessary here to minimize floating point errors when working in seconds
3977
- lastTweenChangeEndTime = round$1(tweenStartTime + tweenUpdateDuration, 12);
4112
+
3978
4113
  prevTween = tween;
3979
4114
  animationAnimationLength++;
3980
4115
 
@@ -4080,8 +4215,11 @@
4080
4215
  tween._updateDuration = normalizeTime(tween._updateDuration * timeScale);
4081
4216
  tween._changeDuration = normalizeTime(tween._changeDuration * timeScale);
4082
4217
  tween._currentTime *= timeScale;
4218
+ tween._delay *= timeScale;
4083
4219
  tween._startTime *= timeScale;
4084
4220
  tween._absoluteStartTime *= timeScale;
4221
+ tween._absoluteUpdateStartTime *= timeScale;
4222
+ tween._absoluteEndTime *= timeScale;
4085
4223
  });
4086
4224
  return super.stretch(newDuration);
4087
4225
  }
@@ -4210,8 +4348,6 @@
4210
4348
 
4211
4349
 
4212
4350
 
4213
-
4214
-
4215
4351
  /**
4216
4352
  * @param {Timeline} tl
4217
4353
  * @return {Number}
@@ -4404,13 +4540,21 @@
4404
4540
  if (!isUnd(synced) && !isUnd(/** @type {WAAPIAnimation} */(synced).persist)) {
4405
4541
  /** @type {WAAPIAnimation} */(synced).persist = true;
4406
4542
  }
4407
- 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;
4408
4552
  }
4409
4553
 
4410
4554
  /**
4411
4555
  * @param {TargetsParam} targets
4412
4556
  * @param {AnimationParams} parameters
4413
- * @param {TimelinePosition} [position]
4557
+ * @param {TimelinePosition|StaggerFunction<Number|String>|TweakRegister} [position]
4414
4558
  * @return {this}
4415
4559
  */
4416
4560
  set(targets, parameters, position) {
@@ -4427,6 +4571,7 @@
4427
4571
  */
4428
4572
  call(callback, position) {
4429
4573
  if (isUnd(callback) || callback && !isFnc(callback)) return this;
4574
+ if (globals.editor && globals.editor.addTimelineCall) position = globals.editor.addTimelineCall(callback, position, this.id);
4430
4575
  return this.add({ duration: 0, delay: 0, onComplete: () => callback(this) }, position);
4431
4576
  }
4432
4577
 
@@ -4438,6 +4583,7 @@
4438
4583
  */
4439
4584
  label(labelName, position) {
4440
4585
  if (isUnd(labelName) || labelName && !isStr(labelName)) return this;
4586
+ if (globals.editor && globals.editor.addTimelineLabel) position = globals.editor.addTimelineLabel(labelName, position, this.id);
4441
4587
  this.labels[labelName] = parseTimelinePosition(this, position);
4442
4588
  return this;
4443
4589
  }
@@ -4546,6 +4692,7 @@
4546
4692
  const callbacksAnimationParams = { v: 1, autoplay: false };
4547
4693
  const properties = {};
4548
4694
  this.targets = [];
4695
+ /** @type {Record<String, JSAnimation>} */
4549
4696
  this.animations = {};
4550
4697
  /** @type {JSAnimation|null} */
4551
4698
  this.callbacks = null;
@@ -5084,6 +5231,9 @@
5084
5231
  */
5085
5232
  const set = (targets, parameters) => {
5086
5233
  if (isUnd(parameters)) return;
5234
+ if (globals.editor && globals.editor.addSet) {
5235
+ return globals.editor.addSet(targets, parameters);
5236
+ }
5087
5237
  parameters.duration = minValue;
5088
5238
  // Do not overrides currently active tweens by default
5089
5239
  parameters.composition = setValue(parameters.composition, compositionTypes.none);
@@ -6320,13 +6470,14 @@
6320
6470
  };
6321
6471
 
6322
6472
  /**
6323
- * @param {(...args: any[]) => Tickable | ((...args: any[]) => void) | void} constructor
6324
- * @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}
6325
6476
  */
6326
6477
  const keepTime = constructor => {
6327
6478
  /** @type {Tickable} */
6328
6479
  let tracked;
6329
- return (...args) => {
6480
+ return /** @type {(...args: any[]) => T extends void ? () => void : T} */(/** @type {*} */((...args) => {
6330
6481
  let currentIteration, currentIterationProgress, reversed, alternate, startTime;
6331
6482
  if (tracked) {
6332
6483
  currentIteration = tracked.currentIteration;
@@ -6344,7 +6495,7 @@
6344
6495
  /** @type {Tickable} */(tracked)._startTime = startTime;
6345
6496
  }
6346
6497
  return cleanup || noop;
6347
- }
6498
+ }));
6348
6499
  };
6349
6500
 
6350
6501
 
@@ -6742,12 +6893,14 @@
6742
6893
 
6743
6894
  refreshScrollObservers() {
6744
6895
  forEachChildren(this, (/** @type {ScrollObserver} */child) => {
6896
+ if (!child.ready) return;
6745
6897
  if (child._debug) {
6746
6898
  child.removeDebug();
6747
6899
  }
6748
6900
  });
6749
6901
  this.updateBounds();
6750
6902
  forEachChildren(this, (/** @type {ScrollObserver} */child) => {
6903
+ if (!child.ready) return;
6751
6904
  child.refresh();
6752
6905
  child.onResize(child);
6753
6906
  if (child._debug) {
@@ -9845,11 +9998,12 @@
9845
9998
  * Adapted from https://bost.ocks.org/mike/shuffle/
9846
9999
  *
9847
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)
9848
10002
  * @return {Array} The same array reference, now shuffled
9849
10003
  */
9850
- const shuffle = items => {
10004
+ const shuffle = (items, rnd = random) => {
9851
10005
  let m = items.length, t, i;
9852
- 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; }
9853
10007
  return items;
9854
10008
  };
9855
10009
 
@@ -9894,6 +10048,7 @@
9894
10048
  let values = [];
9895
10049
  let maxValue = 0;
9896
10050
  let cachedOffset;
10051
+ let jitterSamples = null;
9897
10052
  const from = params.from;
9898
10053
  const reversed = params.reversed;
9899
10054
  const ease = params.ease;
@@ -9915,27 +10070,42 @@
9915
10070
  const val2 = isRange ? parseNumber(val[1]) : 0;
9916
10071
  const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
9917
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;
9918
10081
  let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
9919
10082
  return (target, i, t, _, tl) => {
9920
10083
  const [ registeredTarget ] = registerTargets(target);
9921
10084
  const total = isUnd(customTotal) ? t.length : customTotal;
9922
10085
  const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
9923
- 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;
9924
10089
  if (fromCenter) fromIndex = (total - 1) / 2;
9925
10090
  if (fromLast) fromIndex = total - 1;
9926
10091
  if (!values.length) {
9927
10092
  if (autoGrid) {
9928
10093
  let hasPositions = true;
10094
+ let has3D = false;
9929
10095
  let minPosX = Infinity;
9930
10096
  let minPosY = Infinity;
10097
+ let minPosZ = Infinity;
9931
10098
  let maxPosX = -Infinity;
9932
10099
  let maxPosY = -Infinity;
10100
+ let maxPosZ = -Infinity;
9933
10101
  const pxArr = [];
9934
10102
  const pyArr = [];
10103
+ const pzArr = [];
9935
10104
  for (let index = 0; index < total; index++) {
9936
10105
  const el = t[index];
9937
10106
  let px = 0;
9938
10107
  let py = 0;
10108
+ let pz = 0;
9939
10109
  let found = false;
9940
10110
  if (el && isFnc(el.getBoundingClientRect)) {
9941
10111
  const rect = el.getBoundingClientRect();
@@ -9947,6 +10117,10 @@
9947
10117
  if (obj && isNum(obj.x) && isNum(obj.y)) {
9948
10118
  px = obj.x;
9949
10119
  py = obj.y;
10120
+ if (isNum(obj.z)) {
10121
+ pz = obj.z;
10122
+ has3D = true;
10123
+ }
9950
10124
  found = true;
9951
10125
  }
9952
10126
  }
@@ -9956,42 +10130,52 @@
9956
10130
  }
9957
10131
  pxArr.push(px);
9958
10132
  pyArr.push(py);
10133
+ pzArr.push(pz);
9959
10134
  if (px < minPosX) minPosX = px;
9960
10135
  if (py < minPosY) minPosY = py;
10136
+ if (pz < minPosZ) minPosZ = pz;
9961
10137
  if (px > maxPosX) maxPosX = px;
9962
10138
  if (py > maxPosY) maxPosY = py;
10139
+ if (pz > maxPosZ) maxPosZ = pz;
9963
10140
  }
9964
10141
  if (hasPositions) {
9965
10142
  let fX = pxArr[0];
9966
10143
  let fY = pyArr[0];
10144
+ let fZ = pzArr[0];
9967
10145
  if (fromArr) {
9968
10146
  fX = minPosX + from[0] * (maxPosX - minPosX);
9969
10147
  fY = minPosY + from[1] * (maxPosY - minPosY);
10148
+ fZ = has3D ? minPosZ + (from.length >= 3 ? from[2] : 0.5) * (maxPosZ - minPosZ) : 0;
9970
10149
  } else if (fromCenter) {
9971
10150
  fX = (minPosX + maxPosX) / 2;
9972
10151
  fY = (minPosY + maxPosY) / 2;
10152
+ fZ = (minPosZ + maxPosZ) / 2;
9973
10153
  } else if (fromLast) {
9974
10154
  fX = pxArr[total - 1];
9975
10155
  fY = pyArr[total - 1];
10156
+ fZ = pzArr[total - 1];
9976
10157
  } else if (isNum(from)) {
9977
10158
  fX = pxArr[from];
9978
10159
  fY = pyArr[from];
10160
+ fZ = pzArr[from];
9979
10161
  }
9980
10162
  for (let index = 0; index < total; index++) {
9981
10163
  const distanceX = fX - pxArr[index];
9982
10164
  const distanceY = fY - pyArr[index];
9983
- 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));
9984
10167
  if (axis === 'x') value = -distanceX;
9985
10168
  if (axis === 'y') value = -distanceY;
10169
+ if (axis === 'z') value = -distanceZ;
9986
10170
  values.push(value);
9987
10171
  }
9988
10172
  let minDist = Infinity;
9989
- for (let index = 0, l = values.length; index < l; index++) {
10173
+ for (let index = 0; index < total; index++) {
9990
10174
  const absVal = abs(values[index]);
9991
10175
  if (absVal > 0 && absVal < minDist) minDist = absVal;
9992
10176
  }
9993
10177
  if (minDist > 0 && minDist < Infinity) {
9994
- for (let index = 0, l = values.length; index < l; index++) {
10178
+ for (let index = 0; index < total; index++) {
9995
10179
  values[index] = values[index] / minDist;
9996
10180
  }
9997
10181
  }
@@ -10005,32 +10189,51 @@
10005
10189
  if (!grid) {
10006
10190
  values.push(abs(fromIndex - index));
10007
10191
  } else {
10008
- let fromX, fromY;
10192
+ const dims = grid.length;
10193
+ const wh = grid[0] * grid[1];
10194
+ let fromX, fromY, fromZ;
10009
10195
  if (fromArr) {
10010
10196
  fromX = from[0] * (grid[0] - 1);
10011
10197
  fromY = from[1] * (grid[1] - 1);
10198
+ fromZ = dims === 3 ? (from.length >= 3 ? from[2] : 0.5) * (grid[2] - 1) : 0;
10012
10199
  } else if (fromCenter) {
10013
10200
  fromX = (grid[0] - 1) / 2;
10014
10201
  fromY = (grid[1] - 1) / 2;
10202
+ fromZ = dims === 3 ? (grid[2] - 1) / 2 : 0;
10015
10203
  } else {
10016
10204
  fromX = fromIndex % grid[0];
10017
- fromY = floor(fromIndex / grid[0]);
10205
+ fromY = floor(fromIndex / grid[0]) % grid[1];
10206
+ fromZ = dims === 3 ? floor(fromIndex / wh) : 0;
10018
10207
  }
10019
10208
  const toX = index % grid[0];
10020
- const toY = floor(index / grid[0]);
10209
+ const toY = floor(index / grid[0]) % grid[1];
10210
+ const toZ = dims === 3 ? floor(index / wh) : 0;
10021
10211
  const distanceX = fromX - toX;
10022
10212
  const distanceY = fromY - toY;
10023
- 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));
10024
10215
  if (axis === 'x') value = -distanceX;
10025
10216
  if (axis === 'y') value = -distanceY;
10217
+ if (axis === 'z') value = -distanceZ;
10026
10218
  values.push(value);
10027
10219
  }
10028
10220
  }
10029
10221
  }
10030
- maxValue = max(...values);
10031
- if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue);
10032
- if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
10033
- 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);
10034
10237
  }
10035
10238
  const spacing = isRange ? (val2 - val1) / maxValue : val1;
10036
10239
  if (isUnd(cachedOffset)) {
@@ -10038,6 +10241,11 @@
10038
10241
  }
10039
10242
  /** @type {String|Number} */
10040
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
+ }
10041
10249
  if (params.modifier) output = params.modifier(/** @type {Number} */(output));
10042
10250
  if (unitMatch) output = `${output}${unitMatch[2]}`;
10043
10251
  return output;
@@ -10407,6 +10615,7 @@
10407
10615
  */
10408
10616
  const generateTemplate = (type, params = {}) => {
10409
10617
  let template = ``;
10618
+ if (!params) params = {};
10410
10619
  const classString = isStr(params.class) ? ` class="${params.class}"` : '';
10411
10620
  const cloneType = setValue(params.clone, false);
10412
10621
  const wrapType = setValue(params.wrap, false);
@@ -10788,6 +10997,16 @@
10788
10997
 
10789
10998
 
10790
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
+
10791
11010
  /**
10792
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-')
10793
11012
  * @param {String} str
@@ -10825,7 +11044,7 @@
10825
11044
  * progressively revealing the original text.
10826
11045
  *
10827
11046
  * @param {ScrambleTextParams} [params]
10828
- * @return {FunctionValue}
11047
+ * @return {FunctionValue<ScrambleTextTween>}
10829
11048
  */
10830
11049
  const scrambleText = (params = {}) => {
10831
11050
  if (!params) params = {};