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,16 +1,16 @@
1
1
  /**
2
2
  * Anime.js - layout - CJS
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
  'use strict';
9
9
 
10
10
  var helpers = require('../core/helpers.cjs');
11
11
  var targets = require('../core/targets.cjs');
12
+ var parser = require('../easings/eases/parser.cjs');
12
13
  var values = require('../core/values.cjs');
13
- var consts = require('../core/consts.cjs');
14
14
  var timeline = require('../timeline/timeline.cjs');
15
15
  var waapi = require('../waapi/waapi.cjs');
16
16
  var globals = require('../core/globals.cjs');
@@ -18,6 +18,10 @@ var globals = require('../core/globals.cjs');
18
18
  /**
19
19
  * @import {
20
20
  * AnimationParams,
21
+ * RenderableCallbacks,
22
+ * TickableCallbacks,
23
+ * TimelineParams,
24
+ * TimerParams,
21
25
  * } from '../types/index.js'
22
26
  */
23
27
 
@@ -33,13 +37,18 @@ var globals = require('../core/globals.cjs');
33
37
  * } from '../waapi/waapi.js'
34
38
  */
35
39
 
40
+ /**
41
+ * @import {
42
+ * Spring,
43
+ } from '../easings/spring/index.js'
44
+ */
45
+
36
46
  /**
37
47
  * @import {
38
48
  * DOMTarget,
39
49
  * DOMTargetSelector,
40
50
  * FunctionValue,
41
51
  * EasingParam,
42
- * Callback,
43
52
  } from '../types/index.js'
44
53
  */
45
54
 
@@ -48,29 +57,47 @@ var globals = require('../core/globals.cjs');
48
57
  */
49
58
 
50
59
  /**
51
- * @typedef {Record<String, Number|String>} LayoutStateParams
60
+ * @typedef {Object} LayoutAnimationTimingsParams
61
+ * @property {Number|FunctionValue} [delay]
62
+ * @property {Number|FunctionValue} [duration]
63
+ * @property {EasingParam|FunctionValue} [ease]
64
+ */
65
+
66
+ /**
67
+ * @typedef {Record<String, Number|String|FunctionValue>} LayoutStateAnimationProperties
52
68
  */
53
69
 
54
70
  /**
55
- * @typedef {Object} LayoutAnimationParams
71
+ * @typedef {LayoutStateAnimationProperties & LayoutAnimationTimingsParams} LayoutStateParams
72
+ */
73
+
74
+ /**
75
+ * @typedef {Object} LayoutSpecificAnimationParams
56
76
  * @property {Number|FunctionValue} [delay]
57
77
  * @property {Number|FunctionValue} [duration]
58
- * @property {EasingParam} [ease]
59
- * @property {LayoutStateParams} [frozen]
60
- * @property {LayoutStateParams} [added]
61
- * @property {LayoutStateParams} [removed]
62
- * @property {Callback<AutoLayout>} [onComplete]
78
+ * @property {EasingParam|FunctionValue} [ease]
79
+ * @property {EasingParam} [playbackEase]
80
+ * @property {LayoutStateParams} [swapAt]
81
+ * @property {LayoutStateParams} [enterFrom]
82
+ * @property {LayoutStateParams} [leaveTo]
83
+ */
84
+
85
+ /**
86
+ * @typedef {LayoutSpecificAnimationParams & TimerParams & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} LayoutAnimationParams
63
87
  */
64
88
 
65
89
  /**
66
- * @typedef {LayoutAnimationParams & {
67
- * children?: LayoutChildrenParam,
68
- * properties?: Array<String>,
69
- * }} AutoLayoutParams
90
+ * @typedef {Object} LayoutOptions
91
+ * @property {LayoutChildrenParam} [children]
92
+ * @property {Array<String>} [properties]
70
93
  */
71
94
 
72
95
  /**
73
- * @typedef {Record<String, Number|String> & {
96
+ * @typedef {LayoutAnimationParams & LayoutOptions} AutoLayoutParams
97
+ */
98
+
99
+ /**
100
+ * @typedef {Record<String, Number|String|FunctionValue> & {
74
101
  * transform: String,
75
102
  * x: Number,
76
103
  * y: Number,
@@ -91,13 +118,15 @@ var globals = require('../core/globals.cjs');
91
118
  * @property {Number} total
92
119
  * @property {Number} delay
93
120
  * @property {Number} duration
121
+ * @property {EasingParam} ease
94
122
  * @property {DOMTarget} $measure
95
123
  * @property {LayoutSnapshot} state
96
124
  * @property {AutoLayout} layout
97
125
  * @property {LayoutNode|null} parentNode
98
126
  * @property {Boolean} isTarget
127
+ * @property {Boolean} isEntering
128
+ * @property {Boolean} isLeaving
99
129
  * @property {Boolean} hasTransform
100
- * @property {Boolean} isAnimated
101
130
  * @property {Array<String>} inlineStyles
102
131
  * @property {String|null} inlineTransforms
103
132
  * @property {String|null} inlineTransition
@@ -238,7 +267,7 @@ const detachNode = node => {
238
267
  * @param {DOMTarget} $el
239
268
  * @param {LayoutNode|null} parentNode
240
269
  * @param {LayoutSnapshot} state
241
- * @param {LayoutNode} [recycledNode]
270
+ * @param {LayoutNode} recycledNode
242
271
  * @return {LayoutNode}
243
272
  */
