animejs 4.2.1 → 4.3.0-beta.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 (133) hide show
  1. package/README.md +82 -28
  2. package/dist/bundles/anime.esm.js +2707 -1301
  3. package/dist/bundles/anime.esm.min.js +2 -2
  4. package/dist/bundles/anime.umd.js +2717 -1309
  5. package/dist/bundles/anime.umd.min.js +2 -2
  6. package/dist/modules/animatable/animatable.cjs +1 -1
  7. package/dist/modules/animatable/animatable.js +1 -1
  8. package/dist/modules/animatable/index.cjs +1 -1
  9. package/dist/modules/animatable/index.js +1 -1
  10. package/dist/modules/animation/additive.cjs +1 -1
  11. package/dist/modules/animation/additive.js +1 -1
  12. package/dist/modules/animation/animation.cjs +18 -9
  13. package/dist/modules/animation/animation.js +19 -10
  14. package/dist/modules/animation/composition.cjs +1 -1
  15. package/dist/modules/animation/composition.js +1 -1
  16. package/dist/modules/animation/index.cjs +1 -1
  17. package/dist/modules/animation/index.js +1 -1
  18. package/dist/modules/core/clock.cjs +10 -10
  19. package/dist/modules/core/clock.d.ts +1 -1
  20. package/dist/modules/core/clock.js +10 -10
  21. package/dist/modules/core/colors.cjs +1 -1
  22. package/dist/modules/core/colors.js +1 -1
  23. package/dist/modules/core/consts.cjs +6 -4
  24. package/dist/modules/core/consts.d.ts +13 -5
  25. package/dist/modules/core/consts.js +6 -4
  26. package/dist/modules/core/globals.cjs +5 -2
  27. package/dist/modules/core/globals.d.ts +1 -0
  28. package/dist/modules/core/globals.js +5 -3
  29. package/dist/modules/core/helpers.cjs +1 -1
  30. package/dist/modules/core/helpers.js +1 -1
  31. package/dist/modules/core/render.cjs +1 -1
  32. package/dist/modules/core/render.js +1 -1
  33. package/dist/modules/core/styles.cjs +1 -1
  34. package/dist/modules/core/styles.js +1 -1
  35. package/dist/modules/core/targets.cjs +1 -1
  36. package/dist/modules/core/targets.js +1 -1
  37. package/dist/modules/core/transforms.cjs +1 -1
  38. package/dist/modules/core/transforms.js +1 -1
  39. package/dist/modules/core/units.cjs +1 -1
  40. package/dist/modules/core/units.js +1 -1
  41. package/dist/modules/core/values.cjs +1 -1
  42. package/dist/modules/core/values.js +1 -1
  43. package/dist/modules/draggable/draggable.cjs +1 -1
  44. package/dist/modules/draggable/draggable.js +1 -1
  45. package/dist/modules/draggable/index.cjs +1 -1
  46. package/dist/modules/draggable/index.js +1 -1
  47. package/dist/modules/easings/cubic-bezier/index.cjs +1 -1
  48. package/dist/modules/easings/cubic-bezier/index.js +1 -1
  49. package/dist/modules/easings/eases/index.cjs +1 -1
  50. package/dist/modules/easings/eases/index.js +1 -1
  51. package/dist/modules/easings/eases/parser.cjs +1 -1
  52. package/dist/modules/easings/eases/parser.js +1 -1
  53. package/dist/modules/easings/index.cjs +1 -1
  54. package/dist/modules/easings/index.js +1 -1
  55. package/dist/modules/easings/irregular/index.cjs +1 -1
  56. package/dist/modules/easings/irregular/index.js +1 -1
  57. package/dist/modules/easings/linear/index.cjs +1 -1
  58. package/dist/modules/easings/linear/index.js +1 -1
  59. package/dist/modules/easings/none.cjs +1 -1
  60. package/dist/modules/easings/none.js +1 -1
  61. package/dist/modules/easings/spring/index.cjs +1 -1
  62. package/dist/modules/easings/spring/index.js +1 -1
  63. package/dist/modules/easings/steps/index.cjs +1 -1
  64. package/dist/modules/easings/steps/index.js +1 -1
  65. package/dist/modules/engine/engine.cjs +2 -2
  66. package/dist/modules/engine/engine.js +2 -2
  67. package/dist/modules/engine/index.cjs +1 -1
  68. package/dist/modules/engine/index.js +1 -1
  69. package/dist/modules/events/index.cjs +1 -1
  70. package/dist/modules/events/index.js +1 -1
  71. package/dist/modules/events/scroll.cjs +1 -1
  72. package/dist/modules/events/scroll.js +1 -1
  73. package/dist/modules/index.cjs +4 -1
  74. package/dist/modules/index.d.ts +1 -0
  75. package/dist/modules/index.js +2 -1
  76. package/dist/modules/layout/index.cjs +15 -0
  77. package/dist/modules/layout/index.d.ts +1 -0
  78. package/dist/modules/layout/index.js +8 -0
  79. package/dist/modules/layout/layout.cjs +1384 -0
  80. package/dist/modules/layout/layout.d.ts +211 -0
  81. package/dist/modules/layout/layout.js +1381 -0
  82. package/dist/modules/scope/index.cjs +1 -1
  83. package/dist/modules/scope/index.js +1 -1
  84. package/dist/modules/scope/scope.cjs +1 -1
  85. package/dist/modules/scope/scope.js +1 -1
  86. package/dist/modules/svg/drawable.cjs +1 -1
  87. package/dist/modules/svg/drawable.js +1 -1
  88. package/dist/modules/svg/helpers.cjs +1 -1
  89. package/dist/modules/svg/helpers.js +1 -1
  90. package/dist/modules/svg/index.cjs +1 -1
  91. package/dist/modules/svg/index.js +1 -1
  92. package/dist/modules/svg/morphto.cjs +1 -1
  93. package/dist/modules/svg/morphto.js +1 -1
  94. package/dist/modules/svg/motionpath.cjs +11 -7
  95. package/dist/modules/svg/motionpath.js +11 -7
  96. package/dist/modules/text/index.cjs +1 -1
  97. package/dist/modules/text/index.js +1 -1
  98. package/dist/modules/text/split.cjs +23 -9
  99. package/dist/modules/text/split.js +23 -9
  100. package/dist/modules/timeline/index.cjs +1 -1
  101. package/dist/modules/timeline/index.js +1 -1
  102. package/dist/modules/timeline/position.cjs +1 -1
  103. package/dist/modules/timeline/position.js +1 -1
  104. package/dist/modules/timeline/timeline.cjs +14 -6
  105. package/dist/modules/timeline/timeline.d.ts +2 -0
  106. package/dist/modules/timeline/timeline.js +15 -7
  107. package/dist/modules/timer/index.cjs +1 -1
  108. package/dist/modules/timer/index.js +1 -1
  109. package/dist/modules/timer/timer.cjs +26 -13
  110. package/dist/modules/timer/timer.d.ts +1 -0
  111. package/dist/modules/timer/timer.js +27 -14
  112. package/dist/modules/types/index.d.ts +3 -1
  113. package/dist/modules/utils/chainable.cjs +1 -1
  114. package/dist/modules/utils/chainable.js +1 -1
  115. package/dist/modules/utils/index.cjs +1 -1
  116. package/dist/modules/utils/index.js +1 -1
  117. package/dist/modules/utils/number.cjs +1 -1
  118. package/dist/modules/utils/number.js +1 -1
  119. package/dist/modules/utils/random.cjs +1 -1
  120. package/dist/modules/utils/random.js +1 -1
  121. package/dist/modules/utils/stagger.cjs +1 -1
  122. package/dist/modules/utils/stagger.js +1 -1
  123. package/dist/modules/utils/target.cjs +1 -1
  124. package/dist/modules/utils/target.js +1 -1
  125. package/dist/modules/utils/time.cjs +1 -1
  126. package/dist/modules/utils/time.js +1 -1
  127. package/dist/modules/waapi/composition.cjs +1 -1
  128. package/dist/modules/waapi/composition.js +1 -1
  129. package/dist/modules/waapi/index.cjs +1 -1
  130. package/dist/modules/waapi/index.js +1 -1
  131. package/dist/modules/waapi/waapi.cjs +15 -7
  132. package/dist/modules/waapi/waapi.js +16 -8
  133. package/package.json +8 -2
