animejs 4.2.2 → 4.3.0-beta.1

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