244
273
  const createNode = ($el, parentNode, state, recycledNode) => {
@@ -252,12 +281,15 @@ const createNode = ($el, parentNode, state, recycledNode) => {
252
281
  node.total = 1;
253
282
  node.delay = 0;
254
283
  node.duration = 0;
284
+ node.ease = null;
255
285
  node.state = state;
256
286
  node.layout = state.layout;
257
287
  node.parentNode = parentNode || null;
258
288
  node.isTarget = false;
289
+ node.isEntering = false;
290
+ node.isLeaving = false;
291
+ node.isInlined = false;
259
292
  node.hasTransform = false;
260
- node.isAnimated = false;
261
293
  node.inlineStyles = [];
262
294
  node.inlineTransforms = null;
263
295
  node.inlineTransition = null;
@@ -265,7 +297,6 @@ const createNode = ($el, parentNode, state, recycledNode) => {
265
297
  node.branchRemoved = false;
266
298
  node.branchNotRendered = false;
267
299
  node.sizeChanged = false;
268
- node.isInlined = false;
269
300
  node.hasVisibilitySwap = false;
270
301
  node.hasDisplayNone = false;
271
302
  node.hasVisibilityHidden = false;
@@ -408,7 +439,7 @@ const recordNodeState = (node, $measure, computedStyle, skipMeasurements) => {
408
439
 
409
440
  /**
410
441
  * @param {LayoutNode} node
411
- * @param {LayoutStateParams} [props]
442
+ * @param {LayoutStateAnimationProperties} [props]
412
443
  */
413
444
  const updateNodeProperties = (node, props) => {
414
445
  if (!props) return;
@@ -417,6 +448,19 @@ const updateNodeProperties = (node, props) => {
417
448
  }
418
449
  };
419
450
 
451
+ /**
452
+ * @param {LayoutNode} node
453
+ * @param {LayoutAnimationTimingsParams} params
454
+ */
455
+ const updateNodeTimingParams = (node, params) => {
456
+ const easeFunctionResult = values.getFunctionValue(params.ease, node.$el, node.index, node.total);
457
+ const keyEasing = helpers.isFnc(easeFunctionResult) ? easeFunctionResult : params.ease;
458
+ const hasSpring = !helpers.isUnd(keyEasing) && !helpers.isUnd(/** @type {Spring} */(keyEasing).ease);
459
+ node.ease = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing;
460
+ node.duration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : values.getFunctionValue(params.duration, node.$el, node.index, node.total);
461
+ node.delay = values.getFunctionValue(params.delay, node.$el, node.index, node.total);
462
+ };
463
+
420
464
  /**
421
465
  * @param {LayoutNode} node
422
466
  */
@@ -491,9 +535,9 @@ const restoreNodeVisualState = node => {
491
535
  node.$measure.style.removeProperty('visibility');
492
536
  }
493
537
  }
494
- if (node.measuredIsRemoved) {
495
- node.layout.pendingRemoved.delete(node.$el);
496
- }
538
+ // if (node.measuredIsRemoved) {
539
+ node.layout.pendingRemoval.delete(node.$el);
540
+ // }
497
541
  };
498
542
 
499
543
  /**
@@ -543,6 +587,7 @@ class LayoutSnapshot {
543
587
  */
544
588
  revert() {
545
589
  this.forEachNode(node => {
590
+ this.layout.pendingRemoval.delete(node.$el);
546
591
  node.$el.removeAttribute('data-layout-id');
547
592
  node.$measure.removeAttribute('data-layout-id');
548
593
  });
@@ -554,34 +599,22 @@ class LayoutSnapshot {
554
599
 
555
600
  /**
556
601
  * @param {DOMTarget} $el
557
- * @return {LayoutNodeProperties|undefined}
602
+ * @return {LayoutNode}
558
603
  */
559
- get($el) {
560
- const node = this.nodes.get($el.dataset.layoutId);
561
- if (!node) {
562
- console.warn(`No node found on state`);
563
- return;
564
- }
565
- return node.properties;
604
+ getNode($el) {
605
+ if (!$el || !$el.dataset) return;
606
+ return this.nodes.get($el.dataset.layoutId);
566
607
  }
567
608
 
568
609
  /**
569
610
  * @param {DOMTarget} $el
570
611
  * @param {String} prop
571
- * @return {Number|String|undefined}
612
+ * @return {Number|String}
572
613
  */
573
- getValue($el, prop) {
574
- if (!$el || !$el.dataset) {
575
- console.warn(`No element found on state (${$el})`);
576
- return;
577
- }
578
- const node = this.nodes.get($el.dataset.layoutId);
579
- if (!node) {
580
- console.warn(`No node found on state`);
581
- return;
582
- }
583
- const value = node.properties[prop];
584
- if (!helpers.isUnd(value)) return values.getFunctionValue(value, $el, node.index, node.total);
614
+ getComputedValue($el, prop) {
615
+ const node = this.getNode($el);
616
+ if (!node) return;
617
+ return /** @type {Number|String} */(node.properties[prop]);
585
618
  }
586
619
 
587
620
  /**
@@ -642,10 +675,10 @@ class LayoutSnapshot {
642
675
  const $parent = /** @type {LayoutNode|null} */(stack.pop());
643
676
  /** @type {DOMTarget|null} */
644
677
  const $current = /** @type {DOMTarget|null} */(stack.pop());
678
+
645
679
  if (!$current || $current.nodeType !== 1 || helpers.isSvg($current)) continue;
646
680
 
647
681
  const skipMeasurements = $parent ? $parent.measuredIsRemoved : false;
648
-
649
682
  const computedStyle = skipMeasurements ? hiddenComputedStyle : getComputedStyle($current);
650
683
  const hasDisplayNone = skipMeasurements ? true : computedStyle.display === 'none';
651
684
  const hasVisibilityHidden = skipMeasurements ? true : computedStyle.visibility === 'hidden';
@@ -692,11 +725,10 @@ class LayoutSnapshot {
692
725
  node.branchRemoved = false;
693
726
  node.branchNotRendered = false;
694
727
  node.isTarget = false;
695
- node.isAnimated = false;
728
+ node.sizeChanged = false;
696
729
  node.hasVisibilityHidden = hasVisibilityHidden;
697
730
  node.hasDisplayNone = hasDisplayNone;
698
731
  node.hasVisibilitySwap = (hasVisibilityHidden && !node.measuredHasVisibilityHidden) || (hasDisplayNone && !node.measuredHasDisplayNone);
699
- // node.hasVisibilitySwap = (hasVisibilityHidden !== node.measuredHasVisibilityHidden) || (hasDisplayNone !== node.measuredHasDisplayNone);
700
732
 
701
733
  this.nodes.set(node.id, node);
702
734
 
@@ -715,6 +747,7 @@ class LayoutSnapshot {
715
747
  $parent._tail = node;
716
748
  }
717
749
  } else {
750
+ // Each disconnected subtree becomes its own root in the snapshot graph
718
751
  this.rootNodes.add(node);
719
752
  }
720
753
 
@@ -758,11 +791,29 @@ class LayoutSnapshot {
758
791
  * @return {this}
759
792
  */
760
793
  record() {
761
- const { children, root } = this.layout;
794
+ const layout = this.layout;
795
+ const children = layout.children;
796
+ const root = layout.root;
762
797
  const toParse = helpers.isArr(children) ? children : [children];
763
798
  const scoped = [];
764
799
  const scopeRoot = children === '*' ? root : globals.scope.root;
765
800
 
801
+ // Mute transition and transforms of root ancestors before recording the state
802
+
803
+ /** @type {Array<DOMTarget|String|null>} */
804
+ const rootAncestorTransformStore = [];
805
+ let $ancestor = root.parentElement;
806
+ while ($ancestor && $ancestor.nodeType === 1) {
807
+ const computedStyle = getComputedStyle($ancestor);
808
+ if (computedStyle.transform && computedStyle.transform !== 'none') {
809
+ const inlineTransform = $ancestor.style.transform || '';
810
+ const inlineTransition = muteElementTransition($ancestor);
811
+ rootAncestorTransformStore.push($ancestor, inlineTransform, inlineTransition);
812
+ $ancestor.style.transform = 'none';
813
+ }
814
+ $ancestor = $ancestor.parentElement;
815
+ }
816
+
766
817
  for (let i = 0, l = toParse.length; i < l; i++) {
767
818
  const child = toParse[i];
768
819
  scoped[i] = helpers.isStr(child) ? scopeRoot.querySelectorAll(child) : child;
@@ -778,9 +829,13 @@ class LayoutSnapshot {
778
829
  rootNode.isTarget = true;
779
830
  this.rootNode = rootNode;
780
831
 
781
- // Track ids of nodes that belong to the current root to filter detached matches
782
832
  const inRootNodeIds = new Set();
833
+ // Update index and total for inital timing calculation
834
+ let index = 0, total = this.nodes.size;
783
835
  this.nodes.forEach((node, id) => {
836
+ node.index = index++;
837
+ node.total = total;
838
+ // Track ids of nodes that belong to the current root to filter detached matches
784
839
  if (node && node.measuredIsInsideRoot) {
785
840
  inRootNodeIds.add(id);
786
841
  }
@@ -810,7 +865,7 @@ class LayoutSnapshot {
810
865
 
811
866
  for (let i = 0, l = parsedChildren.length; i < l; i++) {
812
867
  const $el = parsedChildren[i];
813
- const node = this.nodes.get($el.dataset.layoutId);
868
+ const node = this.getNode($el);
814
869
  if (node) {
815
870
  let cur = node;
816
871
  while (cur) {
@@ -824,18 +879,52 @@ class LayoutSnapshot {
824
879
  this.scrollX = window.scrollX;
825
880
  this.scrollY = window.scrollY;
826
881
 
827
- const total = this.nodes.size;
828
-
829
882
  this.forEachNode(restoreNodeTransform);
830
- this.forEachNode((node, i) => {
831
- node.index = i;
832
- node.total = total;
833
- });
883
+
884
+ // Restore transition and transforms of root ancestors
885
+
886
+ for (let i = 0, l = rootAncestorTransformStore.length; i < l; i += 3) {
887
+ const $el = /** @type {DOMTarget} */(rootAncestorTransformStore[i]);
888
+ const inlineTransform = /** @type {String} */(rootAncestorTransformStore[i + 1]);
889
+ const inlineTransition = /** @type {String|null} */(rootAncestorTransformStore[i + 2]);
890
+ if (inlineTransform && inlineTransform !== '') {
891
+ $el.style.transform = inlineTransform;
892
+ } else {
893
+ $el.style.removeProperty('transform');
894
+ }
895
+ restoreElementTransition($el, inlineTransition);
896
+ }
834
897
 
835
898
  return this;
836
899
  }
837
900
  }
838
901
 
902
+ /**
903
+ * @param {LayoutStateParams} params
904
+ * @return {[LayoutStateAnimationProperties, LayoutAnimationTimingsParams]}
905
+ */
906
+ function splitPropertiesFromParams(params) {
907
+ /** @type {LayoutStateAnimationProperties} */
908
+ const properties = {};
909
+ /** @type {LayoutAnimationTimingsParams} */
910
+ const parameters = {};
911
+ for (let name in params) {
912
+ const value = params[name];
913
+ const isEase = name === 'ease';
914
+ const isTiming = name === 'duration' || name === 'delay';
915
+ if (isTiming || isEase) {
916
+ if (isEase) {
917
+ parameters[name] = /** @type {EasingParam} */(value);
918
+ } else {
919
+ parameters[name] = /** @type {Number|FunctionValue} */(value);
920
+ }
921
+ } else {
922
+ properties[name] = /** @type {Number|String} */(value);
923
+ }
924
+ }
925
+ return [properties, parameters];
926
+ }
927
+
839
928
  class AutoLayout {
840
929
  /**
841
930
  * @param {DOMTargetSelector} root
@@ -843,10 +932,16 @@ class AutoLayout {
843
932
  */
844
933
  constructor(root, params = {}) {
845
934
  if (globals.scope.current) globals.scope.current.register(this);
846
- const frozenParams = params.frozen;
847
- const addedParams = params.added;
848
- const removedParams = params.removed;
849
- const propsParams = params.properties;
935
+ const swapAtSplitParams = splitPropertiesFromParams(params.swapAt);
936
+ const enterFromSplitParams = splitPropertiesFromParams(params.enterFrom);
937
+ const leaveToSplitParams = splitPropertiesFromParams(params.leaveTo);
938
+ const transitionProperties = params.properties;
939
+ /** @type {Number|FunctionValue} */
940
+ params.duration = values.setValue(params.duration, 350);
941
+ /** @type {Number|FunctionValue} */
942
+ params.delay = values.setValue(params.delay, 0);
943
+ /** @type {EasingParam|FunctionValue} */
944
+ params.ease = values.setValue(params.ease, 'inOut(3.5)');
850
945
  /** @type {AutoLayoutParams} */
851
946
  this.params = params;
852
947
  /** @type {DOMTarget} */
@@ -857,29 +952,27 @@ class AutoLayout {
857
952
  this.children = params.children || '*';
858
953
  /** @type {Boolean} */
859
954
  this.absoluteCoords = false;
860
- /** @type {Number|FunctionValue} */
861
- this.duration = values.setValue(params.duration, 500);
862
- /** @type {Number|FunctionValue} */
863
- this.delay = values.setValue(params.delay, 0);
864
- /** @type {EasingParam} */
865
- this.ease = values.setValue(params.ease, 'inOut(3.5)');
866
- /** @type {Callback<this>} */
867
- this.onComplete = values.setValue(params.onComplete, /** @type {Callback<this>} */(consts.noop));
868
955
  /** @type {LayoutStateParams} */
869
- this.frozenParams = frozenParams || { opacity: 0 };
956
+ this.swapAtParams = helpers.mergeObjects(params.swapAt || { opacity: 0 }, { ease: 'inOut(1.75)' });
870
957
  /** @type {LayoutStateParams} */
871
- this.addedParams = addedParams || { opacity: 0 };
958
+ this.enterFromParams = params.enterFrom || { opacity: 0 };
872
959
  /** @type {LayoutStateParams} */
873
- this.removedParams = removedParams || { opacity: 0 };
960
+ this.leaveToParams = params.leaveTo || { opacity: 0 };
874
961
  /** @type {Set<String>} */
875
962
  this.properties = new Set([
876
963
  'opacity',
964
+ 'fontSize',
965
+ 'color',
966
+ 'backgroundColor',
877
967
  'borderRadius',
968
+ 'border',
969
+ 'filter',
970
+ 'clipPath',
878
971
  ]);
879
- if (frozenParams) for (let name in frozenParams) this.properties.add(name);
880
- if (addedParams) for (let name in addedParams) this.properties.add(name);
881
- if (removedParams) for (let name in removedParams) this.properties.add(name);
882
- if (propsParams) for (let i = 0, l = propsParams.length; i < l; i++) this.properties.add(propsParams[i]);
972
+ if (swapAtSplitParams[0]) for (let name in swapAtSplitParams[0]) this.properties.add(name);
973
+ if (enterFromSplitParams[0]) for (let name in enterFromSplitParams[0]) this.properties.add(name);
974
+ if (leaveToSplitParams[0]) for (let name in leaveToSplitParams[0]) this.properties.add(name);
975
+ if (transitionProperties) for (let i = 0, l = transitionProperties.length; i < l; i++) this.properties.add(transitionProperties[i]);
883
976
  /** @type {Set<String>} */
884
977
  this.recordedProperties = new Set([
885
978
  'display',
@@ -899,24 +992,26 @@ class AutoLayout {
899
992
  ]);
900
993
  this.properties.forEach(prop => this.recordedProperties.add(prop));
901
994
  /** @type {WeakSet<DOMTarget>} */
902
- this.pendingRemoved = new WeakSet();
995
+ this.pendingRemoval = new WeakSet();
903
996
  /** @type {Map<DOMTarget, String|null>} */
904
997
  this.transitionMuteStore = new Map();
905
998
  /** @type {LayoutSnapshot} */
906
999
  this.oldState = new LayoutSnapshot(this);
907
1000
  /** @type {LayoutSnapshot} */
908
1001
  this.newState = new LayoutSnapshot(this);
909
- /** @type {Timeline|null} */
1002
+ /** @type {Timeline} */
910
1003
  this.timeline = null;
911
- /** @type {WAAPIAnimation|null} */
1004
+ /** @type {WAAPIAnimation} */
912
1005
  this.transformAnimation = null;
913
1006
  /** @type {Array<DOMTarget>} */
914
- this.frozen = [];
1007
+ this.animating = [];
1008
+ /** @type {Array<DOMTarget>} */
1009
+ this.swapping = [];
915
1010
  /** @type {Array<DOMTarget>} */
916
- this.removed = [];
1011
+ this.leaving = [];
917
1012
  /** @type {Array<DOMTarget>} */
918
- this.added = [];
919
- // Record the current state as the old state to init the data attributes
1013
+ this.entering = [];
1014
+ // Record the current state as the old state to init the data attributes and allow imediate .animate()
920
1015
  this.oldState.record();
921
1016
  // And all layout transition muted during the record
922
1017
  restoreLayoutTransition(this.transitionMuteStore);
@@ -926,6 +1021,7 @@ class AutoLayout {
926
1021
  * @return {this}
927
1022
  */
928
1023
  revert() {
1024
+ this.root.classList.remove('is-animated');
929
1025
  if (this.timeline) {
930
1026
  this.timeline.complete();
931
1027
  this.timeline = null;
@@ -934,8 +1030,7 @@ class AutoLayout {
934
1030
  this.transformAnimation.complete();
935
1031
  this.transformAnimation = null;
936
1032
  }
937
- this.root.classList.remove('is-animated');
938
- this.frozen.length = this.removed.length = this.added.length = 0;
1033
+ this.animating.length = this.swapping.length = this.leaving.length = this.entering.length = 0;
939
1034
  this.oldState.revert();
940
1035
  this.newState.revert();
941
1036
  requestAnimationFrame(() => restoreLayoutTransition(this.transitionMuteStore));
@@ -968,20 +1063,72 @@ class AutoLayout {
968
1063
  * @return {Timeline}
969
1064
  */
970
1065
  animate(params = {}) {
971
- const delay = values.setValue(params.delay, this.delay);
972
- const duration = values.setValue(params.duration, this.duration);
973
- const onComplete = values.setValue(params.onComplete, this.onComplete);
974
- const frozenParams = params.frozen ? helpers.mergeObjects(params.frozen, this.frozenParams) : this.frozenParams;
975
- const addedParams = params.added ? helpers.mergeObjects(params.added, this.addedParams) : this.addedParams;
976
- const removedParams = params.removed ? helpers.mergeObjects(params.removed, this.removedParams) : this.removedParams;
1066
+ /** @type { LayoutAnimationTimingsParams } */
1067
+ const animationTimings = {
1068
+ ease: values.setValue(params.ease, this.params.ease),
1069
+ delay: values.setValue(params.delay, this.params.delay),
1070
+ duration: values.setValue(params.duration, this.params.duration),
1071
+ };
1072
+ /** @type {TimelineParams} */
1073
+ const tlParams = {};
1074
+ const onComplete = values.setValue(params.onComplete, this.params.onComplete);
1075
+ const onPause = values.setValue(params.onPause, this.params.onPause);
1076
+ for (let name in globals.defaults) {
1077
+ if (name !== 'ease' && name !== 'duration' && name !== 'delay') {
1078
+ if (!helpers.isUnd(params[name])) {
1079
+ tlParams[name] = params[name];
1080
+ } else if (!helpers.isUnd(this.params[name])) {
1081
+ tlParams[name] = this.params[name];
1082
+ }
1083
+ }
1084
+ }
1085
+ tlParams.onComplete = () => {
1086
+ // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
1087
+ if (this.transformAnimation) this.transformAnimation.cancel();
1088
+ newState.forEachRootNode(node => {
1089
+ restoreNodeVisualState(node);
1090
+ restoreNodeInlineStyles(node);
1091
+ });
1092
+ for (let i = 0, l = transformed.length; i < l; i++) {
1093
+ const $el = transformed[i];
1094
+ $el.style.transform = newState.getComputedValue($el, 'transform');
1095
+ }
1096
+ if (this.root.classList.contains('is-animated')) {
1097
+ this.root.classList.remove('is-animated');
1098
+ if (onComplete) onComplete(this.timeline);
1099
+ }
1100
+ // Avoid CSS transitions at the end of the animation by restoring them on the next frame
1101
+ requestAnimationFrame(() => {
1102
+ if (this.root.classList.contains('is-animated')) return;
1103
+ restoreLayoutTransition(this.transitionMuteStore);
1104
+ });
1105
+ };
1106
+ tlParams.onPause = () => {
1107
+ if (!this.root.classList.contains('is-animated')) return;
1108
+ if (this.transformAnimation) this.transformAnimation.cancel();
1109
+ newState.forEachRootNode(restoreNodeVisualState);
1110
+ this.root.classList.remove('is-animated');
1111
+ if (onComplete) onComplete(this.timeline);
1112
+ if (onPause) onPause(this.timeline);
1113
+ };
1114
+ tlParams.composition = false;
1115
+
1116
+ const swapAtParams = helpers.mergeObjects(helpers.mergeObjects(params.swapAt || {}, this.swapAtParams), animationTimings);
1117
+ const enterFromParams = helpers.mergeObjects(helpers.mergeObjects(params.enterFrom || {}, this.enterFromParams), animationTimings);
1118
+ const leaveToParams = helpers.mergeObjects(helpers.mergeObjects(params.leaveTo || {}, this.leaveToParams), animationTimings);
1119
+ const [ swapAtProps, swapAtTimings ] = splitPropertiesFromParams(swapAtParams);
1120
+ const [ enterFromProps, enterFromTimings ] = splitPropertiesFromParams(enterFromParams);
1121
+ const [ leaveToProps, leaveToTimings ] = splitPropertiesFromParams(leaveToParams);
1122
+
977
1123
  const oldState = this.oldState;
978
1124
  const newState = this.newState;
979
- const added = this.added;
980
- const removed = this.removed;
981
- const frozen = this.frozen;
982
- const pendingRemoved = this.pendingRemoved;
1125
+ const animating = this.animating;
1126
+ const swapping = this.swapping;
1127
+ const entering = this.entering;
1128
+ const leaving = this.leaving;
1129
+ const pendingRemoval = this.pendingRemoval;
983
1130
 
984
- added.length = removed.length = frozen.length = 0;
1131
+ animating.length = swapping.length = entering.length = leaving.length = 0;
985
1132
 
986
1133
  // Mute old state CSS transitions to prevent wrong properties calculation
987
1134
  oldState.forEachRootNode(muteNodeTransition);
@@ -992,10 +1139,12 @@ class AutoLayout {
992
1139
  const targets = [];
993
1140
  const animated = [];
994
1141
  const transformed = [];
995
- const animatedFrozen = [];
996
- const root = newState.rootNode.$el;
1142
+ const animatedSwap = [];
1143
+ const rootNode = newState.rootNode;
1144
+ const $root = rootNode.$el;
997
1145
 
998
1146
  newState.forEachRootNode(node => {
1147
+
999
1148
  const $el = node.$el;
1000
1149
  const id = node.id;
1001
1150
  const parent = node.parentNode;
@@ -1003,10 +1152,6 @@ class AutoLayout {
1003
1152
  const parentRemoved = parent ? parent.branchRemoved : false;
1004
1153
  const parentNotRendered = parent ? parent.branchNotRendered : false;
1005
1154
 
1006
- // Delay and duration must be calculated in the animate() call to support delay override
1007
- node.delay = +(helpers.isFnc(delay) ? delay($el, node.index, node.total) : delay);
1008
- node.duration = +(helpers.isFnc(duration) ? duration($el, node.index, node.total) : duration);
1009
-
1010
1155
  let oldStateNode = oldState.nodes.get(id);
1011
1156
 
1012
1157
  const hasNoOldState = !oldStateNode;
@@ -1056,7 +1201,7 @@ class AutoLayout {
1056
1201
  }
1057
1202
  }
1058
1203
 
1059
- const wasPendingRemoval = pendingRemoved.has($el);
1204
+ const wasPendingRemoval = pendingRemoval.has($el);
1060
1205
  const wasVisibleBefore = oldStateNode.measuredIsVisible;
1061
1206
  const isVisibleNow = node.measuredIsVisible;
1062
1207
  const becomeVisible = !wasVisibleBefore && isVisibleNow && !parentNotRendered;
@@ -1064,113 +1209,156 @@ class AutoLayout {
1064
1209
  const newlyRemoved = isRemovedNow && !wasRemovedBefore && !parentRemoved;
1065
1210
  const topLevelRemoved = newlyRemoved || isRemovedNow && wasPendingRemoval && !parentRemoved;
1066
1211
 
1067
- if (node.measuredIsRemoved && wasVisibleBefore) {
1212
+ node.branchAdded = parentAdded || topLevelAdded;
1213
+ node.branchRemoved = parentRemoved || topLevelRemoved;
1214
+ node.branchNotRendered = parentNotRendered || isRemovedNow;
1215
+
1216
+ if (isRemovedNow && wasVisibleBefore) {
1068
1217
  node.$el.style.display = oldStateNode.measuredDisplay;
1069
1218
  node.$el.style.visibility = 'visible';
1070
1219
  cloneNodeProperties(oldStateNode, node, newState);
1071
1220
  }
1072
1221
 
1222
+ // Node is leaving
1073
1223
  if (newlyRemoved) {
1074
- removed.push($el);
1075
- pendingRemoved.add($el);
1224
+ if (node.isTarget) {
1225
+ leaving.push($el);
1226
+ node.isLeaving = true;
1227
+ }
1228
+ pendingRemoval.add($el);
1076
1229
  } else if (!isRemovedNow && wasPendingRemoval) {
1077
- pendingRemoved.delete($el);
1230
+ pendingRemoval.delete($el);
1078
1231
  }
1079
1232
 
1080
- // Node is added
1233
+ // Node is entering
1081
1234
  if ((topLevelAdded && !parentNotRendered) || becomeVisible) {
1082
- updateNodeProperties(oldStateNode, addedParams);
1083
- added.push($el);
1084
- // Node is removed
1235
+ updateNodeProperties(oldStateNode, enterFromProps);
1236
+ if (node.isTarget) {
1237
+ entering.push($el);
1238
+ node.isEntering = true;
1239
+ }
1240
+ // Node is leaving
1085
1241
  } else if (topLevelRemoved && !parentNotRendered) {
1086
- updateNodeProperties(node, removedParams);
1242
+ updateNodeProperties(node, leaveToProps);
1087
1243
  }
1088
1244
 
1089
- // Compute function based propety values before cheking for changes
1090
- for (let name in node.properties) {
1091
- node.properties[name] = newState.getValue(node.$el, name);
1092
- // NOTE: I'm using node.$el to get the value of old state, make sure this is valid instead of oldStateNode.$el
1093
- oldStateNode.properties[name] = oldState.getValue(node.$el, name);
1245
+ // Node is animating
1246
+ // The animating array is used only to calculate delays and duration on root children
1247
+ if (node !== rootNode && node.isTarget && !node.isEntering && !node.isLeaving) {
1248
+ animating.push($el);
1094
1249
  }
1095
1250
 
1096
- const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
1097
- let propertyChanged = false;
1251
+ targets.push($el);
1098
1252
 
1253
+ });
1099
1254
 
1100
- if (node.isTarget && (!node.measuredIsRemoved && wasVisibleBefore || node.measuredIsRemoved && isVisibleNow)) {
1101
- if (!node.isInlined && (node.properties.transform !== 'none' || oldStateNode.properties.transform !== 'none')) {
1102
- node.hasTransform = true;
1103
- propertyChanged = true;
1104
- transformed.push($el);
1105
- }
1106
- for (let name in node.properties) {
1107
- if (name !== 'transform' && (node.properties[name] !== oldStateNode.properties[name] || hiddenStateChanged)) {
1108
- propertyChanged = true;
1109
- animated.push($el);
1110
- break;
1111
- }
1112
- }
1255
+ let enteringIndex = 0;
1256
+ let leavingIndex = 0;
1257
+ let animatingIndex = 0;
1258
+
1259
+ newState.forEachRootNode(node => {
1260
+
1261
+ const $el = node.$el;
1262
+ const parent = node.parentNode;
1263
+ const oldStateNode = oldState.nodes.get(node.id);
1264
+ const nodeProperties = node.properties;
1265
+ const oldStateNodeProperties = oldStateNode.properties;
1266
+
1267
+ // Use closest animated parent index and total values so that children staggered delays are in sync with their parent
1268
+ let animatedParent = parent !== rootNode && parent;
1269
+ while (animatedParent && !animatedParent.isTarget && animatedParent !== rootNode) {
1270
+ animatedParent = animatedParent.parentNode;
1113
1271
  }
1114
1272
 
1115
- const nodeHasChanged = (propertyChanged || topLevelAdded || topLevelRemoved || becomeVisible);
1116
- const nodeIsAnimated = node.isTarget && nodeHasChanged;
1273
+ const animatingTotal = animating.length;
1274
+
1275
+ // Root is always animated first in sync with the first child (animating.length is the total of children)
1276
+ if (node === rootNode) {
1277
+ node.index = 0;
1278
+ node.total = animatingTotal;
1279
+ updateNodeTimingParams(node, animationTimings);
1280
+ } else if (node.isEntering) {
1281
+ node.index = animatedParent ? animatedParent.index : enteringIndex;
1282
+ node.total = animatedParent ? animatingTotal : entering.length;
1283
+ updateNodeTimingParams(node, enterFromTimings);
1284
+ enteringIndex++;
1285
+ } else if (node.isLeaving) {
1286
+ node.index = animatedParent ? animatedParent.index : leavingIndex;
1287
+ node.total = animatedParent ? animatingTotal : leaving.length;
1288
+ leavingIndex++;
1289
+ updateNodeTimingParams(node, leaveToTimings);
1290
+ } else if (node.isTarget) {
1291
+ node.index = animatingIndex++;
1292
+ node.total = animatingTotal;
1293
+ updateNodeTimingParams(node, animationTimings);
1294
+ } else {
1295
+ node.index = animatedParent ? animatedParent.index : 0;
1296
+ node.total = animatingTotal;
1297
+ updateNodeTimingParams(node, swapAtTimings);
1298
+ }
1117
1299
 
1118
- node.isAnimated = nodeIsAnimated;
1119
- node.branchAdded = parentAdded || topLevelAdded;
1120
- node.branchRemoved = parentRemoved || topLevelRemoved;
1121
- node.branchNotRendered = parentNotRendered || node.measuredIsRemoved;
1300
+ // Make sure the old state node has its inex and total values up to date for valid "from" function values calculation
1301
+ oldStateNode.index = node.index;
1302
+ oldStateNode.total = node.total;
1122
1303
 
1304
+ // Computes all values up front so we can check for changes and we don't have to re-compute them inside the animation props
1305
+ for (let prop in nodeProperties) {
1306
+ nodeProperties[prop] = values.getFunctionValue(nodeProperties[prop], $el, node.index, node.total);
1307
+ oldStateNodeProperties[prop] = values.getFunctionValue(oldStateNodeProperties[prop], $el, oldStateNode.index, oldStateNode.total);
1308
+ }
1309
+
1310
+ // Use a 1px tolerance to detect dimensions changes to prevent width / height animations on barelly visible elements
1123
1311
  const sizeTolerance = 1;
1124
- const widthChanged = Math.abs(node.properties.width - oldStateNode.properties.width) > sizeTolerance;
1125
- const heightChanged = Math.abs(node.properties.height - oldStateNode.properties.height) > sizeTolerance;
1312
+ const widthChanged = Math.abs(nodeProperties.width - oldStateNodeProperties.width) > sizeTolerance;
1313
+ const heightChanged = Math.abs(nodeProperties.height - oldStateNodeProperties.height) > sizeTolerance;
1126
1314
 
1127
1315
  node.sizeChanged = (widthChanged || heightChanged);
1128
1316
 
1129
- targets.push($el);
1317
+ // const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
1318
+
1319
+ if (node.isTarget && (!node.measuredIsRemoved && oldStateNode.measuredIsVisible || node.measuredIsRemoved && node.measuredIsVisible)) {
1320
+ if (!node.isInlined && (nodeProperties.transform !== 'none' || oldStateNodeProperties.transform !== 'none')) {
1321
+ node.hasTransform = true;
1322
+ transformed.push($el);
1323
+ }
1324
+ for (let prop in nodeProperties) {
1325
+ // if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop] || hiddenStateChanged)) {
1326
+ if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop])) {
1327
+ animated.push($el);
1328
+ break;
1329
+ }
1330
+ }
1331
+ }
1130
1332
 
1131
1333
  if (!node.isTarget) {
1132
- frozen.push($el);
1133
- if ((nodeHasChanged || node.sizeChanged) && parent && parent.isTarget && parent.isAnimated && parent.sizeChanged) {
1134
- animatedFrozen.push($el);
1334
+ swapping.push($el);
1335
+ if (node.sizeChanged && parent && parent.isTarget && parent.sizeChanged) {
1336
+ if (!node.isInlined && swapAtProps.transform) {
1337
+ node.hasTransform = true;
1338
+ transformed.push($el);
1339
+ }
1340
+ animatedSwap.push($el);
1135
1341
  }
1136
1342
  }
1343
+
1137
1344
  });
1138
1345
 
1139
- const defaults = {
1140
- ease: values.setValue(params.ease, this.ease),
1141
- duration: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).duration,
1142
- delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
1346
+ const timingParams = {
1347
+ delay: (/** @type {HTMLElement} */$el) => newState.getNode($el).delay,
1348
+ duration: (/** @type {HTMLElement} */$el) => newState.getNode($el).duration,
1349
+ ease: (/** @type {HTMLElement} */$el) => newState.getNode($el).ease,
1143
1350
  };
1144
1351
 
1145
- this.timeline = timeline.createTimeline({
1146
- onComplete: () => {
1147
- // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
1148
- if (this.transformAnimation) this.transformAnimation.cancel();
1149
- newState.forEachRootNode(node => {
1150
- restoreNodeVisualState(node);
1151
- restoreNodeInlineStyles(node);
1152
- });
1153
- for (let i = 0, l = transformed.length; i < l; i++) {
1154
- const $el = transformed[i];
1155
- $el.style.transform = newState.getValue($el, 'transform');
1156
- }
1157
- this.root.classList.remove('is-animated');
1158
- if (onComplete) onComplete(this);
1159
- // Avoid CSS transitions at the end of the animation by restoring them on the next frame
1160
- requestAnimationFrame(() => {
1161
- if (this.root.classList.contains('is-animated')) return;
1162
- restoreLayoutTransition(this.transitionMuteStore);
1163
- });
1164
- },
1165
- onPause: () => {
1166
- if (this.transformAnimation) this.transformAnimation.cancel();
1167
- newState.forEachRootNode(restoreNodeVisualState);
1168
- this.root.classList.remove('is-animated');
1169
- if (onComplete) onComplete(this);
1170
- },
1171
- composition: false,
1172
- defaults,
1173
- });
1352
+ tlParams.defaults = timingParams;
1353
+
1354
+ this.timeline = timeline.createTimeline(tlParams);
1355
+
1356
+ // Imediatly return the timeline if no layout changes detected
1357
+ if (!animated.length && !transformed.length && !swapping.length) {
1358
+ // Make sure to restore all CSS transition if no animation
1359
+ restoreLayoutTransition(this.transitionMuteStore);
1360
+ return this.timeline.complete();
1361
+ }
1174
1362
 
1175
1363
  if (targets.length) {
1176
1364
 
@@ -1183,15 +1371,14 @@ class AutoLayout {
1183
1371
  const newNode = newState.nodes.get(id);
1184
1372
  const oldNodeState = oldNode.properties;
1185
1373
 
1186
- // Make sure to mute all CSS transition before applying the oldState styles back
1187
- muteNodeTransition(newNode);
1374
+ // muteNodeTransition(newNode);
1188
1375
 
1189
1376
  // Don't animate dimensions and positions of inlined elements
1190
1377
  if (!newNode.isInlined) {
1191
1378
  // Display grid can mess with the absolute positioning, so set it to block during transition
1192
- if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.display = 'block';
1193
- // All children must be in position absolue
1194
- if ($el !== root || this.absoluteCoords) {
1379
+ if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.setProperty('display', 'block', 'important');
1380
+ // All children must be in position absolute or fixed
1381
+ if ($el !== $root || this.absoluteCoords) {
1195
1382
  $el.style.position = this.absoluteCoords ? 'fixed' : 'absolute';
1196
1383
  $el.style.left = '0px';
1197
1384
  $el.style.top = '0px';
@@ -1199,7 +1386,7 @@ class AutoLayout {
1199
1386
  $el.style.marginTop = '0px';
1200
1387
  $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
1201
1388
  }
1202
- if ($el === root && newNode.measuredPosition === 'static') {
1389
+ if ($el === $root && newNode.measuredPosition === 'static') {
1203
1390
  $el.style.position = 'relative';
1204
1391
  // Cancel left / trop in case the static element had muted values now activated by potision relative
1205
1392
  $el.style.left = '0px';
@@ -1218,9 +1405,7 @@ class AutoLayout {
1218
1405
  // Restore the scroll position if the oldState differs from the current state
1219
1406
  if (oldState.scrollX !== window.scrollX || oldState.scrollY !== window.scrollY) {
1220
1407
  // Restoring in the next frame avoids race conditions if for example a waapi animation commit styles that affect the root height
1221
- requestAnimationFrame(() => {
1222
- window.scrollTo(oldState.scrollX, oldState.scrollY);
1223
- });
1408
+ requestAnimationFrame(() => window.scrollTo(oldState.scrollX, oldState.scrollY));
1224
1409
  }
1225
1410
 
1226
1411
  for (let i = 0, l = animated.length; i < l; i++) {
@@ -1230,25 +1415,25 @@ class AutoLayout {
1230
1415
  const newNode = newState.nodes.get(id);
1231
1416
  const oldNodeState = oldNode.properties;
1232
1417
  const newNodeState = newNode.properties;
1233
- let hasChanged = false;
1418
+ let nodeHasChanged = false;
1419
+ /** @type {AnimationParams} */
1234
1420
  const animatedProps = {
1235
1421
  composition: 'none',
1236
- // delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
1237
1422
  };
1238
1423
  if (!newNode.isInlined) {
1239
1424
  if (oldNodeState.width !== newNodeState.width) {
1240
1425
  animatedProps.width = [oldNodeState.width, newNodeState.width];
1241
- hasChanged = true;
1426
+ nodeHasChanged = true;
1242
1427
  }
1243
1428
  if (oldNodeState.height !== newNodeState.height) {
1244
1429
  animatedProps.height = [oldNodeState.height, newNodeState.height];
1245
- hasChanged = true;
1430
+ nodeHasChanged = true;
1246
1431
  }
1247
1432
  // If the node has transforms we handle the translate animation in wappi otherwise translate and other transforms can be out of sync
1248
1433
  // Always animate translate
1249
1434
  if (!newNode.hasTransform) {
1250
1435
  animatedProps.translate = [`${oldNodeState.x}px ${oldNodeState.y}px`, `${newNodeState.x}px ${newNodeState.y}px`];
1251
- hasChanged = true;
1436
+ nodeHasChanged = true;
1252
1437
  }
1253
1438
  }
1254
1439
  this.properties.forEach(prop => {
@@ -1256,73 +1441,77 @@ class AutoLayout {
1256
1441
  const newVal = newNodeState[prop];
1257
1442
  if (prop !== 'transform' && oldVal !== newVal) {
1258
1443
  animatedProps[prop] = [oldVal, newVal];
1259
- hasChanged = true;
1444
+ nodeHasChanged = true;
1260
1445
  }
1261
1446
  });
1262
- if (hasChanged) {
1447
+ if (nodeHasChanged) {
1263
1448
  this.timeline.add($el, animatedProps, 0);
1264
1449
  }
1265
1450
  }
1266
1451
 
1267
1452
  }
1268
1453
 
1269
- if (frozen.length) {
1454
+ if (swapping.length) {
1270
1455
 
1271
- for (let i = 0, l = frozen.length; i < l; i++) {
1272
- const $el = frozen[i];
1273
- const oldNode = oldState.nodes.get($el.dataset.layoutId);
1456
+ for (let i = 0, l = swapping.length; i < l; i++) {
1457
+ const $el = swapping[i];
1458
+ const oldNode = oldState.getNode($el);
1274
1459
  if (!oldNode.isInlined) {
1275
- const oldNodeState = oldState.get($el);
1276
- $el.style.width = `${oldNodeState.width}px`;
1277
- $el.style.height = `${oldNodeState.height}px`;
1460
+ const oldNodeProps = oldNode.properties;
1461
+ $el.style.width = `${oldNodeProps.width}px`;
1462
+ $el.style.height = `${oldNodeProps.height}px`;
1278
1463
  // Overrides user defined min and max to prevents width and height clamping
1279
1464
  $el.style.minWidth = `auto`;
1280
1465
  $el.style.minHeight = `auto`;
1281
1466
  $el.style.maxWidth = `none`;
1282
1467
  $el.style.maxHeight = `none`;
1283
- $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
1468
+ $el.style.translate = `${oldNodeProps.x}px ${oldNodeProps.y}px`;
1284
1469
  }
1285
1470
  this.properties.forEach(prop => {
1286
1471
  if (prop !== 'transform') {
1287
- $el.style[prop] = `${oldState.getValue($el, prop)}`;
1472
+ $el.style[prop] = `${oldState.getComputedValue($el, prop)}`;
1288
1473
  }
1289
1474
  });
1290
1475
  }
1291
1476
 
1292
- for (let i = 0, l = frozen.length; i < l; i++) {
1293
- const $el = frozen[i];
1294
- const newNode = newState.nodes.get($el.dataset.layoutId);
1295
- const newNodeState = newState.get($el);
1477
+ for (let i = 0, l = swapping.length; i < l; i++) {
1478
+ const $el = swapping[i];
1479
+ const newNode = newState.getNode($el);
1480
+ const newNodeProps = newNode.properties;
1296
1481
  this.timeline.call(() => {
1297
1482
  if (!newNode.isInlined) {
1298
- $el.style.width = `${newNodeState.width}px`;
1299
- $el.style.height = `${newNodeState.height}px`;
1483
+ $el.style.width = `${newNodeProps.width}px`;
1484
+ $el.style.height = `${newNodeProps.height}px`;
1300
1485
  // Overrides user defined min and max to prevents width and height clamping
1301
1486
  $el.style.minWidth = `auto`;
1302
1487
  $el.style.minHeight = `auto`;
1303
1488
  $el.style.maxWidth = `none`;
1304
1489
  $el.style.maxHeight = `none`;
1305
- $el.style.translate = `${newNodeState.x}px ${newNodeState.y}px`;
1490
+ $el.style.translate = `${newNodeProps.x}px ${newNodeProps.y}px`;
1306
1491
  }
1307
1492
  this.properties.forEach(prop => {
1308
1493
  if (prop !== 'transform') {
1309
- $el.style[prop] = `${newState.getValue($el, prop)}`;
1494
+ $el.style[prop] = `${newState.getComputedValue($el, prop)}`;
1310
1495
  }
1311
1496
  });
1312
1497
  }, newNode.delay + newNode.duration / 2);
1313
1498
  }
1314
1499
 
1315
- if (animatedFrozen.length) {
1316
- const animatedFrozenParams = /** @type {AnimationParams} */({});
1317
- if (frozenParams) {
1318
- for (let prop in frozenParams) {
1319
- animatedFrozenParams[prop] = [
1320
- { from: (/** @type {HTMLElement} */$el) => oldState.getValue($el, prop), ease: 'in(1.75)', to: frozenParams[prop] },
1321
- { from: frozenParams[prop], to: (/** @type {HTMLElement} */$el) => newState.getValue($el, prop), ease: 'out(1.75)' }
1322
- ];
1500
+ if (animatedSwap.length) {
1501
+ const ease = parser.parseEase(newState.nodes.get(animatedSwap[0].dataset.layoutId).ease);
1502
+ const inverseEased = t => 1 - ease(1 - t);
1503
+ const animatedSwapParams = /** @type {AnimationParams} */({});
1504
+ if (swapAtProps) {
1505
+ for (let prop in swapAtProps) {
1506
+ if (prop !== 'transform') {
1507
+ animatedSwapParams[prop] = [
1508
+ { from: (/** @type {HTMLElement} */$el) => oldState.getComputedValue($el, prop), to: swapAtProps[prop] },
1509
+ { from: swapAtProps[prop], to: (/** @type {HTMLElement} */$el) => newState.getComputedValue($el, prop), ease: inverseEased }
1510
+ ];
1511
+ }
1323
1512
  }
1324
1513
  }
1325
- this.timeline.add(animatedFrozen, animatedFrozenParams, 0);
1514
+ this.timeline.add(animatedSwap, animatedSwapParams, 0);
1326
1515
  }
1327
1516
 
1328
1517
  }
@@ -1330,18 +1519,29 @@ class AutoLayout {
1330
1519
  const transformedLength = transformed.length;
1331
1520
 
1332
1521
  if (transformedLength) {
1333
- // We only need to set the transform property here since translate is alread defined the targets loop
1522
+ // We only need to set the transform property here since translate is alread defined in the targets loop
1334
1523
  for (let i = 0; i < transformedLength; i++) {
1335
1524
  const $el = transformed[i];
1336
- $el.style.translate = `${oldState.get($el).x}px ${oldState.get($el).y}px`,
1337
- $el.style.transform = oldState.getValue($el, 'transform');
1525
+ $el.style.translate = `${oldState.getComputedValue($el, 'x')}px ${oldState.getComputedValue($el, 'y')}px`,
1526
+ $el.style.transform = oldState.getComputedValue($el, 'transform');
1527
+ if (animatedSwap.includes($el)) {
1528
+ const node = newState.getNode($el);
1529
+ node.ease = values.getFunctionValue(swapAtParams.ease, $el, node.index, node.total);
1530
+ node.duration = values.getFunctionValue(swapAtParams.duration, $el, node.index, node.total);
1531
+ }
1338
1532
  }
1339
1533
  this.transformAnimation = waapi.waapi.animate(transformed, {
1340
- translate: (/** @type {HTMLElement} */$el) => `${newState.get($el).x}px ${newState.get($el).y}px`,
1341
- transform: (/** @type {HTMLElement} */$el) => newState.getValue($el, 'transform'),
1534
+ translate: (/** @type {HTMLElement} */$el) => `${newState.getComputedValue($el, 'x')}px ${newState.getComputedValue($el, 'y')}px`,
1535
+ transform: (/** @type {HTMLElement} */$el) => {
1536
+ const newValue = newState.getComputedValue($el, 'transform');
1537
+ if (!animatedSwap.includes($el)) return newValue;
1538
+ const oldValue = oldState.getComputedValue($el, 'transform');
1539
+ const node = newState.getNode($el);
1540
+ return [oldValue, values.getFunctionValue(swapAtProps.transform, $el, node.index, node.total), newValue]
1541
+ },
1342
1542
  autoplay: false,
1343
1543
  persist: true,
1344
- ...defaults,
1544
+ ...timingParams,
1345
1545
  });
1346
1546
  this.timeline.sync(this.transformAnimation, 0);
1347
1547
  }
@@ -1352,13 +1552,12 @@ class AutoLayout {
1352
1552
  /**
1353
1553
  * @param {(layout: this) => void} callback
1354
1554
  * @param {LayoutAnimationParams} [params]
1355
- * @return {this}
1555
+ * @return {Timeline}
1356
1556
  */
1357
1557
  update(callback, params = {}) {
1358
1558
  this.record();
1359
1559
  callback(this);
1360
- this.animate(params);
1361
- return this;
1560
+ return this.animate(params);
1362
1561
  }
1363
1562
  }
1364
1563