animejs 4.3.0-beta.2 → 4.3.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 (127) hide show
  1. package/README.md +16 -17
  2. package/dist/bundles/anime.esm.js +446 -245
  3. package/dist/bundles/anime.esm.min.js +3 -3
  4. package/dist/bundles/anime.umd.js +446 -245
  5. package/dist/bundles/anime.umd.min.js +3 -3
  6. package/dist/modules/animatable/animatable.cjs +2 -2
  7. package/dist/modules/animatable/animatable.js +2 -2
  8. package/dist/modules/animatable/index.cjs +2 -2
  9. package/dist/modules/animatable/index.js +2 -2
  10. package/dist/modules/animation/additive.cjs +2 -2
  11. package/dist/modules/animation/additive.js +2 -2
  12. package/dist/modules/animation/animation.cjs +8 -5
  13. package/dist/modules/animation/animation.js +9 -6
  14. package/dist/modules/animation/composition.cjs +2 -2
  15. package/dist/modules/animation/composition.js +2 -2
  16. package/dist/modules/animation/index.cjs +2 -2
  17. package/dist/modules/animation/index.js +2 -2
  18. package/dist/modules/core/clock.cjs +2 -2
  19. package/dist/modules/core/clock.js +2 -2
  20. package/dist/modules/core/colors.cjs +2 -2
  21. package/dist/modules/core/colors.js +2 -2
  22. package/dist/modules/core/consts.cjs +2 -2
  23. package/dist/modules/core/consts.js +2 -2
  24. package/dist/modules/core/globals.cjs +3 -3
  25. package/dist/modules/core/globals.js +3 -3
  26. package/dist/modules/core/helpers.cjs +4 -4
  27. package/dist/modules/core/helpers.js +4 -4
  28. package/dist/modules/core/render.cjs +2 -2
  29. package/dist/modules/core/render.js +2 -2
  30. package/dist/modules/core/styles.cjs +2 -2
  31. package/dist/modules/core/styles.js +2 -2
  32. package/dist/modules/core/targets.cjs +2 -2
  33. package/dist/modules/core/targets.js +2 -2
  34. package/dist/modules/core/transforms.cjs +2 -2
  35. package/dist/modules/core/transforms.js +2 -2
  36. package/dist/modules/core/units.cjs +2 -2
  37. package/dist/modules/core/units.js +2 -2
  38. package/dist/modules/core/values.cjs +2 -2
  39. package/dist/modules/core/values.js +2 -2
  40. package/dist/modules/draggable/draggable.cjs +2 -2
  41. package/dist/modules/draggable/draggable.js +2 -2
  42. package/dist/modules/draggable/index.cjs +2 -2
  43. package/dist/modules/draggable/index.js +2 -2
  44. package/dist/modules/easings/cubic-bezier/index.cjs +2 -2
  45. package/dist/modules/easings/cubic-bezier/index.js +2 -2
  46. package/dist/modules/easings/eases/index.cjs +2 -2
  47. package/dist/modules/easings/eases/index.js +2 -2
  48. package/dist/modules/easings/eases/parser.cjs +2 -2
  49. package/dist/modules/easings/eases/parser.js +2 -2
  50. package/dist/modules/easings/index.cjs +2 -2
  51. package/dist/modules/easings/index.js +2 -2
  52. package/dist/modules/easings/irregular/index.cjs +2 -2
  53. package/dist/modules/easings/irregular/index.js +2 -2
  54. package/dist/modules/easings/linear/index.cjs +2 -2
  55. package/dist/modules/easings/linear/index.js +2 -2
  56. package/dist/modules/easings/none.cjs +2 -2
  57. package/dist/modules/easings/none.js +2 -2
  58. package/dist/modules/easings/spring/index.cjs +2 -2
  59. package/dist/modules/easings/spring/index.js +2 -2
  60. package/dist/modules/easings/steps/index.cjs +2 -2
  61. package/dist/modules/easings/steps/index.js +2 -2
  62. package/dist/modules/engine/engine.cjs +2 -2
  63. package/dist/modules/engine/engine.js +2 -2
  64. package/dist/modules/engine/index.cjs +2 -2
  65. package/dist/modules/engine/index.js +2 -2
  66. package/dist/modules/events/index.cjs +2 -2
  67. package/dist/modules/events/index.js +2 -2
  68. package/dist/modules/events/scroll.cjs +2 -2
  69. package/dist/modules/events/scroll.js +2 -2
  70. package/dist/modules/index.cjs +2 -2
  71. package/dist/modules/index.js +2 -2
  72. package/dist/modules/layout/index.cjs +2 -2
  73. package/dist/modules/layout/index.js +2 -2
  74. package/dist/modules/layout/layout.cjs +427 -228
  75. package/dist/modules/layout/layout.d.ts +44 -38
  76. package/dist/modules/layout/layout.js +429 -230
  77. package/dist/modules/scope/index.cjs +2 -2
  78. package/dist/modules/scope/index.js +2 -2
  79. package/dist/modules/scope/scope.cjs +2 -2
  80. package/dist/modules/scope/scope.js +2 -2
  81. package/dist/modules/svg/drawable.cjs +2 -2
  82. package/dist/modules/svg/drawable.js +2 -2
  83. package/dist/modules/svg/helpers.cjs +2 -2
  84. package/dist/modules/svg/helpers.js +2 -2
  85. package/dist/modules/svg/index.cjs +2 -2
  86. package/dist/modules/svg/index.js +2 -2
  87. package/dist/modules/svg/morphto.cjs +2 -2
  88. package/dist/modules/svg/morphto.js +2 -2
  89. package/dist/modules/svg/motionpath.cjs +2 -2
  90. package/dist/modules/svg/motionpath.js +2 -2
  91. package/dist/modules/text/index.cjs +2 -2
  92. package/dist/modules/text/index.js +2 -2
  93. package/dist/modules/text/split.cjs +2 -2
  94. package/dist/modules/text/split.js +2 -2
  95. package/dist/modules/timeline/index.cjs +2 -2
  96. package/dist/modules/timeline/index.js +2 -2
  97. package/dist/modules/timeline/position.cjs +2 -2
  98. package/dist/modules/timeline/position.js +2 -2
  99. package/dist/modules/timeline/timeline.cjs +2 -2
  100. package/dist/modules/timeline/timeline.js +2 -2
  101. package/dist/modules/timer/index.cjs +2 -2
  102. package/dist/modules/timer/index.js +2 -2
  103. package/dist/modules/timer/timer.cjs +5 -4
  104. package/dist/modules/timer/timer.d.ts +2 -1
  105. package/dist/modules/timer/timer.js +5 -4
  106. package/dist/modules/types/index.d.ts +6 -6
  107. package/dist/modules/utils/chainable.cjs +2 -2
  108. package/dist/modules/utils/chainable.js +2 -2
  109. package/dist/modules/utils/index.cjs +2 -2
  110. package/dist/modules/utils/index.js +2 -2
  111. package/dist/modules/utils/number.cjs +2 -2
  112. package/dist/modules/utils/number.js +2 -2
  113. package/dist/modules/utils/random.cjs +2 -2
  114. package/dist/modules/utils/random.js +2 -2
  115. package/dist/modules/utils/stagger.cjs +2 -2
  116. package/dist/modules/utils/stagger.js +2 -2
  117. package/dist/modules/utils/target.cjs +2 -2
  118. package/dist/modules/utils/target.js +2 -2
  119. package/dist/modules/utils/time.cjs +2 -2
  120. package/dist/modules/utils/time.js +2 -2
  121. package/dist/modules/waapi/composition.cjs +2 -2
  122. package/dist/modules/waapi/composition.js +2 -2
  123. package/dist/modules/waapi/index.cjs +2 -2
  124. package/dist/modules/waapi/index.js +2 -2
  125. package/dist/modules/waapi/waapi.cjs +12 -7
  126. package/dist/modules/waapi/waapi.js +12 -7
  127. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Anime.js - UMD bundle
3
- * @version v4.3.0-beta.2
3
+ * @version v4.3.0
4
4
  * @license MIT
5
- * @copyright 2025 - Julian Garnier
5
+ * @copyright 2026 - Julian Garnier
6
6
  */
7
7
 
8
8
  // Global types
@@ -22,7 +22,7 @@
22
22
  * @property {Number|FunctionValue} [duration]
23
23
  * @property {Number|FunctionValue} [delay]
24
24
  * @property {Number} [loopDelay]
25
- * @property {EasingParam} [ease]
25
+ * @property {EasingParam|FunctionValue} [ease]
26
26
  * @property {'none'|'replace'|'blend'|compositionTypes} [composition]
27
27
  * @property {(v: any) => any} [modifier]
28
28
  * @property {Callback<Tickable>} [onBegin]
