animejs 4.3.0-beta.1 → 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 +456 -266
  3. package/dist/bundles/anime.esm.min.js +3 -3
  4. package/dist/bundles/anime.umd.js +456 -266
  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 +437 -249
  75. package/dist/modules/layout/layout.d.ts +44 -38
  76. package/dist/modules/layout/layout.js +439 -251
  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 - ESM bundle
3
- * @version v4.3.0-beta.1
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]
@@ -801,7 +801,7 @@ const globals = {
801
801
 
802
802
  const devTools = isBrowser && win.AnimeJSDevTools;
803
803
 
804
- const globalVersions = { version: '4.3.0-beta.1', engine: null };
804
+ const globalVersions = { version: '4.3.0', engine: null };
805
805
 
806
806
  if (isBrowser) {
807
807
  if (!win.AnimeJS) win.AnimeJS = [];
@@ -849,9 +849,9 @@ const isSvg = a => isBrowser && a instanceof SVGElement;
849
849
  /**@param {any} a @return {Boolean} */
850
850
  const isHex = a => hexTestRgx.test(a);
851
851
  /**@param {any} a @return {Boolean} */
852
- const isRgb = a => stringStartsWith(a, 'rgb');
852
+ const isRgb = a => stringStartsWith(a, 'rgb') && a[a.length - 1] === ')';
853
853
  /**@param {any} a @return {Boolean} */
854
- const isHsl = a => stringStartsWith(a, 'hsl');
854
+ const isHsl = a => stringStartsWith(a, 'hsl') && a[a.length - 1] === ')';
855
855
  /**@param {any} a @return {Boolean} */
856
856
  const isCol = a => isHex(a) || isRgb(a) || isHsl(a);
857
857
  /**@param {any} a @return {Boolean} */
@@ -2894,10 +2894,11 @@ class Timer extends Clock {
2894
2894
 
2895
2895
  /**
2896
2896
  * Imediatly completes the timer, cancels it and triggers the onComplete callback
2897
+ * @param {Boolean|Number} [muteCallbacks]
2897
2898
  * @return {this}
2898
2899
  */
2899
- complete() {
2900
- return this.seek(this.duration).cancel();
2900
+ complete(muteCallbacks = 0) {
2901
+ return this.seek(this.duration, muteCallbacks).cancel();
2901
2902
  }
2902
2903
 
2903
2904
  /**
@@ -3523,10 +3524,13 @@ class JSAnimation extends Timer {
3523
3524
  tweenToValue = computedToValue;
3524
3525
  }
3525
3526
  const tweenFromValue = getFunctionValue(key.from, target, ti, tl);
3526
- const keyEasing = key.ease;
3527
+ const easeToParse = key.ease || tEasing;
3528
+
3529
+ const easeFunctionResult = getFunctionValue(easeToParse, target, ti, tl);
3530
+ const keyEasing = isFnc(easeFunctionResult) || isStr(easeFunctionResult) ? easeFunctionResult : easeToParse;
3531
+
3527
3532
  const hasSpring = !isUnd(keyEasing) && !isUnd(/** @type {Spring} */(keyEasing).ease);
3528
- // 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
3529
- const tweenEasing = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing || tEasing;
3533
+ const tweenEasing = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing;
3530
3534
  // Calculate default individual keyframe duration by dividing the tl of keyframes
3531
3535
  const tweenDuration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : getFunctionValue(setValue(key.duration, (l > 1 ? getFunctionValue(tDuration, target, ti, tl) / l : tDuration)), target, ti, tl);
3532
3536
  // Default delay value should only be applied to the first tween
@@ -7469,8 +7473,6 @@ class WAAPIAnimation {
7469
7473
  console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`);
7470
7474
  }
7471
7475
 
7472
- const ease = setValue(params.ease, parseWAAPIEasing(globals.defaults.ease));
7473
- const spring = /** @type {Spring} */(ease).ease && ease;
7474
7476
  const autoplay = setValue(params.autoplay, globals.defaults.autoplay);
7475
7477
  const scroll = autoplay && /** @type {ScrollObserver} */(autoplay).link ? autoplay : false;
7476
7478
  const alternate = params.alternate && /** @type {Boolean} */(params.alternate) === true;
@@ -7481,8 +7483,6 @@ class WAAPIAnimation {
7481
7483
  const direction = alternate ? reversed ? 'alternate-reverse' : 'alternate' : reversed ? 'reverse' : 'normal';
7482
7484
  /** @type {FillMode} */
7483
7485
  const fill = 'both'; // We use 'both' here because the animation can be reversed during playback
7484
- /** @type {String} */
7485
- const easing = parseWAAPIEasing(ease);
7486
7486
  const timeScale = (globals.timeScale === 1 ? 1 : K);
7487
7487
 
7488
7488
  /** @type {DOMTargetsArray}] */
@@ -7523,6 +7523,15 @@ class WAAPIAnimation {
7523
7523
  const elStyle = $el.style;
7524
7524
  const inlineStyles = this._inlineStyles[i] = {};
7525
7525
 
7526
+ const easeToParse = setValue(params.ease, globals.defaults.ease);
7527
+
7528
+ const easeFunctionResult = getFunctionValue(easeToParse, $el, i, targetsLength);
7529
+ const keyEasing = isFnc(easeFunctionResult) || isStr(easeFunctionResult) ? easeFunctionResult : easeToParse;
7530
+
7531
+ const spring = /** @type {Spring} */(easeToParse).ease && easeToParse;
7532
+ /** @type {String} */
7533
+ const easing = parseWAAPIEasing(keyEasing);
7534
+
7526
7535
  /** @type {Number} */
7527
7536
  const duration = (spring ? /** @type {Spring} */(spring).settlingDuration : getFunctionValue(setValue(params.duration, globals.defaults.duration), $el, i, targetsLength)) * timeScale;
7528
7537
  /** @type {Number} */
@@ -7547,7 +7556,7 @@ class WAAPIAnimation {
7547
7556
  let parsedPropertyValue;
7548
7557
  if (isObj(propertyValue)) {
7549
7558
  const tweenOptions = /** @type {WAAPITweenOptions} */(propertyValue);
7550
- const tweenOptionsEase = setValue(tweenOptions.ease, ease);
7559
+ const tweenOptionsEase = setValue(tweenOptions.ease, easing);
7551
7560
  const tweenOptionsSpring = /** @type {Spring} */(tweenOptionsEase).ease && tweenOptionsEase;
7552
7561
  const to = /** @type {WAAPITweenOptions} */(tweenOptions).to;
7553
7562
  const from = /** @type {WAAPITweenOptions} */(tweenOptions).from;
@@ -7786,34 +7795,54 @@ const waapi = {
7786
7795
 
7787
7796
 
7788
7797
 
7798
+
7799
+
7789
7800
  /**
7790
7801
  * @typedef {DOMTargetSelector|Array<DOMTargetSelector>} LayoutChildrenParam
7791
7802
  */
7792
7803
 
7793
7804
  /**
7794
- * @typedef {Record<String, Number|String>} LayoutStateParams
7805
+ * @typedef {Object} LayoutAnimationTimingsParams
7806
+ * @property {Number|FunctionValue} [delay]
7807
+ * @property {Number|FunctionValue} [duration]
7808
+ * @property {EasingParam|FunctionValue} [ease]
7809
+ */
7810
+
7811
+ /**
7812
+ * @typedef {Record<String, Number|String|FunctionValue>} LayoutStateAnimationProperties
7813
+ */
7814
+
7815
+ /**
7816
+ * @typedef {LayoutStateAnimationProperties & LayoutAnimationTimingsParams} LayoutStateParams
7795
7817
  */
7796
7818
 
7797
7819
  /**
7798
- * @typedef {Object} LayoutAnimationParams
7820
+ * @typedef {Object} LayoutSpecificAnimationParams
7799
7821
  * @property {Number|FunctionValue} [delay]
7800
7822
  * @property {Number|FunctionValue} [duration]
7801
- * @property {EasingParam} [ease]
7802
- * @property {LayoutStateParams} [frozen]
7803
- * @property {LayoutStateParams} [added]
7804
- * @property {LayoutStateParams} [removed]
7805
- * @property {Callback<AutoLayout>} [onComplete]
7823
+ * @property {EasingParam|FunctionValue} [ease]
7824
+ * @property {EasingParam} [playbackEase]
7825
+ * @property {LayoutStateParams} [swapAt]
7826
+ * @property {LayoutStateParams} [enterFrom]
7827
+ * @property {LayoutStateParams} [leaveTo]
7828
+ */
7829
+
7830
+ /**
7831
+ * @typedef {LayoutSpecificAnimationParams & TimerParams & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} LayoutAnimationParams
7832
+ */
7833
+
7834
+ /**
7835
+ * @typedef {Object} LayoutOptions
7836
+ * @property {LayoutChildrenParam} [children]
7837
+ * @property {Array<String>} [properties]
7806
7838
  */
7807
7839
 
7808
7840
  /**
7809
- * @typedef {LayoutAnimationParams & {
7810
- * children?: LayoutChildrenParam,
7811
- * properties?: Array<String>,
7812
- * }} AutoLayoutParams
7841
+ * @typedef {LayoutAnimationParams & LayoutOptions} AutoLayoutParams
7813
7842
  */
7814
7843
 
7815
7844
  /**
7816
- * @typedef {Record<String, Number|String> & {
7845
+ * @typedef {Record<String, Number|String|FunctionValue> & {
7817
7846
  * transform: String,
7818
7847
  * x: Number,
7819
7848
  * y: Number,
@@ -7834,13 +7863,15 @@ const waapi = {
7834
7863
  * @property {Number} total
7835
7864
  * @property {Number} delay
7836
7865
  * @property {Number} duration
7866
+ * @property {EasingParam} ease
7837
7867
  * @property {DOMTarget} $measure
7838
7868
  * @property {LayoutSnapshot} state
7839
7869
  * @property {AutoLayout} layout
7840
7870
  * @property {LayoutNode|null} parentNode
7841
7871
  * @property {Boolean} isTarget
7872
+ * @property {Boolean} isEntering
7873
+ * @property {Boolean} isLeaving
7842
7874
  * @property {Boolean} hasTransform
7843
- * @property {Boolean} isAnimated
7844
7875
  * @property {Array<String>} inlineStyles
7845
7876
  * @property {String|null} inlineTransforms
7846
7877
  * @property {String|null} inlineTransition
@@ -7981,7 +8012,7 @@ const detachNode = node => {
7981
8012
  * @param {DOMTarget} $el
7982
8013
  * @param {LayoutNode|null} parentNode
7983
8014
  * @param {LayoutSnapshot} state
7984
- * @param {LayoutNode} [recycledNode]
8015
+ * @param {LayoutNode} recycledNode
7985
8016
  * @return {LayoutNode}
7986
8017
  */
7987
8018
  const createNode = ($el, parentNode, state, recycledNode) => {
@@ -7995,12 +8026,15 @@ const createNode = ($el, parentNode, state, recycledNode) => {
7995
8026
  node.total = 1;
7996
8027
  node.delay = 0;
7997
8028
  node.duration = 0;
8029
+ node.ease = null;
7998
8030
  node.state = state;
7999
8031
  node.layout = state.layout;
8000
8032
  node.parentNode = parentNode || null;
8001
8033
  node.isTarget = false;
8034
+ node.isEntering = false;
8035
+ node.isLeaving = false;
8036
+ node.isInlined = false;
8002
8037
  node.hasTransform = false;
8003
- node.isAnimated = false;
8004
8038
  node.inlineStyles = [];
8005
8039
  node.inlineTransforms = null;
8006
8040
  node.inlineTransition = null;
@@ -8008,7 +8042,6 @@ const createNode = ($el, parentNode, state, recycledNode) => {
8008
8042
  node.branchRemoved = false;
8009
8043
  node.branchNotRendered = false;
8010
8044
  node.sizeChanged = false;
8011
- node.isInlined = false;
8012
8045
  node.hasVisibilitySwap = false;
8013
8046
  node.hasDisplayNone = false;
8014
8047
  node.hasVisibilityHidden = false;
@@ -8151,7 +8184,7 @@ const recordNodeState = (node, $measure, computedStyle, skipMeasurements) => {
8151
8184
 
8152
8185
  /**
8153
8186
  * @param {LayoutNode} node
8154
- * @param {LayoutStateParams} [props]
8187
+ * @param {LayoutStateAnimationProperties} [props]
8155
8188
  */
8156
8189
  const updateNodeProperties = (node, props) => {
8157
8190
  if (!props) return;
@@ -8160,6 +8193,19 @@ const updateNodeProperties = (node, props) => {
8160
8193
  }
8161
8194
  };
8162
8195
 
8196
+ /**
8197
+ * @param {LayoutNode} node
8198
+ * @param {LayoutAnimationTimingsParams} params
8199
+ */
8200
+ const updateNodeTimingParams = (node, params) => {
8201
+ const easeFunctionResult = getFunctionValue(params.ease, node.$el, node.index, node.total);
8202
+ const keyEasing = isFnc(easeFunctionResult) ? easeFunctionResult : params.ease;
8203
+ const hasSpring = !isUnd(keyEasing) && !isUnd(/** @type {Spring} */(keyEasing).ease);
8204
+ node.ease = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing;
8205
+ node.duration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : getFunctionValue(params.duration, node.$el, node.index, node.total);
8206
+ node.delay = getFunctionValue(params.delay, node.$el, node.index, node.total);
8207
+ };
8208
+
8163
8209
  /**
8164
8210
  * @param {LayoutNode} node
8165
8211
  */
@@ -8234,9 +8280,9 @@ const restoreNodeVisualState = node => {
8234
8280
  node.$measure.style.removeProperty('visibility');
8235
8281
  }
8236
8282
  }
8237
- if (node.measuredIsRemoved) {
8238
- node.layout.pendingRemoved.delete(node.$el);
8239
- }
8283
+ // if (node.measuredIsRemoved) {
8284
+ node.layout.pendingRemoval.delete(node.$el);
8285
+ // }
8240
8286
  };
8241
8287
 
8242
8288
  /**
@@ -8286,6 +8332,7 @@ class LayoutSnapshot {
8286
8332
  */
8287
8333
  revert() {
8288
8334
  this.forEachNode(node => {
8335
+ this.layout.pendingRemoval.delete(node.$el);
8289
8336
  node.$el.removeAttribute('data-layout-id');
8290
8337
  node.$measure.removeAttribute('data-layout-id');
8291
8338
  });
@@ -8297,34 +8344,22 @@ class LayoutSnapshot {
8297
8344
 
8298
8345
  /**
8299
8346
  * @param {DOMTarget} $el
8300
- * @return {LayoutNodeProperties|undefined}
8347
+ * @return {LayoutNode}
8301
8348
  */
8302
- get($el) {
8303
- const node = this.nodes.get($el.dataset.layoutId);
8304
- if (!node) {
8305
- console.warn(`No node found on state`);
8306
- return;
8307
- }
8308
- return node.properties;
8349
+ getNode($el) {
8350
+ if (!$el || !$el.dataset) return;
8351
+ return this.nodes.get($el.dataset.layoutId);
8309
8352
  }
8310
8353
 
8311
8354
  /**
8312
8355
  * @param {DOMTarget} $el
8313
8356
  * @param {String} prop
8314
- * @return {Number|String|undefined}
8357
+ * @return {Number|String}
8315
8358
  */
8316
- getValue($el, prop) {
8317
- if (!$el || !$el.dataset) {
8318
- console.warn(`No element found on state (${$el})`);
8319
- return;
8320
- }
8321
- const node = this.nodes.get($el.dataset.layoutId);
8322
- if (!node) {
8323
- console.warn(`No node found on state`);
8324
- return;
8325
- }
8326
- const value = node.properties[prop];
8327
- if (!isUnd(value)) return getFunctionValue(value, $el, node.index, node.total);
8359
+ getComputedValue($el, prop) {
8360
+ const node = this.getNode($el);
8361
+ if (!node) return;
8362
+ return /** @type {Number|String} */(node.properties[prop]);
8328
8363
  }
8329
8364
 
8330
8365
  /**
@@ -8385,10 +8420,10 @@ class LayoutSnapshot {
8385
8420
  const $parent = /** @type {LayoutNode|null} */(stack.pop());
8386
8421
  /** @type {DOMTarget|null} */
8387
8422
  const $current = /** @type {DOMTarget|null} */(stack.pop());
8423
+
8388
8424
  if (!$current || $current.nodeType !== 1 || isSvg($current)) continue;
8389
8425
 
8390
8426
  const skipMeasurements = $parent ? $parent.measuredIsRemoved : false;
8391
-
8392
8427
  const computedStyle = skipMeasurements ? hiddenComputedStyle : getComputedStyle($current);
8393
8428
  const hasDisplayNone = skipMeasurements ? true : computedStyle.display === 'none';
8394
8429
  const hasVisibilityHidden = skipMeasurements ? true : computedStyle.visibility === 'hidden';
@@ -8435,11 +8470,10 @@ class LayoutSnapshot {
8435
8470
  node.branchRemoved = false;
8436
8471
  node.branchNotRendered = false;
8437
8472
  node.isTarget = false;
8438
- node.isAnimated = false;
8473
+ node.sizeChanged = false;
8439
8474
  node.hasVisibilityHidden = hasVisibilityHidden;
8440
8475
  node.hasDisplayNone = hasDisplayNone;
8441
8476
  node.hasVisibilitySwap = (hasVisibilityHidden && !node.measuredHasVisibilityHidden) || (hasDisplayNone && !node.measuredHasDisplayNone);
8442
- // node.hasVisibilitySwap = (hasVisibilityHidden !== node.measuredHasVisibilityHidden) || (hasDisplayNone !== node.measuredHasDisplayNone);
8443
8477
 
8444
8478
  this.nodes.set(node.id, node);
8445
8479
 
@@ -8458,6 +8492,7 @@ class LayoutSnapshot {
8458
8492
  $parent._tail = node;
8459
8493
  }
8460
8494
  } else {
8495
+ // Each disconnected subtree becomes its own root in the snapshot graph
8461
8496
  this.rootNodes.add(node);
8462
8497
  }
8463
8498
 
@@ -8501,11 +8536,29 @@ class LayoutSnapshot {
8501
8536
  * @return {this}
8502
8537
  */
8503
8538
  record() {
8504
- const { children, root } = this.layout;
8539
+ const layout = this.layout;
8540
+ const children = layout.children;
8541
+ const root = layout.root;
8505
8542
  const toParse = isArr(children) ? children : [children];
8506
8543
  const scoped = [];
8507
8544
  const scopeRoot = children === '*' ? root : scope.root;
8508
8545
 
8546
+ // Mute transition and transforms of root ancestors before recording the state
8547
+
8548
+ /** @type {Array<DOMTarget|String|null>} */
8549
+ const rootAncestorTransformStore = [];
8550
+ let $ancestor = root.parentElement;
8551
+ while ($ancestor && $ancestor.nodeType === 1) {
8552
+ const computedStyle = getComputedStyle($ancestor);
8553
+ if (computedStyle.transform && computedStyle.transform !== 'none') {
8554
+ const inlineTransform = $ancestor.style.transform || '';
8555
+ const inlineTransition = muteElementTransition($ancestor);
8556
+ rootAncestorTransformStore.push($ancestor, inlineTransform, inlineTransition);
8557
+ $ancestor.style.transform = 'none';
8558
+ }
8559
+ $ancestor = $ancestor.parentElement;
8560
+ }
8561
+
8509
8562
  for (let i = 0, l = toParse.length; i < l; i++) {
8510
8563
  const child = toParse[i];
8511
8564
  scoped[i] = isStr(child) ? scopeRoot.querySelectorAll(child) : child;
@@ -8521,9 +8574,13 @@ class LayoutSnapshot {
8521
8574
  rootNode.isTarget = true;
8522
8575
  this.rootNode = rootNode;
8523
8576
 
8524
- // Track ids of nodes that belong to the current root to filter detached matches
8525
8577
  const inRootNodeIds = new Set();
8578
+ // Update index and total for inital timing calculation
8579
+ let index = 0, total = this.nodes.size;
8526
8580
  this.nodes.forEach((node, id) => {
8581
+ node.index = index++;
8582
+ node.total = total;
8583
+ // Track ids of nodes that belong to the current root to filter detached matches
8527
8584
  if (node && node.measuredIsInsideRoot) {
8528
8585
  inRootNodeIds.add(id);
8529
8586
  }
@@ -8553,7 +8610,7 @@ class LayoutSnapshot {
8553
8610
 
8554
8611
  for (let i = 0, l = parsedChildren.length; i < l; i++) {
8555
8612
  const $el = parsedChildren[i];
8556
- const node = this.nodes.get($el.dataset.layoutId);
8613
+ const node = this.getNode($el);
8557
8614
  if (node) {
8558
8615
  let cur = node;
8559
8616
  while (cur) {
@@ -8567,18 +8624,52 @@ class LayoutSnapshot {
8567
8624
  this.scrollX = window.scrollX;
8568
8625
  this.scrollY = window.scrollY;
8569
8626
 
8570
- const total = this.nodes.size;
8571
-
8572
8627
  this.forEachNode(restoreNodeTransform);
8573
- this.forEachNode((node, i) => {
8574
- node.index = i;
8575
- node.total = total;
8576
- });
8628
+
8629
+ // Restore transition and transforms of root ancestors
8630
+
8631
+ for (let i = 0, l = rootAncestorTransformStore.length; i < l; i += 3) {
8632
+ const $el = /** @type {DOMTarget} */(rootAncestorTransformStore[i]);
8633
+ const inlineTransform = /** @type {String} */(rootAncestorTransformStore[i + 1]);
8634
+ const inlineTransition = /** @type {String|null} */(rootAncestorTransformStore[i + 2]);
8635
+ if (inlineTransform && inlineTransform !== '') {
8636
+ $el.style.transform = inlineTransform;
8637
+ } else {
8638
+ $el.style.removeProperty('transform');
8639
+ }
8640
+ restoreElementTransition($el, inlineTransition);
8641
+ }
8577
8642
 
8578
8643
  return this;
8579
8644
  }
8580
8645
  }
8581
8646
 
8647
+ /**
8648
+ * @param {LayoutStateParams} params
8649
+ * @return {[LayoutStateAnimationProperties, LayoutAnimationTimingsParams]}
8650
+ */
8651
+ function splitPropertiesFromParams(params) {
8652
+ /** @type {LayoutStateAnimationProperties} */
8653
+ const properties = {};
8654
+ /** @type {LayoutAnimationTimingsParams} */
8655
+ const parameters = {};
8656
+ for (let name in params) {
8657
+ const value = params[name];
8658
+ const isEase = name === 'ease';
8659
+ const isTiming = name === 'duration' || name === 'delay';
8660
+ if (isTiming || isEase) {
8661
+ if (isEase) {
8662
+ parameters[name] = /** @type {EasingParam} */(value);
8663
+ } else {
8664
+ parameters[name] = /** @type {Number|FunctionValue} */(value);
8665
+ }
8666
+ } else {
8667
+ properties[name] = /** @type {Number|String} */(value);
8668
+ }
8669
+ }
8670
+ return [properties, parameters];
8671
+ }
8672
+
8582
8673
  class AutoLayout {
8583
8674
  /**
8584
8675
  * @param {DOMTargetSelector} root
@@ -8586,10 +8677,16 @@ class AutoLayout {
8586
8677
  */
8587
8678
  constructor(root, params = {}) {
8588
8679
  if (scope.current) scope.current.register(this);
8589
- const frozenParams = params.frozen;
8590
- const addedParams = params.added;
8591
- const removedParams = params.removed;
8592
- const propsParams = params.properties;
8680
+ const swapAtSplitParams = splitPropertiesFromParams(params.swapAt);
8681
+ const enterFromSplitParams = splitPropertiesFromParams(params.enterFrom);
8682
+ const leaveToSplitParams = splitPropertiesFromParams(params.leaveTo);
8683
+ const transitionProperties = params.properties;
8684
+ /** @type {Number|FunctionValue} */
8685
+ params.duration = setValue(params.duration, 350);
8686
+ /** @type {Number|FunctionValue} */
8687
+ params.delay = setValue(params.delay, 0);
8688
+ /** @type {EasingParam|FunctionValue} */
8689
+ params.ease = setValue(params.ease, 'inOut(3.5)');
8593
8690
  /** @type {AutoLayoutParams} */
8594
8691
  this.params = params;
8595
8692
  /** @type {DOMTarget} */
@@ -8600,29 +8697,27 @@ class AutoLayout {
8600
8697
  this.children = params.children || '*';
8601
8698
  /** @type {Boolean} */
8602
8699
  this.absoluteCoords = false;
8603
- /** @type {Number|FunctionValue} */
8604
- this.duration = setValue(params.duration, 500);
8605
- /** @type {Number|FunctionValue} */
8606
- this.delay = setValue(params.delay, 0);
8607
- /** @type {EasingParam} */
8608
- this.ease = setValue(params.ease, 'inOut(3.5)');
8609
- /** @type {Callback<this>} */
8610
- this.onComplete = setValue(params.onComplete, /** @type {Callback<this>} */(noop));
8611
8700
  /** @type {LayoutStateParams} */
8612
- this.frozenParams = frozenParams || { opacity: 0 };
8701
+ this.swapAtParams = mergeObjects(params.swapAt || { opacity: 0 }, { ease: 'inOut(1.75)' });
8613
8702
  /** @type {LayoutStateParams} */
8614
- this.addedParams = addedParams || { opacity: 0 };
8703
+ this.enterFromParams = params.enterFrom || { opacity: 0 };
8615
8704
  /** @type {LayoutStateParams} */
8616
- this.removedParams = removedParams || { opacity: 0 };
8705
+ this.leaveToParams = params.leaveTo || { opacity: 0 };
8617
8706
  /** @type {Set<String>} */
8618
8707
  this.properties = new Set([
8619
8708
  'opacity',
8709
+ 'fontSize',
8710
+ 'color',
8711
+ 'backgroundColor',
8620
8712
  'borderRadius',
8713
+ 'border',
8714
+ 'filter',
8715
+ 'clipPath',
8621
8716
  ]);
8622
- if (frozenParams) for (let name in frozenParams) this.properties.add(name);
8623
- if (addedParams) for (let name in addedParams) this.properties.add(name);
8624
- if (removedParams) for (let name in removedParams) this.properties.add(name);
8625
- if (propsParams) for (let i = 0, l = propsParams.length; i < l; i++) this.properties.add(propsParams[i]);
8717
+ if (swapAtSplitParams[0]) for (let name in swapAtSplitParams[0]) this.properties.add(name);
8718
+ if (enterFromSplitParams[0]) for (let name in enterFromSplitParams[0]) this.properties.add(name);
8719
+ if (leaveToSplitParams[0]) for (let name in leaveToSplitParams[0]) this.properties.add(name);
8720
+ if (transitionProperties) for (let i = 0, l = transitionProperties.length; i < l; i++) this.properties.add(transitionProperties[i]);
8626
8721
  /** @type {Set<String>} */
8627
8722
  this.recordedProperties = new Set([
8628
8723
  'display',
@@ -8642,24 +8737,26 @@ class AutoLayout {
8642
8737
  ]);
8643
8738
  this.properties.forEach(prop => this.recordedProperties.add(prop));
8644
8739
  /** @type {WeakSet<DOMTarget>} */
8645
- this.pendingRemoved = new WeakSet();
8740
+ this.pendingRemoval = new WeakSet();
8646
8741
  /** @type {Map<DOMTarget, String|null>} */
8647
8742
  this.transitionMuteStore = new Map();
8648
8743
  /** @type {LayoutSnapshot} */
8649
8744
  this.oldState = new LayoutSnapshot(this);
8650
8745
  /** @type {LayoutSnapshot} */
8651
8746
  this.newState = new LayoutSnapshot(this);
8652
- /** @type {Timeline|null} */
8747
+ /** @type {Timeline} */
8653
8748
  this.timeline = null;
8654
- /** @type {WAAPIAnimation|null} */
8749
+ /** @type {WAAPIAnimation} */
8655
8750
  this.transformAnimation = null;
8656
8751
  /** @type {Array<DOMTarget>} */
8657
- this.frozen = [];
8752
+ this.animating = [];
8658
8753
  /** @type {Array<DOMTarget>} */
8659
- this.removed = [];
8754
+ this.swapping = [];
8660
8755
  /** @type {Array<DOMTarget>} */
8661
- this.added = [];
8662
- // Record the current state as the old state to init the data attributes
8756
+ this.leaving = [];
8757
+ /** @type {Array<DOMTarget>} */
8758
+ this.entering = [];
8759
+ // Record the current state as the old state to init the data attributes and allow imediate .animate()
8663
8760
  this.oldState.record();
8664
8761
  // And all layout transition muted during the record
8665
8762
  restoreLayoutTransition(this.transitionMuteStore);
@@ -8669,6 +8766,7 @@ class AutoLayout {
8669
8766
  * @return {this}
8670
8767
  */
8671
8768
  revert() {
8769
+ this.root.classList.remove('is-animated');
8672
8770
  if (this.timeline) {
8673
8771
  this.timeline.complete();
8674
8772
  this.timeline = null;
@@ -8677,8 +8775,7 @@ class AutoLayout {
8677
8775
  this.transformAnimation.complete();
8678
8776
  this.transformAnimation = null;
8679
8777
  }
8680
- this.root.classList.remove('is-animated');
8681
- this.frozen.length = this.removed.length = this.added.length = 0;
8778
+ this.animating.length = this.swapping.length = this.leaving.length = this.entering.length = 0;
8682
8779
  this.oldState.revert();
8683
8780
  this.newState.revert();
8684
8781
  requestAnimationFrame(() => restoreLayoutTransition(this.transitionMuteStore));
@@ -8711,20 +8808,72 @@ class AutoLayout {
8711
8808
  * @return {Timeline}
8712
8809
  */
8713
8810
  animate(params = {}) {
8714
- const delay = setValue(params.delay, this.delay);
8715
- const duration = setValue(params.duration, this.duration);
8716
- const onComplete = setValue(params.onComplete, this.onComplete);
8717
- const frozenParams = params.frozen ? mergeObjects(params.frozen, this.frozenParams) : this.frozenParams;
8718
- const addedParams = params.added ? mergeObjects(params.added, this.addedParams) : this.addedParams;
8719
- const removedParams = params.removed ? mergeObjects(params.removed, this.removedParams) : this.removedParams;
8811
+ /** @type { LayoutAnimationTimingsParams } */
8812
+ const animationTimings = {
8813
+ ease: setValue(params.ease, this.params.ease),
8814
+ delay: setValue(params.delay, this.params.delay),
8815
+ duration: setValue(params.duration, this.params.duration),
8816
+ };
8817
+ /** @type {TimelineParams} */
8818
+ const tlParams = {};
8819
+ const onComplete = setValue(params.onComplete, this.params.onComplete);
8820
+ const onPause = setValue(params.onPause, this.params.onPause);
8821
+ for (let name in defaults) {
8822
+ if (name !== 'ease' && name !== 'duration' && name !== 'delay') {
8823
+ if (!isUnd(params[name])) {
8824
+ tlParams[name] = params[name];
8825
+ } else if (!isUnd(this.params[name])) {
8826
+ tlParams[name] = this.params[name];
8827
+ }
8828
+ }
8829
+ }
8830
+ tlParams.onComplete = () => {
8831
+ // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
8832
+ if (this.transformAnimation) this.transformAnimation.cancel();
8833
+ newState.forEachRootNode(node => {
8834
+ restoreNodeVisualState(node);
8835
+ restoreNodeInlineStyles(node);
8836
+ });
8837
+ for (let i = 0, l = transformed.length; i < l; i++) {
8838
+ const $el = transformed[i];
8839
+ $el.style.transform = newState.getComputedValue($el, 'transform');
8840
+ }
8841
+ if (this.root.classList.contains('is-animated')) {
8842
+ this.root.classList.remove('is-animated');
8843
+ if (onComplete) onComplete(this.timeline);
8844
+ }
8845
+ // Avoid CSS transitions at the end of the animation by restoring them on the next frame
8846
+ requestAnimationFrame(() => {
8847
+ if (this.root.classList.contains('is-animated')) return;
8848
+ restoreLayoutTransition(this.transitionMuteStore);
8849
+ });
8850
+ };
8851
+ tlParams.onPause = () => {
8852
+ if (!this.root.classList.contains('is-animated')) return;
8853
+ if (this.transformAnimation) this.transformAnimation.cancel();
8854
+ newState.forEachRootNode(restoreNodeVisualState);
8855
+ this.root.classList.remove('is-animated');
8856
+ if (onComplete) onComplete(this.timeline);
8857
+ if (onPause) onPause(this.timeline);
8858
+ };
8859
+ tlParams.composition = false;
8860
+
8861
+ const swapAtParams = mergeObjects(mergeObjects(params.swapAt || {}, this.swapAtParams), animationTimings);
8862
+ const enterFromParams = mergeObjects(mergeObjects(params.enterFrom || {}, this.enterFromParams), animationTimings);
8863
+ const leaveToParams = mergeObjects(mergeObjects(params.leaveTo || {}, this.leaveToParams), animationTimings);
8864
+ const [ swapAtProps, swapAtTimings ] = splitPropertiesFromParams(swapAtParams);
8865
+ const [ enterFromProps, enterFromTimings ] = splitPropertiesFromParams(enterFromParams);
8866
+ const [ leaveToProps, leaveToTimings ] = splitPropertiesFromParams(leaveToParams);
8867
+
8720
8868
  const oldState = this.oldState;
8721
8869
  const newState = this.newState;
8722
- const added = this.added;
8723
- const removed = this.removed;
8724
- const frozen = this.frozen;
8725
- const pendingRemoved = this.pendingRemoved;
8870
+ const animating = this.animating;
8871
+ const swapping = this.swapping;
8872
+ const entering = this.entering;
8873
+ const leaving = this.leaving;
8874
+ const pendingRemoval = this.pendingRemoval;
8726
8875
 
8727
- added.length = removed.length = frozen.length = 0;
8876
+ animating.length = swapping.length = entering.length = leaving.length = 0;
8728
8877
 
8729
8878
  // Mute old state CSS transitions to prevent wrong properties calculation
8730
8879
  oldState.forEachRootNode(muteNodeTransition);
@@ -8735,10 +8884,12 @@ class AutoLayout {
8735
8884
  const targets = [];
8736
8885
  const animated = [];
8737
8886
  const transformed = [];
8738
- const animatedFrozen = [];
8739
- const root = newState.rootNode.$el;
8887
+ const animatedSwap = [];
8888
+ const rootNode = newState.rootNode;
8889
+ const $root = rootNode.$el;
8740
8890
 
8741
8891
  newState.forEachRootNode(node => {
8892
+
8742
8893
  const $el = node.$el;
8743
8894
  const id = node.id;
8744
8895
  const parent = node.parentNode;
@@ -8746,10 +8897,6 @@ class AutoLayout {
8746
8897
  const parentRemoved = parent ? parent.branchRemoved : false;
8747
8898
  const parentNotRendered = parent ? parent.branchNotRendered : false;
8748
8899
 
8749
- // Delay and duration must be calculated in the animate() call to support delay override
8750
- node.delay = +(isFnc(delay) ? delay($el, node.index, node.total) : delay);
8751
- node.duration = +(isFnc(duration) ? duration($el, node.index, node.total) : duration);
8752
-
8753
8900
  let oldStateNode = oldState.nodes.get(id);
8754
8901
 
8755
8902
  const hasNoOldState = !oldStateNode;
@@ -8773,26 +8920,16 @@ class AutoLayout {
8773
8920
 
8774
8921
  // Recalculate postion relative to their parent for elements that have been moved
8775
8922
  if (!oldStateNode.measuredIsRemoved && !isRemovedNow && !hasNoOldState && (parentChanged || elementChanged)) {
8776
- let offsetX = 0;
8777
- let offsetY = 0;
8778
- let current = node.parentNode;
8779
- while (current) {
8780
- offsetX += current.properties.x || 0;
8781
- offsetY += current.properties.y || 0;
8782
- if (current.parentNode === newState.rootNode) break;
8783
- current = current.parentNode;
8784
- }
8785
- let oldOffsetX = 0;
8786
- let oldOffsetY = 0;
8787
- let oldCurrent = oldStateNode.parentNode;
8788
- while (oldCurrent) {
8789
- oldOffsetX += oldCurrent.properties.x || 0;
8790
- oldOffsetY += oldCurrent.properties.y || 0;
8791
- if (oldCurrent.parentNode === oldState.rootNode) break;
8792
- oldCurrent = oldCurrent.parentNode;
8793
- }
8794
- oldStateNode.properties.x += oldOffsetX - offsetX;
8795
- oldStateNode.properties.y += oldOffsetY - offsetY;
8923
+ const oldAbsoluteLeft = oldStateNode.properties.left;
8924
+ const oldAbsoluteTop = oldStateNode.properties.top;
8925
+ const newParent = parent || newState.rootNode;
8926
+ const oldParent = newParent.id ? oldState.nodes.get(newParent.id) : null;
8927
+ const parentLeft = oldParent ? oldParent.properties.left : newParent.properties.left;
8928
+ const parentTop = oldParent ? oldParent.properties.top : newParent.properties.top;
8929
+ const borderLeft = oldParent ? oldParent.properties.clientLeft : newParent.properties.clientLeft;
8930
+ const borderTop = oldParent ? oldParent.properties.clientTop : newParent.properties.clientTop;
8931
+ oldStateNode.properties.x = oldAbsoluteLeft - parentLeft - borderLeft;
8932
+ oldStateNode.properties.y = oldAbsoluteTop - parentTop - borderTop;
8796
8933
  }
8797
8934
 
8798
8935
  if (node.hasVisibilitySwap) {
@@ -8809,7 +8946,7 @@ class AutoLayout {
8809
8946
  }
8810
8947
  }
8811
8948
 
8812
- const wasPendingRemoval = pendingRemoved.has($el);
8949
+ const wasPendingRemoval = pendingRemoval.has($el);
8813
8950
  const wasVisibleBefore = oldStateNode.measuredIsVisible;
8814
8951
  const isVisibleNow = node.measuredIsVisible;
8815
8952
  const becomeVisible = !wasVisibleBefore && isVisibleNow && !parentNotRendered;
@@ -8817,113 +8954,156 @@ class AutoLayout {
8817
8954
  const newlyRemoved = isRemovedNow && !wasRemovedBefore && !parentRemoved;
8818
8955
  const topLevelRemoved = newlyRemoved || isRemovedNow && wasPendingRemoval && !parentRemoved;
8819
8956
 
8820
- if (node.measuredIsRemoved && wasVisibleBefore) {
8957
+ node.branchAdded = parentAdded || topLevelAdded;
8958
+ node.branchRemoved = parentRemoved || topLevelRemoved;
8959
+ node.branchNotRendered = parentNotRendered || isRemovedNow;
8960
+
8961
+ if (isRemovedNow && wasVisibleBefore) {
8821
8962
  node.$el.style.display = oldStateNode.measuredDisplay;
8822
8963
  node.$el.style.visibility = 'visible';
8823
8964
  cloneNodeProperties(oldStateNode, node, newState);
8824
8965
  }
8825
8966
 
8967
+ // Node is leaving
8826
8968
  if (newlyRemoved) {
8827
- removed.push($el);
8828
- pendingRemoved.add($el);
8969
+ if (node.isTarget) {
8970
+ leaving.push($el);
8971
+ node.isLeaving = true;
8972
+ }
8973
+ pendingRemoval.add($el);
8829
8974
  } else if (!isRemovedNow && wasPendingRemoval) {
8830
- pendingRemoved.delete($el);
8975
+ pendingRemoval.delete($el);
8831
8976
  }
8832
8977
 
8833
- // Node is added
8978
+ // Node is entering
8834
8979
  if ((topLevelAdded && !parentNotRendered) || becomeVisible) {
8835
- updateNodeProperties(oldStateNode, addedParams);
8836
- added.push($el);
8837
- // Node is removed
8980
+ updateNodeProperties(oldStateNode, enterFromProps);
8981
+ if (node.isTarget) {
8982
+ entering.push($el);
8983
+ node.isEntering = true;
8984
+ }
8985
+ // Node is leaving
8838
8986
  } else if (topLevelRemoved && !parentNotRendered) {
8839
- updateNodeProperties(node, removedParams);
8987
+ updateNodeProperties(node, leaveToProps);
8840
8988
  }
8841
8989
 
8842
- // Compute function based propety values before cheking for changes
8843
- for (let name in node.properties) {
8844
- node.properties[name] = newState.getValue(node.$el, name);
8845
- // NOTE: I'm using node.$el to get the value of old state, make sure this is valid instead of oldStateNode.$el
8846
- oldStateNode.properties[name] = oldState.getValue(node.$el, name);
8990
+ // Node is animating
8991
+ // The animating array is used only to calculate delays and duration on root children
8992
+ if (node !== rootNode && node.isTarget && !node.isEntering && !node.isLeaving) {
8993
+ animating.push($el);
8847
8994
  }
8848
8995
 
8849
- const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
8850
- let propertyChanged = false;
8996
+ targets.push($el);
8851
8997
 
8998
+ });
8852
8999
 
8853
- if (node.isTarget && (!node.measuredIsRemoved && wasVisibleBefore || node.measuredIsRemoved && isVisibleNow)) {
8854
- if (!node.isInlined && (node.properties.transform !== 'none' || oldStateNode.properties.transform !== 'none')) {
8855
- node.hasTransform = true;
8856
- propertyChanged = true;
8857
- transformed.push($el);
8858
- }
8859
- for (let name in node.properties) {
8860
- if (name !== 'transform' && (node.properties[name] !== oldStateNode.properties[name] || hiddenStateChanged)) {
8861
- propertyChanged = true;
8862
- animated.push($el);
8863
- break;
8864
- }
8865
- }
9000
+ let enteringIndex = 0;
9001
+ let leavingIndex = 0;
9002
+ let animatingIndex = 0;
9003
+
9004
+ newState.forEachRootNode(node => {
9005
+
9006
+ const $el = node.$el;
9007
+ const parent = node.parentNode;
9008
+ const oldStateNode = oldState.nodes.get(node.id);
9009
+ const nodeProperties = node.properties;
9010
+ const oldStateNodeProperties = oldStateNode.properties;
9011
+
9012
+ // Use closest animated parent index and total values so that children staggered delays are in sync with their parent
9013
+ let animatedParent = parent !== rootNode && parent;
9014
+ while (animatedParent && !animatedParent.isTarget && animatedParent !== rootNode) {
9015
+ animatedParent = animatedParent.parentNode;
8866
9016
  }
8867
9017
 
8868
- const nodeHasChanged = (propertyChanged || topLevelAdded || topLevelRemoved || becomeVisible);
8869
- const nodeIsAnimated = node.isTarget && nodeHasChanged;
9018
+ const animatingTotal = animating.length;
9019
+
9020
+ // Root is always animated first in sync with the first child (animating.length is the total of children)
9021
+ if (node === rootNode) {
9022
+ node.index = 0;
9023
+ node.total = animatingTotal;
9024
+ updateNodeTimingParams(node, animationTimings);
9025
+ } else if (node.isEntering) {
9026
+ node.index = animatedParent ? animatedParent.index : enteringIndex;
9027
+ node.total = animatedParent ? animatingTotal : entering.length;
9028
+ updateNodeTimingParams(node, enterFromTimings);
9029
+ enteringIndex++;
9030
+ } else if (node.isLeaving) {
9031
+ node.index = animatedParent ? animatedParent.index : leavingIndex;
9032
+ node.total = animatedParent ? animatingTotal : leaving.length;
9033
+ leavingIndex++;
9034
+ updateNodeTimingParams(node, leaveToTimings);
9035
+ } else if (node.isTarget) {
9036
+ node.index = animatingIndex++;
9037
+ node.total = animatingTotal;
9038
+ updateNodeTimingParams(node, animationTimings);
9039
+ } else {
9040
+ node.index = animatedParent ? animatedParent.index : 0;
9041
+ node.total = animatingTotal;
9042
+ updateNodeTimingParams(node, swapAtTimings);
9043
+ }
8870
9044
 
8871
- node.isAnimated = nodeIsAnimated;
8872
- node.branchAdded = parentAdded || topLevelAdded;
8873
- node.branchRemoved = parentRemoved || topLevelRemoved;
8874
- node.branchNotRendered = parentNotRendered || node.measuredIsRemoved;
9045
+ // Make sure the old state node has its inex and total values up to date for valid "from" function values calculation
9046
+ oldStateNode.index = node.index;
9047
+ oldStateNode.total = node.total;
8875
9048
 
9049
+ // Computes all values up front so we can check for changes and we don't have to re-compute them inside the animation props
9050
+ for (let prop in nodeProperties) {
9051
+ nodeProperties[prop] = getFunctionValue(nodeProperties[prop], $el, node.index, node.total);
9052
+ oldStateNodeProperties[prop] = getFunctionValue(oldStateNodeProperties[prop], $el, oldStateNode.index, oldStateNode.total);
9053
+ }
9054
+
9055
+ // Use a 1px tolerance to detect dimensions changes to prevent width / height animations on barelly visible elements
8876
9056
  const sizeTolerance = 1;
8877
- const widthChanged = Math.abs(node.properties.width - oldStateNode.properties.width) > sizeTolerance;
8878
- const heightChanged = Math.abs(node.properties.height - oldStateNode.properties.height) > sizeTolerance;
9057
+ const widthChanged = Math.abs(nodeProperties.width - oldStateNodeProperties.width) > sizeTolerance;
9058
+ const heightChanged = Math.abs(nodeProperties.height - oldStateNodeProperties.height) > sizeTolerance;
8879
9059
 
8880
9060
  node.sizeChanged = (widthChanged || heightChanged);
8881
9061
 
8882
- targets.push($el);
9062
+ // const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
9063
+
9064
+ if (node.isTarget && (!node.measuredIsRemoved && oldStateNode.measuredIsVisible || node.measuredIsRemoved && node.measuredIsVisible)) {
9065
+ if (!node.isInlined && (nodeProperties.transform !== 'none' || oldStateNodeProperties.transform !== 'none')) {
9066
+ node.hasTransform = true;
9067
+ transformed.push($el);
9068
+ }
9069
+ for (let prop in nodeProperties) {
9070
+ // if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop] || hiddenStateChanged)) {
9071
+ if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop])) {
9072
+ animated.push($el);
9073
+ break;
9074
+ }
9075
+ }
9076
+ }
8883
9077
 
8884
9078
  if (!node.isTarget) {
8885
- frozen.push($el);
8886
- if ((nodeHasChanged || node.sizeChanged) && parent && parent.isTarget && parent.isAnimated && parent.sizeChanged) {
8887
- animatedFrozen.push($el);
9079
+ swapping.push($el);
9080
+ if (node.sizeChanged && parent && parent.isTarget && parent.sizeChanged) {
9081
+ if (!node.isInlined && swapAtProps.transform) {
9082
+ node.hasTransform = true;
9083
+ transformed.push($el);
9084
+ }
9085
+ animatedSwap.push($el);
8888
9086
  }
8889
9087
  }
9088
+
8890
9089
  });
8891
9090
 
8892
- const defaults = {
8893
- ease: setValue(params.ease, this.ease),
8894
- duration: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).duration,
8895
- delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
9091
+ const timingParams = {
9092
+ delay: (/** @type {HTMLElement} */$el) => newState.getNode($el).delay,
9093
+ duration: (/** @type {HTMLElement} */$el) => newState.getNode($el).duration,
9094
+ ease: (/** @type {HTMLElement} */$el) => newState.getNode($el).ease,
8896
9095
  };
8897
9096
 
8898
- this.timeline = createTimeline({
8899
- onComplete: () => {
8900
- // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
8901
- if (this.transformAnimation) this.transformAnimation.cancel();
8902
- newState.forEachRootNode(node => {
8903
- restoreNodeVisualState(node);
8904
- restoreNodeInlineStyles(node);
8905
- });
8906
- for (let i = 0, l = transformed.length; i < l; i++) {
8907
- const $el = transformed[i];
8908
- $el.style.transform = newState.getValue($el, 'transform');
8909
- }
8910
- this.root.classList.remove('is-animated');
8911
- if (onComplete) onComplete(this);
8912
- // Avoid CSS transitions at the end of the animation by restoring them on the next frame
8913
- requestAnimationFrame(() => {
8914
- if (this.root.classList.contains('is-animated')) return;
8915
- restoreLayoutTransition(this.transitionMuteStore);
8916
- });
8917
- },
8918
- onPause: () => {
8919
- if (this.transformAnimation) this.transformAnimation.cancel();
8920
- newState.forEachRootNode(restoreNodeVisualState);
8921
- this.root.classList.remove('is-animated');
8922
- if (onComplete) onComplete(this);
8923
- },
8924
- composition: false,
8925
- defaults,
8926
- });
9097
+ tlParams.defaults = timingParams;
9098
+
9099
+ this.timeline = createTimeline(tlParams);
9100
+
9101
+ // Imediatly return the timeline if no layout changes detected
9102
+ if (!animated.length && !transformed.length && !swapping.length) {
9103
+ // Make sure to restore all CSS transition if no animation
9104
+ restoreLayoutTransition(this.transitionMuteStore);
9105
+ return this.timeline.complete();
9106
+ }
8927
9107
 
8928
9108
  if (targets.length) {
8929
9109
 
@@ -8936,16 +9116,14 @@ class AutoLayout {
8936
9116
  const newNode = newState.nodes.get(id);
8937
9117
  const oldNodeState = oldNode.properties;
8938
9118
 
8939
- // Make sure to mute all CSS transition before applying the oldState styles back
8940
- muteNodeTransition(newNode);
9119
+ // muteNodeTransition(newNode);
8941
9120
 
8942
9121
  // Don't animate dimensions and positions of inlined elements
8943
9122
  if (!newNode.isInlined) {
8944
9123
  // Display grid can mess with the absolute positioning, so set it to block during transition
8945
- // if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.display = 'block';
8946
- $el.style.display = 'block';
8947
- // All children must be in position absolue
8948
- if ($el !== root || this.absoluteCoords) {
9124
+ if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.setProperty('display', 'block', 'important');
9125
+ // All children must be in position absolute or fixed
9126
+ if ($el !== $root || this.absoluteCoords) {
8949
9127
  $el.style.position = this.absoluteCoords ? 'fixed' : 'absolute';
8950
9128
  $el.style.left = '0px';
8951
9129
  $el.style.top = '0px';
@@ -8953,7 +9131,7 @@ class AutoLayout {
8953
9131
  $el.style.marginTop = '0px';
8954
9132
  $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
8955
9133
  }
8956
- if ($el === root && newNode.measuredPosition === 'static') {
9134
+ if ($el === $root && newNode.measuredPosition === 'static') {
8957
9135
  $el.style.position = 'relative';
8958
9136
  // Cancel left / trop in case the static element had muted values now activated by potision relative
8959
9137
  $el.style.left = '0px';
@@ -8972,9 +9150,7 @@ class AutoLayout {
8972
9150
  // Restore the scroll position if the oldState differs from the current state
8973
9151
  if (oldState.scrollX !== window.scrollX || oldState.scrollY !== window.scrollY) {
8974
9152
  // Restoring in the next frame avoids race conditions if for example a waapi animation commit styles that affect the root height
8975
- requestAnimationFrame(() => {
8976
- window.scrollTo(oldState.scrollX, oldState.scrollY);
8977
- });
9153
+ requestAnimationFrame(() => window.scrollTo(oldState.scrollX, oldState.scrollY));
8978
9154
  }
8979
9155
 
8980
9156
  for (let i = 0, l = animated.length; i < l; i++) {
@@ -8984,25 +9160,25 @@ class AutoLayout {
8984
9160
  const newNode = newState.nodes.get(id);
8985
9161
  const oldNodeState = oldNode.properties;
8986
9162
  const newNodeState = newNode.properties;
8987
- let hasChanged = false;
9163
+ let nodeHasChanged = false;
9164
+ /** @type {AnimationParams} */
8988
9165
  const animatedProps = {
8989
9166
  composition: 'none',
8990
- // delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
8991
9167
  };
8992
9168
  if (!newNode.isInlined) {
8993
9169
  if (oldNodeState.width !== newNodeState.width) {
8994
9170
  animatedProps.width = [oldNodeState.width, newNodeState.width];
8995
- hasChanged = true;
9171
+ nodeHasChanged = true;
8996
9172
  }
8997
9173
  if (oldNodeState.height !== newNodeState.height) {
8998
9174
  animatedProps.height = [oldNodeState.height, newNodeState.height];
8999
- hasChanged = true;
9175
+ nodeHasChanged = true;
9000
9176
  }
9001
9177
  // If the node has transforms we handle the translate animation in wappi otherwise translate and other transforms can be out of sync
9002
9178
  // Always animate translate
9003
9179
  if (!newNode.hasTransform) {
9004
9180
  animatedProps.translate = [`${oldNodeState.x}px ${oldNodeState.y}px`, `${newNodeState.x}px ${newNodeState.y}px`];
9005
- hasChanged = true;
9181
+ nodeHasChanged = true;
9006
9182
  }
9007
9183
  }
9008
9184
  this.properties.forEach(prop => {
@@ -9010,73 +9186,77 @@ class AutoLayout {
9010
9186
  const newVal = newNodeState[prop];
9011
9187
  if (prop !== 'transform' && oldVal !== newVal) {
9012
9188
  animatedProps[prop] = [oldVal, newVal];
9013
- hasChanged = true;
9189
+ nodeHasChanged = true;
9014
9190
  }
9015
9191
  });
9016
- if (hasChanged) {
9192
+ if (nodeHasChanged) {
9017
9193
  this.timeline.add($el, animatedProps, 0);
9018
9194
  }
9019
9195
  }
9020
9196
 
9021
9197
  }
9022
9198
 
9023
- if (frozen.length) {
9199
+ if (swapping.length) {
9024
9200
 
9025
- for (let i = 0, l = frozen.length; i < l; i++) {
9026
- const $el = frozen[i];
9027
- const oldNode = oldState.nodes.get($el.dataset.layoutId);
9201
+ for (let i = 0, l = swapping.length; i < l; i++) {
9202
+ const $el = swapping[i];
9203
+ const oldNode = oldState.getNode($el);
9028
9204
  if (!oldNode.isInlined) {
9029
- const oldNodeState = oldState.get($el);
9030
- $el.style.width = `${oldNodeState.width}px`;
9031
- $el.style.height = `${oldNodeState.height}px`;
9205
+ const oldNodeProps = oldNode.properties;
9206
+ $el.style.width = `${oldNodeProps.width}px`;
9207
+ $el.style.height = `${oldNodeProps.height}px`;
9032
9208
  // Overrides user defined min and max to prevents width and height clamping
9033
9209
  $el.style.minWidth = `auto`;
9034
9210
  $el.style.minHeight = `auto`;
9035
9211
  $el.style.maxWidth = `none`;
9036
9212
  $el.style.maxHeight = `none`;
9037
- $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
9213
+ $el.style.translate = `${oldNodeProps.x}px ${oldNodeProps.y}px`;
9038
9214
  }
9039
9215
  this.properties.forEach(prop => {
9040
9216
  if (prop !== 'transform') {
9041
- $el.style[prop] = `${oldState.getValue($el, prop)}`;
9217
+ $el.style[prop] = `${oldState.getComputedValue($el, prop)}`;
9042
9218
  }
9043
9219
  });
9044
9220
  }
9045
9221
 
9046
- for (let i = 0, l = frozen.length; i < l; i++) {
9047
- const $el = frozen[i];
9048
- const newNode = newState.nodes.get($el.dataset.layoutId);
9049
- const newNodeState = newState.get($el);
9222
+ for (let i = 0, l = swapping.length; i < l; i++) {
9223
+ const $el = swapping[i];
9224
+ const newNode = newState.getNode($el);
9225
+ const newNodeProps = newNode.properties;
9050
9226
  this.timeline.call(() => {
9051
9227
  if (!newNode.isInlined) {
9052
- $el.style.width = `${newNodeState.width}px`;
9053
- $el.style.height = `${newNodeState.height}px`;
9228
+ $el.style.width = `${newNodeProps.width}px`;
9229
+ $el.style.height = `${newNodeProps.height}px`;
9054
9230
  // Overrides user defined min and max to prevents width and height clamping
9055
9231
  $el.style.minWidth = `auto`;
9056
9232
  $el.style.minHeight = `auto`;
9057
9233
  $el.style.maxWidth = `none`;
9058
9234
  $el.style.maxHeight = `none`;
9059
- $el.style.translate = `${newNodeState.x}px ${newNodeState.y}px`;
9235
+ $el.style.translate = `${newNodeProps.x}px ${newNodeProps.y}px`;
9060
9236
  }
9061
9237
  this.properties.forEach(prop => {
9062
9238
  if (prop !== 'transform') {
9063
- $el.style[prop] = `${newState.getValue($el, prop)}`;
9239
+ $el.style[prop] = `${newState.getComputedValue($el, prop)}`;
9064
9240
  }
9065
9241
  });
9066
9242
  }, newNode.delay + newNode.duration / 2);
9067
9243
  }
9068
9244
 
9069
- if (animatedFrozen.length) {
9070
- const animatedFrozenParams = /** @type {AnimationParams} */({});
9071
- if (frozenParams) {
9072
- for (let prop in frozenParams) {
9073
- animatedFrozenParams[prop] = [
9074
- { from: (/** @type {HTMLElement} */$el) => oldState.getValue($el, prop), ease: 'in(1.75)', to: frozenParams[prop] },
9075
- { from: frozenParams[prop], to: (/** @type {HTMLElement} */$el) => newState.getValue($el, prop), ease: 'out(1.75)' }
9076
- ];
9245
+ if (animatedSwap.length) {
9246
+ const ease = parseEase(newState.nodes.get(animatedSwap[0].dataset.layoutId).ease);
9247
+ const inverseEased = t => 1 - ease(1 - t);
9248
+ const animatedSwapParams = /** @type {AnimationParams} */({});
9249
+ if (swapAtProps) {
9250
+ for (let prop in swapAtProps) {
9251
+ if (prop !== 'transform') {
9252
+ animatedSwapParams[prop] = [
9253
+ { from: (/** @type {HTMLElement} */$el) => oldState.getComputedValue($el, prop), to: swapAtProps[prop] },
9254
+ { from: swapAtProps[prop], to: (/** @type {HTMLElement} */$el) => newState.getComputedValue($el, prop), ease: inverseEased }
9255
+ ];
9256
+ }
9077
9257
  }
9078
9258
  }
9079
- this.timeline.add(animatedFrozen, animatedFrozenParams, 0);
9259
+ this.timeline.add(animatedSwap, animatedSwapParams, 0);
9080
9260
  }
9081
9261
 
9082
9262
  }
@@ -9084,18 +9264,29 @@ class AutoLayout {
9084
9264
  const transformedLength = transformed.length;
9085
9265
 
9086
9266
  if (transformedLength) {
9087
- // We only need to set the transform property here since translate is alread defined the targets loop
9267
+ // We only need to set the transform property here since translate is alread defined in the targets loop
9088
9268
  for (let i = 0; i < transformedLength; i++) {
9089
9269
  const $el = transformed[i];
9090
- $el.style.translate = `${oldState.get($el).x}px ${oldState.get($el).y}px`,
9091
- $el.style.transform = oldState.getValue($el, 'transform');
9270
+ $el.style.translate = `${oldState.getComputedValue($el, 'x')}px ${oldState.getComputedValue($el, 'y')}px`,
9271
+ $el.style.transform = oldState.getComputedValue($el, 'transform');
9272
+ if (animatedSwap.includes($el)) {
9273
+ const node = newState.getNode($el);
9274
+ node.ease = getFunctionValue(swapAtParams.ease, $el, node.index, node.total);
9275
+ node.duration = getFunctionValue(swapAtParams.duration, $el, node.index, node.total);
9276
+ }
9092
9277
  }
9093
9278
  this.transformAnimation = waapi.animate(transformed, {
9094
- translate: (/** @type {HTMLElement} */$el) => `${newState.get($el).x}px ${newState.get($el).y}px`,
9095
- transform: (/** @type {HTMLElement} */$el) => newState.getValue($el, 'transform'),
9279
+ translate: (/** @type {HTMLElement} */$el) => `${newState.getComputedValue($el, 'x')}px ${newState.getComputedValue($el, 'y')}px`,
9280
+ transform: (/** @type {HTMLElement} */$el) => {
9281
+ const newValue = newState.getComputedValue($el, 'transform');
9282
+ if (!animatedSwap.includes($el)) return newValue;
9283
+ const oldValue = oldState.getComputedValue($el, 'transform');
9284
+ const node = newState.getNode($el);
9285
+ return [oldValue, getFunctionValue(swapAtProps.transform, $el, node.index, node.total), newValue]
9286
+ },
9096
9287
  autoplay: false,
9097
9288
  persist: true,
9098
- ...defaults,
9289
+ ...timingParams,
9099
9290
  });
9100
9291
  this.timeline.sync(this.transformAnimation, 0);
9101
9292
  }
@@ -9106,13 +9297,12 @@ class AutoLayout {
9106
9297
  /**
9107
9298
  * @param {(layout: this) => void} callback
9108
9299
  * @param {LayoutAnimationParams} [params]
9109
- * @return {this}
9300
+ * @return {Timeline}
9110
9301
  */
9111
9302
  update(callback, params = {}) {
9112
9303
  this.record();
9113
9304
  callback(this);
9114
- this.animate(params);
9115
- return this;
9305
+ return this.animate(params);
9116
9306
  }
9117
9307
  }
9118
9308