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.
- package/README.md +82 -28
- package/dist/bundles/anime.esm.js +2705 -1303
- package/dist/bundles/anime.esm.min.js +2 -2
- package/dist/bundles/anime.umd.js +2710 -1306
- package/dist/bundles/anime.umd.min.js +2 -2
- package/dist/modules/animatable/animatable.cjs +1 -1
- package/dist/modules/animatable/animatable.js +1 -1
- package/dist/modules/animatable/index.cjs +1 -1
- package/dist/modules/animatable/index.js +1 -1
- package/dist/modules/animation/additive.cjs +1 -1
- package/dist/modules/animation/additive.js +1 -1
- package/dist/modules/animation/animation.cjs +18 -9
- package/dist/modules/animation/animation.js +19 -10
- package/dist/modules/animation/composition.cjs +1 -1
- package/dist/modules/animation/composition.js +1 -1
- package/dist/modules/animation/index.cjs +1 -1
- package/dist/modules/animation/index.js +1 -1
- package/dist/modules/core/clock.cjs +10 -10
- package/dist/modules/core/clock.d.ts +1 -1
- package/dist/modules/core/clock.js +10 -10
- package/dist/modules/core/colors.cjs +1 -1
- package/dist/modules/core/colors.js +1 -1
- package/dist/modules/core/consts.cjs +6 -4
- package/dist/modules/core/consts.d.ts +13 -5
- package/dist/modules/core/consts.js +6 -4
- package/dist/modules/core/globals.cjs +5 -2
- package/dist/modules/core/globals.d.ts +1 -0
- package/dist/modules/core/globals.js +5 -3
- package/dist/modules/core/helpers.cjs +1 -1
- package/dist/modules/core/helpers.js +1 -1
- package/dist/modules/core/render.cjs +1 -1
- package/dist/modules/core/render.js +1 -1
- package/dist/modules/core/styles.cjs +1 -1
- package/dist/modules/core/styles.js +1 -1
- package/dist/modules/core/targets.cjs +1 -1
- package/dist/modules/core/targets.js +1 -1
- package/dist/modules/core/transforms.cjs +1 -1
- package/dist/modules/core/transforms.js +1 -1
- package/dist/modules/core/units.cjs +1 -1
- package/dist/modules/core/units.js +1 -1
- package/dist/modules/core/values.cjs +1 -1
- package/dist/modules/core/values.js +1 -1
- package/dist/modules/draggable/draggable.cjs +1 -1
- package/dist/modules/draggable/draggable.js +1 -1
- package/dist/modules/draggable/index.cjs +1 -1
- package/dist/modules/draggable/index.js +1 -1
- package/dist/modules/easings/cubic-bezier/index.cjs +1 -1
- package/dist/modules/easings/cubic-bezier/index.js +1 -1
- package/dist/modules/easings/eases/index.cjs +1 -1
- package/dist/modules/easings/eases/index.js +1 -1
- package/dist/modules/easings/eases/parser.cjs +1 -1
- package/dist/modules/easings/eases/parser.js +1 -1
- package/dist/modules/easings/index.cjs +1 -1
- package/dist/modules/easings/index.js +1 -1
- package/dist/modules/easings/irregular/index.cjs +1 -1
- package/dist/modules/easings/irregular/index.js +1 -1
- package/dist/modules/easings/linear/index.cjs +1 -1
- package/dist/modules/easings/linear/index.js +1 -1
- package/dist/modules/easings/none.cjs +1 -1
- package/dist/modules/easings/none.js +1 -1
- package/dist/modules/easings/spring/index.cjs +1 -1
- package/dist/modules/easings/spring/index.js +1 -1
- package/dist/modules/easings/steps/index.cjs +1 -1
- package/dist/modules/easings/steps/index.js +1 -1
- package/dist/modules/engine/engine.cjs +2 -2
- package/dist/modules/engine/engine.js +2 -2
- package/dist/modules/engine/index.cjs +1 -1
- package/dist/modules/engine/index.js +1 -1
- package/dist/modules/events/index.cjs +1 -1
- package/dist/modules/events/index.js +1 -1
- package/dist/modules/events/scroll.cjs +1 -1
- package/dist/modules/events/scroll.js +1 -1
- package/dist/modules/index.cjs +4 -1
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +2 -1
- package/dist/modules/layout/index.cjs +15 -0
- package/dist/modules/layout/index.d.ts +1 -0
- package/dist/modules/layout/index.js +8 -0
- package/dist/modules/layout/layout.cjs +1384 -0
- package/dist/modules/layout/layout.d.ts +211 -0
- package/dist/modules/layout/layout.js +1381 -0
- package/dist/modules/scope/index.cjs +1 -1
- package/dist/modules/scope/index.js +1 -1
- package/dist/modules/scope/scope.cjs +1 -1
- package/dist/modules/scope/scope.js +1 -1
- package/dist/modules/svg/drawable.cjs +1 -1
- package/dist/modules/svg/drawable.js +1 -1
- package/dist/modules/svg/helpers.cjs +1 -1
- package/dist/modules/svg/helpers.js +1 -1
- package/dist/modules/svg/index.cjs +1 -1
- package/dist/modules/svg/index.js +1 -1
- package/dist/modules/svg/morphto.cjs +1 -1
- package/dist/modules/svg/morphto.js +1 -1
- package/dist/modules/svg/motionpath.cjs +1 -1
- package/dist/modules/svg/motionpath.js +1 -1
- package/dist/modules/text/index.cjs +1 -1
- package/dist/modules/text/index.js +1 -1
- package/dist/modules/text/split.cjs +23 -9
- package/dist/modules/text/split.js +23 -9
- package/dist/modules/timeline/index.cjs +1 -1
- package/dist/modules/timeline/index.js +1 -1
- package/dist/modules/timeline/position.cjs +1 -1
- package/dist/modules/timeline/position.js +1 -1
- package/dist/modules/timeline/timeline.cjs +14 -6
- package/dist/modules/timeline/timeline.d.ts +2 -0
- package/dist/modules/timeline/timeline.js +15 -7
- package/dist/modules/timer/index.cjs +1 -1
- package/dist/modules/timer/index.js +1 -1
- package/dist/modules/timer/timer.cjs +26 -13
- package/dist/modules/timer/timer.d.ts +1 -0
- package/dist/modules/timer/timer.js +27 -14
- package/dist/modules/types/index.d.ts +3 -1
- package/dist/modules/utils/chainable.cjs +1 -1
- package/dist/modules/utils/chainable.js +1 -1
- package/dist/modules/utils/index.cjs +1 -1
- package/dist/modules/utils/index.js +1 -1
- package/dist/modules/utils/number.cjs +1 -1
- package/dist/modules/utils/number.js +1 -1
- package/dist/modules/utils/random.cjs +1 -1
- package/dist/modules/utils/random.js +1 -1
- package/dist/modules/utils/stagger.cjs +1 -1
- package/dist/modules/utils/stagger.js +1 -1
- package/dist/modules/utils/target.cjs +1 -1
- package/dist/modules/utils/target.js +1 -1
- package/dist/modules/utils/time.cjs +1 -1
- package/dist/modules/utils/time.js +1 -1
- package/dist/modules/waapi/composition.cjs +1 -1
- package/dist/modules/waapi/composition.js +1 -1
- package/dist/modules/waapi/index.cjs +1 -1
- package/dist/modules/waapi/index.js +1 -1
- package/dist/modules/waapi/waapi.cjs +15 -7
- package/dist/modules/waapi/waapi.js +16 -8
- 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;
|