framer-motion 12.23.5 → 12.23.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cjs/client.js +171 -167
  2. package/dist/cjs/dom.js +4 -1
  3. package/dist/cjs/{create-Ch9BUY4E.js → feature-bundle-BoyRwN0C.js} +2322 -2340
  4. package/dist/cjs/index.js +120 -119
  5. package/dist/cjs/m.js +803 -822
  6. package/dist/dom.js +1 -1
  7. package/dist/es/client.mjs +1 -1
  8. package/dist/es/components/AnimatePresence/PopChild.mjs +0 -1
  9. package/dist/es/index.mjs +0 -1
  10. package/dist/es/motion/index.mjs +13 -6
  11. package/dist/es/motion/utils/use-visual-element.mjs +4 -3
  12. package/dist/es/motion/utils/use-visual-state.mjs +6 -6
  13. package/dist/es/render/VisualElement.mjs +5 -2
  14. package/dist/es/render/components/create-proxy.mjs +14 -7
  15. package/dist/es/render/components/m/create.mjs +4 -3
  16. package/dist/es/render/components/m/proxy.mjs +2 -3
  17. package/dist/es/render/components/motion/create.mjs +6 -12
  18. package/dist/es/render/components/motion/elements.mjs +165 -165
  19. package/dist/es/render/components/motion/feature-bundle.mjs +13 -0
  20. package/dist/es/render/components/motion/proxy.mjs +4 -3
  21. package/dist/es/render/dom/use-render.mjs +19 -24
  22. package/dist/es/render/html/{config-motion.mjs → use-html-visual-state.mjs} +6 -8
  23. package/dist/es/render/svg/{config-motion.mjs → use-svg-visual-state.mjs} +5 -7
  24. package/dist/es/utils/reduced-motion/use-reduced-motion.mjs +1 -1
  25. package/dist/framer-motion.dev.js +2372 -2386
  26. package/dist/framer-motion.js +1 -1
  27. package/dist/m.d.ts +15 -13
  28. package/dist/size-rollup-animate.js +1 -1
  29. package/dist/size-rollup-dom-animation-assets.js +1 -1
  30. package/dist/size-rollup-dom-animation-m.js +1 -1
  31. package/dist/size-rollup-dom-animation.js +1 -1
  32. package/dist/size-rollup-dom-max-assets.js +1 -1
  33. package/dist/size-rollup-dom-max.js +1 -1
  34. package/dist/size-rollup-m.js +1 -1
  35. package/dist/size-rollup-motion.js +1 -1
  36. package/dist/types/client.d.ts +5 -11
  37. package/dist/types/index.d.ts +7 -23
  38. package/dist/{types.d-D0HXPxHm.d.ts → types.d-Bq-Qm38R.d.ts} +263 -264
  39. package/package.json +4 -4
  40. 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 isBrowser = typeof window !== "undefined";
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 isRefObject(ref) {
119
- return (ref &&
120
- typeof ref === "object" &&
121
- Object.prototype.hasOwnProperty.call(ref, "current"));
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
- * Creates a ref function that, when called, hydrates the provided
126
- * external ref and VisualElement.
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 useMotionRef(visualState, visualElement, externalRef) {
129
- return react.useCallback((instance) => {
130
- if (instance) {
131
- visualState.onMount && visualState.onMount(instance);
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
- if (visualElement) {
134
- if (instance) {
135
- visualElement.mount(instance);
136
- }
137
- else {
138
- visualElement.unmount();
139
- }
117
+ else {
118
+ valueIsDefault = parseFloat(value) === 0;
140
119
  }
141
- if (externalRef) {
142
- if (typeof externalRef === "function") {
143
- externalRef(instance);
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
- else if (isRefObject(externalRef)) {
146
- externalRef.current = instance;
127
+ if (transformTemplate) {
128
+ transform[key] = valueAsType;
147
129
  }
148
130
  }
149
- },
150
- /**
151
- * Only pass a new ref callback to React if we've received a visual element
152
- * factory. Otherwise we'll be mounting/remounting every time externalRef
153
- * or other dependencies change.
154
- */
155
- [visualElement]);
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
- * Convert camelCase to dash-case properties.
160
- */
161
- const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
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
- * If we haven't preloaded a renderer, check to see if we have one lazy-loaded
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
- createVisualElement = createVisualElement || lazyContext.renderer;
189
- if (!visualElementRef.current && createVisualElement) {
190
- visualElementRef.current = createVisualElement(Component, {
191
- visualState,
192
- parent,
193
- props,
194
- presenceContext,
195
- blockInitialAnimation: presenceContext
196
- ? presenceContext.initial === false
197
- : false,
198
- reducedMotionConfig,
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
- * Load Motion gesture and animation features. These are rendered as renderless
204
- * components so each feature can optionally make use of React lifecycle methods.
193
+ * Build a transformOrigin style. Uses the same defaults as the browser for
194
+ * undefined origins.
205
195
  */
206
- const initialLayoutGroupConfig = react.useContext(SwitchLayoutGroupContext);
207
- if (visualElement &&
208
- !visualElement.projection &&
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
- const isMounted = react.useRef(false);
214
- react.useInsertionEffect(() => {
215
- /**
216
- * Check the component has already mounted before calling
217
- * `update` unnecessarily. This ensures we skip the initial update.
218
- */
219
- if (visualElement && isMounted.current) {
220
- visualElement.update(props, presenceContext);
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
- * Cache this value as we want to know whether HandoffAppearAnimations
225
- * was present on initial render - it will be deleted after this.
226
- */
227
- const optimisedAppearId = props[optimizedAppearDataAttribute];
228
- const wantsHandoff = react.useRef(Boolean(optimisedAppearId) &&
229
- !window.MotionHandoffIsComplete?.(optimisedAppearId) &&
230
- window.MotionHasOptimisedAnimation?.(optimisedAppearId));
231
- useIsomorphicLayoutEffect(() => {
232
- if (!visualElement)
233
- return;
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
- * However, we apply transforms as CSS transforms.
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
- if (attrs.transform) {
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
- * SVG's element transform-origin uses its own median as a reference.
629
- * Therefore, transformBox becomes a fill-box
528
+ * If it contains a capital letter, it's an SVG component
630
529
  */
631
- style.transformBox = styleProp?.transformBox ?? "fill-box";
632
- delete attrs.transformBox;
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
- const createSvgRenderState = () => ({
648
- ...createHtmlRenderState(),
649
- attrs: {},
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
- const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
556
+ /**
557
+ * @public
558
+ */
559
+ const PresenceContext =
560
+ /* @__PURE__ */ react.createContext(null);
653
561
 
654
- function useSVGProps(props, visualState, _isStatic, Component) {
655
- const visualProps = react.useMemo(() => {
656
- const state = createSvgRenderState();
657
- buildSVGAttrs(state, visualState, isSVGTag(Component), props.transformTemplate, props.style);
658
- return {
659
- ...state.attrs,
660
- style: { ...state.style },
661
- };
662
- }, [visualState]);
663
- if (props.style) {
664
- const rawStyles = {};
665
- copyRawValuesOnly(rawStyles, props.style, props);
666
- visualProps.style = { ...rawStyles, ...visualProps.style };
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
- return visualProps;
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
- * A list of all valid MotionProps.
598
+ * Creates a constant value over the lifecycle of a component.
673
599
  *
674
- * @privateRemarks
675
- * This doesn't throw if a `MotionProp` name is missing - it should.
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
- const validMotionProps = new Set([
678
- "animate",
679
- "exit",
680
- "variants",
681
- "initial",
682
- "style",
683
- "values",
684
- "variants",
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
- * Check whether a prop name is a valid `MotionProp` key.
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
- * @public
615
+ * TODO: Remove and move to library
716
616
  */
717
- function isValidMotionProp(key) {
718
- return (key.startsWith("while") ||
719
- (key.startsWith("drag") && key !== "draggable") ||
720
- key.startsWith("layout") ||
721
- key.startsWith("onTap") ||
722
- key.startsWith("onPan") ||
723
- key.startsWith("onLayout") ||
724
- validMotionProps.has(key));
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
- let shouldForward = (key) => !isValidMotionProp(key);
728
- function loadExternalIsValidProp(isValidProp) {
729
- if (typeof isValidProp !== "function")
730
- return;
731
- // Explicitly filter our events
732
- shouldForward = (key) => key.startsWith("on") ? !isValidMotionProp(key) : isValidProp(key);
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
- * Emotion and Styled Components both allow users to pass through arbitrary props to their components
736
- * to dynamically generate CSS. They both use the `@emotion/is-prop-valid` package to determine which
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
- try {
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
- * We attempt to import this package but require won't be defined in esm environments, in that case
750
- * isPropValid will have to be provided via `MotionContext`. In a 6.0.0 this should probably be removed
751
- * in favour of explicit injection.
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
- loadExternalIsValidProp(require("@emotion/is-prop-valid").default);
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
- * We keep these listed separately as we use the lowercase tag names as part
785
- * of the runtime bundle to detect SVG components
810
+ * Convert camelCase to dash-case properties.
786
811
  */
787
- const lowercaseSVGElements = [
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
- function isSVGComponent(Component) {
816
- if (
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 it's not a string, it's a custom React component. Currently we only support
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
- typeof Component !== "string" ||
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
- * If it contains a dash, the element is a custom HTML webcomponent.
863
+ * Check the component has already mounted before calling
864
+ * `update` unnecessarily. This ensures we skip the initial update.
824
865
  */
825
- Component.includes("-")) {
826
- return false;
827
- }
828
- else if (
866
+ if (visualElement && isMounted.current) {
867
+ visualElement.update(props, presenceContext);
868
+ }
869
+ });
829
870
  /**
830
- * If it's in our list of lowercase SVG tags, it's an SVG component
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
- lowercaseSVGElements.indexOf(Component) > -1 ||
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
- * If it contains a capital letter, it's an SVG component
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
- /[A-Z]/u.test(Component)) {
837
- return true;
838
- }
839
- return false;
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
- function createUseRender(forwardMotionProps = false) {
843
- const useRender = (Component, props, ref, { latestValues }, isStatic) => {
844
- const useVisualProps = isSVGComponent(Component)
845
- ? useSVGProps
846
- : useHTMLProps;
847
- const visualProps = useVisualProps(props, latestValues, isStatic, Component);
848
- const filteredProps = filterProps(props, typeof Component === "string", forwardMotionProps);
849
- const elementProps = Component !== react.Fragment
850
- ? { ...filteredProps, ...visualProps, ref }
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
- * If component has been handed a motion value as its child,
854
- * memoise its initial value and render that. Subsequent updates
855
- * will be handled by the onChange handler
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
- const { children } = props;
858
- const renderedChildren = react.useMemo(() => (motionDom.isMotionValue(children) ? children.get() : children), [children]);
859
- return react.createElement(Component, {
860
- ...elementProps,
861
- children: renderedChildren,
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 resolveVariantFromProps(props, definition, custom, visualElement) {
876
- /**
877
- * If the variant definition is a function, resolve.
878
- */
879
- if (typeof definition === "function") {
880
- const [current, velocity] = getValueState(visualElement);
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
- * Creates a constant value over the lifecycle of a component.
948
+ * Create a `motion` component.
904
949
  *
905
- * Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
906
- * a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
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
- * TODO: Remove and move to library
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 resolveMotionValue(value) {
923
- return motionDom.isMotionValue(value) ? value.get() : value;
924
- }
925
-
926
- function makeState({ scrapeMotionValuesFromProps, createRenderState, }, props, context, presenceContext) {
927
- const state = {
928
- latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
929
- renderState: createRenderState(),
930
- };
931
- return state;
932
- }
933
- const makeUseVisualState = (config) => (props, isStatic) => {
934
- const context = react.useContext(MotionContext);
935
- const presenceContext = react.useContext(PresenceContext);
936
- const make = () => makeState(config, props, context, presenceContext);
937
- return isStatic ? make() : useConstant(make);
938
- };
939
- function makeLatestValues(props, context, presenceContext, scrapeMotionValues) {
940
- const values = {};
941
- const motionValues = scrapeMotionValues(props, {});
942
- for (const key in motionValues) {
943
- values[key] = resolveMotionValue(motionValues[key]);
944
- }
945
- let { initial, animate } = props;
946
- const isControllingVariants$1 = isControllingVariants(props);
947
- const isVariantNode$1 = isVariantNode(props);
948
- if (context &&
949
- isVariantNode$1 &&
950
- !isControllingVariants$1 &&
951
- props.inherit !== false) {
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
- return values;
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
- function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) {
996
- const { style } = props;
997
- const newValues = {};
998
- for (const key in style) {
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 htmlMotionConfig = {
1011
- useVisualState: makeUseVisualState({
1012
- scrapeMotionValuesFromProps: scrapeMotionValuesFromProps$1,
1013
- createRenderState: createHtmlRenderState,
1014
- }),
1015
- };
1016
-
1017
- function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
1018
- const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
1019
- for (const key in props) {
1020
- if (motionDom.isMotionValue(props[key]) ||
1021
- motionDom.isMotionValue(prevProps[key])) {
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 svgMotionConfig = {
1032
- useVisualState: makeUseVisualState({
1033
- scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
1034
- createRenderState: createSvgRenderState,
1035
- }),
1036
- };
1037
-
1038
- function createMotionComponentFactory(preloadedFeatures, createVisualElement) {
1039
- return function createMotionComponent(Component, { forwardMotionProps } = { forwardMotionProps: false }) {
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
- const createMinimalMotionComponent =
1055
- /*@__PURE__*/ createMotionComponentFactory();
1034
+ function createMinimalMotionComponent(Component, options) {
1035
+ return createMotionComponent(Component, options);
1036
+ }
1056
1037
 
1057
1038
  /**
1058
1039
  * HTML components