framer-motion 12.23.5 → 12.23.6
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 +171 -167
- package/dist/cjs/dom.js +4 -1
- package/dist/cjs/{create-Ch9BUY4E.js → feature-bundle-BoyRwN0C.js} +2322 -2340
- package/dist/cjs/index.js +120 -118
- package/dist/cjs/m.js +803 -822
- package/dist/dom.js +1 -1
- package/dist/es/client.mjs +1 -1
- package/dist/es/index.mjs +0 -1
- package/dist/es/motion/index.mjs +13 -6
- package/dist/es/motion/utils/use-visual-element.mjs +4 -3
- package/dist/es/motion/utils/use-visual-state.mjs +6 -6
- package/dist/es/render/VisualElement.mjs +5 -2
- package/dist/es/render/components/create-proxy.mjs +14 -7
- package/dist/es/render/components/m/create.mjs +4 -3
- package/dist/es/render/components/m/proxy.mjs +2 -3
- package/dist/es/render/components/motion/create.mjs +6 -12
- package/dist/es/render/components/motion/elements.mjs +165 -165
- package/dist/es/render/components/motion/feature-bundle.mjs +13 -0
- package/dist/es/render/components/motion/proxy.mjs +4 -3
- package/dist/es/render/dom/use-render.mjs +19 -24
- package/dist/es/render/html/{config-motion.mjs → use-html-visual-state.mjs} +6 -8
- package/dist/es/render/svg/{config-motion.mjs → use-svg-visual-state.mjs} +5 -7
- package/dist/es/utils/reduced-motion/use-reduced-motion.mjs +1 -1
- package/dist/framer-motion.dev.js +2365 -2381
- package/dist/framer-motion.js +1 -1
- package/dist/m.d.ts +15 -13
- package/dist/size-rollup-animate.js +1 -1
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation-m.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-m.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/types/client.d.ts +5 -11
- package/dist/types/index.d.ts +7 -23
- package/dist/{types.d-D0HXPxHm.d.ts → types.d-Bq-Qm38R.d.ts} +263 -264
- package/package.json +4 -4
- package/dist/es/render/components/create-factory.mjs +0 -23
package/dist/cjs/m.js
CHANGED
|
@@ -75,460 +75,154 @@ function variantLabelsAsDependency(prop) {
|
|
|
75
75
|
return Array.isArray(prop) ? prop.join(" ") : prop;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const featureProps = {
|
|
81
|
-
animation: [
|
|
82
|
-
"animate",
|
|
83
|
-
"variants",
|
|
84
|
-
"whileHover",
|
|
85
|
-
"whileTap",
|
|
86
|
-
"exit",
|
|
87
|
-
"whileInView",
|
|
88
|
-
"whileFocus",
|
|
89
|
-
"whileDrag",
|
|
90
|
-
],
|
|
91
|
-
exit: ["exit"],
|
|
92
|
-
drag: ["drag", "dragControls"],
|
|
93
|
-
focus: ["whileFocus"],
|
|
94
|
-
hover: ["whileHover", "onHoverStart", "onHoverEnd"],
|
|
95
|
-
tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
|
|
96
|
-
pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
|
|
97
|
-
inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
|
|
98
|
-
layout: ["layout", "layoutId"],
|
|
99
|
-
};
|
|
100
|
-
const featureDefinitions = {};
|
|
101
|
-
for (const key in featureProps) {
|
|
102
|
-
featureDefinitions[key] = {
|
|
103
|
-
isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function loadFeatures(features) {
|
|
108
|
-
for (const key in features) {
|
|
109
|
-
featureDefinitions[key] = {
|
|
110
|
-
...featureDefinitions[key],
|
|
111
|
-
...features[key],
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const motionComponentSymbol = Symbol.for("motionComponentSymbol");
|
|
78
|
+
const scaleCorrectors = {};
|
|
117
79
|
|
|
118
|
-
function
|
|
119
|
-
return (
|
|
120
|
-
|
|
121
|
-
|
|
80
|
+
function isForcedMotionValue(key, { layout, layoutId }) {
|
|
81
|
+
return (motionDom.transformProps.has(key) ||
|
|
82
|
+
key.startsWith("origin") ||
|
|
83
|
+
((layout || layoutId !== undefined) &&
|
|
84
|
+
(!!scaleCorrectors[key] || key === "opacity")));
|
|
122
85
|
}
|
|
123
86
|
|
|
87
|
+
const translateAlias = {
|
|
88
|
+
x: "translateX",
|
|
89
|
+
y: "translateY",
|
|
90
|
+
z: "translateZ",
|
|
91
|
+
transformPerspective: "perspective",
|
|
92
|
+
};
|
|
93
|
+
const numTransforms = motionDom.transformPropOrder.length;
|
|
124
94
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
95
|
+
* Build a CSS transform style from individual x/y/scale etc properties.
|
|
96
|
+
*
|
|
97
|
+
* This outputs with a default order of transforms/scales/rotations, this can be customised by
|
|
98
|
+
* providing a transformTemplate function.
|
|
127
99
|
*/
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
100
|
+
function buildTransform(latestValues, transform, transformTemplate) {
|
|
101
|
+
// The transform string we're going to build into.
|
|
102
|
+
let transformString = "";
|
|
103
|
+
let transformIsDefault = true;
|
|
104
|
+
/**
|
|
105
|
+
* Loop over all possible transforms in order, adding the ones that
|
|
106
|
+
* are present to the transform string.
|
|
107
|
+
*/
|
|
108
|
+
for (let i = 0; i < numTransforms; i++) {
|
|
109
|
+
const key = motionDom.transformPropOrder[i];
|
|
110
|
+
const value = latestValues[key];
|
|
111
|
+
if (value === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
let valueIsDefault = true;
|
|
114
|
+
if (typeof value === "number") {
|
|
115
|
+
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
|
132
116
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
visualElement.mount(instance);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
visualElement.unmount();
|
|
139
|
-
}
|
|
117
|
+
else {
|
|
118
|
+
valueIsDefault = parseFloat(value) === 0;
|
|
140
119
|
}
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
120
|
+
if (!valueIsDefault || transformTemplate) {
|
|
121
|
+
const valueAsType = motionDom.getValueAsType(value, motionDom.numberValueTypes[key]);
|
|
122
|
+
if (!valueIsDefault) {
|
|
123
|
+
transformIsDefault = false;
|
|
124
|
+
const transformName = translateAlias[key] || key;
|
|
125
|
+
transformString += `${transformName}(${valueAsType}) `;
|
|
144
126
|
}
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
if (transformTemplate) {
|
|
128
|
+
transform[key] = valueAsType;
|
|
147
129
|
}
|
|
148
130
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
131
|
+
}
|
|
132
|
+
transformString = transformString.trim();
|
|
133
|
+
// If we have a custom `transform` template, pass our transform values and
|
|
134
|
+
// generated transformString to that before returning
|
|
135
|
+
if (transformTemplate) {
|
|
136
|
+
transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
|
|
137
|
+
}
|
|
138
|
+
else if (transformIsDefault) {
|
|
139
|
+
transformString = "none";
|
|
140
|
+
}
|
|
141
|
+
return transformString;
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const optimizedAppearDataId = "framerAppearId";
|
|
164
|
-
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @public
|
|
168
|
-
*/
|
|
169
|
-
const PresenceContext =
|
|
170
|
-
/* @__PURE__ */ react.createContext(null);
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Internal, exported only for usage in Framer
|
|
174
|
-
*/
|
|
175
|
-
const SwitchLayoutGroupContext = react.createContext({});
|
|
176
|
-
|
|
177
|
-
const useIsomorphicLayoutEffect = isBrowser ? react.useLayoutEffect : react.useEffect;
|
|
178
|
-
|
|
179
|
-
function useVisualElement(Component, visualState, props, createVisualElement, ProjectionNodeConstructor) {
|
|
180
|
-
const { visualElement: parent } = react.useContext(MotionContext);
|
|
181
|
-
const lazyContext = react.useContext(LazyContext);
|
|
182
|
-
const presenceContext = react.useContext(PresenceContext);
|
|
183
|
-
const reducedMotionConfig = react.useContext(MotionConfigContext).reducedMotion;
|
|
184
|
-
const visualElementRef = react.useRef(null);
|
|
144
|
+
function buildHTMLStyles(state, latestValues, transformTemplate) {
|
|
145
|
+
const { style, vars, transformOrigin } = state;
|
|
146
|
+
// Track whether we encounter any transform or transformOrigin values.
|
|
147
|
+
let hasTransform = false;
|
|
148
|
+
let hasTransformOrigin = false;
|
|
185
149
|
/**
|
|
186
|
-
*
|
|
150
|
+
* Loop over all our latest animated values and decide whether to handle them
|
|
151
|
+
* as a style or CSS variable.
|
|
152
|
+
*
|
|
153
|
+
* Transforms and transform origins are kept separately for further processing.
|
|
187
154
|
*/
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
155
|
+
for (const key in latestValues) {
|
|
156
|
+
const value = latestValues[key];
|
|
157
|
+
if (motionDom.transformProps.has(key)) {
|
|
158
|
+
// If this is a transform, flag to enable further transform processing
|
|
159
|
+
hasTransform = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
else if (motionDom.isCSSVariableName(key)) {
|
|
163
|
+
vars[key] = value;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Convert the value to its default value type, ie 0 -> "0px"
|
|
168
|
+
const valueAsType = motionDom.getValueAsType(value, motionDom.numberValueTypes[key]);
|
|
169
|
+
if (key.startsWith("origin")) {
|
|
170
|
+
// If this is a transform origin, flag and enable further transform-origin processing
|
|
171
|
+
hasTransformOrigin = true;
|
|
172
|
+
transformOrigin[key] =
|
|
173
|
+
valueAsType;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
style[key] = valueAsType;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!latestValues.transform) {
|
|
181
|
+
if (hasTransform || transformTemplate) {
|
|
182
|
+
style.transform = buildTransform(latestValues, state.transform, transformTemplate);
|
|
183
|
+
}
|
|
184
|
+
else if (style.transform) {
|
|
185
|
+
/**
|
|
186
|
+
* If we have previously created a transform but currently don't have any,
|
|
187
|
+
* reset transform style to none.
|
|
188
|
+
*/
|
|
189
|
+
style.transform = "none";
|
|
190
|
+
}
|
|
200
191
|
}
|
|
201
|
-
const visualElement = visualElementRef.current;
|
|
202
192
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
193
|
+
* Build a transformOrigin style. Uses the same defaults as the browser for
|
|
194
|
+
* undefined origins.
|
|
205
195
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
ProjectionNodeConstructor &&
|
|
210
|
-
(visualElement.type === "html" || visualElement.type === "svg")) {
|
|
211
|
-
createProjectionNode(visualElementRef.current, props, ProjectionNodeConstructor, initialLayoutGroupConfig);
|
|
196
|
+
if (hasTransformOrigin) {
|
|
197
|
+
const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
|
|
198
|
+
style.transformOrigin = `${originX} ${originY} ${originZ}`;
|
|
212
199
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const createHtmlRenderState = () => ({
|
|
203
|
+
style: {},
|
|
204
|
+
transform: {},
|
|
205
|
+
transformOrigin: {},
|
|
206
|
+
vars: {},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
function copyRawValuesOnly(target, source, props) {
|
|
210
|
+
for (const key in source) {
|
|
211
|
+
if (!motionDom.isMotionValue(source[key]) && !isForcedMotionValue(key, props)) {
|
|
212
|
+
target[key] = source[key];
|
|
221
213
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
isMounted.current = true;
|
|
235
|
-
window.MotionIsMounted = true;
|
|
236
|
-
visualElement.updateFeatures();
|
|
237
|
-
motionDom.microtask.render(visualElement.render);
|
|
238
|
-
/**
|
|
239
|
-
* Ideally this function would always run in a useEffect.
|
|
240
|
-
*
|
|
241
|
-
* However, if we have optimised appear animations to handoff from,
|
|
242
|
-
* it needs to happen synchronously to ensure there's no flash of
|
|
243
|
-
* incorrect styles in the event of a hydration error.
|
|
244
|
-
*
|
|
245
|
-
* So if we detect a situtation where optimised appear animations
|
|
246
|
-
* are running, we use useLayoutEffect to trigger animations.
|
|
247
|
-
*/
|
|
248
|
-
if (wantsHandoff.current && visualElement.animationState) {
|
|
249
|
-
visualElement.animationState.animateChanges();
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
react.useEffect(() => {
|
|
253
|
-
if (!visualElement)
|
|
254
|
-
return;
|
|
255
|
-
if (!wantsHandoff.current && visualElement.animationState) {
|
|
256
|
-
visualElement.animationState.animateChanges();
|
|
257
|
-
}
|
|
258
|
-
if (wantsHandoff.current) {
|
|
259
|
-
// This ensures all future calls to animateChanges() in this component will run in useEffect
|
|
260
|
-
queueMicrotask(() => {
|
|
261
|
-
window.MotionHandoffMarkAsComplete?.(optimisedAppearId);
|
|
262
|
-
});
|
|
263
|
-
wantsHandoff.current = false;
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
return visualElement;
|
|
267
|
-
}
|
|
268
|
-
function createProjectionNode(visualElement, props, ProjectionNodeConstructor, initialPromotionConfig) {
|
|
269
|
-
const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, layoutCrossfade, } = props;
|
|
270
|
-
visualElement.projection = new ProjectionNodeConstructor(visualElement.latestValues, props["data-framer-portal-id"]
|
|
271
|
-
? undefined
|
|
272
|
-
: getClosestProjectingNode(visualElement.parent));
|
|
273
|
-
visualElement.projection.setOptions({
|
|
274
|
-
layoutId,
|
|
275
|
-
layout,
|
|
276
|
-
alwaysMeasureLayout: Boolean(drag) || (dragConstraints && isRefObject(dragConstraints)),
|
|
277
|
-
visualElement,
|
|
278
|
-
/**
|
|
279
|
-
* TODO: Update options in an effect. This could be tricky as it'll be too late
|
|
280
|
-
* to update by the time layout animations run.
|
|
281
|
-
* We also need to fix this safeToRemove by linking it up to the one returned by usePresence,
|
|
282
|
-
* ensuring it gets called if there's no potential layout animations.
|
|
283
|
-
*
|
|
284
|
-
*/
|
|
285
|
-
animationType: typeof layout === "string" ? layout : "both",
|
|
286
|
-
initialPromotionConfig,
|
|
287
|
-
crossfade: layoutCrossfade,
|
|
288
|
-
layoutScroll,
|
|
289
|
-
layoutRoot,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
function getClosestProjectingNode(visualElement) {
|
|
293
|
-
if (!visualElement)
|
|
294
|
-
return undefined;
|
|
295
|
-
return visualElement.options.allowProjection !== false
|
|
296
|
-
? visualElement.projection
|
|
297
|
-
: getClosestProjectingNode(visualElement.parent);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Create a `motion` component.
|
|
302
|
-
*
|
|
303
|
-
* This function accepts a Component argument, which can be either a string (ie "div"
|
|
304
|
-
* for `motion.div`), or an actual React component.
|
|
305
|
-
*
|
|
306
|
-
* Alongside this is a config option which provides a way of rendering the provided
|
|
307
|
-
* component "offline", or outside the React render cycle.
|
|
308
|
-
*/
|
|
309
|
-
function createRendererMotionComponent({ preloadedFeatures, createVisualElement, useRender, useVisualState, Component, }) {
|
|
310
|
-
preloadedFeatures && loadFeatures(preloadedFeatures);
|
|
311
|
-
function MotionComponent(props, externalRef) {
|
|
312
|
-
/**
|
|
313
|
-
* If we need to measure the element we load this functionality in a
|
|
314
|
-
* separate class component in order to gain access to getSnapshotBeforeUpdate.
|
|
315
|
-
*/
|
|
316
|
-
let MeasureLayout;
|
|
317
|
-
const configAndProps = {
|
|
318
|
-
...react.useContext(MotionConfigContext),
|
|
319
|
-
...props,
|
|
320
|
-
layoutId: useLayoutId(props),
|
|
321
|
-
};
|
|
322
|
-
const { isStatic } = configAndProps;
|
|
323
|
-
const context = useCreateMotionContext(props);
|
|
324
|
-
const visualState = useVisualState(props, isStatic);
|
|
325
|
-
if (!isStatic && isBrowser) {
|
|
326
|
-
useStrictMode(configAndProps, preloadedFeatures);
|
|
327
|
-
const layoutProjection = getProjectionFunctionality(configAndProps);
|
|
328
|
-
MeasureLayout = layoutProjection.MeasureLayout;
|
|
329
|
-
/**
|
|
330
|
-
* Create a VisualElement for this component. A VisualElement provides a common
|
|
331
|
-
* interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
|
|
332
|
-
* providing a way of rendering to these APIs outside of the React render loop
|
|
333
|
-
* for more performant animations and interactions
|
|
334
|
-
*/
|
|
335
|
-
context.visualElement = useVisualElement(Component, visualState, configAndProps, createVisualElement, layoutProjection.ProjectionNode);
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* The mount order and hierarchy is specific to ensure our element ref
|
|
339
|
-
* is hydrated by the time features fire their effects.
|
|
340
|
-
*/
|
|
341
|
-
return (jsxRuntime.jsxs(MotionContext.Provider, { value: context, children: [MeasureLayout && context.visualElement ? (jsxRuntime.jsx(MeasureLayout, { visualElement: context.visualElement, ...configAndProps })) : null, useRender(Component, props, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, context.visualElement)] }));
|
|
342
|
-
}
|
|
343
|
-
MotionComponent.displayName = `motion.${typeof Component === "string"
|
|
344
|
-
? Component
|
|
345
|
-
: `create(${Component.displayName ?? Component.name ?? ""})`}`;
|
|
346
|
-
const ForwardRefMotionComponent = react.forwardRef(MotionComponent);
|
|
347
|
-
ForwardRefMotionComponent[motionComponentSymbol] = Component;
|
|
348
|
-
return ForwardRefMotionComponent;
|
|
349
|
-
}
|
|
350
|
-
function useLayoutId({ layoutId }) {
|
|
351
|
-
const layoutGroupId = react.useContext(LayoutGroupContext).id;
|
|
352
|
-
return layoutGroupId && layoutId !== undefined
|
|
353
|
-
? layoutGroupId + "-" + layoutId
|
|
354
|
-
: layoutId;
|
|
355
|
-
}
|
|
356
|
-
function useStrictMode(configAndProps, preloadedFeatures) {
|
|
357
|
-
const isStrict = react.useContext(LazyContext).strict;
|
|
358
|
-
/**
|
|
359
|
-
* If we're in development mode, check to make sure we're not rendering a motion component
|
|
360
|
-
* as a child of LazyMotion, as this will break the file-size benefits of using it.
|
|
361
|
-
*/
|
|
362
|
-
if (process.env.NODE_ENV !== "production" &&
|
|
363
|
-
preloadedFeatures &&
|
|
364
|
-
isStrict) {
|
|
365
|
-
const strictMessage = "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.";
|
|
366
|
-
configAndProps.ignoreStrict
|
|
367
|
-
? motionUtils.warning(false, strictMessage, "lazy-strict-mode")
|
|
368
|
-
: motionUtils.invariant(false, strictMessage, "lazy-strict-mode");
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
function getProjectionFunctionality(props) {
|
|
372
|
-
const { drag, layout } = featureDefinitions;
|
|
373
|
-
if (!drag && !layout)
|
|
374
|
-
return {};
|
|
375
|
-
const combined = { ...drag, ...layout };
|
|
376
|
-
return {
|
|
377
|
-
MeasureLayout: drag?.isEnabled(props) || layout?.isEnabled(props)
|
|
378
|
-
? combined.MeasureLayout
|
|
379
|
-
: undefined,
|
|
380
|
-
ProjectionNode: combined.ProjectionNode,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const scaleCorrectors = {};
|
|
385
|
-
|
|
386
|
-
function isForcedMotionValue(key, { layout, layoutId }) {
|
|
387
|
-
return (motionDom.transformProps.has(key) ||
|
|
388
|
-
key.startsWith("origin") ||
|
|
389
|
-
((layout || layoutId !== undefined) &&
|
|
390
|
-
(!!scaleCorrectors[key] || key === "opacity")));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const translateAlias = {
|
|
394
|
-
x: "translateX",
|
|
395
|
-
y: "translateY",
|
|
396
|
-
z: "translateZ",
|
|
397
|
-
transformPerspective: "perspective",
|
|
398
|
-
};
|
|
399
|
-
const numTransforms = motionDom.transformPropOrder.length;
|
|
400
|
-
/**
|
|
401
|
-
* Build a CSS transform style from individual x/y/scale etc properties.
|
|
402
|
-
*
|
|
403
|
-
* This outputs with a default order of transforms/scales/rotations, this can be customised by
|
|
404
|
-
* providing a transformTemplate function.
|
|
405
|
-
*/
|
|
406
|
-
function buildTransform(latestValues, transform, transformTemplate) {
|
|
407
|
-
// The transform string we're going to build into.
|
|
408
|
-
let transformString = "";
|
|
409
|
-
let transformIsDefault = true;
|
|
410
|
-
/**
|
|
411
|
-
* Loop over all possible transforms in order, adding the ones that
|
|
412
|
-
* are present to the transform string.
|
|
413
|
-
*/
|
|
414
|
-
for (let i = 0; i < numTransforms; i++) {
|
|
415
|
-
const key = motionDom.transformPropOrder[i];
|
|
416
|
-
const value = latestValues[key];
|
|
417
|
-
if (value === undefined)
|
|
418
|
-
continue;
|
|
419
|
-
let valueIsDefault = true;
|
|
420
|
-
if (typeof value === "number") {
|
|
421
|
-
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
valueIsDefault = parseFloat(value) === 0;
|
|
425
|
-
}
|
|
426
|
-
if (!valueIsDefault || transformTemplate) {
|
|
427
|
-
const valueAsType = motionDom.getValueAsType(value, motionDom.numberValueTypes[key]);
|
|
428
|
-
if (!valueIsDefault) {
|
|
429
|
-
transformIsDefault = false;
|
|
430
|
-
const transformName = translateAlias[key] || key;
|
|
431
|
-
transformString += `${transformName}(${valueAsType}) `;
|
|
432
|
-
}
|
|
433
|
-
if (transformTemplate) {
|
|
434
|
-
transform[key] = valueAsType;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
transformString = transformString.trim();
|
|
439
|
-
// If we have a custom `transform` template, pass our transform values and
|
|
440
|
-
// generated transformString to that before returning
|
|
441
|
-
if (transformTemplate) {
|
|
442
|
-
transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
|
|
443
|
-
}
|
|
444
|
-
else if (transformIsDefault) {
|
|
445
|
-
transformString = "none";
|
|
446
|
-
}
|
|
447
|
-
return transformString;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function buildHTMLStyles(state, latestValues, transformTemplate) {
|
|
451
|
-
const { style, vars, transformOrigin } = state;
|
|
452
|
-
// Track whether we encounter any transform or transformOrigin values.
|
|
453
|
-
let hasTransform = false;
|
|
454
|
-
let hasTransformOrigin = false;
|
|
455
|
-
/**
|
|
456
|
-
* Loop over all our latest animated values and decide whether to handle them
|
|
457
|
-
* as a style or CSS variable.
|
|
458
|
-
*
|
|
459
|
-
* Transforms and transform origins are kept separately for further processing.
|
|
460
|
-
*/
|
|
461
|
-
for (const key in latestValues) {
|
|
462
|
-
const value = latestValues[key];
|
|
463
|
-
if (motionDom.transformProps.has(key)) {
|
|
464
|
-
// If this is a transform, flag to enable further transform processing
|
|
465
|
-
hasTransform = true;
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
else if (motionDom.isCSSVariableName(key)) {
|
|
469
|
-
vars[key] = value;
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
// Convert the value to its default value type, ie 0 -> "0px"
|
|
474
|
-
const valueAsType = motionDom.getValueAsType(value, motionDom.numberValueTypes[key]);
|
|
475
|
-
if (key.startsWith("origin")) {
|
|
476
|
-
// If this is a transform origin, flag and enable further transform-origin processing
|
|
477
|
-
hasTransformOrigin = true;
|
|
478
|
-
transformOrigin[key] =
|
|
479
|
-
valueAsType;
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
style[key] = valueAsType;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (!latestValues.transform) {
|
|
487
|
-
if (hasTransform || transformTemplate) {
|
|
488
|
-
style.transform = buildTransform(latestValues, state.transform, transformTemplate);
|
|
489
|
-
}
|
|
490
|
-
else if (style.transform) {
|
|
491
|
-
/**
|
|
492
|
-
* If we have previously created a transform but currently don't have any,
|
|
493
|
-
* reset transform style to none.
|
|
494
|
-
*/
|
|
495
|
-
style.transform = "none";
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Build a transformOrigin style. Uses the same defaults as the browser for
|
|
500
|
-
* undefined origins.
|
|
501
|
-
*/
|
|
502
|
-
if (hasTransformOrigin) {
|
|
503
|
-
const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
|
|
504
|
-
style.transformOrigin = `${originX} ${originY} ${originZ}`;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const createHtmlRenderState = () => ({
|
|
509
|
-
style: {},
|
|
510
|
-
transform: {},
|
|
511
|
-
transformOrigin: {},
|
|
512
|
-
vars: {},
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
function copyRawValuesOnly(target, source, props) {
|
|
516
|
-
for (const key in source) {
|
|
517
|
-
if (!motionDom.isMotionValue(source[key]) && !isForcedMotionValue(key, props)) {
|
|
518
|
-
target[key] = source[key];
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function useInitialMotionValues({ transformTemplate }, visualState) {
|
|
523
|
-
return react.useMemo(() => {
|
|
524
|
-
const state = createHtmlRenderState();
|
|
525
|
-
buildHTMLStyles(state, visualState, transformTemplate);
|
|
526
|
-
return Object.assign({}, state.vars, state.style);
|
|
527
|
-
}, [visualState]);
|
|
528
|
-
}
|
|
529
|
-
function useStyle(props, visualState) {
|
|
530
|
-
const styleProp = props.style || {};
|
|
531
|
-
const style = {};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function useInitialMotionValues({ transformTemplate }, visualState) {
|
|
217
|
+
return react.useMemo(() => {
|
|
218
|
+
const state = createHtmlRenderState();
|
|
219
|
+
buildHTMLStyles(state, visualState, transformTemplate);
|
|
220
|
+
return Object.assign({}, state.vars, state.style);
|
|
221
|
+
}, [visualState]);
|
|
222
|
+
}
|
|
223
|
+
function useStyle(props, visualState) {
|
|
224
|
+
const styleProp = props.style || {};
|
|
225
|
+
const style = {};
|
|
532
226
|
/**
|
|
533
227
|
* Copy non-Motion Values straight into style
|
|
534
228
|
*/
|
|
@@ -608,451 +302,738 @@ function buildSVGAttrs(state, { attrX, attrY, attrScale, pathLength, pathSpacing
|
|
|
608
302
|
}
|
|
609
303
|
return;
|
|
610
304
|
}
|
|
611
|
-
state.attrs = state.style;
|
|
612
|
-
state.style = {};
|
|
613
|
-
const { attrs, style } = state;
|
|
305
|
+
state.attrs = state.style;
|
|
306
|
+
state.style = {};
|
|
307
|
+
const { attrs, style } = state;
|
|
308
|
+
/**
|
|
309
|
+
* However, we apply transforms as CSS transforms.
|
|
310
|
+
* So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
|
|
311
|
+
*/
|
|
312
|
+
if (attrs.transform) {
|
|
313
|
+
style.transform = attrs.transform;
|
|
314
|
+
delete attrs.transform;
|
|
315
|
+
}
|
|
316
|
+
if (style.transform || attrs.transformOrigin) {
|
|
317
|
+
style.transformOrigin = attrs.transformOrigin ?? "50% 50%";
|
|
318
|
+
delete attrs.transformOrigin;
|
|
319
|
+
}
|
|
320
|
+
if (style.transform) {
|
|
321
|
+
/**
|
|
322
|
+
* SVG's element transform-origin uses its own median as a reference.
|
|
323
|
+
* Therefore, transformBox becomes a fill-box
|
|
324
|
+
*/
|
|
325
|
+
style.transformBox = styleProp?.transformBox ?? "fill-box";
|
|
326
|
+
delete attrs.transformBox;
|
|
327
|
+
}
|
|
328
|
+
// Render attrX/attrY/attrScale as attributes
|
|
329
|
+
if (attrX !== undefined)
|
|
330
|
+
attrs.x = attrX;
|
|
331
|
+
if (attrY !== undefined)
|
|
332
|
+
attrs.y = attrY;
|
|
333
|
+
if (attrScale !== undefined)
|
|
334
|
+
attrs.scale = attrScale;
|
|
335
|
+
// Build SVG path if one has been defined
|
|
336
|
+
if (pathLength !== undefined) {
|
|
337
|
+
buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const createSvgRenderState = () => ({
|
|
342
|
+
...createHtmlRenderState(),
|
|
343
|
+
attrs: {},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
|
|
347
|
+
|
|
348
|
+
function useSVGProps(props, visualState, _isStatic, Component) {
|
|
349
|
+
const visualProps = react.useMemo(() => {
|
|
350
|
+
const state = createSvgRenderState();
|
|
351
|
+
buildSVGAttrs(state, visualState, isSVGTag(Component), props.transformTemplate, props.style);
|
|
352
|
+
return {
|
|
353
|
+
...state.attrs,
|
|
354
|
+
style: { ...state.style },
|
|
355
|
+
};
|
|
356
|
+
}, [visualState]);
|
|
357
|
+
if (props.style) {
|
|
358
|
+
const rawStyles = {};
|
|
359
|
+
copyRawValuesOnly(rawStyles, props.style, props);
|
|
360
|
+
visualProps.style = { ...rawStyles, ...visualProps.style };
|
|
361
|
+
}
|
|
362
|
+
return visualProps;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* A list of all valid MotionProps.
|
|
367
|
+
*
|
|
368
|
+
* @privateRemarks
|
|
369
|
+
* This doesn't throw if a `MotionProp` name is missing - it should.
|
|
370
|
+
*/
|
|
371
|
+
const validMotionProps = new Set([
|
|
372
|
+
"animate",
|
|
373
|
+
"exit",
|
|
374
|
+
"variants",
|
|
375
|
+
"initial",
|
|
376
|
+
"style",
|
|
377
|
+
"values",
|
|
378
|
+
"variants",
|
|
379
|
+
"transition",
|
|
380
|
+
"transformTemplate",
|
|
381
|
+
"custom",
|
|
382
|
+
"inherit",
|
|
383
|
+
"onBeforeLayoutMeasure",
|
|
384
|
+
"onAnimationStart",
|
|
385
|
+
"onAnimationComplete",
|
|
386
|
+
"onUpdate",
|
|
387
|
+
"onDragStart",
|
|
388
|
+
"onDrag",
|
|
389
|
+
"onDragEnd",
|
|
390
|
+
"onMeasureDragConstraints",
|
|
391
|
+
"onDirectionLock",
|
|
392
|
+
"onDragTransitionEnd",
|
|
393
|
+
"_dragX",
|
|
394
|
+
"_dragY",
|
|
395
|
+
"onHoverStart",
|
|
396
|
+
"onHoverEnd",
|
|
397
|
+
"onViewportEnter",
|
|
398
|
+
"onViewportLeave",
|
|
399
|
+
"globalTapTarget",
|
|
400
|
+
"ignoreStrict",
|
|
401
|
+
"viewport",
|
|
402
|
+
]);
|
|
403
|
+
/**
|
|
404
|
+
* Check whether a prop name is a valid `MotionProp` key.
|
|
405
|
+
*
|
|
406
|
+
* @param key - Name of the property to check
|
|
407
|
+
* @returns `true` is key is a valid `MotionProp`.
|
|
408
|
+
*
|
|
409
|
+
* @public
|
|
410
|
+
*/
|
|
411
|
+
function isValidMotionProp(key) {
|
|
412
|
+
return (key.startsWith("while") ||
|
|
413
|
+
(key.startsWith("drag") && key !== "draggable") ||
|
|
414
|
+
key.startsWith("layout") ||
|
|
415
|
+
key.startsWith("onTap") ||
|
|
416
|
+
key.startsWith("onPan") ||
|
|
417
|
+
key.startsWith("onLayout") ||
|
|
418
|
+
validMotionProps.has(key));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let shouldForward = (key) => !isValidMotionProp(key);
|
|
422
|
+
function loadExternalIsValidProp(isValidProp) {
|
|
423
|
+
if (typeof isValidProp !== "function")
|
|
424
|
+
return;
|
|
425
|
+
// Explicitly filter our events
|
|
426
|
+
shouldForward = (key) => key.startsWith("on") ? !isValidMotionProp(key) : isValidProp(key);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Emotion and Styled Components both allow users to pass through arbitrary props to their components
|
|
430
|
+
* to dynamically generate CSS. They both use the `@emotion/is-prop-valid` package to determine which
|
|
431
|
+
* of these should be passed to the underlying DOM node.
|
|
432
|
+
*
|
|
433
|
+
* However, when styling a Motion component `styled(motion.div)`, both packages pass through *all* props
|
|
434
|
+
* as it's seen as an arbitrary component rather than a DOM node. Motion only allows arbitrary props
|
|
435
|
+
* passed through the `custom` prop so it doesn't *need* the payload or computational overhead of
|
|
436
|
+
* `@emotion/is-prop-valid`, however to fix this problem we need to use it.
|
|
437
|
+
*
|
|
438
|
+
* By making it an optionalDependency we can offer this functionality only in the situations where it's
|
|
439
|
+
* actually required.
|
|
440
|
+
*/
|
|
441
|
+
try {
|
|
442
|
+
/**
|
|
443
|
+
* We attempt to import this package but require won't be defined in esm environments, in that case
|
|
444
|
+
* isPropValid will have to be provided via `MotionContext`. In a 6.0.0 this should probably be removed
|
|
445
|
+
* in favour of explicit injection.
|
|
446
|
+
*/
|
|
447
|
+
loadExternalIsValidProp(require("@emotion/is-prop-valid").default);
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
// We don't need to actually do anything here - the fallback is the existing `isPropValid`.
|
|
451
|
+
}
|
|
452
|
+
function filterProps(props, isDom, forwardMotionProps) {
|
|
453
|
+
const filteredProps = {};
|
|
454
|
+
for (const key in props) {
|
|
455
|
+
/**
|
|
456
|
+
* values is considered a valid prop by Emotion, so if it's present
|
|
457
|
+
* this will be rendered out to the DOM unless explicitly filtered.
|
|
458
|
+
*
|
|
459
|
+
* We check the type as it could be used with the `feColorMatrix`
|
|
460
|
+
* element, which we support.
|
|
461
|
+
*/
|
|
462
|
+
if (key === "values" && typeof props.values === "object")
|
|
463
|
+
continue;
|
|
464
|
+
if (shouldForward(key) ||
|
|
465
|
+
(forwardMotionProps === true && isValidMotionProp(key)) ||
|
|
466
|
+
(!isDom && !isValidMotionProp(key)) ||
|
|
467
|
+
// If trying to use native HTML drag events, forward drag listeners
|
|
468
|
+
(props["draggable"] &&
|
|
469
|
+
key.startsWith("onDrag"))) {
|
|
470
|
+
filteredProps[key] =
|
|
471
|
+
props[key];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return filteredProps;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* We keep these listed separately as we use the lowercase tag names as part
|
|
479
|
+
* of the runtime bundle to detect SVG components
|
|
480
|
+
*/
|
|
481
|
+
const lowercaseSVGElements = [
|
|
482
|
+
"animate",
|
|
483
|
+
"circle",
|
|
484
|
+
"defs",
|
|
485
|
+
"desc",
|
|
486
|
+
"ellipse",
|
|
487
|
+
"g",
|
|
488
|
+
"image",
|
|
489
|
+
"line",
|
|
490
|
+
"filter",
|
|
491
|
+
"marker",
|
|
492
|
+
"mask",
|
|
493
|
+
"metadata",
|
|
494
|
+
"path",
|
|
495
|
+
"pattern",
|
|
496
|
+
"polygon",
|
|
497
|
+
"polyline",
|
|
498
|
+
"rect",
|
|
499
|
+
"stop",
|
|
500
|
+
"switch",
|
|
501
|
+
"symbol",
|
|
502
|
+
"svg",
|
|
503
|
+
"text",
|
|
504
|
+
"tspan",
|
|
505
|
+
"use",
|
|
506
|
+
"view",
|
|
507
|
+
];
|
|
508
|
+
|
|
509
|
+
function isSVGComponent(Component) {
|
|
510
|
+
if (
|
|
511
|
+
/**
|
|
512
|
+
* If it's not a string, it's a custom React component. Currently we only support
|
|
513
|
+
* HTML custom React components.
|
|
514
|
+
*/
|
|
515
|
+
typeof Component !== "string" ||
|
|
516
|
+
/**
|
|
517
|
+
* If it contains a dash, the element is a custom HTML webcomponent.
|
|
518
|
+
*/
|
|
519
|
+
Component.includes("-")) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
else if (
|
|
614
523
|
/**
|
|
615
|
-
*
|
|
616
|
-
* So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
|
|
524
|
+
* If it's in our list of lowercase SVG tags, it's an SVG component
|
|
617
525
|
*/
|
|
618
|
-
|
|
619
|
-
style.transform = attrs.transform;
|
|
620
|
-
delete attrs.transform;
|
|
621
|
-
}
|
|
622
|
-
if (style.transform || attrs.transformOrigin) {
|
|
623
|
-
style.transformOrigin = attrs.transformOrigin ?? "50% 50%";
|
|
624
|
-
delete attrs.transformOrigin;
|
|
625
|
-
}
|
|
626
|
-
if (style.transform) {
|
|
526
|
+
lowercaseSVGElements.indexOf(Component) > -1 ||
|
|
627
527
|
/**
|
|
628
|
-
*
|
|
629
|
-
* Therefore, transformBox becomes a fill-box
|
|
528
|
+
* If it contains a capital letter, it's an SVG component
|
|
630
529
|
*/
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
// Render attrX/attrY/attrScale as attributes
|
|
635
|
-
if (attrX !== undefined)
|
|
636
|
-
attrs.x = attrX;
|
|
637
|
-
if (attrY !== undefined)
|
|
638
|
-
attrs.y = attrY;
|
|
639
|
-
if (attrScale !== undefined)
|
|
640
|
-
attrs.scale = attrScale;
|
|
641
|
-
// Build SVG path if one has been defined
|
|
642
|
-
if (pathLength !== undefined) {
|
|
643
|
-
buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
|
|
530
|
+
/[A-Z]/u.test(Component)) {
|
|
531
|
+
return true;
|
|
644
532
|
}
|
|
533
|
+
return false;
|
|
645
534
|
}
|
|
646
535
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
536
|
+
function useRender(Component, props, ref, { latestValues, }, isStatic, forwardMotionProps = false) {
|
|
537
|
+
const useVisualProps = isSVGComponent(Component)
|
|
538
|
+
? useSVGProps
|
|
539
|
+
: useHTMLProps;
|
|
540
|
+
const visualProps = useVisualProps(props, latestValues, isStatic, Component);
|
|
541
|
+
const filteredProps = filterProps(props, typeof Component === "string", forwardMotionProps);
|
|
542
|
+
const elementProps = Component !== react.Fragment ? { ...filteredProps, ...visualProps, ref } : {};
|
|
543
|
+
/**
|
|
544
|
+
* If component has been handed a motion value as its child,
|
|
545
|
+
* memoise its initial value and render that. Subsequent updates
|
|
546
|
+
* will be handled by the onChange handler
|
|
547
|
+
*/
|
|
548
|
+
const { children } = props;
|
|
549
|
+
const renderedChildren = react.useMemo(() => (motionDom.isMotionValue(children) ? children.get() : children), [children]);
|
|
550
|
+
return react.createElement(Component, {
|
|
551
|
+
...elementProps,
|
|
552
|
+
children: renderedChildren,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
651
555
|
|
|
652
|
-
|
|
556
|
+
/**
|
|
557
|
+
* @public
|
|
558
|
+
*/
|
|
559
|
+
const PresenceContext =
|
|
560
|
+
/* @__PURE__ */ react.createContext(null);
|
|
653
561
|
|
|
654
|
-
function
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
562
|
+
function getValueState(visualElement) {
|
|
563
|
+
const state = [{}, {}];
|
|
564
|
+
visualElement?.values.forEach((value, key) => {
|
|
565
|
+
state[0][key] = value.get();
|
|
566
|
+
state[1][key] = value.getVelocity();
|
|
567
|
+
});
|
|
568
|
+
return state;
|
|
569
|
+
}
|
|
570
|
+
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
571
|
+
/**
|
|
572
|
+
* If the variant definition is a function, resolve.
|
|
573
|
+
*/
|
|
574
|
+
if (typeof definition === "function") {
|
|
575
|
+
const [current, velocity] = getValueState(visualElement);
|
|
576
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
667
577
|
}
|
|
668
|
-
|
|
578
|
+
/**
|
|
579
|
+
* If the variant definition is a variant label, or
|
|
580
|
+
* the function returned a variant label, resolve.
|
|
581
|
+
*/
|
|
582
|
+
if (typeof definition === "string") {
|
|
583
|
+
definition = props.variants && props.variants[definition];
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* At this point we've resolved both functions and variant labels,
|
|
587
|
+
* but the resolved variant label might itself have been a function.
|
|
588
|
+
* If so, resolve. This can only have returned a valid target object.
|
|
589
|
+
*/
|
|
590
|
+
if (typeof definition === "function") {
|
|
591
|
+
const [current, velocity] = getValueState(visualElement);
|
|
592
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
593
|
+
}
|
|
594
|
+
return definition;
|
|
669
595
|
}
|
|
670
596
|
|
|
671
597
|
/**
|
|
672
|
-
*
|
|
598
|
+
* Creates a constant value over the lifecycle of a component.
|
|
673
599
|
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
600
|
+
* Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
|
|
601
|
+
* a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
|
|
602
|
+
* you can ensure that initialisers don't execute twice or more.
|
|
676
603
|
*/
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
"transition",
|
|
686
|
-
"transformTemplate",
|
|
687
|
-
"custom",
|
|
688
|
-
"inherit",
|
|
689
|
-
"onBeforeLayoutMeasure",
|
|
690
|
-
"onAnimationStart",
|
|
691
|
-
"onAnimationComplete",
|
|
692
|
-
"onUpdate",
|
|
693
|
-
"onDragStart",
|
|
694
|
-
"onDrag",
|
|
695
|
-
"onDragEnd",
|
|
696
|
-
"onMeasureDragConstraints",
|
|
697
|
-
"onDirectionLock",
|
|
698
|
-
"onDragTransitionEnd",
|
|
699
|
-
"_dragX",
|
|
700
|
-
"_dragY",
|
|
701
|
-
"onHoverStart",
|
|
702
|
-
"onHoverEnd",
|
|
703
|
-
"onViewportEnter",
|
|
704
|
-
"onViewportLeave",
|
|
705
|
-
"globalTapTarget",
|
|
706
|
-
"ignoreStrict",
|
|
707
|
-
"viewport",
|
|
708
|
-
]);
|
|
604
|
+
function useConstant(init) {
|
|
605
|
+
const ref = react.useRef(null);
|
|
606
|
+
if (ref.current === null) {
|
|
607
|
+
ref.current = init();
|
|
608
|
+
}
|
|
609
|
+
return ref.current;
|
|
610
|
+
}
|
|
611
|
+
|
|
709
612
|
/**
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
* @param key - Name of the property to check
|
|
713
|
-
* @returns `true` is key is a valid `MotionProp`.
|
|
613
|
+
* If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
|
|
714
614
|
*
|
|
715
|
-
*
|
|
615
|
+
* TODO: Remove and move to library
|
|
716
616
|
*/
|
|
717
|
-
function
|
|
718
|
-
return (
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
617
|
+
function resolveMotionValue(value) {
|
|
618
|
+
return motionDom.isMotionValue(value) ? value.get() : value;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function makeState({ scrapeMotionValuesFromProps, createRenderState, }, props, context, presenceContext) {
|
|
622
|
+
const state = {
|
|
623
|
+
latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
|
|
624
|
+
renderState: createRenderState(),
|
|
625
|
+
};
|
|
626
|
+
return state;
|
|
627
|
+
}
|
|
628
|
+
function makeLatestValues(props, context, presenceContext, scrapeMotionValues) {
|
|
629
|
+
const values = {};
|
|
630
|
+
const motionValues = scrapeMotionValues(props, {});
|
|
631
|
+
for (const key in motionValues) {
|
|
632
|
+
values[key] = resolveMotionValue(motionValues[key]);
|
|
633
|
+
}
|
|
634
|
+
let { initial, animate } = props;
|
|
635
|
+
const isControllingVariants$1 = isControllingVariants(props);
|
|
636
|
+
const isVariantNode$1 = isVariantNode(props);
|
|
637
|
+
if (context &&
|
|
638
|
+
isVariantNode$1 &&
|
|
639
|
+
!isControllingVariants$1 &&
|
|
640
|
+
props.inherit !== false) {
|
|
641
|
+
if (initial === undefined)
|
|
642
|
+
initial = context.initial;
|
|
643
|
+
if (animate === undefined)
|
|
644
|
+
animate = context.animate;
|
|
645
|
+
}
|
|
646
|
+
let isInitialAnimationBlocked = presenceContext
|
|
647
|
+
? presenceContext.initial === false
|
|
648
|
+
: false;
|
|
649
|
+
isInitialAnimationBlocked = isInitialAnimationBlocked || initial === false;
|
|
650
|
+
const variantToSet = isInitialAnimationBlocked ? animate : initial;
|
|
651
|
+
if (variantToSet &&
|
|
652
|
+
typeof variantToSet !== "boolean" &&
|
|
653
|
+
!isAnimationControls(variantToSet)) {
|
|
654
|
+
const list = Array.isArray(variantToSet) ? variantToSet : [variantToSet];
|
|
655
|
+
for (let i = 0; i < list.length; i++) {
|
|
656
|
+
const resolved = resolveVariantFromProps(props, list[i]);
|
|
657
|
+
if (resolved) {
|
|
658
|
+
const { transitionEnd, transition, ...target } = resolved;
|
|
659
|
+
for (const key in target) {
|
|
660
|
+
let valueTarget = target[key];
|
|
661
|
+
if (Array.isArray(valueTarget)) {
|
|
662
|
+
/**
|
|
663
|
+
* Take final keyframe if the initial animation is blocked because
|
|
664
|
+
* we want to initialise at the end of that blocked animation.
|
|
665
|
+
*/
|
|
666
|
+
const index = isInitialAnimationBlocked
|
|
667
|
+
? valueTarget.length - 1
|
|
668
|
+
: 0;
|
|
669
|
+
valueTarget = valueTarget[index];
|
|
670
|
+
}
|
|
671
|
+
if (valueTarget !== null) {
|
|
672
|
+
values[key] = valueTarget;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
for (const key in transitionEnd) {
|
|
676
|
+
values[key] = transitionEnd[key];
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return values;
|
|
682
|
+
}
|
|
683
|
+
const makeUseVisualState = (config) => (props, isStatic) => {
|
|
684
|
+
const context = react.useContext(MotionContext);
|
|
685
|
+
const presenceContext = react.useContext(PresenceContext);
|
|
686
|
+
const make = () => makeState(config, props, context, presenceContext);
|
|
687
|
+
return isStatic ? make() : useConstant(make);
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) {
|
|
691
|
+
const { style } = props;
|
|
692
|
+
const newValues = {};
|
|
693
|
+
for (const key in style) {
|
|
694
|
+
if (motionDom.isMotionValue(style[key]) ||
|
|
695
|
+
(prevProps.style &&
|
|
696
|
+
motionDom.isMotionValue(prevProps.style[key])) ||
|
|
697
|
+
isForcedMotionValue(key, props) ||
|
|
698
|
+
visualElement?.getValue(key)?.liveStyle !== undefined) {
|
|
699
|
+
newValues[key] = style[key];
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return newValues;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const useHTMLVisualState = /*@__PURE__*/ makeUseVisualState({
|
|
706
|
+
scrapeMotionValuesFromProps: scrapeMotionValuesFromProps$1,
|
|
707
|
+
createRenderState: createHtmlRenderState,
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
|
|
711
|
+
const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
|
|
712
|
+
for (const key in props) {
|
|
713
|
+
if (motionDom.isMotionValue(props[key]) ||
|
|
714
|
+
motionDom.isMotionValue(prevProps[key])) {
|
|
715
|
+
const targetKey = motionDom.transformPropOrder.indexOf(key) !== -1
|
|
716
|
+
? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
|
|
717
|
+
: key;
|
|
718
|
+
newValues[targetKey] = props[key];
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return newValues;
|
|
725
722
|
}
|
|
726
723
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
724
|
+
const useSVGVisualState = /*@__PURE__*/ makeUseVisualState({
|
|
725
|
+
scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
|
|
726
|
+
createRenderState: createSvgRenderState,
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
const isBrowser = typeof window !== "undefined";
|
|
730
|
+
|
|
731
|
+
const featureProps = {
|
|
732
|
+
animation: [
|
|
733
|
+
"animate",
|
|
734
|
+
"variants",
|
|
735
|
+
"whileHover",
|
|
736
|
+
"whileTap",
|
|
737
|
+
"exit",
|
|
738
|
+
"whileInView",
|
|
739
|
+
"whileFocus",
|
|
740
|
+
"whileDrag",
|
|
741
|
+
],
|
|
742
|
+
exit: ["exit"],
|
|
743
|
+
drag: ["drag", "dragControls"],
|
|
744
|
+
focus: ["whileFocus"],
|
|
745
|
+
hover: ["whileHover", "onHoverStart", "onHoverEnd"],
|
|
746
|
+
tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
|
|
747
|
+
pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
|
|
748
|
+
inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
|
|
749
|
+
layout: ["layout", "layoutId"],
|
|
750
|
+
};
|
|
751
|
+
const featureDefinitions = {};
|
|
752
|
+
for (const key in featureProps) {
|
|
753
|
+
featureDefinitions[key] = {
|
|
754
|
+
isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function loadFeatures(features) {
|
|
759
|
+
for (const key in features) {
|
|
760
|
+
featureDefinitions[key] = {
|
|
761
|
+
...featureDefinitions[key],
|
|
762
|
+
...features[key],
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const motionComponentSymbol = Symbol.for("motionComponentSymbol");
|
|
768
|
+
|
|
769
|
+
function isRefObject(ref) {
|
|
770
|
+
return (ref &&
|
|
771
|
+
typeof ref === "object" &&
|
|
772
|
+
Object.prototype.hasOwnProperty.call(ref, "current"));
|
|
733
773
|
}
|
|
774
|
+
|
|
734
775
|
/**
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
* of these should be passed to the underlying DOM node.
|
|
738
|
-
*
|
|
739
|
-
* However, when styling a Motion component `styled(motion.div)`, both packages pass through *all* props
|
|
740
|
-
* as it's seen as an arbitrary component rather than a DOM node. Motion only allows arbitrary props
|
|
741
|
-
* passed through the `custom` prop so it doesn't *need* the payload or computational overhead of
|
|
742
|
-
* `@emotion/is-prop-valid`, however to fix this problem we need to use it.
|
|
743
|
-
*
|
|
744
|
-
* By making it an optionalDependency we can offer this functionality only in the situations where it's
|
|
745
|
-
* actually required.
|
|
776
|
+
* Creates a ref function that, when called, hydrates the provided
|
|
777
|
+
* external ref and VisualElement.
|
|
746
778
|
*/
|
|
747
|
-
|
|
779
|
+
function useMotionRef(visualState, visualElement, externalRef) {
|
|
780
|
+
return react.useCallback((instance) => {
|
|
781
|
+
if (instance) {
|
|
782
|
+
visualState.onMount && visualState.onMount(instance);
|
|
783
|
+
}
|
|
784
|
+
if (visualElement) {
|
|
785
|
+
if (instance) {
|
|
786
|
+
visualElement.mount(instance);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
visualElement.unmount();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (externalRef) {
|
|
793
|
+
if (typeof externalRef === "function") {
|
|
794
|
+
externalRef(instance);
|
|
795
|
+
}
|
|
796
|
+
else if (isRefObject(externalRef)) {
|
|
797
|
+
externalRef.current = instance;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
},
|
|
748
801
|
/**
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
802
|
+
* Only pass a new ref callback to React if we've received a visual element
|
|
803
|
+
* factory. Otherwise we'll be mounting/remounting every time externalRef
|
|
804
|
+
* or other dependencies change.
|
|
752
805
|
*/
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
// We don't need to actually do anything here - the fallback is the existing `isPropValid`.
|
|
757
|
-
}
|
|
758
|
-
function filterProps(props, isDom, forwardMotionProps) {
|
|
759
|
-
const filteredProps = {};
|
|
760
|
-
for (const key in props) {
|
|
761
|
-
/**
|
|
762
|
-
* values is considered a valid prop by Emotion, so if it's present
|
|
763
|
-
* this will be rendered out to the DOM unless explicitly filtered.
|
|
764
|
-
*
|
|
765
|
-
* We check the type as it could be used with the `feColorMatrix`
|
|
766
|
-
* element, which we support.
|
|
767
|
-
*/
|
|
768
|
-
if (key === "values" && typeof props.values === "object")
|
|
769
|
-
continue;
|
|
770
|
-
if (shouldForward(key) ||
|
|
771
|
-
(forwardMotionProps === true && isValidMotionProp(key)) ||
|
|
772
|
-
(!isDom && !isValidMotionProp(key)) ||
|
|
773
|
-
// If trying to use native HTML drag events, forward drag listeners
|
|
774
|
-
(props["draggable"] &&
|
|
775
|
-
key.startsWith("onDrag"))) {
|
|
776
|
-
filteredProps[key] =
|
|
777
|
-
props[key];
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return filteredProps;
|
|
806
|
+
[visualElement]);
|
|
781
807
|
}
|
|
782
808
|
|
|
783
809
|
/**
|
|
784
|
-
*
|
|
785
|
-
* of the runtime bundle to detect SVG components
|
|
810
|
+
* Convert camelCase to dash-case properties.
|
|
786
811
|
*/
|
|
787
|
-
const
|
|
788
|
-
"animate",
|
|
789
|
-
"circle",
|
|
790
|
-
"defs",
|
|
791
|
-
"desc",
|
|
792
|
-
"ellipse",
|
|
793
|
-
"g",
|
|
794
|
-
"image",
|
|
795
|
-
"line",
|
|
796
|
-
"filter",
|
|
797
|
-
"marker",
|
|
798
|
-
"mask",
|
|
799
|
-
"metadata",
|
|
800
|
-
"path",
|
|
801
|
-
"pattern",
|
|
802
|
-
"polygon",
|
|
803
|
-
"polyline",
|
|
804
|
-
"rect",
|
|
805
|
-
"stop",
|
|
806
|
-
"switch",
|
|
807
|
-
"symbol",
|
|
808
|
-
"svg",
|
|
809
|
-
"text",
|
|
810
|
-
"tspan",
|
|
811
|
-
"use",
|
|
812
|
-
"view",
|
|
813
|
-
];
|
|
812
|
+
const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
|
|
814
813
|
|
|
815
|
-
|
|
816
|
-
|
|
814
|
+
const optimizedAppearDataId = "framerAppearId";
|
|
815
|
+
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Internal, exported only for usage in Framer
|
|
819
|
+
*/
|
|
820
|
+
const SwitchLayoutGroupContext = react.createContext({});
|
|
821
|
+
|
|
822
|
+
const useIsomorphicLayoutEffect = isBrowser ? react.useLayoutEffect : react.useEffect;
|
|
823
|
+
|
|
824
|
+
function useVisualElement(Component, visualState, props, createVisualElement, ProjectionNodeConstructor) {
|
|
825
|
+
const { visualElement: parent } = react.useContext(MotionContext);
|
|
826
|
+
const lazyContext = react.useContext(LazyContext);
|
|
827
|
+
const presenceContext = react.useContext(PresenceContext);
|
|
828
|
+
const reducedMotionConfig = react.useContext(MotionConfigContext).reducedMotion;
|
|
829
|
+
const visualElementRef = react.useRef(null);
|
|
817
830
|
/**
|
|
818
|
-
* If
|
|
819
|
-
* HTML custom React components.
|
|
831
|
+
* If we haven't preloaded a renderer, check to see if we have one lazy-loaded
|
|
820
832
|
*/
|
|
821
|
-
|
|
833
|
+
createVisualElement =
|
|
834
|
+
createVisualElement ||
|
|
835
|
+
lazyContext.renderer;
|
|
836
|
+
if (!visualElementRef.current && createVisualElement) {
|
|
837
|
+
visualElementRef.current = createVisualElement(Component, {
|
|
838
|
+
visualState,
|
|
839
|
+
parent,
|
|
840
|
+
props,
|
|
841
|
+
presenceContext,
|
|
842
|
+
blockInitialAnimation: presenceContext
|
|
843
|
+
? presenceContext.initial === false
|
|
844
|
+
: false,
|
|
845
|
+
reducedMotionConfig,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
const visualElement = visualElementRef.current;
|
|
849
|
+
/**
|
|
850
|
+
* Load Motion gesture and animation features. These are rendered as renderless
|
|
851
|
+
* components so each feature can optionally make use of React lifecycle methods.
|
|
852
|
+
*/
|
|
853
|
+
const initialLayoutGroupConfig = react.useContext(SwitchLayoutGroupContext);
|
|
854
|
+
if (visualElement &&
|
|
855
|
+
!visualElement.projection &&
|
|
856
|
+
ProjectionNodeConstructor &&
|
|
857
|
+
(visualElement.type === "html" || visualElement.type === "svg")) {
|
|
858
|
+
createProjectionNode(visualElementRef.current, props, ProjectionNodeConstructor, initialLayoutGroupConfig);
|
|
859
|
+
}
|
|
860
|
+
const isMounted = react.useRef(false);
|
|
861
|
+
react.useInsertionEffect(() => {
|
|
822
862
|
/**
|
|
823
|
-
*
|
|
863
|
+
* Check the component has already mounted before calling
|
|
864
|
+
* `update` unnecessarily. This ensures we skip the initial update.
|
|
824
865
|
*/
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
866
|
+
if (visualElement && isMounted.current) {
|
|
867
|
+
visualElement.update(props, presenceContext);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
829
870
|
/**
|
|
830
|
-
*
|
|
871
|
+
* Cache this value as we want to know whether HandoffAppearAnimations
|
|
872
|
+
* was present on initial render - it will be deleted after this.
|
|
831
873
|
*/
|
|
832
|
-
|
|
874
|
+
const optimisedAppearId = props[optimizedAppearDataAttribute];
|
|
875
|
+
const wantsHandoff = react.useRef(Boolean(optimisedAppearId) &&
|
|
876
|
+
!window.MotionHandoffIsComplete?.(optimisedAppearId) &&
|
|
877
|
+
window.MotionHasOptimisedAnimation?.(optimisedAppearId));
|
|
878
|
+
useIsomorphicLayoutEffect(() => {
|
|
879
|
+
if (!visualElement)
|
|
880
|
+
return;
|
|
881
|
+
isMounted.current = true;
|
|
882
|
+
window.MotionIsMounted = true;
|
|
883
|
+
visualElement.updateFeatures();
|
|
884
|
+
visualElement.scheduleRenderMicrotask();
|
|
833
885
|
/**
|
|
834
|
-
*
|
|
886
|
+
* Ideally this function would always run in a useEffect.
|
|
887
|
+
*
|
|
888
|
+
* However, if we have optimised appear animations to handoff from,
|
|
889
|
+
* it needs to happen synchronously to ensure there's no flash of
|
|
890
|
+
* incorrect styles in the event of a hydration error.
|
|
891
|
+
*
|
|
892
|
+
* So if we detect a situtation where optimised appear animations
|
|
893
|
+
* are running, we use useLayoutEffect to trigger animations.
|
|
835
894
|
*/
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
895
|
+
if (wantsHandoff.current && visualElement.animationState) {
|
|
896
|
+
visualElement.animationState.animateChanges();
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
react.useEffect(() => {
|
|
900
|
+
if (!visualElement)
|
|
901
|
+
return;
|
|
902
|
+
if (!wantsHandoff.current && visualElement.animationState) {
|
|
903
|
+
visualElement.animationState.animateChanges();
|
|
904
|
+
}
|
|
905
|
+
if (wantsHandoff.current) {
|
|
906
|
+
// This ensures all future calls to animateChanges() in this component will run in useEffect
|
|
907
|
+
queueMicrotask(() => {
|
|
908
|
+
window.MotionHandoffMarkAsComplete?.(optimisedAppearId);
|
|
909
|
+
});
|
|
910
|
+
wantsHandoff.current = false;
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
return visualElement;
|
|
840
914
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
: {};
|
|
915
|
+
function createProjectionNode(visualElement, props, ProjectionNodeConstructor, initialPromotionConfig) {
|
|
916
|
+
const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, layoutCrossfade, } = props;
|
|
917
|
+
visualElement.projection = new ProjectionNodeConstructor(visualElement.latestValues, props["data-framer-portal-id"]
|
|
918
|
+
? undefined
|
|
919
|
+
: getClosestProjectingNode(visualElement.parent));
|
|
920
|
+
visualElement.projection.setOptions({
|
|
921
|
+
layoutId,
|
|
922
|
+
layout,
|
|
923
|
+
alwaysMeasureLayout: Boolean(drag) || (dragConstraints && isRefObject(dragConstraints)),
|
|
924
|
+
visualElement,
|
|
852
925
|
/**
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
926
|
+
* TODO: Update options in an effect. This could be tricky as it'll be too late
|
|
927
|
+
* to update by the time layout animations run.
|
|
928
|
+
* We also need to fix this safeToRemove by linking it up to the one returned by usePresence,
|
|
929
|
+
* ensuring it gets called if there's no potential layout animations.
|
|
930
|
+
*
|
|
856
931
|
*/
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
});
|
|
863
|
-
};
|
|
864
|
-
return useRender;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function getValueState(visualElement) {
|
|
868
|
-
const state = [{}, {}];
|
|
869
|
-
visualElement?.values.forEach((value, key) => {
|
|
870
|
-
state[0][key] = value.get();
|
|
871
|
-
state[1][key] = value.getVelocity();
|
|
932
|
+
animationType: typeof layout === "string" ? layout : "both",
|
|
933
|
+
initialPromotionConfig,
|
|
934
|
+
crossfade: layoutCrossfade,
|
|
935
|
+
layoutScroll,
|
|
936
|
+
layoutRoot,
|
|
872
937
|
});
|
|
873
|
-
return state;
|
|
874
938
|
}
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* If the variant definition is a variant label, or
|
|
885
|
-
* the function returned a variant label, resolve.
|
|
886
|
-
*/
|
|
887
|
-
if (typeof definition === "string") {
|
|
888
|
-
definition = props.variants && props.variants[definition];
|
|
889
|
-
}
|
|
890
|
-
/**
|
|
891
|
-
* At this point we've resolved both functions and variant labels,
|
|
892
|
-
* but the resolved variant label might itself have been a function.
|
|
893
|
-
* If so, resolve. This can only have returned a valid target object.
|
|
894
|
-
*/
|
|
895
|
-
if (typeof definition === "function") {
|
|
896
|
-
const [current, velocity] = getValueState(visualElement);
|
|
897
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
898
|
-
}
|
|
899
|
-
return definition;
|
|
939
|
+
function getClosestProjectingNode(visualElement) {
|
|
940
|
+
if (!visualElement)
|
|
941
|
+
return undefined;
|
|
942
|
+
return visualElement.options.allowProjection !== false
|
|
943
|
+
? visualElement.projection
|
|
944
|
+
: getClosestProjectingNode(visualElement.parent);
|
|
900
945
|
}
|
|
901
946
|
|
|
902
947
|
/**
|
|
903
|
-
*
|
|
948
|
+
* Create a `motion` component.
|
|
904
949
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
* you can ensure that initialisers don't execute twice or more.
|
|
908
|
-
*/
|
|
909
|
-
function useConstant(init) {
|
|
910
|
-
const ref = react.useRef(null);
|
|
911
|
-
if (ref.current === null) {
|
|
912
|
-
ref.current = init();
|
|
913
|
-
}
|
|
914
|
-
return ref.current;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
/**
|
|
918
|
-
* If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
|
|
950
|
+
* This function accepts a Component argument, which can be either a string (ie "div"
|
|
951
|
+
* for `motion.div`), or an actual React component.
|
|
919
952
|
*
|
|
920
|
-
*
|
|
953
|
+
* Alongside this is a config option which provides a way of rendering the provided
|
|
954
|
+
* component "offline", or outside the React render cycle.
|
|
921
955
|
*/
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (initial === undefined)
|
|
953
|
-
initial = context.initial;
|
|
954
|
-
if (animate === undefined)
|
|
955
|
-
animate = context.animate;
|
|
956
|
-
}
|
|
957
|
-
let isInitialAnimationBlocked = presenceContext
|
|
958
|
-
? presenceContext.initial === false
|
|
959
|
-
: false;
|
|
960
|
-
isInitialAnimationBlocked = isInitialAnimationBlocked || initial === false;
|
|
961
|
-
const variantToSet = isInitialAnimationBlocked ? animate : initial;
|
|
962
|
-
if (variantToSet &&
|
|
963
|
-
typeof variantToSet !== "boolean" &&
|
|
964
|
-
!isAnimationControls(variantToSet)) {
|
|
965
|
-
const list = Array.isArray(variantToSet) ? variantToSet : [variantToSet];
|
|
966
|
-
for (let i = 0; i < list.length; i++) {
|
|
967
|
-
const resolved = resolveVariantFromProps(props, list[i]);
|
|
968
|
-
if (resolved) {
|
|
969
|
-
const { transitionEnd, transition, ...target } = resolved;
|
|
970
|
-
for (const key in target) {
|
|
971
|
-
let valueTarget = target[key];
|
|
972
|
-
if (Array.isArray(valueTarget)) {
|
|
973
|
-
/**
|
|
974
|
-
* Take final keyframe if the initial animation is blocked because
|
|
975
|
-
* we want to initialise at the end of that blocked animation.
|
|
976
|
-
*/
|
|
977
|
-
const index = isInitialAnimationBlocked
|
|
978
|
-
? valueTarget.length - 1
|
|
979
|
-
: 0;
|
|
980
|
-
valueTarget = valueTarget[index];
|
|
981
|
-
}
|
|
982
|
-
if (valueTarget !== null) {
|
|
983
|
-
values[key] = valueTarget;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
for (const key in transitionEnd) {
|
|
987
|
-
values[key] = transitionEnd[key];
|
|
988
|
-
}
|
|
989
|
-
}
|
|
956
|
+
function createMotionComponent(Component, { forwardMotionProps = false } = {}, preloadedFeatures, createVisualElement) {
|
|
957
|
+
preloadedFeatures && loadFeatures(preloadedFeatures);
|
|
958
|
+
const useVisualState = isSVGComponent(Component)
|
|
959
|
+
? useSVGVisualState
|
|
960
|
+
: useHTMLVisualState;
|
|
961
|
+
function MotionDOMComponent(props, externalRef) {
|
|
962
|
+
/**
|
|
963
|
+
* If we need to measure the element we load this functionality in a
|
|
964
|
+
* separate class component in order to gain access to getSnapshotBeforeUpdate.
|
|
965
|
+
*/
|
|
966
|
+
let MeasureLayout;
|
|
967
|
+
const configAndProps = {
|
|
968
|
+
...react.useContext(MotionConfigContext),
|
|
969
|
+
...props,
|
|
970
|
+
layoutId: useLayoutId(props),
|
|
971
|
+
};
|
|
972
|
+
const { isStatic } = configAndProps;
|
|
973
|
+
const context = useCreateMotionContext(props);
|
|
974
|
+
const visualState = useVisualState(props, isStatic);
|
|
975
|
+
if (!isStatic && isBrowser) {
|
|
976
|
+
useStrictMode(configAndProps, preloadedFeatures);
|
|
977
|
+
const layoutProjection = getProjectionFunctionality(configAndProps);
|
|
978
|
+
MeasureLayout = layoutProjection.MeasureLayout;
|
|
979
|
+
/**
|
|
980
|
+
* Create a VisualElement for this component. A VisualElement provides a common
|
|
981
|
+
* interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
|
|
982
|
+
* providing a way of rendering to these APIs outside of the React render loop
|
|
983
|
+
* for more performant animations and interactions
|
|
984
|
+
*/
|
|
985
|
+
context.visualElement = useVisualElement(Component, visualState, configAndProps, createVisualElement, layoutProjection.ProjectionNode);
|
|
990
986
|
}
|
|
987
|
+
/**
|
|
988
|
+
* The mount order and hierarchy is specific to ensure our element ref
|
|
989
|
+
* is hydrated by the time features fire their effects.
|
|
990
|
+
*/
|
|
991
|
+
return (jsxRuntime.jsxs(MotionContext.Provider, { value: context, children: [MeasureLayout && context.visualElement ? (jsxRuntime.jsx(MeasureLayout, { visualElement: context.visualElement, ...configAndProps })) : null, useRender(Component, props, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, forwardMotionProps)] }));
|
|
991
992
|
}
|
|
992
|
-
|
|
993
|
+
MotionDOMComponent.displayName = `motion.${typeof Component === "string"
|
|
994
|
+
? Component
|
|
995
|
+
: `create(${Component.displayName ?? Component.name ?? ""})`}`;
|
|
996
|
+
const ForwardRefMotionComponent = react.forwardRef(MotionDOMComponent);
|
|
997
|
+
ForwardRefMotionComponent[motionComponentSymbol] = Component;
|
|
998
|
+
return ForwardRefMotionComponent;
|
|
993
999
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if (motionDom.isMotionValue(style[key]) ||
|
|
1000
|
-
(prevProps.style &&
|
|
1001
|
-
motionDom.isMotionValue(prevProps.style[key])) ||
|
|
1002
|
-
isForcedMotionValue(key, props) ||
|
|
1003
|
-
visualElement?.getValue(key)?.liveStyle !== undefined) {
|
|
1004
|
-
newValues[key] = style[key];
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
return newValues;
|
|
1000
|
+
function useLayoutId({ layoutId }) {
|
|
1001
|
+
const layoutGroupId = react.useContext(LayoutGroupContext).id;
|
|
1002
|
+
return layoutGroupId && layoutId !== undefined
|
|
1003
|
+
? layoutGroupId + "-" + layoutId
|
|
1004
|
+
: layoutId;
|
|
1008
1005
|
}
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const targetKey = motionDom.transformPropOrder.indexOf(key) !== -1
|
|
1023
|
-
? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
|
|
1024
|
-
: key;
|
|
1025
|
-
newValues[targetKey] = props[key];
|
|
1026
|
-
}
|
|
1006
|
+
function useStrictMode(configAndProps, preloadedFeatures) {
|
|
1007
|
+
const isStrict = react.useContext(LazyContext).strict;
|
|
1008
|
+
/**
|
|
1009
|
+
* If we're in development mode, check to make sure we're not rendering a motion component
|
|
1010
|
+
* as a child of LazyMotion, as this will break the file-size benefits of using it.
|
|
1011
|
+
*/
|
|
1012
|
+
if (process.env.NODE_ENV !== "production" &&
|
|
1013
|
+
preloadedFeatures &&
|
|
1014
|
+
isStrict) {
|
|
1015
|
+
const strictMessage = "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.";
|
|
1016
|
+
configAndProps.ignoreStrict
|
|
1017
|
+
? motionUtils.warning(false, strictMessage, "lazy-strict-mode")
|
|
1018
|
+
: motionUtils.invariant(false, strictMessage, "lazy-strict-mode");
|
|
1027
1019
|
}
|
|
1028
|
-
return newValues;
|
|
1029
1020
|
}
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const baseConfig = isSVGComponent(Component)
|
|
1041
|
-
? svgMotionConfig
|
|
1042
|
-
: htmlMotionConfig;
|
|
1043
|
-
const config = {
|
|
1044
|
-
...baseConfig,
|
|
1045
|
-
preloadedFeatures,
|
|
1046
|
-
useRender: createUseRender(forwardMotionProps),
|
|
1047
|
-
createVisualElement,
|
|
1048
|
-
Component,
|
|
1049
|
-
};
|
|
1050
|
-
return createRendererMotionComponent(config);
|
|
1021
|
+
function getProjectionFunctionality(props) {
|
|
1022
|
+
const { drag, layout } = featureDefinitions;
|
|
1023
|
+
if (!drag && !layout)
|
|
1024
|
+
return {};
|
|
1025
|
+
const combined = { ...drag, ...layout };
|
|
1026
|
+
return {
|
|
1027
|
+
MeasureLayout: drag?.isEnabled(props) || layout?.isEnabled(props)
|
|
1028
|
+
? combined.MeasureLayout
|
|
1029
|
+
: undefined,
|
|
1030
|
+
ProjectionNode: combined.ProjectionNode,
|
|
1051
1031
|
};
|
|
1052
1032
|
}
|
|
1053
1033
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1034
|
+
function createMinimalMotionComponent(Component, options) {
|
|
1035
|
+
return createMotionComponent(Component, options);
|
|
1036
|
+
}
|
|
1056
1037
|
|
|
1057
1038
|
/**
|
|
1058
1039
|
* HTML components
|