@@ -187,7 +187,7 @@
187
187
  * @param {Target} target - The animated target
188
188
  * @param {Number} index - The target index
189
189
  * @param {Number} length - The total number of animated targets
190
- * @return {Number|String|TweenObjectValue|Array.<Number|String|TweenObjectValue>}
190
+ * @return {Number|String|TweenObjectValue|EasingParam|Array.<Number|String|TweenObjectValue>}
191
191
  */
192
192
 
193
193
  /**
@@ -255,7 +255,7 @@
255
255
  // JSAnimation types
256
256
 
257
257
  /**
258
- * @typedef {Number|String|FunctionValue} TweenParamValue
258
+ * @typedef {Number|String|FunctionValue|EasingParam} TweenParamValue
259
259
  */
260
260
 
261
261
  /**
@@ -270,7 +270,7 @@
270
270
  * @typedef {Object} TweenParamsOptions
271
271
  * @property {TweenParamValue} [duration]
272
272
  * @property {TweenParamValue} [delay]
273
- * @property {EasingParam} [ease]
273
+ * @property {EasingParam|FunctionValue} [ease]
274
274
  * @property {TweenModifier} [modifier]
275
275
  * @property {TweenComposition} [composition]
276
276
  */
@@ -379,7 +379,7 @@
379
379
  * @param {DOMTarget} target - The animated target
380
380
  * @param {Number} index - The target index
381
381
  * @param {Number} length - The total number of animated targets
382
- * @return {WAAPITweenValue}
382
+ * @return {WAAPITweenValue|WAAPIEasingParam}
383
383
  */
384
384
 
385
385
  /**
@@ -405,7 +405,7 @@
405
405
  * @property {Number} [playbackRate]
406
406
  * @property {Number|WAAPIFunctionValue} [duration]
407
407
  * @property {Number|WAAPIFunctionValue} [delay]
408
- * @property {WAAPIEasingParam} [ease]
408
+ * @property {WAAPIEasingParam|WAAPIFunctionValue} [ease]
409
409
  * @property {CompositeOperation} [composition]
410
410
  * @property {Boolean} [persist]
411
411
  * @property {Callback<WAAPIAnimation>} [onComplete]
@@ -807,7 +807,7 @@
807
807
 
808
808
  const devTools = isBrowser && win.AnimeJSDevTools;
809
809
 
810
- const globalVersions = { version: '4.3.0-beta.2', engine: null };
810
+ const globalVersions = { version: '4.3.0', engine: null };
811
811
 
812
812
  if (isBrowser) {
813
813
  if (!win.AnimeJS) win.AnimeJS = [];
@@ -855,9 +855,9 @@
855
855
  /**@param {any} a @return {Boolean} */
856
856
  const isHex = a => hexTestRgx.test(a);
857
857
  /**@param {any} a @return {Boolean} */
858
- const isRgb = a => stringStartsWith(a, 'rgb');
858
+ const isRgb = a => stringStartsWith(a, 'rgb') && a[a.length - 1] === ')';
859
859
  /**@param {any} a @return {Boolean} */
860
- const isHsl = a => stringStartsWith(a, 'hsl');
860
+ const isHsl = a => stringStartsWith(a, 'hsl') && a[a.length - 1] === ')';
861
861
  /**@param {any} a @return {Boolean} */
862
862
  const isCol = a => isHex(a) || isRgb(a) || isHsl(a);
863
863
  /**@param {any} a @return {Boolean} */
@@ -2900,10 +2900,11 @@
2900
2900
 
2901
2901
  /**
2902
2902
  * Imediatly completes the timer, cancels it and triggers the onComplete callback
2903
+ * @param {Boolean|Number} [muteCallbacks]
2903
2904
  * @return {this}
2904
2905
  */
2905
- complete() {
2906
- return this.seek(this.duration).cancel();
2906
+ complete(muteCallbacks = 0) {
2907
+ return this.seek(this.duration, muteCallbacks).cancel();
2907
2908
  }
2908
2909
 
2909
2910
  /**
@@ -3529,10 +3530,13 @@
3529
3530
  tweenToValue = computedToValue;
3530
3531
  }
3531
3532
  const tweenFromValue = getFunctionValue(key.from, target, ti, tl);
3532
- const keyEasing = key.ease;
3533
+ const easeToParse = key.ease || tEasing;
3534
+
3535
+ const easeFunctionResult = getFunctionValue(easeToParse, target, ti, tl);
3536
+ const keyEasing = isFnc(easeFunctionResult) || isStr(easeFunctionResult) ? easeFunctionResult : easeToParse;
3537
+
3533
3538
  const hasSpring = !isUnd(keyEasing) && !isUnd(/** @type {Spring} */(keyEasing).ease);
3534
- // Easing are treated differently and don't accept function based value to prevent having to pass a function wrapper that returns an other function all the time
3535
- const tweenEasing = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing || tEasing;
3539
+ const tweenEasing = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing;
3536
3540
  // Calculate default individual keyframe duration by dividing the tl of keyframes
3537
3541
  const tweenDuration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : getFunctionValue(setValue(key.duration, (l > 1 ? getFunctionValue(tDuration, target, ti, tl) / l : tDuration)), target, ti, tl);
3538
3542
  // Default delay value should only be applied to the first tween
