framer-motion 12.23.15 → 12.23.18

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.
@@ -5037,6 +5037,60 @@
5037
5037
  reducedMotion: "never",
5038
5038
  });
5039
5039
 
5040
+ /**
5041
+ * Set a given ref to a given value
5042
+ * This utility takes care of different types of refs: callback refs and RefObject(s)
5043
+ */
5044
+ function setRef(ref, value) {
5045
+ if (typeof ref === "function") {
5046
+ return ref(value);
5047
+ }
5048
+ else if (ref !== null && ref !== undefined) {
5049
+ ref.current = value;
5050
+ }
5051
+ }
5052
+ /**
5053
+ * A utility to compose multiple refs together
5054
+ * Accepts callback refs and RefObject(s)
5055
+ */
5056
+ function composeRefs(...refs) {
5057
+ return (node) => {
5058
+ let hasCleanup = false;
5059
+ const cleanups = refs.map((ref) => {
5060
+ const cleanup = setRef(ref, node);
5061
+ if (!hasCleanup && typeof cleanup === "function") {
5062
+ hasCleanup = true;
5063
+ }
5064
+ return cleanup;
5065
+ });
5066
+ // React <19 will log an error to the console if a callback ref returns a
5067
+ // value. We don't use ref cleanups internally so this will only happen if a
5068
+ // user's ref callback returns a value, which we only expect if they are
5069
+ // using the cleanup functionality added in React 19.
5070
+ if (hasCleanup) {
5071
+ return () => {
5072
+ for (let i = 0; i < cleanups.length; i++) {
5073
+ const cleanup = cleanups[i];
5074
+ if (typeof cleanup === "function") {
5075
+ cleanup();
5076
+ }
5077
+ else {
5078
+ setRef(refs[i], null);
5079
+ }
5080
+ }
5081
+ };
5082
+ }
5083
+ };
5084
+ }
5085
+ /**
5086
+ * A custom hook that composes multiple refs
5087
+ * Accepts callback refs and RefObject(s)
5088
+ */
5089
+ function useComposedRefs(...refs) {
5090
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5091
+ return React__namespace.useCallback(composeRefs(...refs), refs);
5092
+ }
5093
+
5040
5094
  /**
5041
5095
  * Measurement functionality has to be within a separate component
5042
5096
  * to leverage snapshot lifecycle.
@@ -5077,6 +5131,7 @@
5077
5131
  right: 0,
5078
5132
  });
5079
5133
  const { nonce } = React$1.useContext(MotionConfigContext);
5134
+ const composedRef = useComposedRefs(ref, children?.ref);
5080
5135
  /**
5081
5136
  * We create and inject a style block so we can apply this explicit
5082
5137
  * sizing in a non-destructive manner by just deleting the style block.
@@ -5114,7 +5169,7 @@
5114
5169
  }
5115
5170
  };
5116
5171
  }, [isPresent]);
5117
- return (jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React__namespace.cloneElement(children, { ref }) }));
5172
+ return (jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React__namespace.cloneElement(children, { ref: composedRef }) }));
5118
5173
  }
5119
5174
 
5120
5175
  const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, root }) => {
@@ -9546,16 +9601,22 @@
9546
9601
  * external ref and VisualElement.
9547
9602
  */
9548
9603
  function useMotionRef(visualState, visualElement, externalRef) {
9604
+ const currentInstanceRef = React__namespace.useRef(null);
9549
9605
  return React$1.useCallback((instance) => {
9550
- if (instance) {
9551
- visualState.onMount && visualState.onMount(instance);
9552
- }
9553
- if (visualElement) {
9606
+ const prevInstance = currentInstanceRef.current;
9607
+ currentInstanceRef.current = instance;
9608
+ // Only run mount/unmount logic when the instance actually changes
9609
+ if (instance !== prevInstance) {
9554
9610
  if (instance) {
9555
- visualElement.mount(instance);
9611
+ visualState.onMount && visualState.onMount(instance);
9556
9612
  }
9557
- else {
9558
- visualElement.unmount();
9613
+ if (visualElement) {
9614
+ if (instance) {
9615
+ visualElement.mount(instance);
9616
+ }
9617
+ else {
9618
+ visualElement.unmount();
9619
+ }
9559
9620
  }
9560
9621
  }
9561
9622
  if (externalRef) {
@@ -9571,7 +9632,7 @@
9571
9632
  * Include externalRef in dependencies to ensure the callback updates
9572
9633
  * when the ref changes, allowing proper ref forwarding.
9573
9634
  */
9574
- [visualElement]);
9635
+ [visualElement, externalRef]);
9575
9636
  }
9576
9637
 
9577
9638
  /**
@@ -12412,13 +12473,22 @@
12412
12473
  */
12413
12474
  function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
12414
12475
  let animations = [];
12476
+ let animationOnComplete;
12415
12477
  if (isSequence(subjectOrSequence)) {
12416
12478
  animations = animateSequence(subjectOrSequence, optionsOrKeyframes, scope);
12417
12479
  }
12418
12480
  else {
12419
- animations = animateSubject(subjectOrSequence, optionsOrKeyframes, options, scope);
12481
+ // Extract top-level onComplete so it doesn't get applied per-value
12482
+ const { onComplete, ...rest } = options || {};
12483
+ if (typeof onComplete === "function") {
12484
+ animationOnComplete = onComplete;
12485
+ }
12486
+ animations = animateSubject(subjectOrSequence, optionsOrKeyframes, rest, scope);
12420
12487
  }
12421
12488
  const animation = new GroupAnimationWithThen(animations);
12489
+ if (animationOnComplete) {
12490
+ animation.finished.then(animationOnComplete);
12491
+ }
12422
12492
  if (scope) {
12423
12493
  scope.animations.push(animation);
12424
12494
  animation.finished.then(() => {