@@ -0,0 +1,1381 @@
1
+ /**
2
+ * Anime.js - layout - ESM
3
+ * @version v4.3.0-beta.0
4
+ * @license MIT
5
+ * @copyright 2025 - Julian Garnier
6
+ */
7
+
8
+ import { mergeObjects, isFnc, isUnd, isSvg, isStr, isArr } from '../core/helpers.js';
9
+ import { registerTargets } from '../core/targets.js';
10
+ import { setValue, getFunctionValue } from '../core/values.js';
11
+ import { noop } from '../core/consts.js';
12
+ import { createTimeline } from '../timeline/timeline.js';
13
+ import { waapi } from '../waapi/waapi.js';
14
+ import { scope } from '../core/globals.js';
15
+
16
+ /**
17
+ * @import {
18
+ * AnimationParams,
19
+ * } from '../types/index.js'
20
+ */
21
+
22
+ /**
23
+ * @import {
24
+ * Timeline,
25
+ * } from '../timeline/timeline.js'
26
+ */
27
+
28
+ /**
29
+ * @import {
30
+ * WAAPIAnimation
31
+ * } from '../waapi/waapi.js'
32
+ */
33
+
34
+ /**
35
+ * @import {
36
+ * DOMTarget,
37
+ * DOMTargetSelector,
38
+ * FunctionValue,
39
+ * EasingParam,
40
+ * Callback,
41
+ } from '../types/index.js'
42
+ */
43
+
44
+ /**
45
+ * @typedef {DOMTargetSelector|Array<DOMTargetSelector>} LayoutChildrenParam
46
+ */
47
+
48
+ /**
49
+ * @typedef {Record<String, Number|String>} LayoutStateParams
50
+ */
51
+
52
+ /**
53
+ * @typedef {Object} LayoutAnimationParams
54
+ * @property {Number} [duration]
55
+ * @property {Number|FunctionValue} [delay]
56
+ * @property {EasingParam} [ease]
57
+ * @property {LayoutStateParams} [frozen]
58
+ * @property {LayoutStateParams} [added]
59
+ * @property {LayoutStateParams} [removed]
60
+ * @property {Callback<AutoLayout>} [onComplete]
61
+ */
62
+
63
+ /**
64
+ * @typedef {LayoutAnimationParams & {
65
+ * children?: LayoutChildrenParam,
66
+ * properties?: Array<String>,
67
+ * }} AutoLayoutParams
68
+ */
69
+
70
+ /**
71
+ * @typedef {Record<String, Number|String> & {
72
+ * transform: String,
73
+ * x: Number,
74
+ * y: Number,
75
+ * left: Number,
76
+ * top: Number,
77
+ * clientLeft: Number,
78
+ * clientTop: Number,
79
+ * width: Number,
80
+ * height: Number,
81
+ * }} LayoutNodeProperties
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} LayoutNode
86
+ * @property {String} id
87
+ * @property {DOMTarget} $el
88
+ * @property {Number} index
89
+ * @property {Number} total
90
+ * @property {Number} delay
91
+ * @property {Number} duration
92
+ * @property {DOMTarget} $measure
93
+ * @property {LayoutSnapshot} state
94
+ * @property {AutoLayout} layout
95
+ * @property {LayoutNode|null} parentNode
96
+ * @property {Boolean} isTarget
97
+ * @property {Boolean} hasTransform
98
+ * @property {Boolean} isAnimated
99
+ * @property {Array<String>} inlineStyles
100
+ * @property {String|null} inlineTransforms
101
+ * @property {String|null} inlineTransition
102
+ * @property {Boolean} branchAdded
103
+ * @property {Boolean} branchRemoved
104
+ * @property {Boolean} branchNotRendered
105
+ * @property {Boolean} sizeChanged
106
+ * @property {Boolean} isInlined
107
+ * @property {Boolean} hasVisibilitySwap
108
+ * @property {Boolean} hasDisplayNone
109
+ * @property {Boolean} hasVisibilityHidden
110
+ * @property {String|null} measuredInlineTransform
111
+ * @property {String|null} measuredInlineTransition
112
+ * @property {String|null} measuredDisplay
113
+ * @property {String|null} measuredVisibility
114
+ * @property {String|null} measuredPosition
115
+ * @property {Boolean} measuredHasDisplayNone
116
+ * @property {Boolean} measuredHasVisibilityHidden
117
+ * @property {Boolean} measuredIsVisible
118
+ * @property {Boolean} measuredIsRemoved
119
+ * @property {Boolean} measuredIsInsideRoot
120
+ * @property {LayoutNodeProperties} properties
121
+ * @property {LayoutNode|null} _head
122
+ * @property {LayoutNode|null} _tail
123
+ * @property {LayoutNode|null} _prev
124
+ * @property {LayoutNode|null} _next
125
+ */
126
+
127
+ /**
128
+ * @callback LayoutNodeIterator
129
+ * @param {LayoutNode} node
130
+ * @param {Number} index
131
+ * @return {void}
132
+ */
133
+
134
+ let layoutId = 0;
135
+ let nodeId = 0;
136
+
137
+ /**
138
+ * @param {DOMTarget} root
139
+ * @param {DOMTarget} $el
140
+ * @return {Boolean}
141
+ */
142
+ const isElementInRoot = (root, $el) => {
143
+ if (!root || !$el) return false;
144
+ return root === $el || root.contains($el);
145
+ };
146
+
147
+ /**
148
+ * @param {Node} node
149
+ * @param {'previousSibling'|'nextSibling'} direction
150
+ * @return {Boolean}
151
+ */
152
+ const hasTextSibling = (node, direction) => {
153
+ let sibling = node[direction];
154
+ while (sibling && sibling.nodeType === Node.TEXT_NODE && !sibling.textContent.trim()) {
155
+ sibling = sibling[direction];
156
+ }
157
+ return sibling && sibling.nodeType === Node.TEXT_NODE;
158
+ };
159
+
160
+ /**
161
+ * @param {DOMTarget} $el
162
+ * @return {Boolean}
163
+ */
164
+ const isElementSurroundedByText = $el => hasTextSibling($el, 'previousSibling') || hasTextSibling($el, 'nextSibling');
165
+
166
+ /**
167
+ * @param {DOMTarget|null} $el
168
+ * @return {String|null}
169
+ */
170
+ const muteElementTransition = $el => {
171
+ if (!$el) return null;
172
+ const style = $el.style;
173
+ const transition = style.transition || '';
174
+ style.setProperty('transition', 'none', 'important');
175
+ return transition;
176
+ };
177
+
178
+ /**
179
+ * @param {DOMTarget|null} $el
180
+ * @param {String|null} transition
181
+ */
182
+ const restoreElementTransition = ($el, transition) => {
183
+ if (!$el) return;
184
+ const style = $el.style;
185
+ if (transition) {
186
+ style.transition = transition;
187
+ } else {
188
+ style.removeProperty('transition');
189
+ }
190
+ };
191
+
192
+ /**
193
+ * @param {LayoutNode} node
194
+ */
195
+ const muteNodeTransition = node => {
196
+ const store = node.layout.transitionMuteStore;
197
+ const $el = node.$el;
198
+ const $measure = node.$measure;
199
+ if ($el && !store.has($el)) store.set($el, muteElementTransition($el));
200
+ if ($measure && !store.has($measure)) store.set($measure, muteElementTransition($measure));
201
+ };
202
+
203
+ /**
204
+ * @param {Map<DOMTarget, String|null>} store
205
+ */
206
+ const restoreLayoutTransition = store => {
207
+ store.forEach((value, $el) => restoreElementTransition($el, value));
208
+ store.clear();
209
+ };
210
+
211
+ const hiddenComputedStyle = /** @type {CSSStyleDeclaration} */({
212
+ display: 'none',
213
+ visibility: 'hidden',
214
+ opacity: '0',
215
+ transform: 'none',
216
+ position: 'static',
217
+ });
218
+
219
+ /**
220
+ * @param {LayoutNode|null} node
221
+ */
222
+ const detachNode = node => {
223
+ if (!node) return;
224
+ const parent = node.parentNode;
225
+ if (!parent) return;
226
+ if (parent._head === node) parent._head = node._next;
227
+ if (parent._tail === node) parent._tail = node._prev;
228
+ if (node._prev) node._prev._next = node._next;
229
+ if (node._next) node._next._prev = node._prev;
230
+ node._prev = null;
231
+ node._next = null;
232
+ node.parentNode = null;
233
+ };
234
+
235
+ /**
236
+ * @param {DOMTarget} $el
237
+ * @param {LayoutNode|null} parentNode
238
+ * @param {LayoutSnapshot} state
239
+ * @param {LayoutNode} [recycledNode]
240
+ * @return {LayoutNode}
241
+ */
242
+ const createNode = ($el, parentNode, state, recycledNode) => {
243
+ let dataId = $el.dataset.layoutId;
244
+ if (!dataId) dataId = $el.dataset.layoutId = `node-${nodeId++}`;
245
+ const node = recycledNode ? recycledNode : /** @type {LayoutNode} */({});
246
+ node.$el = $el;
247
+ node.$measure = $el;
248
+ node.id = dataId;
249
+ node.index = 0;
250
+ node.total = 1;
251
+ node.delay = 0;
252
+ node.duration = 0;
253
+ node.state = state;
254
+ node.layout = state.layout;
255
+ node.parentNode = parentNode || null;
256
+ node.isTarget = false;
257
+ node.hasTransform = false;
258
+ node.isAnimated = false;
259
+ node.inlineStyles = [];
260
+ node.inlineTransforms = null;
261
+ node.inlineTransition = null;
262
+ node.branchAdded = false;
263
+ node.branchRemoved = false;
264
+ node.branchNotRendered = false;
265
+ node.sizeChanged = false;
266
+ node.isInlined = false;
267
+ node.hasVisibilitySwap = false;
268
+ node.hasDisplayNone = false;
269
+ node.hasVisibilityHidden = false;
270
+ node.measuredInlineTransform = null;
271
+ node.measuredInlineTransition = null;
272
+ node.measuredDisplay = null;
273
+ node.measuredVisibility = null;
274
+ node.measuredPosition = null;
275
+ node.measuredHasDisplayNone = false;
276
+ node.measuredHasVisibilityHidden = false;
277
+ node.measuredIsVisible = false;
278
+ node.measuredIsRemoved = false;
279
+ node.measuredIsInsideRoot = false;
280
+ node.properties = /** @type {LayoutNodeProperties} */({
281
+ transform: 'none',
282
+ x: 0,
283
+ y: 0,
284
+ left: 0,
285
+ top: 0,
286
+ clientLeft: 0,
287
+ clientTop: 0,
288
+ width: 0,
289
+ height: 0,
290
+ });
291
+ node.layout.properties.forEach(prop => node.properties[prop] = 0);
292
+ node._head = null;
293
+ node._tail = null;
294
+ node._prev = null;
295
+ node._next = null;
296
+ return node;
297
+ };
298
+
299
+ /**
300
+ * @param {LayoutNode} node
301
+ * @param {DOMTarget} $measure
302
+ * @param {CSSStyleDeclaration} computedStyle
303
+ * @param {Boolean} skipMeasurements
304
+ * @return {LayoutNode}
305
+ */
306
+ const recordNodeState = (node, $measure, computedStyle, skipMeasurements) => {
307
+ const $el = node.$el;
308
+ const root = node.layout.root;
309
+ const isRoot = root === $el;
310
+ const properties = node.properties;
311
+ const rootNode = node.state.rootNode;
312
+ const parentNode = node.parentNode;
313
+ const computedTransforms = computedStyle.transform;
314
+ const inlineTransforms = $el.style.transform;
315
+ const parentNotRendered = parentNode ? parentNode.measuredIsRemoved : false;
316
+ const position = computedStyle.position;
317
+ if (isRoot) node.layout.absoluteCoords = position === 'fixed' || position === 'absolute';
318
+ node.$measure = $measure;
319
+ node.inlineTransforms = inlineTransforms;
320
+ node.hasTransform = computedTransforms && computedTransforms !== 'none';
321
+ node.measuredIsInsideRoot = isElementInRoot(root, $measure);
322
+ node.measuredInlineTransform = null;
323
+ node.measuredDisplay = computedStyle.display;
324
+ node.measuredVisibility = computedStyle.visibility;
325
+ node.measuredPosition = position;
326
+ node.measuredHasDisplayNone = computedStyle.display === 'none';
327
+ node.measuredHasVisibilityHidden = computedStyle.visibility === 'hidden';
328
+ node.measuredIsVisible = !(node.measuredHasDisplayNone || node.measuredHasVisibilityHidden);
329
+ node.measuredIsRemoved = node.measuredHasDisplayNone || node.measuredHasVisibilityHidden || parentNotRendered;
330
+ node.isInlined = node.measuredDisplay.includes('inline') && isElementSurroundedByText($el);
331
+
332
+ // Mute transforms (and transition to avoid triggering an animation) before the position calculation
333
+ if (node.hasTransform && !skipMeasurements) {
334
+ const transitionMuteStore = node.layout.transitionMuteStore;
335
+ if (!transitionMuteStore.get($el)) node.inlineTransition = muteElementTransition($el);
336
+ if ($measure === $el) {
337
+ $el.style.transform = 'none';
338
+ } else {
339
+ if (!transitionMuteStore.get($measure)) node.measuredInlineTransition = muteElementTransition($measure);
340
+ node.measuredInlineTransform = $measure.style.transform;
341
+ $measure.style.transform = 'none';
342
+ }
343
+ }
344
+
345
+ let left = 0;
346
+ let top = 0;
347
+ let width = 0;
348
+ let height = 0;
349
+
350
+ if (!skipMeasurements) {
351
+ const rect = $measure.getBoundingClientRect();
352
+ left = rect.left;
353
+ top = rect.top;
354
+ width = rect.width;
355
+ height = rect.height;
356
+ }
357
+
358
+ for (let name in properties) {
359
+ const computedProp = name === 'transform' ? computedTransforms : computedStyle[name] || (computedStyle.getPropertyValue && computedStyle.getPropertyValue(name));
360
+ if (!isUnd(computedProp)) properties[name] = computedProp;
361
+ }
362
+
363
+ properties.left = left;
364
+ properties.top = top;
365
+ properties.clientLeft = skipMeasurements ? 0 : $measure.clientLeft;
366
+ properties.clientTop = skipMeasurements ? 0 : $measure.clientTop;
367
+ // Compute local x/y relative to parent
368
+ let absoluteLeft, absoluteTop;
369
+ if (isRoot) {
370
+ if (!node.layout.absoluteCoords) {
371
+ absoluteLeft = 0;
372
+ absoluteTop = 0;
373
+ } else {
374
+ absoluteLeft = left;
375
+ absoluteTop = top;
376
+ }
377
+ } else {
378
+ const p = parentNode || rootNode;
379
+ const parentLeft = p.properties.left;
380
+ const parentTop = p.properties.top;
381
+ const borderLeft = p.properties.clientLeft;
382
+ const borderTop = p.properties.clientTop;
383
+ if (!node.layout.absoluteCoords) {
384
+ if (p === rootNode) {
385
+ const rootLeft = rootNode.properties.left;
386
+ const rootTop = rootNode.properties.top;
387
+ const rootBorderLeft = rootNode.properties.clientLeft;
388
+ const rootBorderTop = rootNode.properties.clientTop;
389
+ absoluteLeft = left - rootLeft - rootBorderLeft;
390
+ absoluteTop = top - rootTop - rootBorderTop;
391
+ } else {
392
+ absoluteLeft = left - parentLeft - borderLeft;
393
+ absoluteTop = top - parentTop - borderTop;
394
+ }
395
+ } else {
396
+ absoluteLeft = left - parentLeft - borderLeft;
397
+ absoluteTop = top - parentTop - borderTop;
398
+ }
399
+ }
400
+ properties.x = absoluteLeft;
401
+ properties.y = absoluteTop;
402
+ properties.width = width;
403
+ properties.height = height;
404
+ return node;
405
+ };
406
+
407
+ /**
408
+ * @param {LayoutNode} node
409
+ * @param {LayoutStateParams} [props]
410
+ */
411
+ const updateNodeProperties = (node, props) => {
412
+ if (!props) return;
413
+ for (let name in props) {
414
+ node.properties[name] = props[name];
415
+ }
416
+ };
417
+
418
+ /**
419
+ * @param {LayoutNode} node
420
+ */
421
+ const recordNodeInlineStyles = node => {
422
+ const style = node.$el.style;
423
+ const stylesStore = node.inlineStyles;
424
+ stylesStore.length = 0;
425
+ node.layout.recordedProperties.forEach(prop => {
426
+ stylesStore.push(prop, style[prop] || '');
427
+ });
428
+ };
429
+
430
+ /**
431
+ * @param {LayoutNode} node
432
+ */
433
+ const restoreNodeInlineStyles = node => {
434
+ const style = node.$el.style;
435
+ const stylesStore = node.inlineStyles;
436
+ for (let i = 0, l = stylesStore.length; i < l; i += 2) {
437
+ const property = stylesStore[i];
438
+ const styleValue = stylesStore[i + 1];
439
+ if (styleValue && styleValue !== '') {
440
+ style[property] = styleValue;
441
+ } else {
442
+ style[property] = '';
443
+ style.removeProperty(property);
444
+ }
445
+ }
446
+ };
447
+
448
+ /**
449
+ * @param {LayoutNode} node
450
+ */
451
+ const restoreNodeTransform = node => {
452
+ const inlineTransforms = node.inlineTransforms;
453
+ const nodeStyle = node.$el.style;
454
+ if (!node.hasTransform || !inlineTransforms || (node.hasTransform && nodeStyle.transform === 'none') || (inlineTransforms && inlineTransforms === 'none')) {
455
+ nodeStyle.removeProperty('transform');
456
+ } else if (inlineTransforms) {
457
+ nodeStyle.transform = inlineTransforms;
458
+ }
459
+ const $measure = node.$measure;
460
+ if (node.hasTransform && $measure !== node.$el) {
461
+ const measuredStyle = $measure.style;
462
+ const measuredInline = node.measuredInlineTransform;
463
+ if (measuredInline && measuredInline !== '') {
464
+ measuredStyle.transform = measuredInline;
465
+ } else {
466
+ measuredStyle.removeProperty('transform');
467
+ }
468
+ }
469
+ node.measuredInlineTransform = null;
470
+ if (node.inlineTransition !== null) {
471
+ restoreElementTransition(node.$el, node.inlineTransition);
472
+ node.inlineTransition = null;
473
+ }
474
+ if ($measure !== node.$el && node.measuredInlineTransition !== null) {
475
+ restoreElementTransition($measure, node.measuredInlineTransition);
476
+ node.measuredInlineTransition = null;
477
+ }
478
+ };
479
+
480
+ /**
481
+ * @param {LayoutNode} node
482
+ */
483
+ const restoreNodeVisualState = node => {
484
+ if (node.measuredIsRemoved || node.hasVisibilitySwap) {
485
+ node.$el.style.removeProperty('display');
486
+ node.$el.style.removeProperty('visibility');
487
+ if (node.hasVisibilitySwap) {
488
+ node.$measure.style.removeProperty('display');
489
+ node.$measure.style.removeProperty('visibility');
490
+ }
491
+ }
492
+ if (node.measuredIsRemoved) {
493
+ node.layout.pendingRemoved.delete(node.$el);
494
+ }
495
+ };
496
+
497
+ /**
498
+ * @param {LayoutNode} node
499
+ * @param {LayoutNode} targetNode
500
+ * @param {LayoutSnapshot} newState
501
+ * @return {LayoutNode}
502
+ */
503
+ const cloneNodeProperties = (node, targetNode, newState) => {
504
+ targetNode.properties = /** @type {LayoutNodeProperties} */({ ...node.properties });
505
+ targetNode.state = newState;
506
+ targetNode.isTarget = node.isTarget;
507
+ targetNode.hasTransform = node.hasTransform;
508
+ targetNode.inlineTransforms = node.inlineTransforms;
509
+ targetNode.measuredIsVisible = node.measuredIsVisible;
510
+ targetNode.measuredDisplay = node.measuredDisplay;
511
+ targetNode.measuredIsRemoved = node.measuredIsRemoved;
512
+ targetNode.measuredHasDisplayNone = node.measuredHasDisplayNone;
513
+ targetNode.measuredHasVisibilityHidden = node.measuredHasVisibilityHidden;
514
+ targetNode.hasDisplayNone = node.hasDisplayNone;
515
+ targetNode.isInlined = node.isInlined;
516
+ targetNode.hasVisibilityHidden = node.hasVisibilityHidden;
517
+ return targetNode;
518
+ };
519
+
520
+ class LayoutSnapshot {
521
+ /**
522
+ * @param {AutoLayout} layout
523
+ */
524
+ constructor(layout) {
525
+ /** @type {AutoLayout} */
526
+ this.layout = layout;
527
+ /** @type {LayoutNode|null} */
528
+ this.rootNode = null;
529
+ /** @type {Set<LayoutNode>} */
530
+ this.rootNodes = new Set();
531
+ /** @type {Map<String, LayoutNode>} */
532
+ this.nodes = new Map();
533
+ /** @type {Number} */
534
+ this.scrollX = 0;
535
+ /** @type {Number} */
536
+ this.scrollY = 0;
537
+ }
538
+
539
+ /**
540
+ * @return {this}
541
+ */
542
+ revert() {
543
+ this.forEachNode(node => {
544
+ node.$el.removeAttribute('data-layout-id');
545
+ node.$measure.removeAttribute('data-layout-id');
546
+ });
547
+ this.rootNode = null;
548
+ this.rootNodes.clear();
549
+ this.nodes.clear();
550
+ return this;
551
+ }
552
+
553
+ /**
554
+ * @param {DOMTarget} $el
555
+ * @return {LayoutNodeProperties|undefined}
556
+ */
557
+ get($el) {
558
+ const node = this.nodes.get($el.dataset.layoutId);
559
+ if (!node) {
560
+ console.warn(`No node found on state`);
561
+ return;
562
+ }
563
+ return node.properties;
564
+ }
565
+
566
+ /**
567
+ * @param {DOMTarget} $el
568
+ * @param {String} prop
569
+ * @return {Number|String|undefined}
570
+ */
571
+ getValue($el, prop) {
572
+ if (!$el || !$el.dataset) {
573
+ console.warn(`No element found on state (${$el})`);
574
+ return;
575
+ }
576
+ const node = this.nodes.get($el.dataset.layoutId);
577
+ if (!node) {
578
+ console.warn(`No node found on state`);
579
+ return;
580
+ }
581
+ const value = node.properties[prop];
582
+ if (!isUnd(value)) return getFunctionValue(value, $el, node.index, node.total);
583
+ }
584
+
585
+ /**
586
+ * @param {LayoutNode|null} rootNode
587
+ * @param {LayoutNodeIterator} cb
588
+ */
589
+ forEach(rootNode, cb) {
590
+ let node = rootNode;
591
+ let i = 0;
592
+ while (node) {
593
+ cb(node, i++);
594
+ if (node._head) {
595
+ node = node._head;
596
+ } else if (node._next) {
597
+ node = node._next;
598
+ } else {
599
+ while (node && !node._next) {
600
+ node = node.parentNode;
601
+ }
602
+ if (node) node = node._next;
603
+ }
604
+ }
605
+ }
606
+
607
+ /**
608
+ * @param {LayoutNodeIterator} cb
609
+ */
610
+ forEachRootNode(cb) {
611
+ this.forEach(this.rootNode, cb);
612
+ }
613
+
614
+ /**
615
+ * @param {LayoutNodeIterator} cb
616
+ */
617
+ forEachNode(cb) {
618
+ for (const rootNode of this.rootNodes) {
619
+ this.forEach(rootNode, cb);
620
+ }
621
+ }
622
+
623
+ /**
624
+ * @param {DOMTarget} $el
625
+ * @param {LayoutNode|null} parentNode
626
+ * @return {LayoutNode|null}
627
+ */
628
+ registerElement($el, parentNode) {
629
+ if (!$el || $el.nodeType !== 1) return null;
630
+
631
+ if (!this.layout.transitionMuteStore.has($el)) this.layout.transitionMuteStore.set($el, muteElementTransition($el));
632
+
633
+ /** @type {Array<DOMTarget|LayoutNode|null>} */
634
+ const stack = [$el, parentNode];
635
+ const root = this.layout.root;
636
+ let firstNode = null;
637
+
638
+ while (stack.length) {
639
+ /** @type {LayoutNode|null} */
640
+ const $parent = /** @type {LayoutNode|null} */(stack.pop());
641
+ /** @type {DOMTarget|null} */
642
+ const $current = /** @type {DOMTarget|null} */(stack.pop());
643
+ if (!$current || $current.nodeType !== 1 || isSvg($current)) continue;
644
+
645
+ const skipMeasurements = $parent ? $parent.measuredIsRemoved : false;
646
+
647
+ const computedStyle = skipMeasurements ? hiddenComputedStyle : getComputedStyle($current);
648
+ const hasDisplayNone = skipMeasurements ? true : computedStyle.display === 'none';
649
+ const hasVisibilityHidden = skipMeasurements ? true : computedStyle.visibility === 'hidden';
650
+ const isVisible = !hasDisplayNone && !hasVisibilityHidden;
651
+ const existingId = $current.dataset.layoutId;
652
+ const isInsideRoot = isElementInRoot(root, $current);
653
+
654
+ let node = existingId ? this.nodes.get(existingId) : null;
655
+
656
+ if (node && node.$el !== $current) {
657
+ const nodeInsideRoot = isElementInRoot(root, node.$el);
658
+ const measuredVisible = node.measuredIsVisible;
659
+ const shouldReassignNode = !nodeInsideRoot && (isInsideRoot || (!isInsideRoot && !measuredVisible && isVisible));
660
+ const shouldReuseMeasurements = nodeInsideRoot && !measuredVisible && isVisible;
661
+ // Rebind nodes that move into the root or whose detached twin just became visible
662
+ if (shouldReassignNode) {
663
+ detachNode(node);
664
+ node = createNode($current, $parent, this, node);
665
+ // for hidden element with in-root sibling, keep the hidden node but borrow measurements from its visible in-root twin element
666
+ } else if (shouldReuseMeasurements) {
667
+ recordNodeState(node, $current, computedStyle, skipMeasurements);
668
+ let $child = $current.lastElementChild;
669
+ while ($child) {
670
+ stack.push(/** @type {DOMTarget} */($child), node);
671
+ $child = $child.previousElementSibling;
672
+ }
673
+ if (!firstNode) firstNode = node;
674
+ continue;
675
+ // No reassignment needed so keep walking descendants under the current parent
676
+ } else {
677
+ let $child = $current.lastElementChild;
678
+ while ($child) {
679
+ stack.push(/** @type {DOMTarget} */($child), $parent);
680
+ $child = $child.previousElementSibling;
681
+ }
682
+ if (!firstNode) firstNode = node;
683
+ continue;
684
+ }
685
+ } else {
686
+ node = createNode($current, $parent, this, node);
687
+ }
688
+
689
+ node.branchAdded = false;
690
+ node.branchRemoved = false;
691
+ node.branchNotRendered = false;
692
+ node.isTarget = false;
693
+ node.isAnimated = false;
694
+ node.hasVisibilityHidden = hasVisibilityHidden;
695
+ node.hasDisplayNone = hasDisplayNone;
696
+ node.hasVisibilitySwap = (hasVisibilityHidden && !node.measuredHasVisibilityHidden) || (hasDisplayNone && !node.measuredHasDisplayNone);
697
+ // node.hasVisibilitySwap = (hasVisibilityHidden !== node.measuredHasVisibilityHidden) || (hasDisplayNone !== node.measuredHasDisplayNone);
698
+
699
+ this.nodes.set(node.id, node);
700
+
701
+ node.parentNode = $parent || null;
702
+ node._prev = null;
703
+ node._next = null;
704
+
705
+ if ($parent) {
706
+ this.rootNodes.delete(node);
707
+ if (!$parent._head) {
708
+ $parent._head = node;
709
+ $parent._tail = node;
710
+ } else {
711
+ $parent._tail._next = node;
712
+ node._prev = $parent._tail;
713
+ $parent._tail = node;
714
+ }
715
+ } else {
716
+ this.rootNodes.add(node);
717
+ }
718
+
719
+ recordNodeState(node, node.$el, computedStyle, skipMeasurements);
720
+
721
+ let $child = $current.lastElementChild;
722
+ while ($child) {
723
+ stack.push(/** @type {DOMTarget} */($child), node);
724
+ $child = $child.previousElementSibling;
725
+ }
726
+
727
+ if (!firstNode) firstNode = node;
728
+ }
729
+
730
+ return firstNode;
731
+ }
732
+
733
+ /**
734
+ * @param {DOMTarget} $el
735
+ * @param {Set<DOMTarget>} candidates
736
+ * @return {LayoutNode|null}
737
+ */
738
+ ensureDetachedNode($el, candidates) {
739
+ if (!$el || $el === this.layout.root) return null;
740
+ const existingId = $el.dataset.layoutId;
741
+ const existingNode = existingId ? this.nodes.get(existingId) : null;
742
+ if (existingNode && existingNode.$el === $el) return existingNode;
743
+ let parentNode = null;
744
+ let $ancestor = $el.parentElement;
745
+ while ($ancestor && $ancestor !== this.layout.root) {
746
+ if (candidates.has($ancestor)) {
747
+ parentNode = this.ensureDetachedNode($ancestor, candidates);
748
+ break;
749
+ }
750
+ $ancestor = $ancestor.parentElement;
751
+ }
752
+ return this.registerElement($el, parentNode);
753
+ }
754
+
755
+ /**
756
+ * @return {this}
757
+ */
758
+ record() {
759
+ const { children, root } = this.layout;
760
+ const toParse = isArr(children) ? children : [children];
761
+ const scoped = [];
762
+ const scopeRoot = children === '*' ? root : scope.root;
763
+
764
+ for (let i = 0, l = toParse.length; i < l; i++) {
765
+ const child = toParse[i];
766
+ scoped[i] = isStr(child) ? scopeRoot.querySelectorAll(child) : child;
767
+ }
768
+
769
+ const parsedChildren = registerTargets(scoped);
770
+
771
+ this.nodes.clear();
772
+ this.rootNodes.clear();
773
+
774
+ const rootNode = this.registerElement(root, null);
775
+ // Root node are always targets
776
+ rootNode.isTarget = true;
777
+ this.rootNode = rootNode;
778
+
779
+ // Track ids of nodes that belong to the current root to filter detached matches
780
+ const inRootNodeIds = new Set();
781
+ this.nodes.forEach((node, id) => {
782
+ if (node && node.measuredIsInsideRoot) {
783
+ inRootNodeIds.add(id);
784
+ }
785
+ });
786
+
787
+ // Elements with a layout id outside the root that match the children selector
788
+ const detachedElementsLookup = new Set();
789
+ const orderedDetachedElements = [];
790
+
791
+ for (let i = 0, l = parsedChildren.length; i < l; i++) {
792
+ const $el = parsedChildren[i];
793
+ if (!$el || $el.nodeType !== 1 || $el === root) continue;
794
+ const insideRoot = isElementInRoot(root, $el);
795
+ if (!insideRoot) {
796
+ const layoutNodeId = $el.dataset.layoutId;
797
+ if (!layoutNodeId || !inRootNodeIds.has(layoutNodeId)) continue;
798
+ }
799
+ if (!detachedElementsLookup.has($el)) {
800
+ detachedElementsLookup.add($el);
801
+ orderedDetachedElements.push($el);
802
+ }
803
+ }
804
+
805
+ for (let i = 0, l = orderedDetachedElements.length; i < l; i++) {
806
+ this.ensureDetachedNode(orderedDetachedElements[i], detachedElementsLookup);
807
+ }
808
+
809
+ for (let i = 0, l = parsedChildren.length; i < l; i++) {
810
+ const $el = parsedChildren[i];
811
+ const node = this.nodes.get($el.dataset.layoutId);
812
+ if (node) {
813
+ let cur = node;
814
+ while (cur) {
815
+ if (cur.isTarget) break;
816
+ cur.isTarget = true;
817
+ cur = cur.parentNode;
818
+ }
819
+ }
820
+ }
821
+
822
+ this.scrollX = window.scrollX;
823
+ this.scrollY = window.scrollY;
824
+
825
+ const total = this.nodes.size;
826
+
827
+ this.forEachNode(restoreNodeTransform);
828
+ this.forEachNode((node, i) => {
829
+ node.index = i;
830
+ node.total = total;
831
+ });
832
+
833
+ return this;
834
+ }
835
+ }
836
+
837
+ class AutoLayout {
838
+ /**
839
+ * @param {DOMTargetSelector} root
840
+ * @param {AutoLayoutParams} [params]
841
+ */
842
+ constructor(root, params = {}) {
843
+ if (scope.current) scope.current.register(this);
844
+ const frozenParams = params.frozen;
845
+ const addedParams = params.added;
846
+ const removedParams = params.removed;
847
+ const propsParams = params.properties;
848
+ /** @type {AutoLayoutParams} */
849
+ this.params = params;
850
+ /** @type {DOMTarget} */
851
+ this.root = /** @type {DOMTarget} */(registerTargets(root)[0]);
852
+ /** @type {Number} */
853
+ this.id = layoutId++;
854
+ /** @type {LayoutChildrenParam} */
855
+ this.children = params.children || '*';
856
+ /** @type {Boolean} */
857
+ this.absoluteCoords = false;
858
+ /** @type {Number} */
859
+ this.duration = setValue(params.duration, 500);
860
+ /** @type {Number|FunctionValue} */
861
+ this.delay = setValue(params.delay, 0);
862
+ /** @type {EasingParam} */
863
+ this.ease = setValue(params.ease, 'inOutExpo');
864
+ /** @type {Callback<this>} */
865
+ this.onComplete = setValue(params.onComplete, /** @type {Callback<this>} */(noop));
866
+ /** @type {LayoutStateParams} */
867
+ this.frozenParams = frozenParams || { opacity: 0 };
868
+ /** @type {LayoutStateParams} */
869
+ this.addedParams = addedParams || { opacity: 0 };
870
+ /** @type {LayoutStateParams} */
871
+ this.removedParams = removedParams || { opacity: 0 };
872
+ /** @type {Set<String>} */
873
+ this.properties = new Set([
874
+ 'opacity',
875
+ 'borderRadius',
876
+ ]);
877
+ if (frozenParams) for (let name in frozenParams) this.properties.add(name);
878
+ if (addedParams) for (let name in addedParams) this.properties.add(name);
879
+ if (removedParams) for (let name in removedParams) this.properties.add(name);
880
+ if (propsParams) for (let i = 0, l = propsParams.length; i < l; i++) this.properties.add(propsParams[i]);
881
+ /** @type {Set<String>} */
882
+ this.recordedProperties = new Set([
883
+ 'display',
884
+ 'visibility',
885
+ 'translate',
886
+ 'position',
887
+ 'left',
888
+ 'top',
889
+ 'marginLeft',
890
+ 'marginTop',
891
+ 'width',
892
+ 'height',
893
+ 'maxWidth',
894
+ 'maxHeight',
895
+ 'minWidth',
896
+ 'minHeight',
897
+ ]);
898
+ this.properties.forEach(prop => this.recordedProperties.add(prop));
899
+ /** @type {WeakSet<DOMTarget>} */
900
+ this.pendingRemoved = new WeakSet();
901
+ /** @type {Map<DOMTarget, String|null>} */
902
+ this.transitionMuteStore = new Map();
903
+ /** @type {LayoutSnapshot} */
904
+ this.oldState = new LayoutSnapshot(this);
905
+ /** @type {LayoutSnapshot} */
906
+ this.newState = new LayoutSnapshot(this);
907
+ /** @type {Timeline|null} */
908
+ this.timeline = null;
909
+ /** @type {WAAPIAnimation|null} */
910
+ this.transformAnimation = null;
911
+ /** @type {Array<DOMTarget>} */
912
+ this.frozen = [];
913
+ /** @type {Array<DOMTarget>} */
914
+ this.removed = [];
915
+ /** @type {Array<DOMTarget>} */
916
+ this.added = [];
917
+ // Record the current state as the old state to init the data attributes
918
+ this.oldState.record();
919
+ // And all layout transition muted during the record
920
+ restoreLayoutTransition(this.transitionMuteStore);
921
+ }
922
+
923
+ /**
924
+ * @return {this}
925
+ */
926
+ revert() {
927
+ if (this.timeline) {
928
+ this.timeline.complete();
929
+ this.timeline = null;
930
+ }
931
+ if (this.transformAnimation) {
932
+ this.transformAnimation.complete();
933
+ this.transformAnimation = null;
934
+ }
935
+ this.root.classList.remove('is-animated');
936
+ this.frozen.length = this.removed.length = this.added.length = 0;
937
+ this.oldState.revert();
938
+ this.newState.revert();
939
+ requestAnimationFrame(() => restoreLayoutTransition(this.transitionMuteStore));
940
+ return this;
941
+ }
942
+
943
+ /**
944
+ * @return {this}
945
+ */
946
+ record() {
947
+ // Commit transforms before measuring
948
+ if (this.transformAnimation) {
949
+ this.transformAnimation.cancel();
950
+ this.transformAnimation = null;
951
+ }
952
+ // Record the old state
953
+ this.oldState.record();
954
+ // Cancel any running timeline
955
+ if (this.timeline) {
956
+ this.timeline.cancel();
957
+ this.timeline = null;
958
+ }
959
+ // Restore previously captured inline styles
960
+ this.newState.forEachRootNode(restoreNodeInlineStyles);
961
+ return this;
962
+ }
963
+
964
+ /**
965
+ * @param {LayoutAnimationParams} [params]
966
+ * @return {Timeline}
967
+ */
968
+ animate(params = {}) {
969
+ const delay = setValue(params.delay, this.delay);
970
+ const duration = setValue(params.duration, this.duration);
971
+ const onComplete = setValue(params.onComplete, this.onComplete);
972
+ const frozenParams = params.frozen ? mergeObjects(params.frozen, this.frozenParams) : this.frozenParams;
973
+ const addedParams = params.added ? mergeObjects(params.added, this.addedParams) : this.addedParams;
974
+ const removedParams = params.removed ? mergeObjects(params.removed, this.removedParams) : this.removedParams;
975
+ const oldState = this.oldState;
976
+ const newState = this.newState;
977
+ const added = this.added;
978
+ const removed = this.removed;
979
+ const frozen = this.frozen;
980
+ const pendingRemoved = this.pendingRemoved;
981
+
982
+ added.length = removed.length = frozen.length = 0;
983
+
984
+ // Mute old state CSS transitions to prevent wrong properties calculation
985
+ oldState.forEachRootNode(muteNodeTransition);
986
+ // Capture the new state before animation
987
+ newState.record();
988
+ newState.forEachRootNode(recordNodeInlineStyles);
989
+
990
+ const targets = [];
991
+ const animated = [];
992
+ const transformed = [];
993
+ const animatedFrozen = [];
994
+ const root = newState.rootNode.$el;
995
+
996
+ newState.forEachRootNode(node => {
997
+ const $el = node.$el;
998
+ const id = node.id;
999
+ const parent = node.parentNode;
1000
+ const parentAdded = parent ? parent.branchAdded : false;
1001
+ const parentRemoved = parent ? parent.branchRemoved : false;
1002
+ const parentNotRendered = parent ? parent.branchNotRendered : false;
1003
+
1004
+ // Delay and duration must be calculated in the animate() call to support delay override
1005
+ node.delay = +(isFnc(delay) ? delay($el, node.index, node.total) : delay);
1006
+ node.duration = +(isFnc(duration) ? duration($el, node.index, node.total) : duration);
1007
+
1008
+ let oldStateNode = oldState.nodes.get(id);
1009
+
1010
+ const hasNoOldState = !oldStateNode;
1011
+
1012
+ if (hasNoOldState) {
1013
+ oldStateNode = cloneNodeProperties(node, /** @type {LayoutNode} */({}), oldState);
1014
+ oldState.nodes.set(id, oldStateNode);
1015
+ oldStateNode.measuredIsRemoved = true;
1016
+ } else if (oldStateNode.measuredIsRemoved && !node.measuredIsRemoved) {
1017
+ cloneNodeProperties(node, oldStateNode, oldState);
1018
+ oldStateNode.measuredIsRemoved = true;
1019
+ }
1020
+
1021
+ const oldParentNode = oldStateNode.parentNode;
1022
+ const oldParentId = oldParentNode ? oldParentNode.id : null;
1023
+ const newParentId = parent ? parent.id : null;
1024
+ const parentChanged = oldParentId !== newParentId;
1025
+ const elementChanged = oldStateNode.$el !== node.$el;
1026
+ const wasRemovedBefore = oldStateNode.measuredIsRemoved;
1027
+ const isRemovedNow = node.measuredIsRemoved;
1028
+
1029
+ // Recalculate postion relative to their parent for elements that have been moved
1030
+ if (!oldStateNode.measuredIsRemoved && !isRemovedNow && !hasNoOldState && (parentChanged || elementChanged)) {
1031
+ let offsetX = 0;
1032
+ let offsetY = 0;
1033
+ let current = node.parentNode;
1034
+ while (current) {
1035
+ offsetX += current.properties.x || 0;
1036
+ offsetY += current.properties.y || 0;
1037
+ if (current.parentNode === newState.rootNode) break;
1038
+ current = current.parentNode;
1039
+ }
1040
+ let oldOffsetX = 0;
1041
+ let oldOffsetY = 0;
1042
+ let oldCurrent = oldStateNode.parentNode;
1043
+ while (oldCurrent) {
1044
+ oldOffsetX += oldCurrent.properties.x || 0;
1045
+ oldOffsetY += oldCurrent.properties.y || 0;
1046
+ if (oldCurrent.parentNode === oldState.rootNode) break;
1047
+ oldCurrent = oldCurrent.parentNode;
1048
+ }
1049
+ oldStateNode.properties.x += oldOffsetX - offsetX;
1050
+ oldStateNode.properties.y += oldOffsetY - offsetY;
1051
+ }
1052
+
1053
+ if (node.hasVisibilitySwap) {
1054
+ if (node.hasVisibilityHidden) {
1055
+ node.$el.style.visibility = 'visible';
1056
+ node.$measure.style.visibility = 'hidden';
1057
+ }
1058
+ if (node.hasDisplayNone) {
1059
+ node.$el.style.display = oldStateNode.measuredDisplay || node.measuredDisplay || '';
1060
+ // Setting visibility 'hidden' instead of display none to avoid calculation issues
1061
+ node.$measure.style.visibility = 'hidden';
1062
+ // @TODO: check why setting display here can cause calculation issues
1063
+ // node.$measure.style.display = 'none';
1064
+ }
1065
+ }
1066
+
1067
+ const wasPendingRemoval = pendingRemoved.has($el);
1068
+ const wasVisibleBefore = oldStateNode.measuredIsVisible;
1069
+ const isVisibleNow = node.measuredIsVisible;
1070
+ const becomeVisible = !wasVisibleBefore && isVisibleNow && !parentNotRendered;
1071
+ const topLevelAdded = !isRemovedNow && (wasRemovedBefore || wasPendingRemoval) && !parentAdded;
1072
+ const newlyRemoved = isRemovedNow && !wasRemovedBefore && !parentRemoved;
1073
+ const topLevelRemoved = newlyRemoved || isRemovedNow && wasPendingRemoval && !parentRemoved;
1074
+
1075
+ if (node.measuredIsRemoved && wasVisibleBefore) {
1076
+ node.$el.style.display = oldStateNode.measuredDisplay;
1077
+ node.$el.style.visibility = 'visible';
1078
+ cloneNodeProperties(oldStateNode, node, newState);
1079
+ }
1080
+
1081
+ if (newlyRemoved) {
1082
+ removed.push($el);
1083
+ pendingRemoved.add($el);
1084
+ } else if (!isRemovedNow && wasPendingRemoval) {
1085
+ pendingRemoved.delete($el);
1086
+ }
1087
+
1088
+ // Node is added
1089
+ if ((topLevelAdded && !parentNotRendered) || becomeVisible) {
1090
+ updateNodeProperties(oldStateNode, addedParams);
1091
+ added.push($el);
1092
+ // Node is removed
1093
+ } else if (topLevelRemoved && !parentNotRendered) {
1094
+ updateNodeProperties(node, removedParams);
1095
+ }
1096
+
1097
+ // Compute function based propety values before cheking for changes
1098
+ for (let name in node.properties) {
1099
+ node.properties[name] = newState.getValue(node.$el, name);
1100
+ // NOTE: I'm using node.$el to get the value of old state, make sure this is valid instead of oldStateNode.$el
1101
+ oldStateNode.properties[name] = oldState.getValue(node.$el, name);
1102
+ }
1103
+
1104
+ const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow;
1105
+ let propertyChanged = false;
1106
+
1107
+
1108
+ if (node.isTarget && (!node.measuredIsRemoved && wasVisibleBefore || node.measuredIsRemoved && isVisibleNow)) {
1109
+ if (!node.isInlined && (node.properties.transform !== 'none' || oldStateNode.properties.transform !== 'none')) {
1110
+ node.hasTransform = true;
1111
+ propertyChanged = true;
1112
+ transformed.push($el);
1113
+ }
1114
+ for (let name in node.properties) {
1115
+ if (name !== 'transform' && (node.properties[name] !== oldStateNode.properties[name] || hiddenStateChanged)) {
1116
+ propertyChanged = true;
1117
+ animated.push($el);
1118
+ break;
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ const nodeHasChanged = (propertyChanged || topLevelAdded || topLevelRemoved || becomeVisible);
1124
+ const nodeIsAnimated = node.isTarget && nodeHasChanged;
1125
+
1126
+ node.isAnimated = nodeIsAnimated;
1127
+ node.branchAdded = parentAdded || topLevelAdded;
1128
+ node.branchRemoved = parentRemoved || topLevelRemoved;
1129
+ node.branchNotRendered = parentNotRendered || node.measuredIsRemoved;
1130
+
1131
+ const sizeTolerance = 1;
1132
+ const widthChanged = Math.abs(node.properties.width - oldStateNode.properties.width) > sizeTolerance;
1133
+ const heightChanged = Math.abs(node.properties.height - oldStateNode.properties.height) > sizeTolerance;
1134
+
1135
+ node.sizeChanged = (widthChanged || heightChanged);
1136
+
1137
+ targets.push($el);
1138
+
1139
+ if (!node.isTarget) {
1140
+ frozen.push($el);
1141
+ if ((nodeHasChanged || node.sizeChanged) && parent && parent.isTarget && parent.isAnimated && parent.sizeChanged) {
1142
+ animatedFrozen.push($el);
1143
+ }
1144
+ }
1145
+ });
1146
+
1147
+ const defaults = {
1148
+ ease: setValue(params.ease, this.ease),
1149
+ duration: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).duration,
1150
+ delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
1151
+ };
1152
+
1153
+ this.timeline = createTimeline({
1154
+ onComplete: () => {
1155
+ // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted
1156
+ if (this.transformAnimation) this.transformAnimation.cancel();
1157
+ newState.forEachRootNode(node => {
1158
+ restoreNodeVisualState(node);
1159
+ restoreNodeInlineStyles(node);
1160
+ });
1161
+ for (let i = 0, l = transformed.length; i < l; i++) {
1162
+ const $el = transformed[i];
1163
+ $el.style.transform = newState.getValue($el, 'transform');
1164
+ }
1165
+ this.root.classList.remove('is-animated');
1166
+ if (onComplete) onComplete(this);
1167
+ // Avoid CSS transitions at the end of the animation by restoring them on the next frame
1168
+ requestAnimationFrame(() => {
1169
+ if (this.root.classList.contains('is-animated')) return;
1170
+ restoreLayoutTransition(this.transitionMuteStore);
1171
+ });
1172
+ },
1173
+ onPause: () => {
1174
+ if (this.transformAnimation) this.transformAnimation.cancel();
1175
+ newState.forEachRootNode(restoreNodeVisualState);
1176
+ this.root.classList.remove('is-animated');
1177
+ if (onComplete) onComplete(this);
1178
+ },
1179
+ composition: false,
1180
+ defaults,
1181
+ });
1182
+
1183
+ if (targets.length) {
1184
+
1185
+ this.root.classList.add('is-animated');
1186
+
1187
+ for (let i = 0, l = targets.length; i < l; i++) {
1188
+ const $el = targets[i];
1189
+ const id = $el.dataset.layoutId;
1190
+ const oldNode = oldState.nodes.get(id);
1191
+ const newNode = newState.nodes.get(id);
1192
+ const oldNodeState = oldNode.properties;
1193
+
1194
+ // Make sure to mute all CSS transition before applying the oldState styles back
1195
+ muteNodeTransition(newNode);
1196
+
1197
+ // Don't animate dimensions and positions of inlined elements
1198
+ if (!newNode.isInlined) {
1199
+ // Display grid can mess with the absolute positioning, so set it to block during transition
1200
+ // if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.display = 'block';
1201
+ $el.style.display = 'block';
1202
+ // All children must be in position absolue
1203
+ if ($el !== root || this.absoluteCoords) {
1204
+ $el.style.position = this.absoluteCoords ? 'fixed' : 'absolute';
1205
+ $el.style.left = '0px';
1206
+ $el.style.top = '0px';
1207
+ $el.style.marginLeft = '0px';
1208
+ $el.style.marginTop = '0px';
1209
+ $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
1210
+ }
1211
+ if ($el === root && newNode.measuredPosition === 'static') {
1212
+ $el.style.position = 'relative';
1213
+ // Cancel left / trop in case the static element had muted values now activated by potision relative
1214
+ $el.style.left = '0px';
1215
+ $el.style.top = '0px';
1216
+ }
1217
+ $el.style.width = `${oldNodeState.width}px`;
1218
+ $el.style.height = `${oldNodeState.height}px`;
1219
+ // Overrides user defined min and max to prevents width and height clamping
1220
+ $el.style.minWidth = `auto`;
1221
+ $el.style.minHeight = `auto`;
1222
+ $el.style.maxWidth = `none`;
1223
+ $el.style.maxHeight = `none`;
1224
+ }
1225
+ }
1226
+
1227
+ // Restore the scroll position if the oldState differs from the current state
1228
+ if (oldState.scrollX !== window.scrollX || oldState.scrollY !== window.scrollY) {
1229
+ // Restoring in the next frame avoids race conditions if for example a waapi animation commit styles that affect the root height
1230
+ requestAnimationFrame(() => {
1231
+ window.scrollTo(oldState.scrollX, oldState.scrollY);
1232
+ });
1233
+ }
1234
+
1235
+ for (let i = 0, l = animated.length; i < l; i++) {
1236
+ const $el = animated[i];
1237
+ const id = $el.dataset.layoutId;
1238
+ const oldNode = oldState.nodes.get(id);
1239
+ const newNode = newState.nodes.get(id);
1240
+ const oldNodeState = oldNode.properties;
1241
+ const newNodeState = newNode.properties;
1242
+ let hasChanged = false;
1243
+ const animatedProps = {
1244
+ composition: 'none',
1245
+ // delay: (/** @type {HTMLElement} */$el) => newState.nodes.get($el.dataset.layoutId).delay,
1246
+ };
1247
+ if (!newNode.isInlined) {
1248
+ if (oldNodeState.width !== newNodeState.width) {
1249
+ animatedProps.width = [oldNodeState.width, newNodeState.width];
1250
+ hasChanged = true;
1251
+ }
1252
+ if (oldNodeState.height !== newNodeState.height) {
1253
+ animatedProps.height = [oldNodeState.height, newNodeState.height];
1254
+ hasChanged = true;
1255
+ }
1256
+ // If the node has transforms we handle the translate animation in wappi otherwise translate and other transforms can be out of sync
1257
+ // Always animate translate
1258
+ if (!newNode.hasTransform) {
1259
+ animatedProps.translate = [`${oldNodeState.x}px ${oldNodeState.y}px`, `${newNodeState.x}px ${newNodeState.y}px`];
1260
+ hasChanged = true;
1261
+ }
1262
+ }
1263
+ this.properties.forEach(prop => {
1264
+ const oldVal = oldNodeState[prop];
1265
+ const newVal = newNodeState[prop];
1266
+ if (prop !== 'transform' && oldVal !== newVal) {
1267
+ animatedProps[prop] = [oldVal, newVal];
1268
+ hasChanged = true;
1269
+ }
1270
+ });
1271
+ if (hasChanged) {
1272
+ this.timeline.add($el, animatedProps, 0);
1273
+ }
1274
+ }
1275
+
1276
+ }
1277
+
1278
+ if (frozen.length) {
1279
+
1280
+ for (let i = 0, l = frozen.length; i < l; i++) {
1281
+ const $el = frozen[i];
1282
+ const oldNode = oldState.nodes.get($el.dataset.layoutId);
1283
+ if (!oldNode.isInlined) {
1284
+ const oldNodeState = oldState.get($el);
1285
+ $el.style.width = `${oldNodeState.width}px`;
1286
+ $el.style.height = `${oldNodeState.height}px`;
1287
+ // Overrides user defined min and max to prevents width and height clamping
1288
+ $el.style.minWidth = `auto`;
1289
+ $el.style.minHeight = `auto`;
1290
+ $el.style.maxWidth = `none`;
1291
+ $el.style.maxHeight = `none`;
1292
+ $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`;
1293
+ }
1294
+ this.properties.forEach(prop => {
1295
+ if (prop !== 'transform') {
1296
+ $el.style[prop] = `${oldState.getValue($el, prop)}`;
1297
+ }
1298
+ });
1299
+ }
1300
+
1301
+ for (let i = 0, l = frozen.length; i < l; i++) {
1302
+ const $el = frozen[i];
1303
+ const newNode = newState.nodes.get($el.dataset.layoutId);
1304
+ const newNodeState = newState.get($el);
1305
+ this.timeline.call(() => {
1306
+ if (!newNode.isInlined) {
1307
+ $el.style.width = `${newNodeState.width}px`;
1308
+ $el.style.height = `${newNodeState.height}px`;
1309
+ // Overrides user defined min and max to prevents width and height clamping
1310
+ $el.style.minWidth = `auto`;
1311
+ $el.style.minHeight = `auto`;
1312
+ $el.style.maxWidth = `none`;
1313
+ $el.style.maxHeight = `none`;
1314
+ $el.style.translate = `${newNodeState.x}px ${newNodeState.y}px`;
1315
+ }
1316
+ this.properties.forEach(prop => {
1317
+ if (prop !== 'transform') {
1318
+ $el.style[prop] = `${newState.getValue($el, prop)}`;
1319
+ }
1320
+ });
1321
+ }, newNode.delay + newNode.duration / 2);
1322
+ }
1323
+
1324
+ if (animatedFrozen.length) {
1325
+ const animatedFrozenParams = /** @type {AnimationParams} */({});
1326
+ if (frozenParams) {
1327
+ for (let prop in frozenParams) {
1328
+ animatedFrozenParams[prop] = [
1329
+ { from: (/** @type {HTMLElement} */$el) => oldState.getValue($el, prop), ease: 'in(1.75)', to: frozenParams[prop] },
1330
+ { from: frozenParams[prop], to: (/** @type {HTMLElement} */$el) => newState.getValue($el, prop), ease: 'out(1.75)' }
1331
+ ];
1332
+ }
1333
+ }
1334
+ this.timeline.add(animatedFrozen, animatedFrozenParams, 0);
1335
+ }
1336
+
1337
+ }
1338
+
1339
+ const transformedLength = transformed.length;
1340
+
1341
+ if (transformedLength) {
1342
+ // We only need to set the transform property here since translate is alread defined the targets loop
1343
+ for (let i = 0; i < transformedLength; i++) {
1344
+ const $el = transformed[i];
1345
+ $el.style.translate = `${oldState.get($el).x}px ${oldState.get($el).y}px`,
1346
+ $el.style.transform = oldState.getValue($el, 'transform');
1347
+ }
1348
+ this.transformAnimation = waapi.animate(transformed, {
1349
+ translate: (/** @type {HTMLElement} */$el) => `${newState.get($el).x}px ${newState.get($el).y}px`,
1350
+ transform: (/** @type {HTMLElement} */$el) => newState.getValue($el, 'transform'),
1351
+ autoplay: false,
1352
+ persist: true,
1353
+ ...defaults,
1354
+ });
1355
+ this.timeline.sync(this.transformAnimation, 0);
1356
+ }
1357
+
1358
+ return this.timeline.init();
1359
+ }
1360
+
1361
+ /**
1362
+ * @param {(layout: this) => void} callback
1363
+ * @param {LayoutAnimationParams} [params]
1364
+ * @return {this}
1365
+ */
1366
+ update(callback, params = {}) {
1367
+ this.record();
1368
+ callback(this);
1369
+ this.animate(params);
1370
+ return this;
1371
+ }
1372
+ }
1373
+
1374
+ /**
1375
+ * @param {DOMTargetSelector} root
1376
+ * @param {AutoLayoutParams} [params]
1377
+ * @return {AutoLayout}
1378
+ */
1379
+ const createLayout = (root, params) => new AutoLayout(root, params);
1380
+
1381
+ export { AutoLayout, createLayout };