@@ -7475,8 +7479,6 @@
7475
7479
  console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`);
7476
7480
  }
7477
7481
 
7478
- const ease = setValue(params.ease, parseWAAPIEasing(globals.defaults.ease));
7479
- const spring = /** @type {Spring} */(ease).ease && ease;
7480
7482
  const autoplay = setValue(params.autoplay, globals.defaults.autoplay);
7481
7483
  const scroll = autoplay && /** @type {ScrollObserver} */(autoplay).link ? autoplay : false;
7482
7484
  const alternate = params.alternate && /** @type {Boolean} */(params.alternate) === true;
@@ -7487,8 +7489,6 @@
7487
7489
  const direction = alternate ? reversed ? 'alternate-reverse' : 'alternate' : reversed ? 'reverse' : 'normal';
7488
7490
  /** @type {FillMode} */
7489
7491
  const fill = 'both'; // We use 'both' here because the animation can be reversed during playback
7490
- /** @type {String} */
7491
- const easing = parseWAAPIEasing(ease);
7492
7492
  const timeScale = (globals.timeScale === 1 ? 1 : K);
7493
7493
 
7494
7494
  /** @type {DOMTargetsArray}] */
@@ -7529,6 +7529,15 @@
7529
7529
  const elStyle = $el.style;
7530
7530
  const inlineStyles = this._inlineStyles[i] = {};
7531
7531
 
7532
+ const easeToParse = setValue(params.ease, globals.defaults.ease);
7533
+
7534
+ const easeFunctionResult = getFunctionValue(easeToParse, $el, i, targetsLength);
7535
+ const keyEasing = isFnc(easeFunctionResult) || isStr(easeFunctionResult) ? easeFunctionResult : easeToParse;
7536
+
7537
+ const spring = /** @type {Spring} */(easeToParse).ease && easeToParse;
7538
+ /** @type {String} */
7539
+ const easing = parseWAAPIEasing(keyEasing);
7540
+
7532
7541
  /** @type {Number} */
7533
7542
  const duration = (spring ? /** @type {Spring} */(spring).settlingDuration : getFunctionValue(setValue(params.duration, globals.defaults.duration), $el, i, targetsLength)) * timeScale;
7534
7543
  /** @type {Number} */
@@ -7553,7 +7562,7 @@
7553
7562
  let parsedPropertyValue;
7554
7563
  if (isObj(propertyValue)) {
7555
7564
  const tweenOptions = /** @type {WAAPITweenOptions} */(propertyValue);
7556
- const tweenOptionsEase = setValue(tweenOptions.ease, ease);
7565
+ const tweenOptionsEase = setValue(tweenOptions.ease, easing);
7557
7566
  const tweenOptionsSpring = /** @type {Spring} */(tweenOptionsEase).ease && tweenOptionsEase;
7558
7567
  const to = /** @type {WAAPITweenOptions} */(tweenOptions).to;
7559
7568
  const from = /** @type {WAAPITweenOptions} */(tweenOptions).from;
@@ -7792,34 +7801,54 @@
7792
7801
 
7793
7802
 
7794
7803
 
7804
+
7805
+
7795
7806
  /**
7796
7807
  * @typedef {DOMTargetSelector|Array<DOMTargetSelector>} LayoutChildrenParam
7797
7808
  */
7798
7809
 
7799
7810
  /**
7800
- * @typedef {Record<String, Number|String>} LayoutStateParams
7811
+ * @typedef {Object} LayoutAnimationTimingsParams
7812
+ * @property {Number|FunctionValue} [delay]
7813
+ * @property {Number|FunctionValue} [duration]
7814
+ * @property {EasingParam|FunctionValue} [ease]
7801
7815
  */
7802
7816
 
7803
7817
  /**
7804
- * @typedef {Object} LayoutAnimationParams
7818
+ * @typedef {Record<String, Number|String|FunctionValue>} LayoutStateAnimationProperties
7819
+ */
7820
+
7821
+ /**
7822
+ * @typedef {LayoutStateAnimationProperties & LayoutAnimationTimingsParams} LayoutStateParams
7823
+ */
7824
+
7825
+ /**
7826
+ * @typedef {Object} LayoutSpecificAnimationParams
7805
7827
  * @property {Number|FunctionValue} [delay]
7806
7828
  * @property {Number|FunctionValue} [duration]
7807
- * @property {EasingParam} [ease]
7808
- * @property {LayoutStateParams} [frozen]
7809
- * @property {LayoutStateParams} [added]
7810
- * @property {LayoutStateParams} [removed]
7811
- * @property {Callback<AutoLayout>} [onComplete]
7829
+ * @property {EasingParam|FunctionValue} [ease]
7830
+ * @property {EasingParam} [playbackEase]
7831
+ * @property {LayoutStateParams} [swapAt]
7832
+ * @property {LayoutStateParams} [enterFrom]
7833
+ * @property {LayoutStateParams} [leaveTo]
7812
7834
  */
7813
7835
 
7814
7836
  /**
7815
- * @typedef {LayoutAnimationParams & {
7816
- * children?: LayoutChildrenParam,
7817
- * properties?: Array<String>,
7818
- * }} AutoLayoutParams
7837
+ * @typedef {LayoutSpecificAnimationParams & TimerParams & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} LayoutAnimationParams
7819
7838
  */
7820
7839
 
7821
7840
  /**
7822
- * @typedef {Record<String, Number|String> & {
7841
+ * @typedef {Object} LayoutOptions
7842
+ * @property {LayoutChildrenParam} [children]
7843
+ * @property {Array<String>} [properties]
7844
+ */
7845
+
7846
+ /**
7847
+ * @typedef {LayoutAnimationParams & LayoutOptions} AutoLayoutParams
7848
+ */
7849
+
7850
+ /**
7851
+ * @typedef {Record<String, Number|String|FunctionValue> & {
7823
7852
  * transform: String,
7824
7853
  * x: Number,
7825
7854
  * y: Number,
@@ -7840,13 +7869,15 @@
7840
7869
  * @property {Number} total
7841
7870
  * @property {Number} delay
7842
7871
  * @property {Number} duration
7872
+ * @property {EasingParam} ease
7843
7873
  * @property {DOMTarget} $measure
7844
7874
  * @property {LayoutSnapshot} state
7845
7875
  * @property {AutoLayout} layout
7846
7876
  * @property {LayoutNode|null} parentNode
7847
7877
  * @property {Boolean} isTarget
7878
+ * @property {Boolean} isEntering
7879
+ * @property {Boolean} isLeaving
7848
7880
  * @property {Boolean} hasTransform
7849
- * @property {Boolean} isAnimated
7850
7881
  * @property {Array<String>} inlineStyles
7851
7882
  * @property {String|null} inlineTransforms
7852
7883
  * @property {String|null} inlineTransition
@@ -7987,7 +8018,7 @@
7987
8018
  * @param {DOMTarget} $el
7988
8019
  * @param {LayoutNode|null} parentNode
7989
8020
  * @param {LayoutSnapshot} state
7990
- * @param {LayoutNode} [recycledNode]
8021
+ * @param {LayoutNode} recycledNode
7991
8022
  * @return {LayoutNode}
7992
8023
  */
7993
8024
  const createNode = ($el, parentNode, state, recycledNode) => {
@@ -8001,12 +8032,15 @@
8001
8032
  node.total = 1;
8002
8033
  node.delay = 0;
8003
8034
  node.duration = 0;
8035
+ node.ease = null;
8004
8036
  node.state = state;
8005
8037
  node.layout = state.layout;
8006
8038
  node.parentNode = parentNode || null;
8007
8039
  node.isTarget = false;
8040
+ node.isEntering = false;
8041
+ node.isLeaving = false;
8042
+ node.isInlined = false;
8008
8043
  node.hasTransform = false;
8009
- node.isAnimated = false;
8010
8044
  node.inlineStyles = [];
8011
8045
  node.inlineTransforms = null;
8012
8046
  node.inlineTransition = null;
@@ -8014,7 +8048,6 @@
8014
8048
  node.branchRemoved = false;
8015
8049
  node.branchNotRendered = false;
8016
8050
  node.sizeChanged = false;
8017
- node.isInlined = false;
8018
8051
  node.hasVisibilitySwap = false;
8019
8052
  node.hasDisplayNone = false;
8020
8053
  node.hasVisibilityHidden = false;
@@ -8157,7 +8190,7 @@
8157
8190
 
8158
8191
  /**
8159
8192
  * @param {LayoutNode} node
8160
- * @param {LayoutStateParams} [props]
8193
+ * @param {LayoutStateAnimationProperties} [props]
8161
8194
  */
8162
8195
  const updateNodeProperties = (node, props) => {
8163
8196
  if (!props) return;
@@ -8166,6 +8199,19 @@
8166
8199
  }
8167
8200
  };
8168
8201
 
8202
+ /**
8203
+ * @param {LayoutNode} node
8204
+ * @param {LayoutAnimationTimingsParams} params
8205
+ */
8206
+ const updateNodeTimingParams = (node, params) => {
8207
+ const easeFunctionResult = getFunctionValue(params.ease, node.$el, node.index, node.total);
8208
+ const keyEasing = isFnc(easeFunctionResult) ? easeFunctionResult : params.ease;
8209
+ const hasSpring = !isUnd(keyEasing) && !isUnd(/** @type {Spring} */(keyEasing).ease);
8210
+ node.ease = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing;
8211
+ node.duration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : getFunctionValue(params.duration, node.$el, node.index, node.total);
8212
+ node.delay = getFunctionValue(params.delay, node.$el, node.index, node.total);
8213
+ };
8214
+
8169
8215
  /**
8170
8216
  * @param {LayoutNode} node
8171
8217
  */
@@ -8240,9 +8286,9 @@
8240
8286
  node.$measure.style.removeProperty('visibility');
8241
8287
  }
8242
8288
  }
8243
- if (node.measuredIsRemoved) {
8244
- node.layout.pendingRemoved.delete(node.$el);
8245
- }
8289
+ // if (node.measuredIsRemoved) {
8290
+ node.layout.pendingRemoval.delete(node.$el);
8291
+ // }
8246
8292
  };
8247
8293
 
8248
8294
  /**
@@ -8292,6 +8338,7 @@
8292
8338
  */
8293
8339
  revert() {
8294
8340
  this.forEachNode(node => {
8341
+ this.layout.pendingRemoval.delete(node.$el);
8295
8342
  node.$el.removeAttribute('data-layout-id');
8296
8343
  node.$measure.removeAttribute('data-layout-id');
8297
8344
  });
@@ -8303,34 +8350,22 @@
8303
8350
 
8304
8351
  /**
8305
8352
  * @param {DOMTarget} $el
8306
- * @return {LayoutNodeProperties|undefined}
8353
+ * @return {LayoutNode}
8307
8354
  */
8308
- get($el) {
8309
- const node = this.nodes.get($el.dataset.layoutId);
8310
- if (!node) {
8311
- console.warn(`No node found on state`);
8312
- return;
8313
- }
8314
- return node.properties;
8355
+ getNode($el) {
8356
+ if (!$el || !$el.dataset) return;
8357
+ return this.nodes.get($el.dataset.layoutId);
8315
8358
  }
8316
8359
 
8317
8360
  /**
8318
8361
  * @param {DOMTarget} $el
8319
8362
  * @param {String} prop
8320
- * @return {Number|String|undefined}
8363
+ * @return {Number|String}
8321
8364
  */
8322
- getValue($el, prop) {
8323
- if (!$el || !$el.dataset) {
8324
- console.warn(`No element found on state (${$el})`);
8325
- return;
8326
- }
8327
- const node = this.nodes.get($el.dataset.layoutId);
8328
- if (!node) {
8329
- console.warn(`No node found on state`);
8330
- return;
8331
- }
8332
- const value = node.properties[prop];
8333
- if (!isUnd(value)) return getFunctionValue(value, $el, node.index, node.total);
8365
+ getComputedValue($el, prop) {
8366
+ const node = this.getNode($el);
8367
+ if (!node) return;
8368
+ return /** @type {Number|String} */(node.properties[prop]);
8334
8369
  }
8335
8370
 
8336
8371
  /**
@@ -8391,10 +8426,10 @@
8391
8426
  const $parent = /** @type {LayoutNode|null} */(stack.pop());
8392
8427
  /** @type {DOMTarget|null} */
8393
8428
  const $current = /** @type {DOMTarget|null} */(stack.pop());
8429
+
8394
8430
  if (!$current || $current.nodeType !== 1 || isSvg($current)) continue;
8395
8431
 
8396
8432
  const skipMeasurements = $parent ? $parent.measuredIsRemoved : false;
8397
-
8398
8433
  const computedStyle = skipMeasurements ? hiddenComputedStyle : getComputedStyle($current);
8399
8434
  const hasDisplayNone = skipMeasurements ? true : computedStyle.display === 'none';
8400
8435
  const hasVisibilityHidden = skipMeasurements ? true : computedStyle.visibility === 'hidden';
@@ -8441,11 +8476,10 @@
8441
8476
  node.branchRemoved = false;
8442
8477
  node.branchNotRendered = false;
8443
8478
  node.isTarget = false;
8444
- node.isAnimated = false;
8479
+ node.sizeChanged = false;
8445
8480
  node.hasVisibilityHidden = hasVisibilityHidden;
8446
8481
  node.hasDisplayNone = hasDisplayNone;
8447
8482
  node.hasVisibilitySwap = (hasVisibilityHidden && !node.measuredHasVisibilityHidden) || (hasDisplayNone && !node.measuredHasDisplayNone);
8448
- // node.hasVisibilitySwap = (hasVisibilityHidden !== node.measuredHasVisibilityHidden) || (hasDisplayNone !== node.measuredHasDisplayNone);
8449
8483
 
8450
8484
  this.nodes.set(node.id, node);
8451
8485
 
@@ -8464,6 +8498,7 @@
8464
8498
  $parent._tail = node;
8465
8499
  }
8466
8500
  } else {
8501
+ // Each disconnected subtree becomes its own root in the snapshot graph
8467
8502
  this.rootNodes.add(node);
8468
8503
  }
8469
8504
 
@@ -8507,11 +8542,29 @@
8507
8542
  * @return {this}
8508
8543
  */
8509
8544
  record() {
8510
- const { children, root } = this.layout;
8545
+ const layout = this.layout;
8546
+ const children = layout.children;
8547
+ const root = layout.root;
8511
8548
  const toParse = isArr(children) ? children : [children];
8512
8549
  const scoped = [];
8513
8550
  const scopeRoot = children === '*' ? root : scope.root;
8514
8551
 
8552
+ // Mute transition and transforms of root ancestors before recording the state
8553
+
8554
+ /** @type {Array<DOMTarget|String|null>} */
8555
+ const rootAncestorTransformStore = [];
8556
+ let $ancestor = root.parentElement;
8557
+ while ($ancestor && $ancestor.nodeType === 1) {
8558
+ const computedStyle = getComputedStyle($ancestor);
8559
+ if (computedStyle.transform && computedStyle.transform !== 'none') {
8560
+ const inlineTransform = $ancestor.style.transform || '';
8561
+ const inlineTransition = muteElementTransition($ancestor);
8562
+ rootAncestorTransformStore.push($ancestor, inlineTransform, inlineTransition);
8563
+ $ancestor.style.transform = 'none';
8564
+ }
8565
+ $ancestor = $ancestor.parentElement;
8566
+ }
8567
+
8515
8568
  for (let i = 0, l = toParse.length; i < l; i++) {
8516
8569
  const child = toParse[i];
8517
8570
  scoped[i] = isStr(child) ? scopeRoot.querySelectorAll(child) : child;
@@ -8527,9 +8580,13 @@
8527
8580
  rootNode.isTarget = true;
8528
8581
  this.rootNode = rootNode;
8529
8582
 
8530
- // Track ids of nodes that belong to the current root to filter detached matches
8531
8583
  const inRootNodeIds = new Set();
8584
+ // Update index and total for inital timing calculation
8585
+ let index = 0, total = this.nodes.size;
8532
8586
  this.nodes.forEach((node, id) => {
8587
+ node.index = index++;
8588
+ node.total = total;
8589
+ // Track ids of nodes that belong to the current root to filter detached matches
8533
8590
  if (node && node.measuredIsInsideRoot) {
8534
8591
  inRootNodeIds.add(id);
8535
8592
  }
@@ -8559,7 +8616,7 @@
8559
8616
 
8560
8617
  for (let i = 0, l = parsedChildren.length; i < l; i++) {
8561
8618
  const $el = parsedChildren[i];
8562
- const node = this.nodes.get($el.dataset.layoutId);
8619
+ const node = this.getNode($el);
8563
8620
  if (node) {
8564
8621
  let cur = node;
8565
8622
  while (cur) {
@@ -8573,18 +8630,52 @@
8573
8630
  this.scrollX = window.scrollX;
8574
8631
  this.scrollY = window.scrollY;
8575
8632
 
8576
- const total = this.nodes.size;
8577
-
8578
8633
  this.forEachNode(restoreNodeTransform);
8579
- this.forEachNode((node, i) => {
8580
- node.index = i;
8581
- node.total = total;
8582
- });
8634
+
8635
+ // Restore transition and transforms of root ancestors
8636
+
8637
+ for (let i = 0, l = rootAncestorTransformStore.length; i < l; i += 3) {
8638
+ const $el = /** @type {DOMTarget} */(rootAncestorTransformStore[i]);
8639
+ const inlineTransform = /** @type {String} */(rootAncestorTransformStore[i + 1]);
8640
+ const inlineTransition = /** @type {String|null} */(rootAncestorTransformStore[i + 2]);
8641
+ if (inlineTransform && inlineTransform !== '') {
8642
+ $el.style.transform = inlineTransform;
8643
+ } else {
8644
+ $el.style.removeProperty('transform');
8645
+ }
8646
+ restoreElementTransition($el, inlineTransition);
8647
+ }
8583
8648
 
8584
8649
  return this;
8585
8650
  }
8586
8651
  }
8587
8652
 
8653
+ /**
8654
+ * @param {LayoutStateParams} params
8655
+ * @return {[LayoutStateAnimationProperties, LayoutAnimationTimingsParams]}
8656
+ */
8657
+ function splitPropertiesFromParams(params) {
8658
+ /** @type {LayoutStateAnimationProperties} */
8659
+ const properties = {};
8660
+ /** @type {LayoutAnimationTimingsParams} */
8661
+ const parameters = {};
8662
+ for (let name in params) {
8663
+ const value = params[name];
8664
+ const isEase = name === 'ease';
8665
+ const isTiming = name === 'duration' || name === 'delay';
8666
+ if (isTiming || isEase) {
8667
+ if (isEase) {
8668
+ parameters[name] = /** @type {EasingParam} */(value);
8669
+ } else {
8670
+ parameters[name] = /** @type {Number|FunctionValue} */(value);
8671
+ }
8672
+ } else {
8673
+ properties[name] = /** @type {Number|String} */(value);
8674
+ }
8675
+ }
8676
+ return [properties, parameters];
8677
+ }
8678
+
8588
8679
  class AutoLayout {
8589
8680
  /**
8590
8681
  * @param {DOMTargetSelector} root
@@ -8592,10 +8683,16 @@
8592
8683
  */
8593
8684
  constructor(root, params = {}) {
8594
8685
  if (scope.current) scope.current.register(this);
8595
- const frozenParams = params.frozen;
8596
- const addedParams = params.added;
8597
- const removedParams = params.removed;
8598
- const propsParams = params.properties;
8686
+ const swapAtSplitParams = splitPropertiesFromParams(params.swapAt);
8687
+ const enterFromSplitParams = splitPropertiesFromParams(params.enterFrom);
8688
+ const leaveToSplitParams = splitPropertiesFromParams(params.leaveTo);
8689
+ const transitionProperties = params.properties;
8690
+ /** @type {Number|FunctionValue} */
8691
+ params.duration = setValue(params.duration, 350);
8692
+ /** @type {Number|FunctionValue} */
8693
+ params.delay = setValue(params.delay, 0);
8694
+ /** @type {EasingParam|FunctionValue} */
8695
+ params.ease = setValue(params.ease, 'inOut(3.5)');
8599
8696
  /** @type {AutoLayoutParams} */
8600
8697
  this.params = params;
8601
8698
  /** @type {DOMTarget} */
@@ -8606,29 +8703,27 @@
8606
8703
  this.children = params.children || '*';
8607
8704
  /** @type {Boolean} */
8608
8705
  this.absoluteCoords = false;
8609
- /** @type {Number|FunctionValue} */
8610
- this.duration = setValue(params.duration, 500);
8611
- /** @type {Number|FunctionValue} */
8612
- this.delay = setValue(params.delay, 0);
8613
- /** @type {EasingParam} */
8614
- this.ease = setValue(params.ease, 'inOut(3.5)');
8615
- /** @type {Callback<this>} */
8616
- this.onComplete = setValue(params.onComplete, /** @type {Callback<this>} */(noop));
8617
8706
  /** @type {LayoutStateParams} */
8618
- this.frozenParams = frozenParams || { opacity: 0 };
8707
+ this.swapAtParams = mergeObjects(params.swapAt || { opacity: 0 }, { ease: 'inOut(1.75)' });
8619
8708
  /** @type {LayoutStateParams} */
8620
- this.addedParams = addedParams || { opacity: 0 };
8709
+ this.enterFromParams = params.enterFrom || { opacity: 0 };
8621
8710
  /** @type {LayoutStateParams} */
8622
- this.removedParams = removedParams || { opacity: 0 };
8711
+ this.leaveToParams = params.leaveTo || { opacity: 0 };
8623
8712
  /** @type {Set<String>} */
8624
8713
  this.properties = new Set([
8625
8714
  'opacity',
8715
+ 'fontSize',
8716
+ 'color',
8717
+ 'backgroundColor',
8626
8718
  'borderRadius',
8719
+ 'border',
8720
+ 'filter',
8721
+ 'clipPath',
8627
8722
  ]);
8628
- if (frozenParams) for (let name in frozenParams) this.properties.add(name);
8629
- if (addedParams) for (let name in addedParams) this.properties.add(name);
8630
- if (removedParams) for (let name in removedParams) this.properties.add(name);
8631
- if (propsParams) for (let i = 0, l = propsParams.length; i < l; i++) this.properties.add(propsParams[i]);
8723
+ if (swapAtSplitParams[0]) for (let name in swapAtSplitParams[0]) this.properties.add(name);
8724
+ if (enterFromSplitParams[0]) for (let name in enterFromSplitParams[0]) this.properties.add(name);
8725
+ if (leaveToSplitParams[0]) for (let name in leaveToSplitParams[0]) this.properties.add(name);
8726
+ if (transitionProperties) for (let i = 0, l = transitionProperties.length; i < l; i++) this.properties.add(transitionProperties[i]);
8632
8727
  /** @type {Set<String>} */
8633
8728
  this.recordedProperties = new Set([
8634
8729
  'display',
@@ -8648,24 +8743,26 @@
8648
8743
  ]);
8649
8744
  this.properties.forEach(prop => this.recordedProperties.add(prop));
8650
8745
  /** @type {WeakSet<DOMTarget>} */
8651
- this.pendingRemoved = new WeakSet();
8746
+ this.pendingRemoval = new WeakSet();
8652
8747
  /** @type {Map<DOMTarget, String|null>} */
8653
8748
  this.transitionMuteStore = new Map();
8654
8749
  /** @type {LayoutSnapshot} */
8655
8750
  this.oldState = new LayoutSnapshot(this);
8656
8751
  /** @type {LayoutSnapshot} */
8657
8752
  this.newState = new LayoutSnapshot(this);
8658
- /** @type {Timeline|null} */
8753
+ /** @type {Timeline} */
8659
8754
  this.timeline = null;
8660
- /** @type {WAAPIAnimation|null} */
8755
+ /** @type {WAAPIAnimation} */
8661
8756
  this.transformAnimation = null;
8662
8757
  /** @type {Array<DOMTarget>} */
8663
- this.frozen = [];
8758
+ this.animating = [];
8759
+ /** @type {Array<DOMTarget>} */
8760
+ this.swapping = [];
8664
8761
  /** @type {Array<DOMTarget>} */
8665
- this.removed = [];
8762
+ this.leaving = [];
8666
8763
  /** @type {Array<DOMTarget>} */
8667
- this.added = [];
8668
- // Record the current state as the old state to init the data attributes
8764
+ this.entering = [];
8765
+ // Record the current state as the old state to init the data attributes and allow imediate .animate()
8669
8766
  this.oldState.record();
8670
8767
  // And all layout transition muted during the record
8671
8768
  restoreLayoutTransition(this.transitionMuteStore);
@@ -8675,6 +8772,7 @@
8675
8772
  * @return {this}
8676
8773
  */
8677
8774
  revert() {
8775
+ this.root.classList.remove('is-animated');
8678
8776
  if (this.timeline) {
8679
8777
  this.timeline.complete();
8680
8778
  this.timeline = null;
@@ -8683,8 +8781,7 @@
8683
8781
  this.transformAnimation.complete();
8684
8782
  this.transformAnimation = null;
8685
8783
  }
8686
- this.root.classList.remove('is-animated');
8687
- this.frozen.length = this.removed.length = this.added.length = 0;
8784
+ this.animating.length = this.swapping.length = this.leaving.length = this.entering.length = 0;
8688
8785
  this.oldState.revert();
8689
8786
  this.newState.revert();
8690
8787
  requestAnimationFrame(() => restoreLayoutTransition(this.transitionMuteStore));
@@ -8717,20 +8814,72 @@
8717
8814
  * @return {Timeline}
8718
8815
  */
8719
8816
  animate(params = {}) {
8720
- const delay = setValue(params.delay, this.delay);
8721
- const duration = setValue(params.duration, this.duration);
8722
- const onComplete = setValue(params.onComplete, this.onComplete);
8723
- const frozenParams = params.frozen ? mergeObjects(params.frozen, this.frozenParams) : this.frozenParams;
8724
- const addedParams = params.added ? mergeObjects(params.added, this.addedParams) : this.addedParams;
8725
- const removedParams = params.removed ? mergeObjects(params.removed, this.removedParams) : this.removedParams;
8817
+ /** @type { LayoutAnimationTimingsParams } */
8818
+ const animationTimings = {
8819
+ ease: setValue(params.ease, this.params.ease),
8820
+ delay: setValue(params.delay, this.params.delay),
8821
+ duration: setValue(params.duration, this.params.duration),
8822
+ };
8823
+ /** @type {TimelineParams} */
8824
+ const tlParams = {};
8825
+ const onComplete = setValue(params.onComplete, this.params.onComplete);
8826
+ const onPause = setValue(params.onPause, this.params.onPause);
8827
+ for (let name in defaults) {
8828
+ if (name !== 'ease' && name !== 'duration' && name !== 'delay') {
8829
+ if (!isUnd(params[name])) {
8830
+ tlParams[name] = params[name];
8831
+ } else if (!isUnd(this.params[name])) {
8832
+ tlParams[name] = this.params[name];
8833
+ }
8834
+ }
8835
+ }
8836
+ tlParams.onComplete = () => {
8837
+ // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
8838
+ if (this.transformAnimation) this.transformAnimation.cancel();
8839
+ newState.forEachRootNode(node => {
8840
+ restoreNodeVisualState(node);
8841
+ restoreNodeInlineStyles(node);
8842
+ });
8843
+ for (let i = 0, l = transformed.length; i < l; i++) {
8844
+ const $el = transformed[i];
8845
+ $el.style.transform = newState.getComputedValue($el, 'transform');
8846
+ }
8847
+ if (this.root.classList.contains('is-animated')) {
8848
+ this.root.classList.remove('is-animated');
8849
+ if (onComplete) onComplete(this.timeline);
8850
+ }
8851
+ // Avoid CSS transitions at the end of the animation by restoring them on the next frame
8852
+ requestAnimationFrame(() => {
8853
+ if (this.root.classList.contains('is-animated')) return;
8854
+ restoreLayoutTransition(this.transitionMuteStore);
8855
+ });
8856
+ };
8857
+ tlParams.onPause = () => {
8858
+ if (!this.root.classList.contains('is-animated')) return;
8859
+ if (this.transformAnimation) this.transformAnimation.cancel();
8860
+ newState.forEachRootNode(restoreNodeVisualState);
8861
+ this.root.classList.remove('is-animated');
8862
+ if (onComplete) onComplete(this.timeline);
8863
+ if (onPause) onPause(this.timeline);
8864
+ };
8865
+ tlParams.composition = false;
8866
+
8867
+ const swapAtParams = mergeObjects(mergeObjects(params.swapAt || {}, this.swapAtParams), animationTimings);
8868
+ const enterFromParams = mergeObjects(mergeObjects(params.enterFrom || {}, this.enterFromParams), animationTimings);
8869
+ const leaveToParams = mergeObjects(mergeObjects(params.leaveTo || {}, this.leaveToParams), animationTimings);
8870
+ const [ swapAtProps, swapAtTimings ] = splitPropertiesFromParams(swapAtParams);
8871
+ const [ enterFromProps, enterFromTimings ] = splitPropertiesFromParams(enterFromParams);
8872
+ const [ leaveToProps, leaveToTimings ] = splitPropertiesFromParams(leaveToParams);
8873
+
8726
8874
  const oldState = this.oldState;
8727
8875
  const newState = this.newState;
8728
- const added = this.added;
8729
- const removed = this.removed;
8730
- const frozen = this.frozen;
8731
- const pendingRemoved = this.pendingRemoved;
8876
+ const animating = this.animating;
8877
+ const swapping = this.swapping;
8878
+ const entering = this.entering;
8879
+ const leaving = this.leaving;
8880
+ const pendingRemoval = this.pendingRemoval;
8732
8881
 
8733
- added.length = removed.length = frozen.length = 0;
8882
+ animating.length = swapping.length = entering.length = leaving.length = 0;
8734
8883
 
8735
8884
  // Mute old state CSS transitions to prevent wrong properties calculation
8736
8885
  oldState.forEachRootNode(muteNodeTransition);
@@ -8741,10 +8890,12 @@
8741
8890
  const targets = [];
8742
8891
  const animated = [];
8743
8892
  const transformed = [];
8744
- const animatedFrozen = [];
8745
- const root = newState.rootNode.$el;
8893
+ const animatedSwap = [];
8894
+ const rootNode = newState.rootNode;
8895
+ const $root = rootNode.$el;
8746
8896
 
8747
8897
  newState.forEachRootNode(node => {
8898
+
8748
8899
  const $el = node.$el;
8749
8900
  const id = node.id;
8750
8901
  const parent = node.parentNode;
@@ -8752,10 +8903,6 @@
8752
8903
  const parentRemoved = parent ? parent.branchRemoved : false;
8753
8904
  const parentNotRendered = parent ? parent.branchNotRendered : false;
8754
8905
 
8755
- // Delay and duration must be calculated in the animate() call to support delay override
8756
- node.delay = +(isFnc(delay) ? delay($el, node.index, node.total) : delay);
8757
- node.duration = +(isFnc(duration) ? duration($el, node.index, node.total) : duration);
8758
-
8759
8906
  let oldStateNode = oldState.nodes.get(id);
8760
8907
 
8761
8908
  const hasNoOldState = !oldStateNode;
@@ -8805,7 +8952,7 @@
8805
8952
  }
8806
8953
  }
8807
8954
 
8808
- const wasPendingRemoval = pendingRemoved.has($el);
8955
+ const wasPendingRemoval = pendingRemoval.has($el);
8809
8956
  const wasVisibleBefore = oldStateNode.measuredIsVisible;
8810
8957
  const isVisibleNow = node.measuredIsVisible;
8811
8958
  const becomeVisible = !wasVisibleBefore && isVisibleNow && !parentNotRendered;
@@ -8813,113 +8960,156 @@
8813
8960
  const newlyRemoved = isRemovedNow && !wasRemovedBefore && !parentRemoved;
8814
8961
  const topLevelRemoved = newlyRemoved || isRemovedNow && wasPendingRemoval && !parentRemoved;
8815
8962
 
8816
- if (node.measuredIsRemoved && wasVisibleBefore) {
8963
+ node.branchAdded = parentAdded || topLevelAdded;
8964
+ node.branchRemoved = parentRemoved || topLevelRemoved;
8965
+ node.branchNotRendered = parentNotRendered || isRemovedNow;
8966
+
8967
+ if (isRemovedNow && wasVisibleBefore) {
8817
8968
  node.$el.style.display = oldStateNode.measuredDisplay;
8818
8969
  node.$el.style.visibility = 'visible';
8819
8970
  cloneNodeProperties(oldStateNode, node, newState);
8820
8971
  }
8821
8972
 
8973
+ // Node is leaving
8822
8974
  if (newlyRemoved) {
8823
- removed.push($el);
8824
- pendingRemoved.add($el);
8975
+ if (node.isTarget) {
8976
+ leaving.push($el);
8977
+ node.isLeaving = true;
8978
+ }
8979
+ pendingRemoval.add($el);
8825
8980
  } else if (!isRemovedNow && wasPendingRemoval) {
8826
- pendingRemoved.delete($el);
8981
+ pendingRemoval.delete($el);
8827
8982
  }
8828
8983
 
8829
- // Node is added
8984
+ // Node is entering
8830
8985
  if ((topLevelAdded && !parentNotRendered) || becomeVisible) {
8831
- updateNodeProperties(oldStateNode, addedParams);
8832
- added.push($el);
8833
- // Node is removed
8986
+ updateNodeProperties(oldStateNode, enterFromProps);
8987
+ if (node.isTarget) {
8988
+ entering.push($el);
8989
+ node.isEntering = true;
8990
+ }
8991
+ // Node is leaving
8834
8992
  } else if (topLevelRemoved && !parentNotRendered) {
8835
- updateNodeProperties(node, removedParams);
8993
+ updateNodeProperties(node, leaveToProps);
8836
8994
  }
8837
8995
 
8838
- // Compute function based propety values before cheking for changes
8839
- for (let name in node.properties) {
8840
- node.properties[name] = newState.getValue(node.$el, name);
8841
- // NOTE: I'm using node.$el to get the value of old state, make sure this is valid instead of oldStateNode.$el
8842
- oldStateNode.properties[name] = oldState.getValue(node.$el, name);
8996
+ // Node is animating
8997
+ // The animating array is used only to calculate delays and duration on root children
8998
+ if (node !== rootNode && node.isTarget && !node.isEntering && !node.isLeaving) {
8999
+ animating.push($el);
8843
9000
  }
8844
9001
 
8845
- const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
8846
- let propertyChanged = false;
9002
+ targets.push($el);
8847
9003
 
9004
+ });
8848
9005
 
8849
- if (node.isTarget && (!node.measuredIsRemoved && wasVisibleBefore || node.measuredIsRemoved && isVisibleNow)) {
8850
- if (!node.isInlined && (node.properties.transform !== 'none' || oldStateNode.properties.transform !== 'none')) {
8851
- node.hasTransform = true;
8852
- propertyChanged = true;
8853
- transformed.push($el);
8854
- }
8855
- for (let name in node.properties) {
8856
- if (name !== 'transform' && (node.properties[name] !== oldStateNode.properties[name] || hiddenStateChanged)) {
8857
- propertyChanged = true;
8858
- animated.push($el);
8859
- break;
8860
- }
8861
- }
9006
+ let enteringIndex = 0;
9007
+ let leavingIndex = 0;
9008
+ let animatingIndex = 0;
9009
+
9010
+ newState.forEachRootNode(node => {
9011
+
9012
+ const $el = node.$el;
9013
+ const parent = node.parentNode;
9014
+ const oldStateNode = oldState.nodes.get(node.id);
9015
+ const nodeProperties = node.properties;
9016
+ const oldStateNodeProperties = oldStateNode.properties;
9017
+
9018
+ // Use closest animated parent index and total values so that children staggered delays are in sync with their parent
9019
+ let animatedParent = parent !== rootNode && parent;
9020
+ while (animatedParent && !animatedParent.isTarget && animatedParent !== rootNode) {
9021
+ animatedParent = animatedParent.parentNode;
8862
9022
  }
8863
9023
 
8864
- const nodeHasChanged = (propertyChanged || topLevelAdded || topLevelRemoved || becomeVisible);
8865
- const nodeIsAnimated = node.isTarget && nodeHasChanged;
9024
+ const animatingTotal = animating.length;
9025
+
9026
+ // Root is always animated first in sync with the first child (animating.length is the total of children)
9027
+ if (node === rootNode) {
9028
+ node.index = 0;
9029
+ node.total = animatingTotal;
9030
+ updateNodeTimingParams(node, animationTimings);
9031
+ } else if (node.isEntering) {
9032
+ node.index = animatedParent ? animatedParent.index : enteringIndex;
9033
+ node.total = animatedParent ? animatingTotal : entering.length;
9034
+ updateNodeTimingParams(node, enterFromTimings);
9035
+ enteringIndex++;
9036
+ } else if (node.isLeaving) {
9037
+ node.index = animatedParent ? animatedParent.index : leavingIndex;
9038
+ node.total = animatedParent ? animatingTotal : leaving.length;
9039
+ leavingIndex++;
9040
+ updateNodeTimingParams(node, leaveToTimings);
9041
+ } else if (node.isTarget) {
9042
+ node.index = animatingIndex++;
9043
+ node.total = animatingTotal;
9044
+ updateNodeTimingParams(node, animationTimings);
9045
+ } else {
9046
+ node.index = animatedParent ? animatedParent.index : 0;
9047
+ node.total = animatingTotal;
9048
+ updateNodeTimingParams(node, swapAtTimings);
9049
+ }
8866
9050
 
8867
- node.isAnimated = nodeIsAnimated;
8868
- node.branchAdded = parentAdded || topLevelAdded;
8869
- node.branchRemoved = parentRemoved || topLevelRemoved;
8870
- node.branchNotRendered = parentNotRendered || node.measuredIsRemoved;
9051
+ // Make sure the old state node has its inex and total values up to date for valid "from" function values calculation
9052
+ oldStateNode.index = node.index;
9053
+ oldStateNode.total = node.total;
9054
+
9055
+ // Computes all values up front so we can check for changes and we don't have to re-compute them inside the animation props
9056
+ for (let prop in nodeProperties) {
9057
+ nodeProperties[prop] = getFunctionValue(nodeProperties[prop], $el, node.index, node.total);
9058
+ oldStateNodeProperties[prop] = getFunctionValue(oldStateNodeProperties[prop], $el, oldStateNode.index, oldStateNode.total);
9059
+ }
8871
9060
 
9061
+ // Use a 1px tolerance to detect dimensions changes to prevent width / height animations on barelly visible elements
8872
9062
  const sizeTolerance = 1;
8873
- const widthChanged = Math.abs(node.properties.width - oldStateNode.properties.width) > sizeTolerance;
8874
- const heightChanged = Math.abs(node.properties.height - oldStateNode.properties.height) > sizeTolerance;
9063
+ const widthChanged = Math.abs(nodeProperties.width - oldStateNodeProperties.width) > sizeTolerance;
9064
+ const heightChanged = Math.abs(nodeProperties.height - oldStateNodeProperties.height) > sizeTolerance;
8875
9065
 
8876
9066
  node.sizeChanged = (widthChanged || heightChanged);
8877
9067
 
8878
- targets.push($el);
9068
+ // const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
9069
+
9070
+ if (node.isTarget && (!node.measuredIsRemoved && oldStateNode.measuredIsVisible || node.measuredIsRemoved && node.measuredIsVisible)) {
9071
+ if (!node.isInlined && (nodeProperties.transform !== 'none' || oldStateNodeProperties.transform !== 'none')) {
9072
+ node.hasTransform = true;
9073
+ transformed.push($el);
9074
+ }
9075
+ for (let prop in nodeProperties) {
9076
+ // if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop] || hiddenStateChanged)) {
9077
+ if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop])) {
9078
+ animated.push($el);
9079
+ break;
9080
+ }
9081
+ }
9082
+ }
8879
9083
 
8880
9084
  if (!node.isTarget) {
8881
- frozen.push($el);
8882
- if ((nodeHasChanged || node.sizeChanged) && parent && parent.isTarget && parent.isAnimated && parent.sizeChanged) {
8883
- animatedFrozen.push($el);
9085
+ swapping.push($el);
9086
+ if (node.sizeChanged && parent && parent.isTarget && parent.sizeChanged) {
9087
+ if (!node.isInlined && swapAtProps.transform) {
9088
+ node.hasTransform = true;
9089
+ transformed.push($el);
9090
+ }
9091
+ animatedSwap.push($el);
8884
9092
  }
8885
9093
  }
9094
+
8886
9095
  });
8887
9096
 
8888
- const defaults = {
8889
- ease: setValue(params.ease, this.ease),
8890
- duration: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).duration,
8891
- delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
9097
+ const timingParams = {
9098
+ delay: (/** @type {HTMLElement} */$el) => newState.getNode($el).delay,
9099
+ duration: (/** @type {HTMLElement} */$el) => newState.getNode($el).duration,
9100
+ ease: (/** @type {HTMLElement} */$el) => newState.getNode($el).ease,
8892
9101
  };
8893
9102
 
8894
- this.timeline = createTimeline({
8895
- onComplete: () => {
8896
- // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
8897
- if (this.transformAnimation) this.transformAnimation.cancel();
8898
- newState.forEachRootNode(node => {
8899
- restoreNodeVisualState(node);
8900
- restoreNodeInlineStyles(node);
8901
- });
8902
- for (let i = 0, l = transformed.length; i < l; i++) {
8903
- const $el = transformed[i];
8904
- $el.style.transform = newState.getValue($el, 'transform');
8905
- }
8906
- this.root.classList.remove('is-animated');
8907
- if (onComplete) onComplete(this);
8908
- // Avoid CSS transitions at the end of the animation by restoring them on the next frame
8909
- requestAnimationFrame(() => {
8910
- if (this.root.classList.contains('is-animated')) return;
8911
- restoreLayoutTransition(this.transitionMuteStore);
8912
- });
8913
- },
8914
- onPause: () => {
8915
- if (this.transformAnimation) this.transformAnimation.cancel();
8916
- newState.forEachRootNode(restoreNodeVisualState);
8917
- this.root.classList.remove('is-animated');
8918
- if (onComplete) onComplete(this);
8919
- },
8920
- composition: false,
8921
- defaults,
8922
- });
9103
+ tlParams.defaults = timingParams;
9104
+
9105
+ this.timeline = createTimeline(tlParams);
9106
+
9107
+ // Imediatly return the timeline if no layout changes detected
9108
+ if (!animated.length && !transformed.length && !swapping.length) {
9109
+ // Make sure to restore all CSS transition if no animation
9110
+ restoreLayoutTransition(this.transitionMuteStore);
9111
+ return this.timeline.complete();
9112
+ }
8923
9113
 
8924
9114
  if (targets.length) {
8925
9115
 
@@ -8932,15 +9122,14 @@
8932
9122
  const newNode = newState.nodes.get(id);
8933
9123
  const oldNodeState = oldNode.properties;
8934
9124
 
8935
- // Make sure to mute all CSS transition before applying the oldState styles back
8936
- muteNodeTransition(newNode);
9125
+ // muteNodeTransition(newNode);
8937
9126
 
8938
9127
  // Don't animate dimensions and positions of inlined elements
8939
9128
  if (!newNode.isInlined) {
8940
9129
  // Display grid can mess with the absolute positioning, so set it to block during transition
8941
- if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.display = 'block';
8942
- // All children must be in position absolue
8943
- if ($el !== root || this.absoluteCoords) {
9130
+ if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.setProperty('display', 'block', 'important');
9131
+ // All children must be in position absolute or fixed
9132
+ if ($el !== $root || this.absoluteCoords) {
8944
9133
  $el.style.position = this.absoluteCoords ? 'fixed' : 'absolute';
8945
9134
  $el.style.left = '0px';
8946
9135
  $el.style.top = '0px';
@@ -8948,7 +9137,7 @@
8948
9137
  $el.style.marginTop = '0px';
8949
9138
  $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
8950
9139
  }
8951
- if ($el === root && newNode.measuredPosition === 'static') {
9140
+ if ($el === $root && newNode.measuredPosition === 'static') {
8952
9141
  $el.style.position = 'relative';
8953
9142
  // Cancel left / trop in case the static element had muted values now activated by potision relative
8954
9143
  $el.style.left = '0px';
@@ -8967,9 +9156,7 @@
8967
9156
  // Restore the scroll position if the oldState differs from the current state
8968
9157
  if (oldState.scrollX !== window.scrollX || oldState.scrollY !== window.scrollY) {
8969
9158
  // Restoring in the next frame avoids race conditions if for example a waapi animation commit styles that affect the root height
8970
- requestAnimationFrame(() => {
8971
- window.scrollTo(oldState.scrollX, oldState.scrollY);
8972
- });
9159
+ requestAnimationFrame(() => window.scrollTo(oldState.scrollX, oldState.scrollY));
8973
9160
  }
8974
9161
 
8975
9162
  for (let i = 0, l = animated.length; i < l; i++) {
@@ -8979,25 +9166,25 @@
8979
9166
  const newNode = newState.nodes.get(id);
8980
9167
  const oldNodeState = oldNode.properties;
8981
9168
  const newNodeState = newNode.properties;
8982
- let hasChanged = false;
9169
+ let nodeHasChanged = false;
9170
+ /** @type {AnimationParams} */
8983
9171
  const animatedProps = {
8984
9172
  composition: 'none',
8985
- // delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
8986
9173
  };
8987
9174
  if (!newNode.isInlined) {
8988
9175
  if (oldNodeState.width !== newNodeState.width) {
8989
9176
  animatedProps.width = [oldNodeState.width, newNodeState.width];
8990
- hasChanged = true;
9177
+ nodeHasChanged = true;
8991
9178
  }
8992
9179
  if (oldNodeState.height !== newNodeState.height) {
8993
9180
  animatedProps.height = [oldNodeState.height, newNodeState.height];
8994
- hasChanged = true;
9181
+ nodeHasChanged = true;
8995
9182
  }
8996
9183
  // If the node has transforms we handle the translate animation in wappi otherwise translate and other transforms can be out of sync
8997
9184
  // Always animate translate
8998
9185
  if (!newNode.hasTransform) {
8999
9186
  animatedProps.translate = [`${oldNodeState.x}px ${oldNodeState.y}px`, `${newNodeState.x}px ${newNodeState.y}px`];
9000
- hasChanged = true;
9187
+ nodeHasChanged = true;
9001
9188
  }
9002
9189
  }
9003
9190
  this.properties.forEach(prop => {
@@ -9005,73 +9192,77 @@
9005
9192
  const newVal = newNodeState[prop];
9006
9193
  if (prop !== 'transform' && oldVal !== newVal) {
9007
9194
  animatedProps[prop] = [oldVal, newVal];
9008
- hasChanged = true;
9195
+ nodeHasChanged = true;
9009
9196
  }
9010
9197
  });
9011
- if (hasChanged) {
9198
+ if (nodeHasChanged) {
9012
9199
  this.timeline.add($el, animatedProps, 0);
9013
9200
  }
9014
9201
  }
9015
9202
 
9016
9203
  }
9017
9204
 
9018
- if (frozen.length) {
9205
+ if (swapping.length) {
9019
9206
 
9020
- for (let i = 0, l = frozen.length; i < l; i++) {
9021
- const $el = frozen[i];
9022
- const oldNode = oldState.nodes.get($el.dataset.layoutId);
9207
+ for (let i = 0, l = swapping.length; i < l; i++) {
9208
+ const $el = swapping[i];
9209
+ const oldNode = oldState.getNode($el);
9023
9210
  if (!oldNode.isInlined) {
9024
- const oldNodeState = oldState.get($el);
9025
- $el.style.width = `${oldNodeState.width}px`;
9026
- $el.style.height = `${oldNodeState.height}px`;
9211
+ const oldNodeProps = oldNode.properties;
9212
+ $el.style.width = `${oldNodeProps.width}px`;
9213
+ $el.style.height = `${oldNodeProps.height}px`;
9027
9214
  // Overrides user defined min and max to prevents width and height clamping
9028
9215
  $el.style.minWidth = `auto`;
9029
9216
  $el.style.minHeight = `auto`;
9030
9217
  $el.style.maxWidth = `none`;
9031
9218
  $el.style.maxHeight = `none`;
9032
- $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
9219
+ $el.style.translate = `${oldNodeProps.x}px ${oldNodeProps.y}px`;
9033
9220
  }
9034
9221
  this.properties.forEach(prop => {
9035
9222
  if (prop !== 'transform') {
9036
- $el.style[prop] = `${oldState.getValue($el, prop)}`;
9223
+ $el.style[prop] = `${oldState.getComputedValue($el, prop)}`;
9037
9224
  }
9038
9225
  });
9039
9226
  }
9040
9227
 
9041
- for (let i = 0, l = frozen.length; i < l; i++) {
9042
- const $el = frozen[i];
9043
- const newNode = newState.nodes.get($el.dataset.layoutId);
9044
- const newNodeState = newState.get($el);
9228
+ for (let i = 0, l = swapping.length; i < l; i++) {
9229
+ const $el = swapping[i];
9230
+ const newNode = newState.getNode($el);
9231
+ const newNodeProps = newNode.properties;
9045
9232
  this.timeline.call(() => {
9046
9233
  if (!newNode.isInlined) {
9047
- $el.style.width = `${newNodeState.width}px`;
9048
- $el.style.height = `${newNodeState.height}px`;
9234
+ $el.style.width = `${newNodeProps.width}px`;
9235
+ $el.style.height = `${newNodeProps.height}px`;
9049
9236
  // Overrides user defined min and max to prevents width and height clamping
9050
9237
  $el.style.minWidth = `auto`;
9051
9238
  $el.style.minHeight = `auto`;
9052
9239
  $el.style.maxWidth = `none`;
9053
9240
  $el.style.maxHeight = `none`;
9054
- $el.style.translate = `${newNodeState.x}px ${newNodeState.y}px`;
9241
+ $el.style.translate = `${newNodeProps.x}px ${newNodeProps.y}px`;
9055
9242
  }
9056
9243
  this.properties.forEach(prop => {
9057
9244
  if (prop !== 'transform') {
9058
- $el.style[prop] = `${newState.getValue($el, prop)}`;
9245
+ $el.style[prop] = `${newState.getComputedValue($el, prop)}`;
9059
9246
  }
9060
9247
  });
9061
9248
  }, newNode.delay + newNode.duration / 2);
9062
9249
  }
9063
9250
 
9064
- if (animatedFrozen.length) {
9065
- const animatedFrozenParams = /** @type {AnimationParams} */({});
9066
- if (frozenParams) {
9067
- for (let prop in frozenParams) {
9068
- animatedFrozenParams[prop] = [
9069
- { from: (/** @type {HTMLElement} */$el) => oldState.getValue($el, prop), ease: 'in(1.75)', to: frozenParams[prop] },
9070
- { from: frozenParams[prop], to: (/** @type {HTMLElement} */$el) => newState.getValue($el, prop), ease: 'out(1.75)' }
9071
- ];
9251
+ if (animatedSwap.length) {
9252
+ const ease = parseEase(newState.nodes.get(animatedSwap[0].dataset.layoutId).ease);
9253
+ const inverseEased = t => 1 - ease(1 - t);
9254
+ const animatedSwapParams = /** @type {AnimationParams} */({});
9255
+ if (swapAtProps) {
9256
+ for (let prop in swapAtProps) {
9257
+ if (prop !== 'transform') {
9258
+ animatedSwapParams[prop] = [
9259
+ { from: (/** @type {HTMLElement} */$el) => oldState.getComputedValue($el, prop), to: swapAtProps[prop] },
9260
+ { from: swapAtProps[prop], to: (/** @type {HTMLElement} */$el) => newState.getComputedValue($el, prop), ease: inverseEased }
9261
+ ];
9262
+ }
9072
9263
  }
9073
9264
  }
9074
- this.timeline.add(animatedFrozen, animatedFrozenParams, 0);
9265
+ this.timeline.add(animatedSwap, animatedSwapParams, 0);
9075
9266
  }
9076
9267
 
9077
9268
  }
@@ -9079,18 +9270,29 @@
9079
9270
  const transformedLength = transformed.length;
9080
9271
 
9081
9272
  if (transformedLength) {
9082
- // We only need to set the transform property here since translate is alread defined the targets loop
9273
+ // We only need to set the transform property here since translate is alread defined in the targets loop
9083
9274
  for (let i = 0; i < transformedLength; i++) {
9084
9275
  const $el = transformed[i];
9085
- $el.style.translate = `${oldState.get($el).x}px ${oldState.get($el).y}px`,
9086
- $el.style.transform = oldState.getValue($el, 'transform');
9276
+ $el.style.translate = `${oldState.getComputedValue($el, 'x')}px ${oldState.getComputedValue($el, 'y')}px`,
9277
+ $el.style.transform = oldState.getComputedValue($el, 'transform');
9278
+ if (animatedSwap.includes($el)) {
9279
+ const node = newState.getNode($el);
9280
+ node.ease = getFunctionValue(swapAtParams.ease, $el, node.index, node.total);
9281
+ node.duration = getFunctionValue(swapAtParams.duration, $el, node.index, node.total);
9282
+ }
9087
9283
  }
9088
9284
  this.transformAnimation = waapi.animate(transformed, {
9089
- translate: (/** @type {HTMLElement} */$el) => `${newState.get($el).x}px ${newState.get($el).y}px`,
9090
- transform: (/** @type {HTMLElement} */$el) => newState.getValue($el, 'transform'),
9285
+ translate: (/** @type {HTMLElement} */$el) => `${newState.getComputedValue($el, 'x')}px ${newState.getComputedValue($el, 'y')}px`,
9286
+ transform: (/** @type {HTMLElement} */$el) => {
9287
+ const newValue = newState.getComputedValue($el, 'transform');
9288
+ if (!animatedSwap.includes($el)) return newValue;
9289
+ const oldValue = oldState.getComputedValue($el, 'transform');
9290
+ const node = newState.getNode($el);
9291
+ return [oldValue, getFunctionValue(swapAtProps.transform, $el, node.index, node.total), newValue]
9292
+ },
9091
9293
  autoplay: false,
9092
9294
  persist: true,
9093
- ...defaults,
9295
+ ...timingParams,
9094
9296
  });
9095
9297
  this.timeline.sync(this.transformAnimation, 0);
9096
9298
  }
@@ -9101,13 +9303,12 @@
9101
9303
  /**
9102
9304
  * @param {(layout: this) => void} callback
9103
9305
  * @param {LayoutAnimationParams} [params]
9104
- * @return {this}
9306
+ * @return {Timeline}
9105
9307
  */
9106
9308
  update(callback, params = {}) {
9107
9309
  this.record();
9108
9310
  callback(this);
9109
- this.animate(params);
9110
- return this;
9311
+ return this.animate(params);
9111
9312
  }
9112
9313
  }
9113
9314