framer-motion 12.12.0 → 12.12.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/dist/cjs/client.js +1 -1
- package/dist/cjs/{create-BiMCrvDh.js → create-DCF2FFGK.js} +1 -6
- package/dist/cjs/dom.js +3 -7
- package/dist/cjs/index.js +7 -5
- package/dist/dom.js +1 -1
- package/dist/es/animation/utils/create-visual-element.mjs +2 -2
- package/dist/es/components/AnimatePresence/PopChild.mjs +4 -1
- package/dist/es/projection/node/create-projection-node.mjs +2 -3
- package/dist/es/render/dom/resize/handle-element.mjs +2 -2
- package/dist/es/render/dom/scroll/offsets/inset.mjs +3 -1
- package/dist/framer-motion.dev.js +1457 -1430
- package/dist/framer-motion.js +1 -1
- package/dist/m.d.ts +1 -1
- package/dist/size-rollup-animate.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-rollup-scroll.js +1 -1
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +4 -4
- package/package.json +4 -4
- package/dist/es/render/dom/utils/is-svg-element.mjs +0 -5
|
@@ -83,520 +83,145 @@
|
|
|
83
83
|
const PresenceContext =
|
|
84
84
|
/* @__PURE__ */ React$1.createContext(null);
|
|
85
85
|
|
|
86
|
+
function addUniqueItem(arr, item) {
|
|
87
|
+
if (arr.indexOf(item) === -1)
|
|
88
|
+
arr.push(item);
|
|
89
|
+
}
|
|
90
|
+
function removeItem(arr, item) {
|
|
91
|
+
const index = arr.indexOf(item);
|
|
92
|
+
if (index > -1)
|
|
93
|
+
arr.splice(index, 1);
|
|
94
|
+
}
|
|
95
|
+
// Adapted from array-move
|
|
96
|
+
function moveItem([...arr], fromIndex, toIndex) {
|
|
97
|
+
const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
|
|
98
|
+
if (startIndex >= 0 && startIndex < arr.length) {
|
|
99
|
+
const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
|
|
100
|
+
const [item] = arr.splice(fromIndex, 1);
|
|
101
|
+
arr.splice(endIndex, 0, item);
|
|
102
|
+
}
|
|
103
|
+
return arr;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const clamp = (min, max, v) => {
|
|
107
|
+
if (v > max)
|
|
108
|
+
return max;
|
|
109
|
+
if (v < min)
|
|
110
|
+
return min;
|
|
111
|
+
return v;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
exports.warning = () => { };
|
|
115
|
+
exports.invariant = () => { };
|
|
116
|
+
{
|
|
117
|
+
exports.warning = (check, message) => {
|
|
118
|
+
if (!check && typeof console !== "undefined") {
|
|
119
|
+
console.warn(message);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
exports.invariant = (check, message) => {
|
|
123
|
+
if (!check) {
|
|
124
|
+
throw new Error(message);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const MotionGlobalConfig = {};
|
|
130
|
+
|
|
86
131
|
/**
|
|
87
|
-
*
|
|
132
|
+
* Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
|
|
88
133
|
*/
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
134
|
+
const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
|
|
135
|
+
|
|
136
|
+
function isObject(value) {
|
|
137
|
+
return typeof value === "object" && value !== null;
|
|
138
|
+
}
|
|
94
139
|
|
|
95
140
|
/**
|
|
96
|
-
*
|
|
97
|
-
* to leverage snapshot lifecycle.
|
|
141
|
+
* Check if the value is a zero value string like "0px" or "0%"
|
|
98
142
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
|
|
144
|
+
|
|
145
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
146
|
+
function memo(callback) {
|
|
147
|
+
let result;
|
|
148
|
+
return () => {
|
|
149
|
+
if (result === undefined)
|
|
150
|
+
result = callback();
|
|
151
|
+
return result;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
156
|
+
const noop = (any) => any;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Pipe
|
|
160
|
+
* Compose other transformers to run linearily
|
|
161
|
+
* pipe(min(20), max(40))
|
|
162
|
+
* @param {...functions} transformers
|
|
163
|
+
* @return {function}
|
|
164
|
+
*/
|
|
165
|
+
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
166
|
+
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
Progress within given range
|
|
170
|
+
|
|
171
|
+
Given a lower limit and an upper limit, we return the progress
|
|
172
|
+
(expressed as a number 0-1) represented by the given value, and
|
|
173
|
+
limit that progress to within 0-1.
|
|
174
|
+
|
|
175
|
+
@param [number]: Lower limit
|
|
176
|
+
@param [number]: Upper limit
|
|
177
|
+
@param [number]: Value to find progress within given range
|
|
178
|
+
@return [number]: Progress of value within range as expressed 0-1
|
|
179
|
+
*/
|
|
180
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
181
|
+
const progress = (from, to, value) => {
|
|
182
|
+
const toFromDifference = to - from;
|
|
183
|
+
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
class SubscriptionManager {
|
|
187
|
+
constructor() {
|
|
188
|
+
this.subscriptions = [];
|
|
113
189
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
componentDidUpdate() { }
|
|
118
|
-
render() {
|
|
119
|
-
return this.props.children;
|
|
190
|
+
add(handler) {
|
|
191
|
+
addUniqueItem(this.subscriptions, handler);
|
|
192
|
+
return () => removeItem(this.subscriptions, handler);
|
|
120
193
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const ref = React$1.useRef(null);
|
|
125
|
-
const size = React$1.useRef({
|
|
126
|
-
width: 0,
|
|
127
|
-
height: 0,
|
|
128
|
-
top: 0,
|
|
129
|
-
left: 0,
|
|
130
|
-
right: 0,
|
|
131
|
-
});
|
|
132
|
-
const { nonce } = React$1.useContext(MotionConfigContext);
|
|
133
|
-
/**
|
|
134
|
-
* We create and inject a style block so we can apply this explicit
|
|
135
|
-
* sizing in a non-destructive manner by just deleting the style block.
|
|
136
|
-
*
|
|
137
|
-
* We can't apply size via render as the measurement happens
|
|
138
|
-
* in getSnapshotBeforeUpdate (post-render), likewise if we apply the
|
|
139
|
-
* styles directly on the DOM node, we might be overwriting
|
|
140
|
-
* styles set via the style prop.
|
|
141
|
-
*/
|
|
142
|
-
React$1.useInsertionEffect(() => {
|
|
143
|
-
const { width, height, top, left, right } = size.current;
|
|
144
|
-
if (isPresent || !ref.current || !width || !height)
|
|
194
|
+
notify(a, b, c) {
|
|
195
|
+
const numSubscriptions = this.subscriptions.length;
|
|
196
|
+
if (!numSubscriptions)
|
|
145
197
|
return;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
document.head.appendChild(style);
|
|
152
|
-
if (style.sheet) {
|
|
153
|
-
style.sheet.insertRule(`
|
|
154
|
-
[data-motion-pop-id="${id}"] {
|
|
155
|
-
position: absolute !important;
|
|
156
|
-
width: ${width}px !important;
|
|
157
|
-
height: ${height}px !important;
|
|
158
|
-
${x}px !important;
|
|
159
|
-
top: ${top}px !important;
|
|
160
|
-
}
|
|
161
|
-
`);
|
|
198
|
+
if (numSubscriptions === 1) {
|
|
199
|
+
/**
|
|
200
|
+
* If there's only a single handler we can just call it without invoking a loop.
|
|
201
|
+
*/
|
|
202
|
+
this.subscriptions[0](a, b, c);
|
|
162
203
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
204
|
+
else {
|
|
205
|
+
for (let i = 0; i < numSubscriptions; i++) {
|
|
206
|
+
/**
|
|
207
|
+
* Check whether the handler exists before firing as it's possible
|
|
208
|
+
* the subscriptions were modified during this loop running.
|
|
209
|
+
*/
|
|
210
|
+
const handler = this.subscriptions[i];
|
|
211
|
+
handler && handler(a, b, c);
|
|
166
212
|
}
|
|
167
|
-
}
|
|
168
|
-
}, [isPresent]);
|
|
169
|
-
return (jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React__namespace.cloneElement(children, { ref }) }));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, }) => {
|
|
173
|
-
const presenceChildren = useConstant(newChildrenMap);
|
|
174
|
-
const id = React$1.useId();
|
|
175
|
-
let isReusedContext = true;
|
|
176
|
-
let context = React$1.useMemo(() => {
|
|
177
|
-
isReusedContext = false;
|
|
178
|
-
return {
|
|
179
|
-
id,
|
|
180
|
-
initial,
|
|
181
|
-
isPresent,
|
|
182
|
-
custom,
|
|
183
|
-
onExitComplete: (childId) => {
|
|
184
|
-
presenceChildren.set(childId, true);
|
|
185
|
-
for (const isComplete of presenceChildren.values()) {
|
|
186
|
-
if (!isComplete)
|
|
187
|
-
return; // can stop searching when any is incomplete
|
|
188
|
-
}
|
|
189
|
-
onExitComplete && onExitComplete();
|
|
190
|
-
},
|
|
191
|
-
register: (childId) => {
|
|
192
|
-
presenceChildren.set(childId, false);
|
|
193
|
-
return () => presenceChildren.delete(childId);
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
}, [isPresent, presenceChildren, onExitComplete]);
|
|
197
|
-
/**
|
|
198
|
-
* If the presence of a child affects the layout of the components around it,
|
|
199
|
-
* we want to make a new context value to ensure they get re-rendered
|
|
200
|
-
* so they can detect that layout change.
|
|
201
|
-
*/
|
|
202
|
-
if (presenceAffectsLayout && isReusedContext) {
|
|
203
|
-
context = { ...context };
|
|
213
|
+
}
|
|
204
214
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* component immediately.
|
|
211
|
-
*/
|
|
212
|
-
React__namespace.useEffect(() => {
|
|
213
|
-
!isPresent &&
|
|
214
|
-
!presenceChildren.size &&
|
|
215
|
-
onExitComplete &&
|
|
216
|
-
onExitComplete();
|
|
217
|
-
}, [isPresent]);
|
|
218
|
-
if (mode === "popLayout") {
|
|
219
|
-
children = (jsx(PopChild, { isPresent: isPresent, anchorX: anchorX, children: children }));
|
|
215
|
+
getSize() {
|
|
216
|
+
return this.subscriptions.length;
|
|
217
|
+
}
|
|
218
|
+
clear() {
|
|
219
|
+
this.subscriptions.length = 0;
|
|
220
220
|
}
|
|
221
|
-
return (jsx(PresenceContext.Provider, { value: context, children: children }));
|
|
222
|
-
};
|
|
223
|
-
function newChildrenMap() {
|
|
224
|
-
return new Map();
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
/**
|
|
228
|
-
*
|
|
229
|
-
* to access information about whether it's still present in the React tree.
|
|
230
|
-
*
|
|
231
|
-
* ```jsx
|
|
232
|
-
* import { usePresence } from "framer-motion"
|
|
233
|
-
*
|
|
234
|
-
* export const Component = () => {
|
|
235
|
-
* const [isPresent, safeToRemove] = usePresence()
|
|
236
|
-
*
|
|
237
|
-
* useEffect(() => {
|
|
238
|
-
* !isPresent && setTimeout(safeToRemove, 1000)
|
|
239
|
-
* }, [isPresent])
|
|
240
|
-
*
|
|
241
|
-
* return <div />
|
|
242
|
-
* }
|
|
243
|
-
* ```
|
|
244
|
-
*
|
|
245
|
-
* If `isPresent` is `false`, it means that a component has been removed the tree, but
|
|
246
|
-
* `AnimatePresence` won't really remove it until `safeToRemove` has been called.
|
|
247
|
-
*
|
|
248
|
-
* @public
|
|
249
|
-
*/
|
|
250
|
-
function usePresence(subscribe = true) {
|
|
251
|
-
const context = React$1.useContext(PresenceContext);
|
|
252
|
-
if (context === null)
|
|
253
|
-
return [true, null];
|
|
254
|
-
const { isPresent, onExitComplete, register } = context;
|
|
255
|
-
// It's safe to call the following hooks conditionally (after an early return) because the context will always
|
|
256
|
-
// either be null or non-null for the lifespan of the component.
|
|
257
|
-
const id = React$1.useId();
|
|
258
|
-
React$1.useEffect(() => {
|
|
259
|
-
if (subscribe) {
|
|
260
|
-
return register(id);
|
|
261
|
-
}
|
|
262
|
-
}, [subscribe]);
|
|
263
|
-
const safeToRemove = React$1.useCallback(() => subscribe && onExitComplete && onExitComplete(id), [id, onExitComplete, subscribe]);
|
|
264
|
-
return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Similar to `usePresence`, except `useIsPresent` simply returns whether or not the component is present.
|
|
268
|
-
* There is no `safeToRemove` function.
|
|
269
|
-
*
|
|
270
|
-
* ```jsx
|
|
271
|
-
* import { useIsPresent } from "framer-motion"
|
|
272
|
-
*
|
|
273
|
-
* export const Component = () => {
|
|
274
|
-
* const isPresent = useIsPresent()
|
|
275
|
-
*
|
|
276
|
-
* useEffect(() => {
|
|
277
|
-
* !isPresent && console.log("I've been removed!")
|
|
278
|
-
* }, [isPresent])
|
|
279
|
-
*
|
|
280
|
-
* return <div />
|
|
281
|
-
* }
|
|
282
|
-
* ```
|
|
283
|
-
*
|
|
284
|
-
* @public
|
|
285
|
-
*/
|
|
286
|
-
function useIsPresent() {
|
|
287
|
-
return isPresent(React$1.useContext(PresenceContext));
|
|
288
|
-
}
|
|
289
|
-
function isPresent(context) {
|
|
290
|
-
return context === null ? true : context.isPresent;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const getChildKey = (child) => child.key || "";
|
|
294
|
-
function onlyElements(children) {
|
|
295
|
-
const filtered = [];
|
|
296
|
-
// We use forEach here instead of map as map mutates the component key by preprending `.$`
|
|
297
|
-
React$1.Children.forEach(children, (child) => {
|
|
298
|
-
if (React$1.isValidElement(child))
|
|
299
|
-
filtered.push(child);
|
|
300
|
-
});
|
|
301
|
-
return filtered;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* `AnimatePresence` enables the animation of components that have been removed from the tree.
|
|
306
|
-
*
|
|
307
|
-
* When adding/removing more than a single child, every child **must** be given a unique `key` prop.
|
|
308
|
-
*
|
|
309
|
-
* Any `motion` components that have an `exit` property defined will animate out when removed from
|
|
310
|
-
* the tree.
|
|
311
|
-
*
|
|
312
|
-
* ```jsx
|
|
313
|
-
* import { motion, AnimatePresence } from 'framer-motion'
|
|
314
|
-
*
|
|
315
|
-
* export const Items = ({ items }) => (
|
|
316
|
-
* <AnimatePresence>
|
|
317
|
-
* {items.map(item => (
|
|
318
|
-
* <motion.div
|
|
319
|
-
* key={item.id}
|
|
320
|
-
* initial={{ opacity: 0 }}
|
|
321
|
-
* animate={{ opacity: 1 }}
|
|
322
|
-
* exit={{ opacity: 0 }}
|
|
323
|
-
* />
|
|
324
|
-
* ))}
|
|
325
|
-
* </AnimatePresence>
|
|
326
|
-
* )
|
|
327
|
-
* ```
|
|
328
|
-
*
|
|
329
|
-
* You can sequence exit animations throughout a tree using variants.
|
|
330
|
-
*
|
|
331
|
-
* If a child contains multiple `motion` components with `exit` props, it will only unmount the child
|
|
332
|
-
* once all `motion` components have finished animating out. Likewise, any components using
|
|
333
|
-
* `usePresence` all need to call `safeToRemove`.
|
|
334
|
-
*
|
|
335
|
-
* @public
|
|
336
|
-
*/
|
|
337
|
-
const AnimatePresence = ({ children, custom, initial = true, onExitComplete, presenceAffectsLayout = true, mode = "sync", propagate = false, anchorX = "left", }) => {
|
|
338
|
-
const [isParentPresent, safeToRemove] = usePresence(propagate);
|
|
339
|
-
/**
|
|
340
|
-
* Filter any children that aren't ReactElements. We can only track components
|
|
341
|
-
* between renders with a props.key.
|
|
342
|
-
*/
|
|
343
|
-
const presentChildren = React$1.useMemo(() => onlyElements(children), [children]);
|
|
344
|
-
/**
|
|
345
|
-
* Track the keys of the currently rendered children. This is used to
|
|
346
|
-
* determine which children are exiting.
|
|
347
|
-
*/
|
|
348
|
-
const presentKeys = propagate && !isParentPresent ? [] : presentChildren.map(getChildKey);
|
|
349
|
-
/**
|
|
350
|
-
* If `initial={false}` we only want to pass this to components in the first render.
|
|
351
|
-
*/
|
|
352
|
-
const isInitialRender = React$1.useRef(true);
|
|
353
|
-
/**
|
|
354
|
-
* A ref containing the currently present children. When all exit animations
|
|
355
|
-
* are complete, we use this to re-render the component with the latest children
|
|
356
|
-
* *committed* rather than the latest children *rendered*.
|
|
357
|
-
*/
|
|
358
|
-
const pendingPresentChildren = React$1.useRef(presentChildren);
|
|
359
|
-
/**
|
|
360
|
-
* Track which exiting children have finished animating out.
|
|
361
|
-
*/
|
|
362
|
-
const exitComplete = useConstant(() => new Map());
|
|
363
|
-
/**
|
|
364
|
-
* Save children to render as React state. To ensure this component is concurrent-safe,
|
|
365
|
-
* we check for exiting children via an effect.
|
|
366
|
-
*/
|
|
367
|
-
const [diffedChildren, setDiffedChildren] = React$1.useState(presentChildren);
|
|
368
|
-
const [renderedChildren, setRenderedChildren] = React$1.useState(presentChildren);
|
|
369
|
-
useIsomorphicLayoutEffect(() => {
|
|
370
|
-
isInitialRender.current = false;
|
|
371
|
-
pendingPresentChildren.current = presentChildren;
|
|
372
|
-
/**
|
|
373
|
-
* Update complete status of exiting children.
|
|
374
|
-
*/
|
|
375
|
-
for (let i = 0; i < renderedChildren.length; i++) {
|
|
376
|
-
const key = getChildKey(renderedChildren[i]);
|
|
377
|
-
if (!presentKeys.includes(key)) {
|
|
378
|
-
if (exitComplete.get(key) !== true) {
|
|
379
|
-
exitComplete.set(key, false);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
exitComplete.delete(key);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}, [renderedChildren, presentKeys.length, presentKeys.join("-")]);
|
|
387
|
-
const exitingChildren = [];
|
|
388
|
-
if (presentChildren !== diffedChildren) {
|
|
389
|
-
let nextChildren = [...presentChildren];
|
|
390
|
-
/**
|
|
391
|
-
* Loop through all the currently rendered components and decide which
|
|
392
|
-
* are exiting.
|
|
393
|
-
*/
|
|
394
|
-
for (let i = 0; i < renderedChildren.length; i++) {
|
|
395
|
-
const child = renderedChildren[i];
|
|
396
|
-
const key = getChildKey(child);
|
|
397
|
-
if (!presentKeys.includes(key)) {
|
|
398
|
-
nextChildren.splice(i, 0, child);
|
|
399
|
-
exitingChildren.push(child);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* If we're in "wait" mode, and we have exiting children, we want to
|
|
404
|
-
* only render these until they've all exited.
|
|
405
|
-
*/
|
|
406
|
-
if (mode === "wait" && exitingChildren.length) {
|
|
407
|
-
nextChildren = exitingChildren;
|
|
408
|
-
}
|
|
409
|
-
setRenderedChildren(onlyElements(nextChildren));
|
|
410
|
-
setDiffedChildren(presentChildren);
|
|
411
|
-
/**
|
|
412
|
-
* Early return to ensure once we've set state with the latest diffed
|
|
413
|
-
* children, we can immediately re-render.
|
|
414
|
-
*/
|
|
415
|
-
return null;
|
|
416
|
-
}
|
|
417
|
-
if (mode === "wait" &&
|
|
418
|
-
renderedChildren.length > 1) {
|
|
419
|
-
console.warn(`You're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.`);
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* If we've been provided a forceRender function by the LayoutGroupContext,
|
|
423
|
-
* we can use it to force a re-render amongst all surrounding components once
|
|
424
|
-
* all components have finished animating out.
|
|
425
|
-
*/
|
|
426
|
-
const { forceRender } = React$1.useContext(LayoutGroupContext);
|
|
427
|
-
return (jsx(Fragment, { children: renderedChildren.map((child) => {
|
|
428
|
-
const key = getChildKey(child);
|
|
429
|
-
const isPresent = propagate && !isParentPresent
|
|
430
|
-
? false
|
|
431
|
-
: presentChildren === renderedChildren ||
|
|
432
|
-
presentKeys.includes(key);
|
|
433
|
-
const onExit = () => {
|
|
434
|
-
if (exitComplete.has(key)) {
|
|
435
|
-
exitComplete.set(key, true);
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
let isEveryExitComplete = true;
|
|
441
|
-
exitComplete.forEach((isExitComplete) => {
|
|
442
|
-
if (!isExitComplete)
|
|
443
|
-
isEveryExitComplete = false;
|
|
444
|
-
});
|
|
445
|
-
if (isEveryExitComplete) {
|
|
446
|
-
forceRender?.();
|
|
447
|
-
setRenderedChildren(pendingPresentChildren.current);
|
|
448
|
-
propagate && safeToRemove?.();
|
|
449
|
-
onExitComplete && onExitComplete();
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
return (jsx(PresenceChild, { isPresent: isPresent, initial: !isInitialRender.current || initial
|
|
453
|
-
? undefined
|
|
454
|
-
: false, custom: custom, presenceAffectsLayout: presenceAffectsLayout, mode: mode, onExitComplete: isPresent ? undefined : onExit, anchorX: anchorX, children: child }, key));
|
|
455
|
-
}) }));
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Note: Still used by components generated by old versions of Framer
|
|
460
|
-
*
|
|
461
|
-
* @deprecated
|
|
462
|
-
*/
|
|
463
|
-
const DeprecatedLayoutGroupContext = React$1.createContext(null);
|
|
464
|
-
|
|
465
|
-
function addUniqueItem(arr, item) {
|
|
466
|
-
if (arr.indexOf(item) === -1)
|
|
467
|
-
arr.push(item);
|
|
468
|
-
}
|
|
469
|
-
function removeItem(arr, item) {
|
|
470
|
-
const index = arr.indexOf(item);
|
|
471
|
-
if (index > -1)
|
|
472
|
-
arr.splice(index, 1);
|
|
473
|
-
}
|
|
474
|
-
// Adapted from array-move
|
|
475
|
-
function moveItem([...arr], fromIndex, toIndex) {
|
|
476
|
-
const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
|
|
477
|
-
if (startIndex >= 0 && startIndex < arr.length) {
|
|
478
|
-
const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
|
|
479
|
-
const [item] = arr.splice(fromIndex, 1);
|
|
480
|
-
arr.splice(endIndex, 0, item);
|
|
481
|
-
}
|
|
482
|
-
return arr;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const clamp = (min, max, v) => {
|
|
486
|
-
if (v > max)
|
|
487
|
-
return max;
|
|
488
|
-
if (v < min)
|
|
489
|
-
return min;
|
|
490
|
-
return v;
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
exports.warning = () => { };
|
|
494
|
-
exports.invariant = () => { };
|
|
495
|
-
{
|
|
496
|
-
exports.warning = (check, message) => {
|
|
497
|
-
if (!check && typeof console !== "undefined") {
|
|
498
|
-
console.warn(message);
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
exports.invariant = (check, message) => {
|
|
502
|
-
if (!check) {
|
|
503
|
-
throw new Error(message);
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const MotionGlobalConfig = {};
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
|
|
512
|
-
*/
|
|
513
|
-
const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Check if the value is a zero value string like "0px" or "0%"
|
|
517
|
-
*/
|
|
518
|
-
const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
|
|
519
|
-
|
|
520
|
-
/*#__NO_SIDE_EFFECTS__*/
|
|
521
|
-
function memo(callback) {
|
|
522
|
-
let result;
|
|
523
|
-
return () => {
|
|
524
|
-
if (result === undefined)
|
|
525
|
-
result = callback();
|
|
526
|
-
return result;
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/*#__NO_SIDE_EFFECTS__*/
|
|
531
|
-
const noop = (any) => any;
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Pipe
|
|
535
|
-
* Compose other transformers to run linearily
|
|
536
|
-
* pipe(min(20), max(40))
|
|
537
|
-
* @param {...functions} transformers
|
|
538
|
-
* @return {function}
|
|
539
|
-
*/
|
|
540
|
-
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
541
|
-
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
542
|
-
|
|
543
|
-
/*
|
|
544
|
-
Progress within given range
|
|
545
|
-
|
|
546
|
-
Given a lower limit and an upper limit, we return the progress
|
|
547
|
-
(expressed as a number 0-1) represented by the given value, and
|
|
548
|
-
limit that progress to within 0-1.
|
|
549
|
-
|
|
550
|
-
@param [number]: Lower limit
|
|
551
|
-
@param [number]: Upper limit
|
|
552
|
-
@param [number]: Value to find progress within given range
|
|
553
|
-
@return [number]: Progress of value within range as expressed 0-1
|
|
554
|
-
*/
|
|
555
|
-
/*#__NO_SIDE_EFFECTS__*/
|
|
556
|
-
const progress = (from, to, value) => {
|
|
557
|
-
const toFromDifference = to - from;
|
|
558
|
-
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
class SubscriptionManager {
|
|
562
|
-
constructor() {
|
|
563
|
-
this.subscriptions = [];
|
|
564
|
-
}
|
|
565
|
-
add(handler) {
|
|
566
|
-
addUniqueItem(this.subscriptions, handler);
|
|
567
|
-
return () => removeItem(this.subscriptions, handler);
|
|
568
|
-
}
|
|
569
|
-
notify(a, b, c) {
|
|
570
|
-
const numSubscriptions = this.subscriptions.length;
|
|
571
|
-
if (!numSubscriptions)
|
|
572
|
-
return;
|
|
573
|
-
if (numSubscriptions === 1) {
|
|
574
|
-
/**
|
|
575
|
-
* If there's only a single handler we can just call it without invoking a loop.
|
|
576
|
-
*/
|
|
577
|
-
this.subscriptions[0](a, b, c);
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
for (let i = 0; i < numSubscriptions; i++) {
|
|
581
|
-
/**
|
|
582
|
-
* Check whether the handler exists before firing as it's possible
|
|
583
|
-
* the subscriptions were modified during this loop running.
|
|
584
|
-
*/
|
|
585
|
-
const handler = this.subscriptions[i];
|
|
586
|
-
handler && handler(a, b, c);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
getSize() {
|
|
591
|
-
return this.subscriptions.length;
|
|
592
|
-
}
|
|
593
|
-
clear() {
|
|
594
|
-
this.subscriptions.length = 0;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Converts seconds to milliseconds
|
|
224
|
+
* Converts seconds to milliseconds
|
|
600
225
|
*
|
|
601
226
|
* @param seconds - Time in seconds.
|
|
602
227
|
* @return milliseconds - Converted time in milliseconds.
|
|
@@ -3055,6 +2680,14 @@
|
|
|
3055
2680
|
((type === "spring" || isGenerator(type)) && velocity));
|
|
3056
2681
|
}
|
|
3057
2682
|
|
|
2683
|
+
/**
|
|
2684
|
+
* Checks if an element is an HTML element in a way
|
|
2685
|
+
* that works across iframes
|
|
2686
|
+
*/
|
|
2687
|
+
function isHTMLElement(element) {
|
|
2688
|
+
return isObject(element) && "offsetHeight" in element;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
3058
2691
|
/**
|
|
3059
2692
|
* A list of values that can be hardware-accelerated.
|
|
3060
2693
|
*/
|
|
@@ -3063,16 +2696,13 @@
|
|
|
3063
2696
|
"clipPath",
|
|
3064
2697
|
"filter",
|
|
3065
2698
|
"transform",
|
|
3066
|
-
// TODO:
|
|
3067
|
-
// or until we implement support for linear() easing.
|
|
2699
|
+
// TODO: Could be re-enabled now we have support for linear() easing
|
|
3068
2700
|
// "background-color"
|
|
3069
2701
|
]);
|
|
3070
2702
|
const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
|
|
3071
2703
|
function supportsBrowserAnimation(options) {
|
|
3072
2704
|
const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
|
|
3073
|
-
if (!motionValue
|
|
3074
|
-
!motionValue.owner ||
|
|
3075
|
-
!(motionValue.owner.current instanceof HTMLElement)) {
|
|
2705
|
+
if (!isHTMLElement(motionValue?.owner?.current)) {
|
|
3076
2706
|
return false;
|
|
3077
2707
|
}
|
|
3078
2708
|
const { onUpdate, transformTemplate } = motionValue.owner.getProps();
|
|
@@ -4101,1035 +3731,1432 @@
|
|
|
4101
3731
|
this.events.animationCancel.notify();
|
|
4102
3732
|
}
|
|
4103
3733
|
}
|
|
4104
|
-
this.clearAnimation();
|
|
4105
|
-
}
|
|
4106
|
-
/**
|
|
4107
|
-
* Returns `true` if this value is currently animating.
|
|
4108
|
-
*
|
|
4109
|
-
* @public
|
|
4110
|
-
*/
|
|
4111
|
-
isAnimating() {
|
|
4112
|
-
return !!this.animation;
|
|
4113
|
-
}
|
|
4114
|
-
clearAnimation() {
|
|
4115
|
-
delete this.animation;
|
|
3734
|
+
this.clearAnimation();
|
|
3735
|
+
}
|
|
3736
|
+
/**
|
|
3737
|
+
* Returns `true` if this value is currently animating.
|
|
3738
|
+
*
|
|
3739
|
+
* @public
|
|
3740
|
+
*/
|
|
3741
|
+
isAnimating() {
|
|
3742
|
+
return !!this.animation;
|
|
3743
|
+
}
|
|
3744
|
+
clearAnimation() {
|
|
3745
|
+
delete this.animation;
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Destroy and clean up subscribers to this `MotionValue`.
|
|
3749
|
+
*
|
|
3750
|
+
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
3751
|
+
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
3752
|
+
* created a `MotionValue` via the `motionValue` function.
|
|
3753
|
+
*
|
|
3754
|
+
* @public
|
|
3755
|
+
*/
|
|
3756
|
+
destroy() {
|
|
3757
|
+
this.dependents?.clear();
|
|
3758
|
+
this.events.destroy?.notify();
|
|
3759
|
+
this.clearListeners();
|
|
3760
|
+
this.stop();
|
|
3761
|
+
if (this.stopPassiveEffect) {
|
|
3762
|
+
this.stopPassiveEffect();
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
function motionValue(init, options) {
|
|
3767
|
+
return new MotionValue(init, options);
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
/**
|
|
3771
|
+
* Provided a value and a ValueType, returns the value as that value type.
|
|
3772
|
+
*/
|
|
3773
|
+
const getValueAsType = (value, type) => {
|
|
3774
|
+
return type && typeof value === "number"
|
|
3775
|
+
? type.transform(value)
|
|
3776
|
+
: value;
|
|
3777
|
+
};
|
|
3778
|
+
|
|
3779
|
+
class MotionValueState {
|
|
3780
|
+
constructor() {
|
|
3781
|
+
this.latest = {};
|
|
3782
|
+
this.values = new Map();
|
|
3783
|
+
}
|
|
3784
|
+
set(name, value, render, computed) {
|
|
3785
|
+
const existingValue = this.values.get(name);
|
|
3786
|
+
if (existingValue) {
|
|
3787
|
+
existingValue.onRemove();
|
|
3788
|
+
}
|
|
3789
|
+
const onChange = () => {
|
|
3790
|
+
this.latest[name] = getValueAsType(value.get(), numberValueTypes[name]);
|
|
3791
|
+
render && frame.render(render);
|
|
3792
|
+
};
|
|
3793
|
+
onChange();
|
|
3794
|
+
const cancelOnChange = value.on("change", onChange);
|
|
3795
|
+
computed && value.addDependent(computed);
|
|
3796
|
+
const remove = () => {
|
|
3797
|
+
cancelOnChange();
|
|
3798
|
+
render && cancelFrame(render);
|
|
3799
|
+
this.values.delete(name);
|
|
3800
|
+
computed && value.removeDependent(computed);
|
|
3801
|
+
};
|
|
3802
|
+
this.values.set(name, { value, onRemove: remove });
|
|
3803
|
+
return remove;
|
|
3804
|
+
}
|
|
3805
|
+
get(name) {
|
|
3806
|
+
return this.values.get(name)?.value;
|
|
3807
|
+
}
|
|
3808
|
+
destroy() {
|
|
3809
|
+
for (const value of this.values.values()) {
|
|
3810
|
+
value.onRemove();
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
const translateAlias$1 = {
|
|
3816
|
+
x: "translateX",
|
|
3817
|
+
y: "translateY",
|
|
3818
|
+
z: "translateZ",
|
|
3819
|
+
transformPerspective: "perspective",
|
|
3820
|
+
};
|
|
3821
|
+
function buildTransform$1(state) {
|
|
3822
|
+
let transform = "";
|
|
3823
|
+
let transformIsDefault = true;
|
|
3824
|
+
/**
|
|
3825
|
+
* Loop over all possible transforms in order, adding the ones that
|
|
3826
|
+
* are present to the transform string.
|
|
3827
|
+
*/
|
|
3828
|
+
for (let i = 0; i < transformPropOrder.length; i++) {
|
|
3829
|
+
const key = transformPropOrder[i];
|
|
3830
|
+
const value = state.latest[key];
|
|
3831
|
+
if (value === undefined)
|
|
3832
|
+
continue;
|
|
3833
|
+
let valueIsDefault = true;
|
|
3834
|
+
if (typeof value === "number") {
|
|
3835
|
+
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
|
3836
|
+
}
|
|
3837
|
+
else {
|
|
3838
|
+
valueIsDefault = parseFloat(value) === 0;
|
|
3839
|
+
}
|
|
3840
|
+
if (!valueIsDefault) {
|
|
3841
|
+
transformIsDefault = false;
|
|
3842
|
+
const transformName = translateAlias$1[key] || key;
|
|
3843
|
+
const valueToRender = state.latest[key];
|
|
3844
|
+
transform += `${transformName}(${valueToRender}) `;
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
return transformIsDefault ? "none" : transform.trim();
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
const stateMap = new WeakMap();
|
|
3851
|
+
function styleEffect(subject, values) {
|
|
3852
|
+
const elements = resolveElements(subject);
|
|
3853
|
+
const subscriptions = [];
|
|
3854
|
+
for (let i = 0; i < elements.length; i++) {
|
|
3855
|
+
const element = elements[i];
|
|
3856
|
+
const state = stateMap.get(element) ?? new MotionValueState();
|
|
3857
|
+
stateMap.set(element, state);
|
|
3858
|
+
for (const key in values) {
|
|
3859
|
+
const value = values[key];
|
|
3860
|
+
const remove = addValue(element, state, key, value);
|
|
3861
|
+
subscriptions.push(remove);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
return () => {
|
|
3865
|
+
for (const cancel of subscriptions)
|
|
3866
|
+
cancel();
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
function addValue(element, state, key, value) {
|
|
3870
|
+
let render = undefined;
|
|
3871
|
+
let computed = undefined;
|
|
3872
|
+
if (transformProps.has(key)) {
|
|
3873
|
+
if (!state.get("transform")) {
|
|
3874
|
+
state.set("transform", new MotionValue("none"), () => {
|
|
3875
|
+
element.style.transform = buildTransform$1(state);
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
computed = state.get("transform");
|
|
3879
|
+
}
|
|
3880
|
+
else if (isCSSVar(key)) {
|
|
3881
|
+
render = () => {
|
|
3882
|
+
element.style.setProperty(key, state.latest[key]);
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
else {
|
|
3886
|
+
render = () => {
|
|
3887
|
+
element.style[key] = state.latest[key];
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
return state.set(key, value, render, computed);
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3893
|
+
const { schedule: microtask, cancel: cancelMicrotask } =
|
|
3894
|
+
/* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
|
|
3895
|
+
|
|
3896
|
+
const isDragging = {
|
|
3897
|
+
x: false,
|
|
3898
|
+
y: false,
|
|
3899
|
+
};
|
|
3900
|
+
function isDragActive() {
|
|
3901
|
+
return isDragging.x || isDragging.y;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
function setDragLock(axis) {
|
|
3905
|
+
if (axis === "x" || axis === "y") {
|
|
3906
|
+
if (isDragging[axis]) {
|
|
3907
|
+
return null;
|
|
3908
|
+
}
|
|
3909
|
+
else {
|
|
3910
|
+
isDragging[axis] = true;
|
|
3911
|
+
return () => {
|
|
3912
|
+
isDragging[axis] = false;
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
4116
3915
|
}
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
destroy() {
|
|
4127
|
-
this.dependents?.clear();
|
|
4128
|
-
this.events.destroy?.notify();
|
|
4129
|
-
this.clearListeners();
|
|
4130
|
-
this.stop();
|
|
4131
|
-
if (this.stopPassiveEffect) {
|
|
4132
|
-
this.stopPassiveEffect();
|
|
3916
|
+
else {
|
|
3917
|
+
if (isDragging.x || isDragging.y) {
|
|
3918
|
+
return null;
|
|
3919
|
+
}
|
|
3920
|
+
else {
|
|
3921
|
+
isDragging.x = isDragging.y = true;
|
|
3922
|
+
return () => {
|
|
3923
|
+
isDragging.x = isDragging.y = false;
|
|
3924
|
+
};
|
|
4133
3925
|
}
|
|
4134
3926
|
}
|
|
4135
3927
|
}
|
|
4136
|
-
|
|
4137
|
-
|
|
3928
|
+
|
|
3929
|
+
function setupGesture(elementOrSelector, options) {
|
|
3930
|
+
const elements = resolveElements(elementOrSelector);
|
|
3931
|
+
const gestureAbortController = new AbortController();
|
|
3932
|
+
const eventOptions = {
|
|
3933
|
+
passive: true,
|
|
3934
|
+
...options,
|
|
3935
|
+
signal: gestureAbortController.signal,
|
|
3936
|
+
};
|
|
3937
|
+
const cancel = () => gestureAbortController.abort();
|
|
3938
|
+
return [elements, eventOptions, cancel];
|
|
4138
3939
|
}
|
|
4139
3940
|
|
|
3941
|
+
function isValidHover(event) {
|
|
3942
|
+
return !(event.pointerType === "touch" || isDragActive());
|
|
3943
|
+
}
|
|
4140
3944
|
/**
|
|
4141
|
-
*
|
|
3945
|
+
* Create a hover gesture. hover() is different to .addEventListener("pointerenter")
|
|
3946
|
+
* in that it has an easier syntax, filters out polyfilled touch events, interoperates
|
|
3947
|
+
* with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
|
|
3948
|
+
*
|
|
3949
|
+
* @public
|
|
4142
3950
|
*/
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
3951
|
+
function hover(elementOrSelector, onHoverStart, options = {}) {
|
|
3952
|
+
const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
|
|
3953
|
+
const onPointerEnter = (enterEvent) => {
|
|
3954
|
+
if (!isValidHover(enterEvent))
|
|
3955
|
+
return;
|
|
3956
|
+
const { target } = enterEvent;
|
|
3957
|
+
const onHoverEnd = onHoverStart(target, enterEvent);
|
|
3958
|
+
if (typeof onHoverEnd !== "function" || !target)
|
|
3959
|
+
return;
|
|
3960
|
+
const onPointerLeave = (leaveEvent) => {
|
|
3961
|
+
if (!isValidHover(leaveEvent))
|
|
3962
|
+
return;
|
|
3963
|
+
onHoverEnd(leaveEvent);
|
|
3964
|
+
target.removeEventListener("pointerleave", onPointerLeave);
|
|
3965
|
+
};
|
|
3966
|
+
target.addEventListener("pointerleave", onPointerLeave, eventOptions);
|
|
3967
|
+
};
|
|
3968
|
+
elements.forEach((element) => {
|
|
3969
|
+
element.addEventListener("pointerenter", onPointerEnter, eventOptions);
|
|
3970
|
+
});
|
|
3971
|
+
return cancel;
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
/**
|
|
3975
|
+
* Recursively traverse up the tree to check whether the provided child node
|
|
3976
|
+
* is the parent or a descendant of it.
|
|
3977
|
+
*
|
|
3978
|
+
* @param parent - Element to find
|
|
3979
|
+
* @param child - Element to test against parent
|
|
3980
|
+
*/
|
|
3981
|
+
const isNodeOrChild = (parent, child) => {
|
|
3982
|
+
if (!child) {
|
|
3983
|
+
return false;
|
|
3984
|
+
}
|
|
3985
|
+
else if (parent === child) {
|
|
3986
|
+
return true;
|
|
3987
|
+
}
|
|
3988
|
+
else {
|
|
3989
|
+
return isNodeOrChild(parent, child.parentElement);
|
|
3990
|
+
}
|
|
4147
3991
|
};
|
|
4148
3992
|
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
this.values = new Map();
|
|
3993
|
+
const isPrimaryPointer = (event) => {
|
|
3994
|
+
if (event.pointerType === "mouse") {
|
|
3995
|
+
return typeof event.button !== "number" || event.button <= 0;
|
|
4153
3996
|
}
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
3997
|
+
else {
|
|
3998
|
+
/**
|
|
3999
|
+
* isPrimary is true for all mice buttons, whereas every touch point
|
|
4000
|
+
* is regarded as its own input. So subsequent concurrent touch points
|
|
4001
|
+
* will be false.
|
|
4002
|
+
*
|
|
4003
|
+
* Specifically match against false here as incomplete versions of
|
|
4004
|
+
* PointerEvents in very old browser might have it set as undefined.
|
|
4005
|
+
*/
|
|
4006
|
+
return event.isPrimary !== false;
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
|
|
4010
|
+
const focusableElements = new Set([
|
|
4011
|
+
"BUTTON",
|
|
4012
|
+
"INPUT",
|
|
4013
|
+
"SELECT",
|
|
4014
|
+
"TEXTAREA",
|
|
4015
|
+
"A",
|
|
4016
|
+
]);
|
|
4017
|
+
function isElementKeyboardAccessible(element) {
|
|
4018
|
+
return (focusableElements.has(element.tagName) ||
|
|
4019
|
+
element.tabIndex !== -1);
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
const isPressing = new WeakSet();
|
|
4023
|
+
|
|
4024
|
+
/**
|
|
4025
|
+
* Filter out events that are not "Enter" keys.
|
|
4026
|
+
*/
|
|
4027
|
+
function filterEvents(callback) {
|
|
4028
|
+
return (event) => {
|
|
4029
|
+
if (event.key !== "Enter")
|
|
4030
|
+
return;
|
|
4031
|
+
callback(event);
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
function firePointerEvent(target, type) {
|
|
4035
|
+
target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true }));
|
|
4036
|
+
}
|
|
4037
|
+
const enableKeyboardPress = (focusEvent, eventOptions) => {
|
|
4038
|
+
const element = focusEvent.currentTarget;
|
|
4039
|
+
if (!element)
|
|
4040
|
+
return;
|
|
4041
|
+
const handleKeydown = filterEvents(() => {
|
|
4042
|
+
if (isPressing.has(element))
|
|
4043
|
+
return;
|
|
4044
|
+
firePointerEvent(element, "down");
|
|
4045
|
+
const handleKeyup = filterEvents(() => {
|
|
4046
|
+
firePointerEvent(element, "up");
|
|
4047
|
+
});
|
|
4048
|
+
const handleBlur = () => firePointerEvent(element, "cancel");
|
|
4049
|
+
element.addEventListener("keyup", handleKeyup, eventOptions);
|
|
4050
|
+
element.addEventListener("blur", handleBlur, eventOptions);
|
|
4051
|
+
});
|
|
4052
|
+
element.addEventListener("keydown", handleKeydown, eventOptions);
|
|
4053
|
+
/**
|
|
4054
|
+
* Add an event listener that fires on blur to remove the keydown events.
|
|
4055
|
+
*/
|
|
4056
|
+
element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions);
|
|
4057
|
+
};
|
|
4058
|
+
|
|
4059
|
+
/**
|
|
4060
|
+
* Filter out events that are not primary pointer events, or are triggering
|
|
4061
|
+
* while a Motion gesture is active.
|
|
4062
|
+
*/
|
|
4063
|
+
function isValidPressEvent(event) {
|
|
4064
|
+
return isPrimaryPointer(event) && !isDragActive();
|
|
4065
|
+
}
|
|
4066
|
+
/**
|
|
4067
|
+
* Create a press gesture.
|
|
4068
|
+
*
|
|
4069
|
+
* Press is different to `"pointerdown"`, `"pointerup"` in that it
|
|
4070
|
+
* automatically filters out secondary pointer events like right
|
|
4071
|
+
* click and multitouch.
|
|
4072
|
+
*
|
|
4073
|
+
* It also adds accessibility support for keyboards, where
|
|
4074
|
+
* an element with a press gesture will receive focus and
|
|
4075
|
+
* trigger on Enter `"keydown"` and `"keyup"` events.
|
|
4076
|
+
*
|
|
4077
|
+
* This is different to a browser's `"click"` event, which does
|
|
4078
|
+
* respond to keyboards but only for the `"click"` itself, rather
|
|
4079
|
+
* than the press start and end/cancel. The element also needs
|
|
4080
|
+
* to be focusable for this to work, whereas a press gesture will
|
|
4081
|
+
* make an element focusable by default.
|
|
4082
|
+
*
|
|
4083
|
+
* @public
|
|
4084
|
+
*/
|
|
4085
|
+
function press(targetOrSelector, onPressStart, options = {}) {
|
|
4086
|
+
const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options);
|
|
4087
|
+
const startPress = (startEvent) => {
|
|
4088
|
+
const target = startEvent.currentTarget;
|
|
4089
|
+
if (!isValidPressEvent(startEvent))
|
|
4090
|
+
return;
|
|
4091
|
+
isPressing.add(target);
|
|
4092
|
+
const onPressEnd = onPressStart(target, startEvent);
|
|
4093
|
+
const onPointerEnd = (endEvent, success) => {
|
|
4094
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
4095
|
+
window.removeEventListener("pointercancel", onPointerCancel);
|
|
4096
|
+
if (isPressing.has(target)) {
|
|
4097
|
+
isPressing.delete(target);
|
|
4098
|
+
}
|
|
4099
|
+
if (!isValidPressEvent(endEvent)) {
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
if (typeof onPressEnd === "function") {
|
|
4103
|
+
onPressEnd(endEvent, { success });
|
|
4104
|
+
}
|
|
4162
4105
|
};
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
render && cancelFrame(render);
|
|
4169
|
-
this.values.delete(name);
|
|
4170
|
-
computed && value.removeDependent(computed);
|
|
4106
|
+
const onPointerUp = (upEvent) => {
|
|
4107
|
+
onPointerEnd(upEvent, target === window ||
|
|
4108
|
+
target === document ||
|
|
4109
|
+
options.useGlobalTarget ||
|
|
4110
|
+
isNodeOrChild(target, upEvent.target));
|
|
4171
4111
|
};
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
}
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4112
|
+
const onPointerCancel = (cancelEvent) => {
|
|
4113
|
+
onPointerEnd(cancelEvent, false);
|
|
4114
|
+
};
|
|
4115
|
+
window.addEventListener("pointerup", onPointerUp, eventOptions);
|
|
4116
|
+
window.addEventListener("pointercancel", onPointerCancel, eventOptions);
|
|
4117
|
+
};
|
|
4118
|
+
targets.forEach((target) => {
|
|
4119
|
+
const pointerDownTarget = options.useGlobalTarget ? window : target;
|
|
4120
|
+
pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
|
|
4121
|
+
if (isHTMLElement(target)) {
|
|
4122
|
+
target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
|
|
4123
|
+
if (!isElementKeyboardAccessible(target) &&
|
|
4124
|
+
!target.hasAttribute("tabindex")) {
|
|
4125
|
+
target.tabIndex = 0;
|
|
4126
|
+
}
|
|
4181
4127
|
}
|
|
4182
|
-
}
|
|
4128
|
+
});
|
|
4129
|
+
return cancelEvents;
|
|
4183
4130
|
}
|
|
4184
4131
|
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
};
|
|
4191
|
-
function buildTransform$1(state) {
|
|
4192
|
-
let transform = "";
|
|
4193
|
-
let transformIsDefault = true;
|
|
4194
|
-
/**
|
|
4195
|
-
* Loop over all possible transforms in order, adding the ones that
|
|
4196
|
-
* are present to the transform string.
|
|
4197
|
-
*/
|
|
4198
|
-
for (let i = 0; i < transformPropOrder.length; i++) {
|
|
4199
|
-
const key = transformPropOrder[i];
|
|
4200
|
-
const value = state.latest[key];
|
|
4201
|
-
if (value === undefined)
|
|
4202
|
-
continue;
|
|
4203
|
-
let valueIsDefault = true;
|
|
4204
|
-
if (typeof value === "number") {
|
|
4205
|
-
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
|
4206
|
-
}
|
|
4207
|
-
else {
|
|
4208
|
-
valueIsDefault = parseFloat(value) === 0;
|
|
4209
|
-
}
|
|
4210
|
-
if (!valueIsDefault) {
|
|
4211
|
-
transformIsDefault = false;
|
|
4212
|
-
const transformName = translateAlias$1[key] || key;
|
|
4213
|
-
const valueToRender = state.latest[key];
|
|
4214
|
-
transform += `${transformName}(${valueToRender}) `;
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
return transformIsDefault ? "none" : transform.trim();
|
|
4132
|
+
function getComputedStyle$2(element, name) {
|
|
4133
|
+
const computedStyle = window.getComputedStyle(element);
|
|
4134
|
+
return isCSSVar(name)
|
|
4135
|
+
? computedStyle.getPropertyValue(name)
|
|
4136
|
+
: computedStyle[name];
|
|
4218
4137
|
}
|
|
4219
4138
|
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
const
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
const
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
for (const key in values) {
|
|
4229
|
-
const value = values[key];
|
|
4230
|
-
const remove = addValue(element, state, key, value);
|
|
4231
|
-
subscriptions.push(remove);
|
|
4139
|
+
function observeTimeline(update, timeline) {
|
|
4140
|
+
let prevProgress;
|
|
4141
|
+
const onFrame = () => {
|
|
4142
|
+
const { currentTime } = timeline;
|
|
4143
|
+
const percentage = currentTime === null ? 0 : currentTime.value;
|
|
4144
|
+
const progress = percentage / 100;
|
|
4145
|
+
if (prevProgress !== progress) {
|
|
4146
|
+
update(progress);
|
|
4232
4147
|
}
|
|
4233
|
-
|
|
4234
|
-
return () => {
|
|
4235
|
-
for (const cancel of subscriptions)
|
|
4236
|
-
cancel();
|
|
4148
|
+
prevProgress = progress;
|
|
4237
4149
|
};
|
|
4150
|
+
frame.preUpdate(onFrame, true);
|
|
4151
|
+
return () => cancelFrame(onFrame);
|
|
4238
4152
|
}
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
if (
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
element.style.transform = buildTransform$1(state);
|
|
4246
|
-
});
|
|
4247
|
-
}
|
|
4248
|
-
computed = state.get("transform");
|
|
4249
|
-
}
|
|
4250
|
-
else if (isCSSVar(key)) {
|
|
4251
|
-
render = () => {
|
|
4252
|
-
element.style.setProperty(key, state.latest[key]);
|
|
4253
|
-
};
|
|
4153
|
+
|
|
4154
|
+
function record() {
|
|
4155
|
+
const { value } = statsBuffer;
|
|
4156
|
+
if (value === null) {
|
|
4157
|
+
cancelFrame(record);
|
|
4158
|
+
return;
|
|
4254
4159
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4160
|
+
value.frameloop.rate.push(frameData.delta);
|
|
4161
|
+
value.animations.mainThread.push(activeAnimations.mainThread);
|
|
4162
|
+
value.animations.waapi.push(activeAnimations.waapi);
|
|
4163
|
+
value.animations.layout.push(activeAnimations.layout);
|
|
4164
|
+
}
|
|
4165
|
+
function mean(values) {
|
|
4166
|
+
return values.reduce((acc, value) => acc + value, 0) / values.length;
|
|
4167
|
+
}
|
|
4168
|
+
function summarise(values, calcAverage = mean) {
|
|
4169
|
+
if (values.length === 0) {
|
|
4170
|
+
return {
|
|
4171
|
+
min: 0,
|
|
4172
|
+
max: 0,
|
|
4173
|
+
avg: 0,
|
|
4258
4174
|
};
|
|
4259
4175
|
}
|
|
4260
|
-
return
|
|
4176
|
+
return {
|
|
4177
|
+
min: Math.min(...values),
|
|
4178
|
+
max: Math.max(...values),
|
|
4179
|
+
avg: calcAverage(values),
|
|
4180
|
+
};
|
|
4261
4181
|
}
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
const isDragging = {
|
|
4267
|
-
x: false,
|
|
4268
|
-
y: false,
|
|
4269
|
-
};
|
|
4270
|
-
function isDragActive() {
|
|
4271
|
-
return isDragging.x || isDragging.y;
|
|
4182
|
+
const msToFps = (ms) => Math.round(1000 / ms);
|
|
4183
|
+
function clearStatsBuffer() {
|
|
4184
|
+
statsBuffer.value = null;
|
|
4185
|
+
statsBuffer.addProjectionMetrics = null;
|
|
4272
4186
|
}
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
if (
|
|
4276
|
-
|
|
4277
|
-
return null;
|
|
4278
|
-
}
|
|
4279
|
-
else {
|
|
4280
|
-
isDragging[axis] = true;
|
|
4281
|
-
return () => {
|
|
4282
|
-
isDragging[axis] = false;
|
|
4283
|
-
};
|
|
4284
|
-
}
|
|
4285
|
-
}
|
|
4286
|
-
else {
|
|
4287
|
-
if (isDragging.x || isDragging.y) {
|
|
4288
|
-
return null;
|
|
4289
|
-
}
|
|
4290
|
-
else {
|
|
4291
|
-
isDragging.x = isDragging.y = true;
|
|
4292
|
-
return () => {
|
|
4293
|
-
isDragging.x = isDragging.y = false;
|
|
4294
|
-
};
|
|
4295
|
-
}
|
|
4187
|
+
function reportStats() {
|
|
4188
|
+
const { value } = statsBuffer;
|
|
4189
|
+
if (!value) {
|
|
4190
|
+
throw new Error("Stats are not being measured");
|
|
4296
4191
|
}
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4192
|
+
clearStatsBuffer();
|
|
4193
|
+
cancelFrame(record);
|
|
4194
|
+
const summary = {
|
|
4195
|
+
frameloop: {
|
|
4196
|
+
setup: summarise(value.frameloop.setup),
|
|
4197
|
+
rate: summarise(value.frameloop.rate),
|
|
4198
|
+
read: summarise(value.frameloop.read),
|
|
4199
|
+
resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
|
|
4200
|
+
preUpdate: summarise(value.frameloop.preUpdate),
|
|
4201
|
+
update: summarise(value.frameloop.update),
|
|
4202
|
+
preRender: summarise(value.frameloop.preRender),
|
|
4203
|
+
render: summarise(value.frameloop.render),
|
|
4204
|
+
postRender: summarise(value.frameloop.postRender),
|
|
4205
|
+
},
|
|
4206
|
+
animations: {
|
|
4207
|
+
mainThread: summarise(value.animations.mainThread),
|
|
4208
|
+
waapi: summarise(value.animations.waapi),
|
|
4209
|
+
layout: summarise(value.animations.layout),
|
|
4210
|
+
},
|
|
4211
|
+
layoutProjection: {
|
|
4212
|
+
nodes: summarise(value.layoutProjection.nodes),
|
|
4213
|
+
calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
|
|
4214
|
+
calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
|
|
4215
|
+
},
|
|
4306
4216
|
};
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4217
|
+
/**
|
|
4218
|
+
* Convert the rate to FPS
|
|
4219
|
+
*/
|
|
4220
|
+
const { rate } = summary.frameloop;
|
|
4221
|
+
rate.min = msToFps(rate.min);
|
|
4222
|
+
rate.max = msToFps(rate.max);
|
|
4223
|
+
rate.avg = msToFps(rate.avg);
|
|
4224
|
+
[rate.min, rate.max] = [rate.max, rate.min];
|
|
4225
|
+
return summary;
|
|
4313
4226
|
}
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4227
|
+
function recordStats() {
|
|
4228
|
+
if (statsBuffer.value) {
|
|
4229
|
+
clearStatsBuffer();
|
|
4230
|
+
throw new Error("Stats are already being measured");
|
|
4231
|
+
}
|
|
4232
|
+
const newStatsBuffer = statsBuffer;
|
|
4233
|
+
newStatsBuffer.value = {
|
|
4234
|
+
frameloop: {
|
|
4235
|
+
setup: [],
|
|
4236
|
+
rate: [],
|
|
4237
|
+
read: [],
|
|
4238
|
+
resolveKeyframes: [],
|
|
4239
|
+
preUpdate: [],
|
|
4240
|
+
update: [],
|
|
4241
|
+
preRender: [],
|
|
4242
|
+
render: [],
|
|
4243
|
+
postRender: [],
|
|
4244
|
+
},
|
|
4245
|
+
animations: {
|
|
4246
|
+
mainThread: [],
|
|
4247
|
+
waapi: [],
|
|
4248
|
+
layout: [],
|
|
4249
|
+
},
|
|
4250
|
+
layoutProjection: {
|
|
4251
|
+
nodes: [],
|
|
4252
|
+
calculatedTargetDeltas: [],
|
|
4253
|
+
calculatedProjections: [],
|
|
4254
|
+
},
|
|
4337
4255
|
};
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4256
|
+
newStatsBuffer.addProjectionMetrics = (metrics) => {
|
|
4257
|
+
const { layoutProjection } = newStatsBuffer.value;
|
|
4258
|
+
layoutProjection.nodes.push(metrics.nodes);
|
|
4259
|
+
layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
|
|
4260
|
+
layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
|
|
4261
|
+
};
|
|
4262
|
+
frame.postRender(record, true);
|
|
4263
|
+
return reportStats;
|
|
4342
4264
|
}
|
|
4343
4265
|
|
|
4344
4266
|
/**
|
|
4345
|
-
*
|
|
4346
|
-
*
|
|
4347
|
-
*
|
|
4348
|
-
* @param parent - Element to find
|
|
4349
|
-
* @param child - Element to test against parent
|
|
4267
|
+
* Checks if an element is an SVG element in a way
|
|
4268
|
+
* that works across iframes
|
|
4350
4269
|
*/
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
return false;
|
|
4354
|
-
}
|
|
4355
|
-
else if (parent === child) {
|
|
4356
|
-
return true;
|
|
4357
|
-
}
|
|
4358
|
-
else {
|
|
4359
|
-
return isNodeOrChild(parent, child.parentElement);
|
|
4360
|
-
}
|
|
4361
|
-
};
|
|
4362
|
-
|
|
4363
|
-
const isPrimaryPointer = (event) => {
|
|
4364
|
-
if (event.pointerType === "mouse") {
|
|
4365
|
-
return typeof event.button !== "number" || event.button <= 0;
|
|
4366
|
-
}
|
|
4367
|
-
else {
|
|
4368
|
-
/**
|
|
4369
|
-
* isPrimary is true for all mice buttons, whereas every touch point
|
|
4370
|
-
* is regarded as its own input. So subsequent concurrent touch points
|
|
4371
|
-
* will be false.
|
|
4372
|
-
*
|
|
4373
|
-
* Specifically match against false here as incomplete versions of
|
|
4374
|
-
* PointerEvents in very old browser might have it set as undefined.
|
|
4375
|
-
*/
|
|
4376
|
-
return event.isPrimary !== false;
|
|
4377
|
-
}
|
|
4378
|
-
};
|
|
4379
|
-
|
|
4380
|
-
const focusableElements = new Set([
|
|
4381
|
-
"BUTTON",
|
|
4382
|
-
"INPUT",
|
|
4383
|
-
"SELECT",
|
|
4384
|
-
"TEXTAREA",
|
|
4385
|
-
"A",
|
|
4386
|
-
]);
|
|
4387
|
-
function isElementKeyboardAccessible(element) {
|
|
4388
|
-
return (focusableElements.has(element.tagName) ||
|
|
4389
|
-
element.tabIndex !== -1);
|
|
4270
|
+
function isSVGElement(element) {
|
|
4271
|
+
return isObject(element) && "ownerSVGElement" in element;
|
|
4390
4272
|
}
|
|
4391
4273
|
|
|
4392
|
-
const isPressing = new WeakSet();
|
|
4393
|
-
|
|
4394
4274
|
/**
|
|
4395
|
-
*
|
|
4275
|
+
* Checks if an element is specifically an SVGSVGElement (the root SVG element)
|
|
4276
|
+
* in a way that works across iframes
|
|
4396
4277
|
*/
|
|
4397
|
-
function
|
|
4398
|
-
return (
|
|
4399
|
-
if (event.key !== "Enter")
|
|
4400
|
-
return;
|
|
4401
|
-
callback(event);
|
|
4402
|
-
};
|
|
4278
|
+
function isSVGSVGElement(element) {
|
|
4279
|
+
return isSVGElement(element) && element.tagName === "svg";
|
|
4403
4280
|
}
|
|
4404
|
-
|
|
4405
|
-
|
|
4281
|
+
|
|
4282
|
+
function transform(...args) {
|
|
4283
|
+
const useImmediate = !Array.isArray(args[0]);
|
|
4284
|
+
const argOffset = useImmediate ? 0 : -1;
|
|
4285
|
+
const inputValue = args[0 + argOffset];
|
|
4286
|
+
const inputRange = args[1 + argOffset];
|
|
4287
|
+
const outputRange = args[2 + argOffset];
|
|
4288
|
+
const options = args[3 + argOffset];
|
|
4289
|
+
const interpolator = interpolate(inputRange, outputRange, options);
|
|
4290
|
+
return useImmediate ? interpolator(inputValue) : interpolator;
|
|
4406
4291
|
}
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
const
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
const handleKeyup = filterEvents(() => {
|
|
4416
|
-
firePointerEvent(element, "up");
|
|
4417
|
-
});
|
|
4418
|
-
const handleBlur = () => firePointerEvent(element, "cancel");
|
|
4419
|
-
element.addEventListener("keyup", handleKeyup, eventOptions);
|
|
4420
|
-
element.addEventListener("blur", handleBlur, eventOptions);
|
|
4292
|
+
|
|
4293
|
+
function subscribeValue(inputValues, outputValue, getLatest) {
|
|
4294
|
+
const update = () => outputValue.set(getLatest());
|
|
4295
|
+
const scheduleUpdate = () => frame.preRender(update, false, true);
|
|
4296
|
+
const subscriptions = inputValues.map((v) => v.on("change", scheduleUpdate));
|
|
4297
|
+
outputValue.on("destroy", () => {
|
|
4298
|
+
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
4299
|
+
cancelFrame(update);
|
|
4421
4300
|
});
|
|
4422
|
-
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4303
|
+
/**
|
|
4304
|
+
* Create a `MotionValue` that transforms the output of other `MotionValue`s by
|
|
4305
|
+
* passing their latest values through a transform function.
|
|
4306
|
+
*
|
|
4307
|
+
* Whenever a `MotionValue` referred to in the provided function is updated,
|
|
4308
|
+
* it will be re-evaluated.
|
|
4309
|
+
*
|
|
4310
|
+
* ```jsx
|
|
4311
|
+
* const x = motionValue(0)
|
|
4312
|
+
* const y = transformValue(() => x.get() * 2) // double x
|
|
4313
|
+
* ```
|
|
4314
|
+
*
|
|
4315
|
+
* @param transformer - A transform function. This function must be pure with no side-effects or conditional statements.
|
|
4316
|
+
* @returns `MotionValue`
|
|
4317
|
+
*
|
|
4318
|
+
* @public
|
|
4319
|
+
*/
|
|
4320
|
+
function transformValue(transform) {
|
|
4321
|
+
const collectedValues = [];
|
|
4423
4322
|
/**
|
|
4424
|
-
*
|
|
4323
|
+
* Open session of collectMotionValues. Any MotionValue that calls get()
|
|
4324
|
+
* inside transform will be saved into this array.
|
|
4425
4325
|
*/
|
|
4426
|
-
|
|
4427
|
-
|
|
4326
|
+
collectMotionValues.current = collectedValues;
|
|
4327
|
+
const initialValue = transform();
|
|
4328
|
+
collectMotionValues.current = undefined;
|
|
4329
|
+
const value = motionValue(initialValue);
|
|
4330
|
+
subscribeValue(collectedValues, value, transform);
|
|
4331
|
+
return value;
|
|
4332
|
+
}
|
|
4428
4333
|
|
|
4429
4334
|
/**
|
|
4430
|
-
*
|
|
4431
|
-
*
|
|
4335
|
+
* Create a `MotionValue` that maps the output of another `MotionValue` by
|
|
4336
|
+
* mapping it from one range of values into another.
|
|
4337
|
+
*
|
|
4338
|
+
* @remarks
|
|
4339
|
+
*
|
|
4340
|
+
* Given an input range of `[-200, -100, 100, 200]` and an output range of
|
|
4341
|
+
* `[0, 1, 1, 0]`, the returned `MotionValue` will:
|
|
4342
|
+
*
|
|
4343
|
+
* - When provided a value between `-200` and `-100`, will return a value between `0` and `1`.
|
|
4344
|
+
* - When provided a value between `-100` and `100`, will return `1`.
|
|
4345
|
+
* - When provided a value between `100` and `200`, will return a value between `1` and `0`
|
|
4346
|
+
*
|
|
4347
|
+
* The input range must be a linear series of numbers. The output range
|
|
4348
|
+
* can be any value type supported by Motion: numbers, colors, shadows, etc.
|
|
4349
|
+
*
|
|
4350
|
+
* Every value in the output range must be of the same type and in the same format.
|
|
4351
|
+
*
|
|
4352
|
+
* ```jsx
|
|
4353
|
+
* const x = motionValue(0)
|
|
4354
|
+
* const xRange = [-200, -100, 100, 200]
|
|
4355
|
+
* const opacityRange = [0, 1, 1, 0]
|
|
4356
|
+
* const opacity = mapValue(x, xRange, opacityRange)
|
|
4357
|
+
* ```
|
|
4358
|
+
*
|
|
4359
|
+
* @param inputValue - `MotionValue`
|
|
4360
|
+
* @param inputRange - A linear series of numbers (either all increasing or decreasing)
|
|
4361
|
+
* @param outputRange - A series of numbers, colors or strings. Must be the same length as `inputRange`.
|
|
4362
|
+
* @param options -
|
|
4363
|
+
*
|
|
4364
|
+
* - clamp: boolean. Clamp values to within the given range. Defaults to `true`
|
|
4365
|
+
* - ease: EasingFunction[]. Easing functions to use on the interpolations between each value in the input and output ranges. If provided as an array, the array must be one item shorter than the input and output ranges, as the easings apply to the transition between each.
|
|
4366
|
+
*
|
|
4367
|
+
* @returns `MotionValue`
|
|
4368
|
+
*
|
|
4369
|
+
* @public
|
|
4432
4370
|
*/
|
|
4433
|
-
function
|
|
4434
|
-
|
|
4371
|
+
function mapValue(inputValue, inputRange, outputRange, options) {
|
|
4372
|
+
const map = transform(inputRange, outputRange, options);
|
|
4373
|
+
return transformValue(() => map(inputValue.get()));
|
|
4435
4374
|
}
|
|
4375
|
+
|
|
4376
|
+
const isMotionValue = (value) => Boolean(value && value.getVelocity);
|
|
4377
|
+
|
|
4436
4378
|
/**
|
|
4437
|
-
* Create a
|
|
4438
|
-
*
|
|
4439
|
-
* Press is different to `"pointerdown"`, `"pointerup"` in that it
|
|
4440
|
-
* automatically filters out secondary pointer events like right
|
|
4441
|
-
* click and multitouch.
|
|
4379
|
+
* Create a `MotionValue` that animates to its latest value using a spring.
|
|
4380
|
+
* Can either be a value or track another `MotionValue`.
|
|
4442
4381
|
*
|
|
4443
|
-
*
|
|
4444
|
-
*
|
|
4445
|
-
*
|
|
4382
|
+
* ```jsx
|
|
4383
|
+
* const x = motionValue(0)
|
|
4384
|
+
* const y = transformValue(() => x.get() * 2) // double x
|
|
4385
|
+
* ```
|
|
4446
4386
|
*
|
|
4447
|
-
* This
|
|
4448
|
-
*
|
|
4449
|
-
* than the press start and end/cancel. The element also needs
|
|
4450
|
-
* to be focusable for this to work, whereas a press gesture will
|
|
4451
|
-
* make an element focusable by default.
|
|
4387
|
+
* @param transformer - A transform function. This function must be pure with no side-effects or conditional statements.
|
|
4388
|
+
* @returns `MotionValue`
|
|
4452
4389
|
*
|
|
4453
4390
|
* @public
|
|
4454
4391
|
*/
|
|
4455
|
-
function
|
|
4456
|
-
const
|
|
4457
|
-
const
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
onPressEnd(endEvent, { success });
|
|
4474
|
-
}
|
|
4475
|
-
};
|
|
4476
|
-
const onPointerUp = (upEvent) => {
|
|
4477
|
-
onPointerEnd(upEvent, target === window ||
|
|
4478
|
-
target === document ||
|
|
4479
|
-
options.useGlobalTarget ||
|
|
4480
|
-
isNodeOrChild(target, upEvent.target));
|
|
4481
|
-
};
|
|
4482
|
-
const onPointerCancel = (cancelEvent) => {
|
|
4483
|
-
onPointerEnd(cancelEvent, false);
|
|
4484
|
-
};
|
|
4485
|
-
window.addEventListener("pointerup", onPointerUp, eventOptions);
|
|
4486
|
-
window.addEventListener("pointercancel", onPointerCancel, eventOptions);
|
|
4487
|
-
};
|
|
4488
|
-
targets.forEach((target) => {
|
|
4489
|
-
const pointerDownTarget = options.useGlobalTarget ? window : target;
|
|
4490
|
-
pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
|
|
4491
|
-
if (target instanceof HTMLElement) {
|
|
4492
|
-
target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
|
|
4493
|
-
if (!isElementKeyboardAccessible(target) &&
|
|
4494
|
-
!target.hasAttribute("tabindex")) {
|
|
4495
|
-
target.tabIndex = 0;
|
|
4496
|
-
}
|
|
4392
|
+
function springValue(source, options) {
|
|
4393
|
+
const initialValue = isMotionValue(source) ? source.get() : source;
|
|
4394
|
+
const value = motionValue(initialValue);
|
|
4395
|
+
attachSpring(value, source, options);
|
|
4396
|
+
return value;
|
|
4397
|
+
}
|
|
4398
|
+
function attachSpring(value, source, options) {
|
|
4399
|
+
const initialValue = value.get();
|
|
4400
|
+
let activeAnimation = null;
|
|
4401
|
+
let latestValue = initialValue;
|
|
4402
|
+
let latestSetter;
|
|
4403
|
+
const unit = typeof initialValue === "string"
|
|
4404
|
+
? initialValue.replace(/[\d.-]/g, "")
|
|
4405
|
+
: undefined;
|
|
4406
|
+
const stopAnimation = () => {
|
|
4407
|
+
if (activeAnimation) {
|
|
4408
|
+
activeAnimation.stop();
|
|
4409
|
+
activeAnimation = null;
|
|
4497
4410
|
}
|
|
4498
|
-
}
|
|
4499
|
-
|
|
4411
|
+
};
|
|
4412
|
+
const startAnimation = () => {
|
|
4413
|
+
stopAnimation();
|
|
4414
|
+
activeAnimation = new JSAnimation({
|
|
4415
|
+
keyframes: [asNumber$1(value.get()), asNumber$1(latestValue)],
|
|
4416
|
+
velocity: value.getVelocity(),
|
|
4417
|
+
type: "spring",
|
|
4418
|
+
restDelta: 0.001,
|
|
4419
|
+
restSpeed: 0.01,
|
|
4420
|
+
...options,
|
|
4421
|
+
onUpdate: latestSetter,
|
|
4422
|
+
});
|
|
4423
|
+
};
|
|
4424
|
+
value.attach((v, set) => {
|
|
4425
|
+
latestValue = v;
|
|
4426
|
+
latestSetter = (latest) => set(parseValue(latest, unit));
|
|
4427
|
+
frame.postRender(startAnimation);
|
|
4428
|
+
return value.get();
|
|
4429
|
+
}, stopAnimation);
|
|
4430
|
+
let unsubscribe = undefined;
|
|
4431
|
+
if (isMotionValue(source)) {
|
|
4432
|
+
unsubscribe = source.on("change", (v) => value.set(parseValue(v, unit)));
|
|
4433
|
+
value.on("destroy", unsubscribe);
|
|
4434
|
+
}
|
|
4435
|
+
return unsubscribe;
|
|
4436
|
+
}
|
|
4437
|
+
function parseValue(v, unit) {
|
|
4438
|
+
return unit ? v + unit : v;
|
|
4439
|
+
}
|
|
4440
|
+
function asNumber$1(v) {
|
|
4441
|
+
return typeof v === "number" ? v : parseFloat(v);
|
|
4500
4442
|
}
|
|
4501
4443
|
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4444
|
+
/**
|
|
4445
|
+
* A list of all ValueTypes
|
|
4446
|
+
*/
|
|
4447
|
+
const valueTypes = [...dimensionValueTypes, color, complex];
|
|
4448
|
+
/**
|
|
4449
|
+
* Tests a value against the list of ValueTypes
|
|
4450
|
+
*/
|
|
4451
|
+
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
4452
|
+
|
|
4453
|
+
function chooseLayerType(valueName) {
|
|
4454
|
+
if (valueName === "layout")
|
|
4455
|
+
return "group";
|
|
4456
|
+
if (valueName === "enter" || valueName === "new")
|
|
4457
|
+
return "new";
|
|
4458
|
+
if (valueName === "exit" || valueName === "old")
|
|
4459
|
+
return "old";
|
|
4460
|
+
return "group";
|
|
4507
4461
|
}
|
|
4508
4462
|
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4463
|
+
let pendingRules = {};
|
|
4464
|
+
let style = null;
|
|
4465
|
+
const css = {
|
|
4466
|
+
set: (selector, values) => {
|
|
4467
|
+
pendingRules[selector] = values;
|
|
4468
|
+
},
|
|
4469
|
+
commit: () => {
|
|
4470
|
+
if (!style) {
|
|
4471
|
+
style = document.createElement("style");
|
|
4472
|
+
style.id = "motion-view";
|
|
4517
4473
|
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4474
|
+
let cssText = "";
|
|
4475
|
+
for (const selector in pendingRules) {
|
|
4476
|
+
const rule = pendingRules[selector];
|
|
4477
|
+
cssText += `${selector} {\n`;
|
|
4478
|
+
for (const [property, value] of Object.entries(rule)) {
|
|
4479
|
+
cssText += ` ${property}: ${value};\n`;
|
|
4480
|
+
}
|
|
4481
|
+
cssText += "}\n";
|
|
4482
|
+
}
|
|
4483
|
+
style.textContent = cssText;
|
|
4484
|
+
document.head.appendChild(style);
|
|
4485
|
+
pendingRules = {};
|
|
4486
|
+
},
|
|
4487
|
+
remove: () => {
|
|
4488
|
+
if (style && style.parentElement) {
|
|
4489
|
+
style.parentElement.removeChild(style);
|
|
4490
|
+
}
|
|
4491
|
+
},
|
|
4492
|
+
};
|
|
4523
4493
|
|
|
4524
|
-
function
|
|
4525
|
-
const
|
|
4526
|
-
if (
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
}
|
|
4530
|
-
value.frameloop.rate.push(frameData.delta);
|
|
4531
|
-
value.animations.mainThread.push(activeAnimations.mainThread);
|
|
4532
|
-
value.animations.waapi.push(activeAnimations.waapi);
|
|
4533
|
-
value.animations.layout.push(activeAnimations.layout);
|
|
4494
|
+
function getLayerName(pseudoElement) {
|
|
4495
|
+
const match = pseudoElement.match(/::view-transition-(old|new|group|image-pair)\((.*?)\)/);
|
|
4496
|
+
if (!match)
|
|
4497
|
+
return null;
|
|
4498
|
+
return { layer: match[2], type: match[1] };
|
|
4534
4499
|
}
|
|
4535
|
-
|
|
4536
|
-
|
|
4500
|
+
|
|
4501
|
+
function filterViewAnimations(animation) {
|
|
4502
|
+
const { effect } = animation;
|
|
4503
|
+
if (!effect)
|
|
4504
|
+
return false;
|
|
4505
|
+
return (effect.target === document.documentElement &&
|
|
4506
|
+
effect.pseudoElement?.startsWith("::view-transition"));
|
|
4537
4507
|
}
|
|
4538
|
-
function
|
|
4539
|
-
|
|
4540
|
-
return {
|
|
4541
|
-
min: 0,
|
|
4542
|
-
max: 0,
|
|
4543
|
-
avg: 0,
|
|
4544
|
-
};
|
|
4545
|
-
}
|
|
4546
|
-
return {
|
|
4547
|
-
min: Math.min(...values),
|
|
4548
|
-
max: Math.max(...values),
|
|
4549
|
-
avg: calcAverage(values),
|
|
4550
|
-
};
|
|
4508
|
+
function getViewAnimations() {
|
|
4509
|
+
return document.getAnimations().filter(filterViewAnimations);
|
|
4551
4510
|
}
|
|
4552
|
-
|
|
4553
|
-
function
|
|
4554
|
-
|
|
4555
|
-
statsBuffer.addProjectionMetrics = null;
|
|
4511
|
+
|
|
4512
|
+
function hasTarget(target, targets) {
|
|
4513
|
+
return targets.has(target) && Object.keys(targets.get(target)).length > 0;
|
|
4556
4514
|
}
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4515
|
+
|
|
4516
|
+
const definitionNames = ["layout", "enter", "exit", "new", "old"];
|
|
4517
|
+
function startViewAnimation(builder) {
|
|
4518
|
+
const { update, targets, options: defaultOptions } = builder;
|
|
4519
|
+
if (!document.startViewTransition) {
|
|
4520
|
+
return new Promise(async (resolve) => {
|
|
4521
|
+
await update();
|
|
4522
|
+
resolve(new GroupAnimation([]));
|
|
4523
|
+
});
|
|
4561
4524
|
}
|
|
4562
|
-
|
|
4563
|
-
cancelFrame(record);
|
|
4564
|
-
const summary = {
|
|
4565
|
-
frameloop: {
|
|
4566
|
-
setup: summarise(value.frameloop.setup),
|
|
4567
|
-
rate: summarise(value.frameloop.rate),
|
|
4568
|
-
read: summarise(value.frameloop.read),
|
|
4569
|
-
resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
|
|
4570
|
-
preUpdate: summarise(value.frameloop.preUpdate),
|
|
4571
|
-
update: summarise(value.frameloop.update),
|
|
4572
|
-
preRender: summarise(value.frameloop.preRender),
|
|
4573
|
-
render: summarise(value.frameloop.render),
|
|
4574
|
-
postRender: summarise(value.frameloop.postRender),
|
|
4575
|
-
},
|
|
4576
|
-
animations: {
|
|
4577
|
-
mainThread: summarise(value.animations.mainThread),
|
|
4578
|
-
waapi: summarise(value.animations.waapi),
|
|
4579
|
-
layout: summarise(value.animations.layout),
|
|
4580
|
-
},
|
|
4581
|
-
layoutProjection: {
|
|
4582
|
-
nodes: summarise(value.layoutProjection.nodes),
|
|
4583
|
-
calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
|
|
4584
|
-
calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
|
|
4585
|
-
},
|
|
4586
|
-
};
|
|
4525
|
+
// TODO: Go over existing targets and ensure they all have ids
|
|
4587
4526
|
/**
|
|
4588
|
-
*
|
|
4527
|
+
* If we don't have any animations defined for the root target,
|
|
4528
|
+
* remove it from being captured.
|
|
4589
4529
|
*/
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4530
|
+
if (!hasTarget("root", targets)) {
|
|
4531
|
+
css.set(":root", {
|
|
4532
|
+
"view-transition-name": "none",
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
/**
|
|
4536
|
+
* Set the timing curve to linear for all view transition layers.
|
|
4537
|
+
* This gets baked into the keyframes, which can't be changed
|
|
4538
|
+
* without breaking the generated animation.
|
|
4539
|
+
*
|
|
4540
|
+
* This allows us to set easing via updateTiming - which can be changed.
|
|
4541
|
+
*/
|
|
4542
|
+
css.set("::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*)", { "animation-timing-function": "linear !important" });
|
|
4543
|
+
css.commit(); // Write
|
|
4544
|
+
const transition = document.startViewTransition(async () => {
|
|
4545
|
+
await update();
|
|
4546
|
+
// TODO: Go over new targets and ensure they all have ids
|
|
4547
|
+
});
|
|
4548
|
+
transition.finished.finally(() => {
|
|
4549
|
+
css.remove(); // Write
|
|
4550
|
+
});
|
|
4551
|
+
return new Promise((resolve) => {
|
|
4552
|
+
transition.ready.then(() => {
|
|
4553
|
+
const generatedViewAnimations = getViewAnimations();
|
|
4554
|
+
const animations = [];
|
|
4555
|
+
/**
|
|
4556
|
+
* Create animations for each of our explicitly-defined subjects.
|
|
4557
|
+
*/
|
|
4558
|
+
targets.forEach((definition, target) => {
|
|
4559
|
+
// TODO: If target is not "root", resolve elements
|
|
4560
|
+
// and iterate over each
|
|
4561
|
+
for (const key of definitionNames) {
|
|
4562
|
+
if (!definition[key])
|
|
4563
|
+
continue;
|
|
4564
|
+
const { keyframes, options } = definition[key];
|
|
4565
|
+
for (let [valueName, valueKeyframes] of Object.entries(keyframes)) {
|
|
4566
|
+
if (!valueKeyframes)
|
|
4567
|
+
continue;
|
|
4568
|
+
const valueOptions = {
|
|
4569
|
+
...getValueTransition$1(defaultOptions, valueName),
|
|
4570
|
+
...getValueTransition$1(options, valueName),
|
|
4571
|
+
};
|
|
4572
|
+
const type = chooseLayerType(key);
|
|
4573
|
+
/**
|
|
4574
|
+
* If this is an opacity animation, and keyframes are not an array,
|
|
4575
|
+
* we need to convert them into an array and set an initial value.
|
|
4576
|
+
*/
|
|
4577
|
+
if (valueName === "opacity" &&
|
|
4578
|
+
!Array.isArray(valueKeyframes)) {
|
|
4579
|
+
const initialValue = type === "new" ? 0 : 1;
|
|
4580
|
+
valueKeyframes = [initialValue, valueKeyframes];
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Resolve stagger function if provided.
|
|
4584
|
+
*/
|
|
4585
|
+
if (typeof valueOptions.delay === "function") {
|
|
4586
|
+
valueOptions.delay = valueOptions.delay(0, 1);
|
|
4587
|
+
}
|
|
4588
|
+
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
|
|
4589
|
+
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
|
|
4590
|
+
const animation = new NativeAnimation({
|
|
4591
|
+
...valueOptions,
|
|
4592
|
+
element: document.documentElement,
|
|
4593
|
+
name: valueName,
|
|
4594
|
+
pseudoElement: `::view-transition-${type}(${target})`,
|
|
4595
|
+
keyframes: valueKeyframes,
|
|
4596
|
+
});
|
|
4597
|
+
animations.push(animation);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
});
|
|
4601
|
+
/**
|
|
4602
|
+
* Handle browser generated animations
|
|
4603
|
+
*/
|
|
4604
|
+
for (const animation of generatedViewAnimations) {
|
|
4605
|
+
if (animation.playState === "finished")
|
|
4606
|
+
continue;
|
|
4607
|
+
const { effect } = animation;
|
|
4608
|
+
if (!effect || !(effect instanceof KeyframeEffect))
|
|
4609
|
+
continue;
|
|
4610
|
+
const { pseudoElement } = effect;
|
|
4611
|
+
if (!pseudoElement)
|
|
4612
|
+
continue;
|
|
4613
|
+
const name = getLayerName(pseudoElement);
|
|
4614
|
+
if (!name)
|
|
4615
|
+
continue;
|
|
4616
|
+
const targetDefinition = targets.get(name.layer);
|
|
4617
|
+
if (!targetDefinition) {
|
|
4618
|
+
/**
|
|
4619
|
+
* If transition name is group then update the timing of the animation
|
|
4620
|
+
* whereas if it's old or new then we could possibly replace it using
|
|
4621
|
+
* the above method.
|
|
4622
|
+
*/
|
|
4623
|
+
const transitionName = name.type === "group" ? "layout" : "";
|
|
4624
|
+
let animationTransition = {
|
|
4625
|
+
...getValueTransition$1(defaultOptions, transitionName),
|
|
4626
|
+
};
|
|
4627
|
+
animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
|
|
4628
|
+
animationTransition =
|
|
4629
|
+
applyGeneratorOptions(animationTransition);
|
|
4630
|
+
const easing = mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration);
|
|
4631
|
+
effect.updateTiming({
|
|
4632
|
+
delay: secondsToMilliseconds(animationTransition.delay ?? 0),
|
|
4633
|
+
duration: animationTransition.duration,
|
|
4634
|
+
easing,
|
|
4635
|
+
});
|
|
4636
|
+
animations.push(new NativeAnimationWrapper(animation));
|
|
4637
|
+
}
|
|
4638
|
+
else if (hasOpacity(targetDefinition, "enter") &&
|
|
4639
|
+
hasOpacity(targetDefinition, "exit") &&
|
|
4640
|
+
effect
|
|
4641
|
+
.getKeyframes()
|
|
4642
|
+
.some((keyframe) => keyframe.mixBlendMode)) {
|
|
4643
|
+
animations.push(new NativeAnimationWrapper(animation));
|
|
4644
|
+
}
|
|
4645
|
+
else {
|
|
4646
|
+
animation.cancel();
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
resolve(new GroupAnimation(animations));
|
|
4650
|
+
});
|
|
4651
|
+
});
|
|
4596
4652
|
}
|
|
4597
|
-
function
|
|
4598
|
-
|
|
4599
|
-
clearStatsBuffer();
|
|
4600
|
-
throw new Error("Stats are already being measured");
|
|
4601
|
-
}
|
|
4602
|
-
const newStatsBuffer = statsBuffer;
|
|
4603
|
-
newStatsBuffer.value = {
|
|
4604
|
-
frameloop: {
|
|
4605
|
-
setup: [],
|
|
4606
|
-
rate: [],
|
|
4607
|
-
read: [],
|
|
4608
|
-
resolveKeyframes: [],
|
|
4609
|
-
preUpdate: [],
|
|
4610
|
-
update: [],
|
|
4611
|
-
preRender: [],
|
|
4612
|
-
render: [],
|
|
4613
|
-
postRender: [],
|
|
4614
|
-
},
|
|
4615
|
-
animations: {
|
|
4616
|
-
mainThread: [],
|
|
4617
|
-
waapi: [],
|
|
4618
|
-
layout: [],
|
|
4619
|
-
},
|
|
4620
|
-
layoutProjection: {
|
|
4621
|
-
nodes: [],
|
|
4622
|
-
calculatedTargetDeltas: [],
|
|
4623
|
-
calculatedProjections: [],
|
|
4624
|
-
},
|
|
4625
|
-
};
|
|
4626
|
-
newStatsBuffer.addProjectionMetrics = (metrics) => {
|
|
4627
|
-
const { layoutProjection } = newStatsBuffer.value;
|
|
4628
|
-
layoutProjection.nodes.push(metrics.nodes);
|
|
4629
|
-
layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
|
|
4630
|
-
layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
|
|
4631
|
-
};
|
|
4632
|
-
frame.postRender(record, true);
|
|
4633
|
-
return reportStats;
|
|
4653
|
+
function hasOpacity(target, key) {
|
|
4654
|
+
return target?.[key]?.keyframes.opacity;
|
|
4634
4655
|
}
|
|
4635
4656
|
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
const
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
const interpolator = interpolate(inputRange, outputRange, options);
|
|
4644
|
-
return useImmediate ? interpolator(inputValue) : interpolator;
|
|
4657
|
+
let builders = [];
|
|
4658
|
+
let current = null;
|
|
4659
|
+
function next() {
|
|
4660
|
+
current = null;
|
|
4661
|
+
const [nextBuilder] = builders;
|
|
4662
|
+
if (nextBuilder)
|
|
4663
|
+
start(nextBuilder);
|
|
4645
4664
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
4653
|
-
cancelFrame(update);
|
|
4665
|
+
function start(builder) {
|
|
4666
|
+
removeItem(builders, builder);
|
|
4667
|
+
current = builder;
|
|
4668
|
+
startViewAnimation(builder).then((animation) => {
|
|
4669
|
+
builder.notifyReady(animation);
|
|
4670
|
+
animation.finished.finally(next);
|
|
4654
4671
|
});
|
|
4655
4672
|
}
|
|
4656
|
-
|
|
4657
|
-
/**
|
|
4658
|
-
* Create a `MotionValue` that transforms the output of other `MotionValue`s by
|
|
4659
|
-
* passing their latest values through a transform function.
|
|
4660
|
-
*
|
|
4661
|
-
* Whenever a `MotionValue` referred to in the provided function is updated,
|
|
4662
|
-
* it will be re-evaluated.
|
|
4663
|
-
*
|
|
4664
|
-
* ```jsx
|
|
4665
|
-
* const x = motionValue(0)
|
|
4666
|
-
* const y = transformValue(() => x.get() * 2) // double x
|
|
4667
|
-
* ```
|
|
4668
|
-
*
|
|
4669
|
-
* @param transformer - A transform function. This function must be pure with no side-effects or conditional statements.
|
|
4670
|
-
* @returns `MotionValue`
|
|
4671
|
-
*
|
|
4672
|
-
* @public
|
|
4673
|
-
*/
|
|
4674
|
-
function transformValue(transform) {
|
|
4675
|
-
const collectedValues = [];
|
|
4673
|
+
function processQueue() {
|
|
4676
4674
|
/**
|
|
4677
|
-
*
|
|
4678
|
-
*
|
|
4675
|
+
* Iterate backwards over the builders array. We can ignore the
|
|
4676
|
+
* "wait" animations. If we have an interrupting animation in the
|
|
4677
|
+
* queue then we need to batch all preceeding animations into it.
|
|
4678
|
+
* Currently this only batches the update functions but will also
|
|
4679
|
+
* need to batch the targets.
|
|
4679
4680
|
*/
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
* @remarks
|
|
4693
|
-
*
|
|
4694
|
-
* Given an input range of `[-200, -100, 100, 200]` and an output range of
|
|
4695
|
-
* `[0, 1, 1, 0]`, the returned `MotionValue` will:
|
|
4696
|
-
*
|
|
4697
|
-
* - When provided a value between `-200` and `-100`, will return a value between `0` and `1`.
|
|
4698
|
-
* - When provided a value between `-100` and `100`, will return `1`.
|
|
4699
|
-
* - When provided a value between `100` and `200`, will return a value between `1` and `0`
|
|
4700
|
-
*
|
|
4701
|
-
* The input range must be a linear series of numbers. The output range
|
|
4702
|
-
* can be any value type supported by Motion: numbers, colors, shadows, etc.
|
|
4703
|
-
*
|
|
4704
|
-
* Every value in the output range must be of the same type and in the same format.
|
|
4705
|
-
*
|
|
4706
|
-
* ```jsx
|
|
4707
|
-
* const x = motionValue(0)
|
|
4708
|
-
* const xRange = [-200, -100, 100, 200]
|
|
4709
|
-
* const opacityRange = [0, 1, 1, 0]
|
|
4710
|
-
* const opacity = mapValue(x, xRange, opacityRange)
|
|
4711
|
-
* ```
|
|
4712
|
-
*
|
|
4713
|
-
* @param inputValue - `MotionValue`
|
|
4714
|
-
* @param inputRange - A linear series of numbers (either all increasing or decreasing)
|
|
4715
|
-
* @param outputRange - A series of numbers, colors or strings. Must be the same length as `inputRange`.
|
|
4716
|
-
* @param options -
|
|
4717
|
-
*
|
|
4718
|
-
* - clamp: boolean. Clamp values to within the given range. Defaults to `true`
|
|
4719
|
-
* - ease: EasingFunction[]. Easing functions to use on the interpolations between each value in the input and output ranges. If provided as an array, the array must be one item shorter than the input and output ranges, as the easings apply to the transition between each.
|
|
4720
|
-
*
|
|
4721
|
-
* @returns `MotionValue`
|
|
4722
|
-
*
|
|
4723
|
-
* @public
|
|
4724
|
-
*/
|
|
4725
|
-
function mapValue(inputValue, inputRange, outputRange, options) {
|
|
4726
|
-
const map = transform(inputRange, outputRange, options);
|
|
4727
|
-
return transformValue(() => map(inputValue.get()));
|
|
4728
|
-
}
|
|
4729
|
-
|
|
4730
|
-
const isMotionValue = (value) => Boolean(value && value.getVelocity);
|
|
4731
|
-
|
|
4732
|
-
/**
|
|
4733
|
-
* Create a `MotionValue` that animates to its latest value using a spring.
|
|
4734
|
-
* Can either be a value or track another `MotionValue`.
|
|
4735
|
-
*
|
|
4736
|
-
* ```jsx
|
|
4737
|
-
* const x = motionValue(0)
|
|
4738
|
-
* const y = transformValue(() => x.get() * 2) // double x
|
|
4739
|
-
* ```
|
|
4740
|
-
*
|
|
4741
|
-
* @param transformer - A transform function. This function must be pure with no side-effects or conditional statements.
|
|
4742
|
-
* @returns `MotionValue`
|
|
4743
|
-
*
|
|
4744
|
-
* @public
|
|
4745
|
-
*/
|
|
4746
|
-
function springValue(source, options) {
|
|
4747
|
-
const initialValue = isMotionValue(source) ? source.get() : source;
|
|
4748
|
-
const value = motionValue(initialValue);
|
|
4749
|
-
attachSpring(value, source, options);
|
|
4750
|
-
return value;
|
|
4751
|
-
}
|
|
4752
|
-
function attachSpring(value, source, options) {
|
|
4753
|
-
const initialValue = value.get();
|
|
4754
|
-
let activeAnimation = null;
|
|
4755
|
-
let latestValue = initialValue;
|
|
4756
|
-
let latestSetter;
|
|
4757
|
-
const unit = typeof initialValue === "string"
|
|
4758
|
-
? initialValue.replace(/[\d.-]/g, "")
|
|
4759
|
-
: undefined;
|
|
4760
|
-
const stopAnimation = () => {
|
|
4761
|
-
if (activeAnimation) {
|
|
4762
|
-
activeAnimation.stop();
|
|
4763
|
-
activeAnimation = null;
|
|
4681
|
+
for (let i = builders.length - 1; i >= 0; i--) {
|
|
4682
|
+
const builder = builders[i];
|
|
4683
|
+
const { interrupt } = builder.options;
|
|
4684
|
+
if (interrupt === "immediate") {
|
|
4685
|
+
const batchedUpdates = builders.slice(0, i + 1).map((b) => b.update);
|
|
4686
|
+
const remaining = builders.slice(i + 1);
|
|
4687
|
+
builder.update = () => {
|
|
4688
|
+
batchedUpdates.forEach((update) => update());
|
|
4689
|
+
};
|
|
4690
|
+
// Put the current builder at the front, followed by any "wait" builders
|
|
4691
|
+
builders = [builder, ...remaining];
|
|
4692
|
+
break;
|
|
4764
4693
|
}
|
|
4765
|
-
};
|
|
4766
|
-
const startAnimation = () => {
|
|
4767
|
-
stopAnimation();
|
|
4768
|
-
activeAnimation = new JSAnimation({
|
|
4769
|
-
keyframes: [asNumber$1(value.get()), asNumber$1(latestValue)],
|
|
4770
|
-
velocity: value.getVelocity(),
|
|
4771
|
-
type: "spring",
|
|
4772
|
-
restDelta: 0.001,
|
|
4773
|
-
restSpeed: 0.01,
|
|
4774
|
-
...options,
|
|
4775
|
-
onUpdate: latestSetter,
|
|
4776
|
-
});
|
|
4777
|
-
};
|
|
4778
|
-
value.attach((v, set) => {
|
|
4779
|
-
latestValue = v;
|
|
4780
|
-
latestSetter = (latest) => set(parseValue(latest, unit));
|
|
4781
|
-
frame.postRender(startAnimation);
|
|
4782
|
-
return value.get();
|
|
4783
|
-
}, stopAnimation);
|
|
4784
|
-
let unsubscribe = undefined;
|
|
4785
|
-
if (isMotionValue(source)) {
|
|
4786
|
-
unsubscribe = source.on("change", (v) => value.set(parseValue(v, unit)));
|
|
4787
|
-
value.on("destroy", unsubscribe);
|
|
4788
4694
|
}
|
|
4789
|
-
|
|
4695
|
+
if (!current || builders[0]?.options.interrupt === "immediate") {
|
|
4696
|
+
next();
|
|
4697
|
+
}
|
|
4790
4698
|
}
|
|
4791
|
-
function
|
|
4792
|
-
|
|
4699
|
+
function addToQueue(builder) {
|
|
4700
|
+
builders.push(builder);
|
|
4701
|
+
microtask.render(processQueue);
|
|
4793
4702
|
}
|
|
4794
|
-
|
|
4795
|
-
|
|
4703
|
+
|
|
4704
|
+
class ViewTransitionBuilder {
|
|
4705
|
+
constructor(update, options = {}) {
|
|
4706
|
+
this.currentTarget = "root";
|
|
4707
|
+
this.targets = new Map();
|
|
4708
|
+
this.notifyReady = noop;
|
|
4709
|
+
this.readyPromise = new Promise((resolve) => {
|
|
4710
|
+
this.notifyReady = resolve;
|
|
4711
|
+
});
|
|
4712
|
+
this.update = update;
|
|
4713
|
+
this.options = {
|
|
4714
|
+
interrupt: "wait",
|
|
4715
|
+
...options,
|
|
4716
|
+
};
|
|
4717
|
+
addToQueue(this);
|
|
4718
|
+
}
|
|
4719
|
+
get(selector) {
|
|
4720
|
+
this.currentTarget = selector;
|
|
4721
|
+
return this;
|
|
4722
|
+
}
|
|
4723
|
+
layout(keyframes, options) {
|
|
4724
|
+
this.updateTarget("layout", keyframes, options);
|
|
4725
|
+
return this;
|
|
4726
|
+
}
|
|
4727
|
+
new(keyframes, options) {
|
|
4728
|
+
this.updateTarget("new", keyframes, options);
|
|
4729
|
+
return this;
|
|
4730
|
+
}
|
|
4731
|
+
old(keyframes, options) {
|
|
4732
|
+
this.updateTarget("old", keyframes, options);
|
|
4733
|
+
return this;
|
|
4734
|
+
}
|
|
4735
|
+
enter(keyframes, options) {
|
|
4736
|
+
this.updateTarget("enter", keyframes, options);
|
|
4737
|
+
return this;
|
|
4738
|
+
}
|
|
4739
|
+
exit(keyframes, options) {
|
|
4740
|
+
this.updateTarget("exit", keyframes, options);
|
|
4741
|
+
return this;
|
|
4742
|
+
}
|
|
4743
|
+
crossfade(options) {
|
|
4744
|
+
this.updateTarget("enter", { opacity: 1 }, options);
|
|
4745
|
+
this.updateTarget("exit", { opacity: 0 }, options);
|
|
4746
|
+
return this;
|
|
4747
|
+
}
|
|
4748
|
+
updateTarget(target, keyframes, options = {}) {
|
|
4749
|
+
const { currentTarget, targets } = this;
|
|
4750
|
+
if (!targets.has(currentTarget)) {
|
|
4751
|
+
targets.set(currentTarget, {});
|
|
4752
|
+
}
|
|
4753
|
+
const targetData = targets.get(currentTarget);
|
|
4754
|
+
targetData[target] = { keyframes, options };
|
|
4755
|
+
}
|
|
4756
|
+
then(resolve, reject) {
|
|
4757
|
+
return this.readyPromise.then(resolve, reject);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
function animateView(update, defaultOptions = {}) {
|
|
4761
|
+
return new ViewTransitionBuilder(update, defaultOptions);
|
|
4796
4762
|
}
|
|
4797
4763
|
|
|
4798
4764
|
/**
|
|
4799
|
-
*
|
|
4765
|
+
* @deprecated
|
|
4766
|
+
*
|
|
4767
|
+
* Import as `frame` instead.
|
|
4800
4768
|
*/
|
|
4801
|
-
const
|
|
4769
|
+
const sync = frame;
|
|
4802
4770
|
/**
|
|
4803
|
-
*
|
|
4771
|
+
* @deprecated
|
|
4772
|
+
*
|
|
4773
|
+
* Use cancelFrame(callback) instead.
|
|
4804
4774
|
*/
|
|
4805
|
-
const
|
|
4775
|
+
const cancelSync = stepsOrder.reduce((acc, key) => {
|
|
4776
|
+
acc[key] = (process) => cancelFrame(process);
|
|
4777
|
+
return acc;
|
|
4778
|
+
}, {});
|
|
4806
4779
|
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
}
|
|
4780
|
+
/**
|
|
4781
|
+
* @public
|
|
4782
|
+
*/
|
|
4783
|
+
const MotionConfigContext = React$1.createContext({
|
|
4784
|
+
transformPagePoint: (p) => p,
|
|
4785
|
+
isStatic: false,
|
|
4786
|
+
reducedMotion: "never",
|
|
4787
|
+
});
|
|
4816
4788
|
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
if (!
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
cssText += "}\n";
|
|
4836
|
-
}
|
|
4837
|
-
style.textContent = cssText;
|
|
4838
|
-
document.head.appendChild(style);
|
|
4839
|
-
pendingRules = {};
|
|
4840
|
-
},
|
|
4841
|
-
remove: () => {
|
|
4842
|
-
if (style && style.parentElement) {
|
|
4843
|
-
style.parentElement.removeChild(style);
|
|
4789
|
+
/**
|
|
4790
|
+
* Measurement functionality has to be within a separate component
|
|
4791
|
+
* to leverage snapshot lifecycle.
|
|
4792
|
+
*/
|
|
4793
|
+
class PopChildMeasure extends React__namespace.Component {
|
|
4794
|
+
getSnapshotBeforeUpdate(prevProps) {
|
|
4795
|
+
const element = this.props.childRef.current;
|
|
4796
|
+
if (element && prevProps.isPresent && !this.props.isPresent) {
|
|
4797
|
+
const parent = element.offsetParent;
|
|
4798
|
+
const parentWidth = isHTMLElement(parent)
|
|
4799
|
+
? parent.offsetWidth || 0
|
|
4800
|
+
: 0;
|
|
4801
|
+
const size = this.props.sizeRef.current;
|
|
4802
|
+
size.height = element.offsetHeight || 0;
|
|
4803
|
+
size.width = element.offsetWidth || 0;
|
|
4804
|
+
size.top = element.offsetTop;
|
|
4805
|
+
size.left = element.offsetLeft;
|
|
4806
|
+
size.right = parentWidth - size.width - size.left;
|
|
4844
4807
|
}
|
|
4845
|
-
},
|
|
4846
|
-
};
|
|
4847
|
-
|
|
4848
|
-
function getLayerName(pseudoElement) {
|
|
4849
|
-
const match = pseudoElement.match(/::view-transition-(old|new|group|image-pair)\((.*?)\)/);
|
|
4850
|
-
if (!match)
|
|
4851
4808
|
return null;
|
|
4852
|
-
return { layer: match[2], type: match[1] };
|
|
4853
|
-
}
|
|
4854
|
-
|
|
4855
|
-
function filterViewAnimations(animation) {
|
|
4856
|
-
const { effect } = animation;
|
|
4857
|
-
if (!effect)
|
|
4858
|
-
return false;
|
|
4859
|
-
return (effect.target === document.documentElement &&
|
|
4860
|
-
effect.pseudoElement?.startsWith("::view-transition"));
|
|
4861
|
-
}
|
|
4862
|
-
function getViewAnimations() {
|
|
4863
|
-
return document.getAnimations().filter(filterViewAnimations);
|
|
4864
|
-
}
|
|
4865
|
-
|
|
4866
|
-
function hasTarget(target, targets) {
|
|
4867
|
-
return targets.has(target) && Object.keys(targets.get(target)).length > 0;
|
|
4868
|
-
}
|
|
4869
|
-
|
|
4870
|
-
const definitionNames = ["layout", "enter", "exit", "new", "old"];
|
|
4871
|
-
function startViewAnimation(builder) {
|
|
4872
|
-
const { update, targets, options: defaultOptions } = builder;
|
|
4873
|
-
if (!document.startViewTransition) {
|
|
4874
|
-
return new Promise(async (resolve) => {
|
|
4875
|
-
await update();
|
|
4876
|
-
resolve(new GroupAnimation([]));
|
|
4877
|
-
});
|
|
4878
4809
|
}
|
|
4879
|
-
// TODO: Go over existing targets and ensure they all have ids
|
|
4880
4810
|
/**
|
|
4881
|
-
*
|
|
4882
|
-
|
|
4811
|
+
* Required with getSnapshotBeforeUpdate to stop React complaining.
|
|
4812
|
+
*/
|
|
4813
|
+
componentDidUpdate() { }
|
|
4814
|
+
render() {
|
|
4815
|
+
return this.props.children;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
function PopChild({ children, isPresent, anchorX }) {
|
|
4819
|
+
const id = React$1.useId();
|
|
4820
|
+
const ref = React$1.useRef(null);
|
|
4821
|
+
const size = React$1.useRef({
|
|
4822
|
+
width: 0,
|
|
4823
|
+
height: 0,
|
|
4824
|
+
top: 0,
|
|
4825
|
+
left: 0,
|
|
4826
|
+
right: 0,
|
|
4827
|
+
});
|
|
4828
|
+
const { nonce } = React$1.useContext(MotionConfigContext);
|
|
4829
|
+
/**
|
|
4830
|
+
* We create and inject a style block so we can apply this explicit
|
|
4831
|
+
* sizing in a non-destructive manner by just deleting the style block.
|
|
4832
|
+
*
|
|
4833
|
+
* We can't apply size via render as the measurement happens
|
|
4834
|
+
* in getSnapshotBeforeUpdate (post-render), likewise if we apply the
|
|
4835
|
+
* styles directly on the DOM node, we might be overwriting
|
|
4836
|
+
* styles set via the style prop.
|
|
4837
|
+
*/
|
|
4838
|
+
React$1.useInsertionEffect(() => {
|
|
4839
|
+
const { width, height, top, left, right } = size.current;
|
|
4840
|
+
if (isPresent || !ref.current || !width || !height)
|
|
4841
|
+
return;
|
|
4842
|
+
const x = anchorX === "left" ? `left: ${left}` : `right: ${right}`;
|
|
4843
|
+
ref.current.dataset.motionPopId = id;
|
|
4844
|
+
const style = document.createElement("style");
|
|
4845
|
+
if (nonce)
|
|
4846
|
+
style.nonce = nonce;
|
|
4847
|
+
document.head.appendChild(style);
|
|
4848
|
+
if (style.sheet) {
|
|
4849
|
+
style.sheet.insertRule(`
|
|
4850
|
+
[data-motion-pop-id="${id}"] {
|
|
4851
|
+
position: absolute !important;
|
|
4852
|
+
width: ${width}px !important;
|
|
4853
|
+
height: ${height}px !important;
|
|
4854
|
+
${x}px !important;
|
|
4855
|
+
top: ${top}px !important;
|
|
4856
|
+
}
|
|
4857
|
+
`);
|
|
4858
|
+
}
|
|
4859
|
+
return () => {
|
|
4860
|
+
if (document.head.contains(style)) {
|
|
4861
|
+
document.head.removeChild(style);
|
|
4862
|
+
}
|
|
4863
|
+
};
|
|
4864
|
+
}, [isPresent]);
|
|
4865
|
+
return (jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React__namespace.cloneElement(children, { ref }) }));
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, }) => {
|
|
4869
|
+
const presenceChildren = useConstant(newChildrenMap);
|
|
4870
|
+
const id = React$1.useId();
|
|
4871
|
+
let isReusedContext = true;
|
|
4872
|
+
let context = React$1.useMemo(() => {
|
|
4873
|
+
isReusedContext = false;
|
|
4874
|
+
return {
|
|
4875
|
+
id,
|
|
4876
|
+
initial,
|
|
4877
|
+
isPresent,
|
|
4878
|
+
custom,
|
|
4879
|
+
onExitComplete: (childId) => {
|
|
4880
|
+
presenceChildren.set(childId, true);
|
|
4881
|
+
for (const isComplete of presenceChildren.values()) {
|
|
4882
|
+
if (!isComplete)
|
|
4883
|
+
return; // can stop searching when any is incomplete
|
|
4884
|
+
}
|
|
4885
|
+
onExitComplete && onExitComplete();
|
|
4886
|
+
},
|
|
4887
|
+
register: (childId) => {
|
|
4888
|
+
presenceChildren.set(childId, false);
|
|
4889
|
+
return () => presenceChildren.delete(childId);
|
|
4890
|
+
},
|
|
4891
|
+
};
|
|
4892
|
+
}, [isPresent, presenceChildren, onExitComplete]);
|
|
4893
|
+
/**
|
|
4894
|
+
* If the presence of a child affects the layout of the components around it,
|
|
4895
|
+
* we want to make a new context value to ensure they get re-rendered
|
|
4896
|
+
* so they can detect that layout change.
|
|
4883
4897
|
*/
|
|
4884
|
-
if (
|
|
4885
|
-
|
|
4886
|
-
"view-transition-name": "none",
|
|
4887
|
-
});
|
|
4898
|
+
if (presenceAffectsLayout && isReusedContext) {
|
|
4899
|
+
context = { ...context };
|
|
4888
4900
|
}
|
|
4901
|
+
React$1.useMemo(() => {
|
|
4902
|
+
presenceChildren.forEach((_, key) => presenceChildren.set(key, false));
|
|
4903
|
+
}, [isPresent]);
|
|
4889
4904
|
/**
|
|
4890
|
-
*
|
|
4891
|
-
*
|
|
4892
|
-
* without breaking the generated animation.
|
|
4893
|
-
*
|
|
4894
|
-
* This allows us to set easing via updateTiming - which can be changed.
|
|
4905
|
+
* If there's no `motion` components to fire exit animations, we want to remove this
|
|
4906
|
+
* component immediately.
|
|
4895
4907
|
*/
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
});
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
}
|
|
4905
|
-
return
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
/**
|
|
4910
|
-
* Create animations for each of our explicitly-defined subjects.
|
|
4911
|
-
*/
|
|
4912
|
-
targets.forEach((definition, target) => {
|
|
4913
|
-
// TODO: If target is not "root", resolve elements
|
|
4914
|
-
// and iterate over each
|
|
4915
|
-
for (const key of definitionNames) {
|
|
4916
|
-
if (!definition[key])
|
|
4917
|
-
continue;
|
|
4918
|
-
const { keyframes, options } = definition[key];
|
|
4919
|
-
for (let [valueName, valueKeyframes] of Object.entries(keyframes)) {
|
|
4920
|
-
if (!valueKeyframes)
|
|
4921
|
-
continue;
|
|
4922
|
-
const valueOptions = {
|
|
4923
|
-
...getValueTransition$1(defaultOptions, valueName),
|
|
4924
|
-
...getValueTransition$1(options, valueName),
|
|
4925
|
-
};
|
|
4926
|
-
const type = chooseLayerType(key);
|
|
4927
|
-
/**
|
|
4928
|
-
* If this is an opacity animation, and keyframes are not an array,
|
|
4929
|
-
* we need to convert them into an array and set an initial value.
|
|
4930
|
-
*/
|
|
4931
|
-
if (valueName === "opacity" &&
|
|
4932
|
-
!Array.isArray(valueKeyframes)) {
|
|
4933
|
-
const initialValue = type === "new" ? 0 : 1;
|
|
4934
|
-
valueKeyframes = [initialValue, valueKeyframes];
|
|
4935
|
-
}
|
|
4936
|
-
/**
|
|
4937
|
-
* Resolve stagger function if provided.
|
|
4938
|
-
*/
|
|
4939
|
-
if (typeof valueOptions.delay === "function") {
|
|
4940
|
-
valueOptions.delay = valueOptions.delay(0, 1);
|
|
4941
|
-
}
|
|
4942
|
-
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
|
|
4943
|
-
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
|
|
4944
|
-
const animation = new NativeAnimation({
|
|
4945
|
-
...valueOptions,
|
|
4946
|
-
element: document.documentElement,
|
|
4947
|
-
name: valueName,
|
|
4948
|
-
pseudoElement: `::view-transition-${type}(${target})`,
|
|
4949
|
-
keyframes: valueKeyframes,
|
|
4950
|
-
});
|
|
4951
|
-
animations.push(animation);
|
|
4952
|
-
}
|
|
4953
|
-
}
|
|
4954
|
-
});
|
|
4955
|
-
/**
|
|
4956
|
-
* Handle browser generated animations
|
|
4957
|
-
*/
|
|
4958
|
-
for (const animation of generatedViewAnimations) {
|
|
4959
|
-
if (animation.playState === "finished")
|
|
4960
|
-
continue;
|
|
4961
|
-
const { effect } = animation;
|
|
4962
|
-
if (!effect || !(effect instanceof KeyframeEffect))
|
|
4963
|
-
continue;
|
|
4964
|
-
const { pseudoElement } = effect;
|
|
4965
|
-
if (!pseudoElement)
|
|
4966
|
-
continue;
|
|
4967
|
-
const name = getLayerName(pseudoElement);
|
|
4968
|
-
if (!name)
|
|
4969
|
-
continue;
|
|
4970
|
-
const targetDefinition = targets.get(name.layer);
|
|
4971
|
-
if (!targetDefinition) {
|
|
4972
|
-
/**
|
|
4973
|
-
* If transition name is group then update the timing of the animation
|
|
4974
|
-
* whereas if it's old or new then we could possibly replace it using
|
|
4975
|
-
* the above method.
|
|
4976
|
-
*/
|
|
4977
|
-
const transitionName = name.type === "group" ? "layout" : "";
|
|
4978
|
-
let animationTransition = {
|
|
4979
|
-
...getValueTransition$1(defaultOptions, transitionName),
|
|
4980
|
-
};
|
|
4981
|
-
animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
|
|
4982
|
-
animationTransition =
|
|
4983
|
-
applyGeneratorOptions(animationTransition);
|
|
4984
|
-
const easing = mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration);
|
|
4985
|
-
effect.updateTiming({
|
|
4986
|
-
delay: secondsToMilliseconds(animationTransition.delay ?? 0),
|
|
4987
|
-
duration: animationTransition.duration,
|
|
4988
|
-
easing,
|
|
4989
|
-
});
|
|
4990
|
-
animations.push(new NativeAnimationWrapper(animation));
|
|
4991
|
-
}
|
|
4992
|
-
else if (hasOpacity(targetDefinition, "enter") &&
|
|
4993
|
-
hasOpacity(targetDefinition, "exit") &&
|
|
4994
|
-
effect
|
|
4995
|
-
.getKeyframes()
|
|
4996
|
-
.some((keyframe) => keyframe.mixBlendMode)) {
|
|
4997
|
-
animations.push(new NativeAnimationWrapper(animation));
|
|
4998
|
-
}
|
|
4999
|
-
else {
|
|
5000
|
-
animation.cancel();
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
resolve(new GroupAnimation(animations));
|
|
5004
|
-
});
|
|
5005
|
-
});
|
|
5006
|
-
}
|
|
5007
|
-
function hasOpacity(target, key) {
|
|
5008
|
-
return target?.[key]?.keyframes.opacity;
|
|
4908
|
+
React__namespace.useEffect(() => {
|
|
4909
|
+
!isPresent &&
|
|
4910
|
+
!presenceChildren.size &&
|
|
4911
|
+
onExitComplete &&
|
|
4912
|
+
onExitComplete();
|
|
4913
|
+
}, [isPresent]);
|
|
4914
|
+
if (mode === "popLayout") {
|
|
4915
|
+
children = (jsx(PopChild, { isPresent: isPresent, anchorX: anchorX, children: children }));
|
|
4916
|
+
}
|
|
4917
|
+
return (jsx(PresenceContext.Provider, { value: context, children: children }));
|
|
4918
|
+
};
|
|
4919
|
+
function newChildrenMap() {
|
|
4920
|
+
return new Map();
|
|
5009
4921
|
}
|
|
5010
4922
|
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
4923
|
+
/**
|
|
4924
|
+
* When a component is the child of `AnimatePresence`, it can use `usePresence`
|
|
4925
|
+
* to access information about whether it's still present in the React tree.
|
|
4926
|
+
*
|
|
4927
|
+
* ```jsx
|
|
4928
|
+
* import { usePresence } from "framer-motion"
|
|
4929
|
+
*
|
|
4930
|
+
* export const Component = () => {
|
|
4931
|
+
* const [isPresent, safeToRemove] = usePresence()
|
|
4932
|
+
*
|
|
4933
|
+
* useEffect(() => {
|
|
4934
|
+
* !isPresent && setTimeout(safeToRemove, 1000)
|
|
4935
|
+
* }, [isPresent])
|
|
4936
|
+
*
|
|
4937
|
+
* return <div />
|
|
4938
|
+
* }
|
|
4939
|
+
* ```
|
|
4940
|
+
*
|
|
4941
|
+
* If `isPresent` is `false`, it means that a component has been removed the tree, but
|
|
4942
|
+
* `AnimatePresence` won't really remove it until `safeToRemove` has been called.
|
|
4943
|
+
*
|
|
4944
|
+
* @public
|
|
4945
|
+
*/
|
|
4946
|
+
function usePresence(subscribe = true) {
|
|
4947
|
+
const context = React$1.useContext(PresenceContext);
|
|
4948
|
+
if (context === null)
|
|
4949
|
+
return [true, null];
|
|
4950
|
+
const { isPresent, onExitComplete, register } = context;
|
|
4951
|
+
// It's safe to call the following hooks conditionally (after an early return) because the context will always
|
|
4952
|
+
// either be null or non-null for the lifespan of the component.
|
|
4953
|
+
const id = React$1.useId();
|
|
4954
|
+
React$1.useEffect(() => {
|
|
4955
|
+
if (subscribe) {
|
|
4956
|
+
return register(id);
|
|
4957
|
+
}
|
|
4958
|
+
}, [subscribe]);
|
|
4959
|
+
const safeToRemove = React$1.useCallback(() => subscribe && onExitComplete && onExitComplete(id), [id, onExitComplete, subscribe]);
|
|
4960
|
+
return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
|
|
5018
4961
|
}
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
4962
|
+
/**
|
|
4963
|
+
* Similar to `usePresence`, except `useIsPresent` simply returns whether or not the component is present.
|
|
4964
|
+
* There is no `safeToRemove` function.
|
|
4965
|
+
*
|
|
4966
|
+
* ```jsx
|
|
4967
|
+
* import { useIsPresent } from "framer-motion"
|
|
4968
|
+
*
|
|
4969
|
+
* export const Component = () => {
|
|
4970
|
+
* const isPresent = useIsPresent()
|
|
4971
|
+
*
|
|
4972
|
+
* useEffect(() => {
|
|
4973
|
+
* !isPresent && console.log("I've been removed!")
|
|
4974
|
+
* }, [isPresent])
|
|
4975
|
+
*
|
|
4976
|
+
* return <div />
|
|
4977
|
+
* }
|
|
4978
|
+
* ```
|
|
4979
|
+
*
|
|
4980
|
+
* @public
|
|
4981
|
+
*/
|
|
4982
|
+
function useIsPresent() {
|
|
4983
|
+
return isPresent(React$1.useContext(PresenceContext));
|
|
4984
|
+
}
|
|
4985
|
+
function isPresent(context) {
|
|
4986
|
+
return context === null ? true : context.isPresent;
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
const getChildKey = (child) => child.key || "";
|
|
4990
|
+
function onlyElements(children) {
|
|
4991
|
+
const filtered = [];
|
|
4992
|
+
// We use forEach here instead of map as map mutates the component key by preprending `.$`
|
|
4993
|
+
React$1.Children.forEach(children, (child) => {
|
|
4994
|
+
if (React$1.isValidElement(child))
|
|
4995
|
+
filtered.push(child);
|
|
5025
4996
|
});
|
|
4997
|
+
return filtered;
|
|
5026
4998
|
}
|
|
5027
|
-
|
|
4999
|
+
|
|
5000
|
+
/**
|
|
5001
|
+
* `AnimatePresence` enables the animation of components that have been removed from the tree.
|
|
5002
|
+
*
|
|
5003
|
+
* When adding/removing more than a single child, every child **must** be given a unique `key` prop.
|
|
5004
|
+
*
|
|
5005
|
+
* Any `motion` components that have an `exit` property defined will animate out when removed from
|
|
5006
|
+
* the tree.
|
|
5007
|
+
*
|
|
5008
|
+
* ```jsx
|
|
5009
|
+
* import { motion, AnimatePresence } from 'framer-motion'
|
|
5010
|
+
*
|
|
5011
|
+
* export const Items = ({ items }) => (
|
|
5012
|
+
* <AnimatePresence>
|
|
5013
|
+
* {items.map(item => (
|
|
5014
|
+
* <motion.div
|
|
5015
|
+
* key={item.id}
|
|
5016
|
+
* initial={{ opacity: 0 }}
|
|
5017
|
+
* animate={{ opacity: 1 }}
|
|
5018
|
+
* exit={{ opacity: 0 }}
|
|
5019
|
+
* />
|
|
5020
|
+
* ))}
|
|
5021
|
+
* </AnimatePresence>
|
|
5022
|
+
* )
|
|
5023
|
+
* ```
|
|
5024
|
+
*
|
|
5025
|
+
* You can sequence exit animations throughout a tree using variants.
|
|
5026
|
+
*
|
|
5027
|
+
* If a child contains multiple `motion` components with `exit` props, it will only unmount the child
|
|
5028
|
+
* once all `motion` components have finished animating out. Likewise, any components using
|
|
5029
|
+
* `usePresence` all need to call `safeToRemove`.
|
|
5030
|
+
*
|
|
5031
|
+
* @public
|
|
5032
|
+
*/
|
|
5033
|
+
const AnimatePresence = ({ children, custom, initial = true, onExitComplete, presenceAffectsLayout = true, mode = "sync", propagate = false, anchorX = "left", }) => {
|
|
5034
|
+
const [isParentPresent, safeToRemove] = usePresence(propagate);
|
|
5028
5035
|
/**
|
|
5029
|
-
*
|
|
5030
|
-
*
|
|
5031
|
-
* queue then we need to batch all preceeding animations into it.
|
|
5032
|
-
* Currently this only batches the update functions but will also
|
|
5033
|
-
* need to batch the targets.
|
|
5036
|
+
* Filter any children that aren't ReactElements. We can only track components
|
|
5037
|
+
* between renders with a props.key.
|
|
5034
5038
|
*/
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5039
|
+
const presentChildren = React$1.useMemo(() => onlyElements(children), [children]);
|
|
5040
|
+
/**
|
|
5041
|
+
* Track the keys of the currently rendered children. This is used to
|
|
5042
|
+
* determine which children are exiting.
|
|
5043
|
+
*/
|
|
5044
|
+
const presentKeys = propagate && !isParentPresent ? [] : presentChildren.map(getChildKey);
|
|
5045
|
+
/**
|
|
5046
|
+
* If `initial={false}` we only want to pass this to components in the first render.
|
|
5047
|
+
*/
|
|
5048
|
+
const isInitialRender = React$1.useRef(true);
|
|
5049
|
+
/**
|
|
5050
|
+
* A ref containing the currently present children. When all exit animations
|
|
5051
|
+
* are complete, we use this to re-render the component with the latest children
|
|
5052
|
+
* *committed* rather than the latest children *rendered*.
|
|
5053
|
+
*/
|
|
5054
|
+
const pendingPresentChildren = React$1.useRef(presentChildren);
|
|
5055
|
+
/**
|
|
5056
|
+
* Track which exiting children have finished animating out.
|
|
5057
|
+
*/
|
|
5058
|
+
const exitComplete = useConstant(() => new Map());
|
|
5059
|
+
/**
|
|
5060
|
+
* Save children to render as React state. To ensure this component is concurrent-safe,
|
|
5061
|
+
* we check for exiting children via an effect.
|
|
5062
|
+
*/
|
|
5063
|
+
const [diffedChildren, setDiffedChildren] = React$1.useState(presentChildren);
|
|
5064
|
+
const [renderedChildren, setRenderedChildren] = React$1.useState(presentChildren);
|
|
5065
|
+
useIsomorphicLayoutEffect(() => {
|
|
5066
|
+
isInitialRender.current = false;
|
|
5067
|
+
pendingPresentChildren.current = presentChildren;
|
|
5068
|
+
/**
|
|
5069
|
+
* Update complete status of exiting children.
|
|
5070
|
+
*/
|
|
5071
|
+
for (let i = 0; i < renderedChildren.length; i++) {
|
|
5072
|
+
const key = getChildKey(renderedChildren[i]);
|
|
5073
|
+
if (!presentKeys.includes(key)) {
|
|
5074
|
+
if (exitComplete.get(key) !== true) {
|
|
5075
|
+
exitComplete.set(key, false);
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
else {
|
|
5079
|
+
exitComplete.delete(key);
|
|
5080
|
+
}
|
|
5047
5081
|
}
|
|
5048
|
-
}
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
this.readyPromise = new Promise((resolve) => {
|
|
5064
|
-
this.notifyReady = resolve;
|
|
5065
|
-
});
|
|
5066
|
-
this.update = update;
|
|
5067
|
-
this.options = {
|
|
5068
|
-
interrupt: "wait",
|
|
5069
|
-
...options,
|
|
5070
|
-
};
|
|
5071
|
-
addToQueue(this);
|
|
5072
|
-
}
|
|
5073
|
-
get(selector) {
|
|
5074
|
-
this.currentTarget = selector;
|
|
5075
|
-
return this;
|
|
5076
|
-
}
|
|
5077
|
-
layout(keyframes, options) {
|
|
5078
|
-
this.updateTarget("layout", keyframes, options);
|
|
5079
|
-
return this;
|
|
5080
|
-
}
|
|
5081
|
-
new(keyframes, options) {
|
|
5082
|
-
this.updateTarget("new", keyframes, options);
|
|
5083
|
-
return this;
|
|
5084
|
-
}
|
|
5085
|
-
old(keyframes, options) {
|
|
5086
|
-
this.updateTarget("old", keyframes, options);
|
|
5087
|
-
return this;
|
|
5088
|
-
}
|
|
5089
|
-
enter(keyframes, options) {
|
|
5090
|
-
this.updateTarget("enter", keyframes, options);
|
|
5091
|
-
return this;
|
|
5092
|
-
}
|
|
5093
|
-
exit(keyframes, options) {
|
|
5094
|
-
this.updateTarget("exit", keyframes, options);
|
|
5095
|
-
return this;
|
|
5096
|
-
}
|
|
5097
|
-
crossfade(options) {
|
|
5098
|
-
this.updateTarget("enter", { opacity: 1 }, options);
|
|
5099
|
-
this.updateTarget("exit", { opacity: 0 }, options);
|
|
5100
|
-
return this;
|
|
5101
|
-
}
|
|
5102
|
-
updateTarget(target, keyframes, options = {}) {
|
|
5103
|
-
const { currentTarget, targets } = this;
|
|
5104
|
-
if (!targets.has(currentTarget)) {
|
|
5105
|
-
targets.set(currentTarget, {});
|
|
5082
|
+
}, [renderedChildren, presentKeys.length, presentKeys.join("-")]);
|
|
5083
|
+
const exitingChildren = [];
|
|
5084
|
+
if (presentChildren !== diffedChildren) {
|
|
5085
|
+
let nextChildren = [...presentChildren];
|
|
5086
|
+
/**
|
|
5087
|
+
* Loop through all the currently rendered components and decide which
|
|
5088
|
+
* are exiting.
|
|
5089
|
+
*/
|
|
5090
|
+
for (let i = 0; i < renderedChildren.length; i++) {
|
|
5091
|
+
const child = renderedChildren[i];
|
|
5092
|
+
const key = getChildKey(child);
|
|
5093
|
+
if (!presentKeys.includes(key)) {
|
|
5094
|
+
nextChildren.splice(i, 0, child);
|
|
5095
|
+
exitingChildren.push(child);
|
|
5096
|
+
}
|
|
5106
5097
|
}
|
|
5107
|
-
|
|
5108
|
-
|
|
5098
|
+
/**
|
|
5099
|
+
* If we're in "wait" mode, and we have exiting children, we want to
|
|
5100
|
+
* only render these until they've all exited.
|
|
5101
|
+
*/
|
|
5102
|
+
if (mode === "wait" && exitingChildren.length) {
|
|
5103
|
+
nextChildren = exitingChildren;
|
|
5104
|
+
}
|
|
5105
|
+
setRenderedChildren(onlyElements(nextChildren));
|
|
5106
|
+
setDiffedChildren(presentChildren);
|
|
5107
|
+
/**
|
|
5108
|
+
* Early return to ensure once we've set state with the latest diffed
|
|
5109
|
+
* children, we can immediately re-render.
|
|
5110
|
+
*/
|
|
5111
|
+
return null;
|
|
5109
5112
|
}
|
|
5110
|
-
|
|
5111
|
-
|
|
5113
|
+
if (mode === "wait" &&
|
|
5114
|
+
renderedChildren.length > 1) {
|
|
5115
|
+
console.warn(`You're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.`);
|
|
5112
5116
|
}
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
+
/**
|
|
5118
|
+
* If we've been provided a forceRender function by the LayoutGroupContext,
|
|
5119
|
+
* we can use it to force a re-render amongst all surrounding components once
|
|
5120
|
+
* all components have finished animating out.
|
|
5121
|
+
*/
|
|
5122
|
+
const { forceRender } = React$1.useContext(LayoutGroupContext);
|
|
5123
|
+
return (jsx(Fragment, { children: renderedChildren.map((child) => {
|
|
5124
|
+
const key = getChildKey(child);
|
|
5125
|
+
const isPresent = propagate && !isParentPresent
|
|
5126
|
+
? false
|
|
5127
|
+
: presentChildren === renderedChildren ||
|
|
5128
|
+
presentKeys.includes(key);
|
|
5129
|
+
const onExit = () => {
|
|
5130
|
+
if (exitComplete.has(key)) {
|
|
5131
|
+
exitComplete.set(key, true);
|
|
5132
|
+
}
|
|
5133
|
+
else {
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
let isEveryExitComplete = true;
|
|
5137
|
+
exitComplete.forEach((isExitComplete) => {
|
|
5138
|
+
if (!isExitComplete)
|
|
5139
|
+
isEveryExitComplete = false;
|
|
5140
|
+
});
|
|
5141
|
+
if (isEveryExitComplete) {
|
|
5142
|
+
forceRender?.();
|
|
5143
|
+
setRenderedChildren(pendingPresentChildren.current);
|
|
5144
|
+
propagate && safeToRemove?.();
|
|
5145
|
+
onExitComplete && onExitComplete();
|
|
5146
|
+
}
|
|
5147
|
+
};
|
|
5148
|
+
return (jsx(PresenceChild, { isPresent: isPresent, initial: !isInitialRender.current || initial
|
|
5149
|
+
? undefined
|
|
5150
|
+
: false, custom: custom, presenceAffectsLayout: presenceAffectsLayout, mode: mode, onExitComplete: isPresent ? undefined : onExit, anchorX: anchorX, children: child }, key));
|
|
5151
|
+
}) }));
|
|
5152
|
+
};
|
|
5117
5153
|
|
|
5118
5154
|
/**
|
|
5119
|
-
*
|
|
5155
|
+
* Note: Still used by components generated by old versions of Framer
|
|
5120
5156
|
*
|
|
5121
|
-
* Import as `frame` instead.
|
|
5122
|
-
*/
|
|
5123
|
-
const sync = frame;
|
|
5124
|
-
/**
|
|
5125
5157
|
* @deprecated
|
|
5126
|
-
*
|
|
5127
|
-
* Use cancelFrame(callback) instead.
|
|
5128
5158
|
*/
|
|
5129
|
-
const
|
|
5130
|
-
acc[key] = (process) => cancelFrame(process);
|
|
5131
|
-
return acc;
|
|
5132
|
-
}, {});
|
|
5159
|
+
const DeprecatedLayoutGroupContext = React$1.createContext(null);
|
|
5133
5160
|
|
|
5134
5161
|
const SCALE_PRECISION = 0.0001;
|
|
5135
5162
|
const SCALE_MIN = 1 - SCALE_PRECISION;
|
|
@@ -5369,10 +5396,6 @@
|
|
|
5369
5396
|
return visualElement.props[optimizedAppearDataAttribute];
|
|
5370
5397
|
}
|
|
5371
5398
|
|
|
5372
|
-
function isSVGElement(element) {
|
|
5373
|
-
return element instanceof SVGElement && element.tagName !== "svg";
|
|
5374
|
-
}
|
|
5375
|
-
|
|
5376
5399
|
const compareByDepth = (a, b) => a.depth - b.depth;
|
|
5377
5400
|
|
|
5378
5401
|
class FlatTree {
|
|
@@ -6170,7 +6193,7 @@
|
|
|
6170
6193
|
mount(instance) {
|
|
6171
6194
|
if (this.instance)
|
|
6172
6195
|
return;
|
|
6173
|
-
this.isSVG = isSVGElement(instance);
|
|
6196
|
+
this.isSVG = isSVGElement(instance) && !isSVGSVGElement(instance);
|
|
6174
6197
|
this.instance = instance;
|
|
6175
6198
|
const { layoutId, layout, visualElement } = this.options;
|
|
6176
6199
|
if (visualElement && !visualElement.current) {
|
|
@@ -11959,7 +11982,7 @@
|
|
|
11959
11982
|
latestValues: {},
|
|
11960
11983
|
},
|
|
11961
11984
|
};
|
|
11962
|
-
const node = isSVGElement(element)
|
|
11985
|
+
const node = isSVGElement(element) && !isSVGSVGElement(element)
|
|
11963
11986
|
? new SVGVisualElement(options)
|
|
11964
11987
|
: new HTMLVisualElement(options);
|
|
11965
11988
|
node.mount(element);
|
|
@@ -12177,7 +12200,7 @@
|
|
|
12177
12200
|
const { inlineSize, blockSize } = borderBoxSize[0];
|
|
12178
12201
|
return { width: inlineSize, height: blockSize };
|
|
12179
12202
|
}
|
|
12180
|
-
else if (target
|
|
12203
|
+
else if (isSVGElement(target) && "getBBox" in target) {
|
|
12181
12204
|
return target.getBBox();
|
|
12182
12205
|
}
|
|
12183
12206
|
else {
|
|
@@ -12319,7 +12342,7 @@
|
|
|
12319
12342
|
const inset = { x: 0, y: 0 };
|
|
12320
12343
|
let current = element;
|
|
12321
12344
|
while (current && current !== container) {
|
|
12322
|
-
if (current
|
|
12345
|
+
if (isHTMLElement(current)) {
|
|
12323
12346
|
inset.x += current.offsetLeft;
|
|
12324
12347
|
inset.y += current.offsetTop;
|
|
12325
12348
|
current = current.offsetParent;
|
|
@@ -13868,11 +13891,15 @@
|
|
|
13868
13891
|
exports.isDragging = isDragging;
|
|
13869
13892
|
exports.isEasingArray = isEasingArray;
|
|
13870
13893
|
exports.isGenerator = isGenerator;
|
|
13894
|
+
exports.isHTMLElement = isHTMLElement;
|
|
13871
13895
|
exports.isMotionComponent = isMotionComponent;
|
|
13872
13896
|
exports.isMotionValue = isMotionValue;
|
|
13873
13897
|
exports.isNodeOrChild = isNodeOrChild;
|
|
13874
13898
|
exports.isNumericalString = isNumericalString;
|
|
13899
|
+
exports.isObject = isObject;
|
|
13875
13900
|
exports.isPrimaryPointer = isPrimaryPointer;
|
|
13901
|
+
exports.isSVGElement = isSVGElement;
|
|
13902
|
+
exports.isSVGSVGElement = isSVGSVGElement;
|
|
13876
13903
|
exports.isValidMotionProp = isValidMotionProp;
|
|
13877
13904
|
exports.isWaapiSupportedEasing = isWaapiSupportedEasing;
|
|
13878
13905
|
exports.isZeroValueString = isZeroValueString;
|