motion 14.4.0 → 14.6.0

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.
@@ -247,8 +247,8 @@
247
247
  : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
248
248
 
249
249
  const circIn = (p) => 1 - Math.sin(Math.acos(p));
250
- const circOut = reverseEasing(circIn);
251
- const circInOut = mirrorEasing(circIn);
250
+ const circOut = /* @__PURE__ */ reverseEasing(circIn);
251
+ const circInOut = /* @__PURE__ */ mirrorEasing(circIn);
252
252
 
253
253
  const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
254
254
  const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
@@ -928,6 +928,11 @@
928
928
  */
929
929
  let isProcessing = false;
930
930
  let flushNextFrame = false;
931
+ /**
932
+ * The callback currently running. If it schedules itself with `immediate`
933
+ * it's left in place, so as before it doesn't run again this step.
934
+ */
935
+ let running;
931
936
  /**
932
937
  * A set of processes which were marked keepAlive when scheduled.
933
938
  * A keepAlive process is always also held by a frame queue until
@@ -945,7 +950,7 @@
945
950
  nextFrame.add(callback);
946
951
  runNextFrame();
947
952
  }
948
- callback(latestFrameData);
953
+ (running = callback)(latestFrameData);
949
954
  }
950
955
  const step = {
951
956
  /**
@@ -956,6 +961,14 @@
956
961
  const queue = addToCurrentFrame ? thisFrame : nextFrame;
957
962
  if (keepAlive)
958
963
  toKeepAlive.add(callback);
964
+ /**
965
+ * A callback scheduled with `immediate` during processing may
966
+ * already have run this step (for instance, a value it reads was
967
+ * changed by a later callback). Re-adding moves it to the end of
968
+ * the queue, so it runs again. The running callback is excluded,
969
+ * so one that re-schedules itself can't loop forever.
970
+ */
971
+ addToCurrentFrame && callback !== running && queue.delete(callback);
959
972
  queue.add(callback);
960
973
  return callback;
961
974
  },
@@ -990,6 +1003,7 @@
990
1003
  // Clear the frame so no callbacks remain. This is to avoid
991
1004
  // memory leaks should this render step not run for a while.
992
1005
  thisFrame.clear();
1006
+ running = undefined;
993
1007
  isProcessing = false;
994
1008
  if (flushNextFrame) {
995
1009
  flushNextFrame = false;
@@ -1336,7 +1350,7 @@
1336
1350
  }
1337
1351
  return springOptions;
1338
1352
  }
1339
- function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
1353
+ function springGenerator(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
1340
1354
  const options = typeof optionsOrVisualDuration !== "object"
1341
1355
  ? {
1342
1356
  visualDuration: optionsOrVisualDuration,
@@ -1521,13 +1535,15 @@
1521
1535
  };
1522
1536
  return generator;
1523
1537
  }
1524
- spring.applyToOptions = (options) => {
1525
- const generatorOptions = createGeneratorEasing(options, 100, spring);
1526
- options.ease = generatorOptions.ease;
1527
- options.duration = secondsToMilliseconds(generatorOptions.duration);
1528
- options.type = "keyframes";
1529
- return options;
1530
- };
1538
+ const spring = /* @__PURE__ */ Object.assign(springGenerator, {
1539
+ applyToOptions: (options) => {
1540
+ const generatorOptions = createGeneratorEasing(options, 100, springGenerator);
1541
+ options.ease = generatorOptions.ease;
1542
+ options.duration = secondsToMilliseconds(generatorOptions.duration);
1543
+ options.type = "keyframes";
1544
+ return options;
1545
+ },
1546
+ });
1531
1547
 
1532
1548
  function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
1533
1549
  const origin = keyframes[0];
@@ -2609,7 +2625,7 @@
2609
2625
 
2610
2626
  const isNumOrPxType = (v) => v === number || v === px;
2611
2627
  const transformKeys = new Set(["x", "y", "z"]);
2612
- const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
2628
+ const nonTranslationalTransformKeys = /* @__PURE__ */ transformPropOrder.filter((key) => !transformKeys.has(key));
2613
2629
  /**
2614
2630
  * Reset any bounding box-changing transforms so the element can be
2615
2631
  * measured. Returns the values to restore. Values already at their
@@ -2651,6 +2667,8 @@
2651
2667
  ? size
2652
2668
  : size - parseFloat(paddingStart) - parseFloat(paddingEnd);
2653
2669
  }
2670
+ const transformX = ({ transform }) => parseValueFromTransform(transform, "x");
2671
+ const transformY = ({ transform }) => parseValueFromTransform(transform, "y");
2654
2672
  const positionalValues = {
2655
2673
  // Dimensions
2656
2674
  width: ({ width, paddingLeft = "0", paddingRight = "0", boxSizing }, measureBox) => measureDimension(width, measureBox, "x", paddingLeft, paddingRight, boxSizing),
@@ -2665,13 +2683,12 @@
2665
2683
  const { x } = measureBox();
2666
2684
  return parseFloat(left) + (x.max - x.min);
2667
2685
  },
2668
- // Transform
2669
- x: ({ transform }) => parseValueFromTransform(transform, "x"),
2670
- y: ({ transform }) => parseValueFromTransform(transform, "y"),
2686
+ // Transform, including translate longform names
2687
+ x: transformX,
2688
+ y: transformY,
2689
+ translateX: transformX,
2690
+ translateY: transformY,
2671
2691
  };
2672
- // Alias translate longform names
2673
- positionalValues.translateX = positionalValues.x;
2674
- positionalValues.translateY = positionalValues.y;
2675
2692
 
2676
2693
  const toResolve = new Set();
2677
2694
  let isScheduled = false;
@@ -3025,18 +3042,24 @@
3025
3042
  if (this.isStopped)
3026
3043
  return;
3027
3044
  this.isStopped = true;
3028
- const { state } = this;
3029
- if (state === "idle" || state === "finished") {
3045
+ /**
3046
+ * Before its finish event, a finished animation (an instant one is
3047
+ * as soon as it starts) is still showing its end, so it's stopped
3048
+ * like a running one. Otherwise that event would apply its end
3049
+ * over whatever animates the value next.
3050
+ */
3051
+ if (this.state === "idle" || this.finishedTime !== null)
3030
3052
  return;
3031
- }
3032
3053
  if (this.updateMotionValue) {
3033
3054
  this.updateMotionValue();
3034
3055
  }
3035
3056
  else {
3036
3057
  this.commitStyles();
3037
3058
  }
3038
- if (!this.isPseudoElement)
3059
+ if (!this.isPseudoElement) {
3060
+ this.animation.onfinish = null;
3039
3061
  this.cancel();
3062
+ }
3040
3063
  }
3041
3064
  /**
3042
3065
  * WAAPI doesn't natively have any interruption capabilities.
@@ -3345,7 +3368,13 @@
3345
3368
  "borderBottomColor",
3346
3369
  "borderLeftColor",
3347
3370
  ]);
3348
- const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
3371
+ /**
3372
+ * WAAPI runs on the browser's clock, so on a manual clock every animation
3373
+ * runs in JS where processFrame can drive it. Set the flag before starting
3374
+ * animations: running WAAPI animations stay on the browser's clock.
3375
+ */
3376
+ const supportsWaapi = () => !MotionGlobalConfig.useManualTiming &&
3377
+ Object.hasOwnProperty.call(Element.prototype, "animate");
3349
3378
  function supportsBrowserAnimation(options) {
3350
3379
  const { motionValue, name, repeatDelay, repeatType, damping, type, keyframes, } = options;
3351
3380
  /**
@@ -5536,12 +5565,8 @@
5536
5565
  }
5537
5566
  }
5538
5567
 
5539
- function camelToDash(str) {
5540
- return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
5541
- }
5542
-
5543
5568
  const optimizedAppearDataId = "framerAppearId";
5544
- const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
5569
+ const optimizedAppearDataAttribute = "data-framer-appear-id";
5545
5570
 
5546
5571
  function getOptimisedAppearId(visualElement) {
5547
5572
  return visualElement.props[optimizedAppearDataAttribute];
@@ -6136,6 +6161,43 @@
6136
6161
  return isObject(element) && "ownerSVGElement" in element;
6137
6162
  }
6138
6163
 
6164
+ const activeScope = {};
6165
+ /**
6166
+ * Register `cleanup` with the active scope, if there is one, so stopping
6167
+ * the scope also runs it.
6168
+ */
6169
+ function addToScope(cleanup) {
6170
+ activeScope.current?.add(cleanup);
6171
+ return cleanup;
6172
+ }
6173
+ /**
6174
+ * Collect the animation a builder like `animateLayout()` or
6175
+ * `animateView()` resolves with. If the scope stops first, the animation
6176
+ * is stopped as soon as it starts.
6177
+ */
6178
+ function addBuilderToScope(builder) {
6179
+ const scope = activeScope.current;
6180
+ if (!scope)
6181
+ return;
6182
+ let isStopped = false;
6183
+ let stop;
6184
+ const remove = scope.add(() => {
6185
+ isStopped = true;
6186
+ stop?.();
6187
+ });
6188
+ builder.then((animation) => {
6189
+ if (!animation)
6190
+ return;
6191
+ stop = () => animation.stop();
6192
+ isStopped ? stop() : animation.finished.then(remove);
6193
+ }, remove);
6194
+ }
6195
+ /**
6196
+ * Bind `callback` to the active scope, so anything it starts later (for
6197
+ * instance an animation in an `inView()` callback) is collected too.
6198
+ */
6199
+ const bindToScope = (callback) => activeScope.current ? activeScope.current.bind(callback) : callback;
6200
+
6139
6201
  function resolveElements(elementOrSelector, scope, selectorCache) {
6140
6202
  if (elementOrSelector == null) {
6141
6203
  return [];
@@ -6144,10 +6206,9 @@
6144
6206
  return [elementOrSelector];
6145
6207
  }
6146
6208
  else if (typeof elementOrSelector === "string") {
6147
- let root = document;
6148
- if (scope) {
6149
- root = scope.current;
6150
- }
6209
+ const root = scope
6210
+ ? scope.current
6211
+ : activeScope.current?.root || document;
6151
6212
  const elements = selectorCache?.[elementOrSelector] ??
6152
6213
  root.querySelectorAll(elementOrSelector);
6153
6214
  return elements ? Array.from(elements) : [];
@@ -6433,6 +6494,10 @@
6433
6494
  const styleEffect =
6434
6495
  /*@__PURE__*/ createSelectorEffect(styleSubjectEffect);
6435
6496
 
6497
+ function camelToDash(str) {
6498
+ return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
6499
+ }
6500
+
6436
6501
  function buildHTMLStyles(state, latestValues, transformTemplate) {
6437
6502
  const { style, vars, transformOrigin } = state;
6438
6503
  // Track whether we encounter any transform or transformOrigin values.
@@ -6937,12 +7002,20 @@
6937
7002
  * sharing values and a renderer.
6938
7003
  */
6939
7004
  function animateElement(element, keyframes, transition, visualElement) {
7005
+ activeScope.current?.track(element);
6940
7006
  if (visualElement) {
6941
7007
  return animateValues((key) => visualElement.getValue(key, null), keyframes, transition, visualElement);
6942
7008
  }
6943
7009
  const effect = getElementEffect(element);
6944
7010
  return animateEffectSubject(effect, element, keyframes, transition, new EffectSubject(effect, element));
6945
7011
  }
7012
+ /**
7013
+ * Detach the values styleEffect/svgEffect bound to `element`, so the
7014
+ * next animation reads its starting values from the DOM again.
7015
+ */
7016
+ const releaseElement = (element) => getElementEffect(element)
7017
+ .state(element)
7018
+ ?.release();
6946
7019
  /**
6947
7020
  * Hand the values styleEffect/svgEffect are rendering on `element`,
6948
7021
  * whether bound by animate() or directly, to a VisualElement that now
@@ -7000,6 +7073,41 @@
7000
7073
  const { schedule: microtask, cancel: cancelMicrotask } =
7001
7074
  /* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
7002
7075
 
7076
+ /**
7077
+ * Process a frame now, for driving Motion from a loop other than
7078
+ * window.requestAnimationFrame, like an XRSession's during an immersive
7079
+ * WebXR session. Wherever window.requestAnimationFrame does fire, Motion
7080
+ * keeps processing frames from it too.
7081
+ *
7082
+ * `timestamp` defaults to now. For a timeline you control, set
7083
+ * `MotionGlobalConfig.useManualTiming = true` and pass each frame's time in
7084
+ * milliseconds.
7085
+ *
7086
+ * Animations Motion hands to the browser (WAAPI), like `opacity` or
7087
+ * `transform` on an HTML or SVG element, follow the browser's clock. With
7088
+ * `useManualTiming` on, Motion runs those in JS instead, except for
7089
+ * `motion/mini`, view transitions and optimized appear animations, and
7090
+ * colours only the browser can mix, like `oklch()`, which jump to their end.
7091
+ *
7092
+ * Ignored when called inside a frame.
7093
+ */
7094
+ const processFrame = (timestamp = time.now()) => {
7095
+ if (frameData.isProcessing)
7096
+ return;
7097
+ const delta = timestamp - frameData.timestamp;
7098
+ // Clamp like the requestAnimationFrame loop, unless the timeline is ours
7099
+ frameData.delta = MotionGlobalConfig.useManualTiming
7100
+ ? delta
7101
+ : Math.max(Math.min(delta, 40), 1);
7102
+ time.set((frameData.timestamp = timestamp));
7103
+ frameData.isProcessing = true;
7104
+ // frameSteps' keys are in stepsOrder
7105
+ for (const step in frameSteps) {
7106
+ frameSteps[step].process(frameData);
7107
+ }
7108
+ frameData.isProcessing = false;
7109
+ };
7110
+
7003
7111
  function animateSingleValue(value, keyframes, options) {
7004
7112
  const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
7005
7113
  motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
@@ -7026,6 +7134,7 @@
7026
7134
  */
7027
7135
  function delay(callback, timeout) {
7028
7136
  const start = time.now();
7137
+ callback = bindToScope(callback);
7029
7138
  const checkElapsed = ({ timestamp }) => {
7030
7139
  const elapsed = timestamp - start;
7031
7140
  if (elapsed >= timeout) {
@@ -7034,7 +7143,7 @@
7034
7143
  }
7035
7144
  };
7036
7145
  frame.setup(checkElapsed, true);
7037
- return () => cancelFrame(checkElapsed);
7146
+ return addToScope(() => cancelFrame(checkElapsed));
7038
7147
  }
7039
7148
  function delayInSeconds(callback, timeout) {
7040
7149
  return delay(callback, secondsToMilliseconds(timeout));
@@ -7458,10 +7567,9 @@
7458
7567
  };
7459
7568
 
7460
7569
  const defaultScaleCorrectors = {
7461
- borderRadius: {
7462
- ...correctBorderRadius,
7463
- applyTo: [...cornerRadiusProps],
7464
- },
7570
+ borderRadius: /* @__PURE__ */ Object.assign({}, correctBorderRadius, {
7571
+ applyTo: /* @__PURE__ */ cornerRadiusProps.slice(),
7572
+ }),
7465
7573
  borderTopLeftRadius: correctBorderRadius,
7466
7574
  borderTopRightRadius: correctBorderRadius,
7467
7575
  borderBottomLeftRadius: correctBorderRadius,
@@ -7566,14 +7674,6 @@
7566
7674
  hasEverUpdated: false,
7567
7675
  };
7568
7676
 
7569
- /**
7570
- * Correctors registered via addScaleCorrector before projection loaded
7571
- * (e.g. with an async LazyMotion bundle) take precedence over the defaults.
7572
- */
7573
- Object.assign(scaleCorrectors, {
7574
- ...defaultScaleCorrectors,
7575
- ...scaleCorrectors,
7576
- });
7577
7677
  const metrics = {
7578
7678
  nodes: 0,
7579
7679
  calculatedTargetDeltas: 0,
@@ -7616,6 +7716,16 @@
7616
7716
  }
7617
7717
  }
7618
7718
  function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
7719
+ /**
7720
+ * Correctors registered via addScaleCorrector before projection loaded
7721
+ * (e.g. with an async LazyMotion bundle) take precedence over the defaults.
7722
+ * Applied here rather than at module scope so bundlers can drop projection
7723
+ * when it's unused.
7724
+ */
7725
+ Object.assign(scaleCorrectors, {
7726
+ ...defaultScaleCorrectors,
7727
+ ...scaleCorrectors,
7728
+ });
7619
7729
  return class ProjectionNode {
7620
7730
  constructor(latestValues = {}, parent = defaultParent?.()) {
7621
7731
  /**
@@ -8272,7 +8382,7 @@
8272
8382
  }
8273
8383
  return boxWithoutScroll;
8274
8384
  }
8275
- applyTransform(box, transformOnly = false, output) {
8385
+ applyTransform(box, transformOnly = false, output, skipOwnTransform) {
8276
8386
  const withTransforms = output || createBox();
8277
8387
  copyBoxInto(withTransforms, box);
8278
8388
  for (let i = 0; i < this.path.length; i++) {
@@ -8288,7 +8398,7 @@
8288
8398
  continue;
8289
8399
  transformBox(withTransforms, node.latestValues, node.layout?.layoutBox);
8290
8400
  }
8291
- if (hasTransform(this.latestValues)) {
8401
+ if (!skipOwnTransform && hasTransform(this.latestValues)) {
8292
8402
  transformBox(withTransforms, this.latestValues, this.layout?.layoutBox);
8293
8403
  }
8294
8404
  return withTransforms;
@@ -8423,7 +8533,11 @@
8423
8533
  }
8424
8534
  else if (this.targetDelta) {
8425
8535
  if (Boolean(this.resumingFrom)) {
8426
- this.applyTransform(this.layout.layoutBox, false, this.target);
8536
+ /**
8537
+ * Own transforms are applied later, in applyTransformsToTarget,
8538
+ * so only apply ancestor transforms here or they'd be doubled.
8539
+ */
8540
+ this.applyTransform(this.layout.layoutBox, false, this.target, true);
8427
8541
  }
8428
8542
  else {
8429
8543
  copyBoxInto(this.target, this.layout.layoutBox);
@@ -9192,10 +9306,13 @@
9192
9306
  * A dragged node doesn't layout animate, so it jumps to its new layout. Shift
9193
9307
  * its descendants' snapshots by the same amount, so they only animate layout
9194
9308
  * changes relative to it, rather than lagging behind.
9309
+ *
9310
+ * If its drag ended since the snapshot (e.g. drag disabled from onDrag), it
9311
+ * layout animates again, so its descendants mustn't follow its jump.
9195
9312
  */
9196
9313
  function followDraggedAncestor(node) {
9197
9314
  const { snapshot, dragAncestor } = node;
9198
- const to = dragAncestor?.layout;
9315
+ const to = dragAncestor?.isAnimationBlocked && dragAncestor.layout;
9199
9316
  if (!snapshot || !to || node.resumeFrom || node.isAnimationBlocked)
9200
9317
  return;
9201
9318
  const from = dragAncestor.dragSnapshot;
@@ -9309,11 +9426,16 @@
9309
9426
  return () => target.removeEventListener(eventName, handler, options);
9310
9427
  }
9311
9428
 
9312
- const DocumentProjectionNode = createProjectionNode({
9429
+ const DocumentProjectionNode =
9430
+ /* @__PURE__ */ createProjectionNode({
9313
9431
  attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
9314
9432
  measureScroll: () => ({
9315
- x: document.documentElement.scrollLeft || document.body?.scrollLeft || 0,
9316
- y: document.documentElement.scrollTop || document.body?.scrollTop || 0,
9433
+ x: document.documentElement.scrollLeft ||
9434
+ document.body?.scrollLeft ||
9435
+ 0,
9436
+ y: document.documentElement.scrollTop ||
9437
+ document.body?.scrollTop ||
9438
+ 0,
9317
9439
  }),
9318
9440
  checkIsScrollRoot: () => true,
9319
9441
  });
@@ -9321,7 +9443,9 @@
9321
9443
  const rootProjectionNode = {
9322
9444
  current: undefined,
9323
9445
  };
9324
- const HTMLProjectionNode = createProjectionNode({
9446
+
9447
+ const HTMLProjectionNode =
9448
+ /* @__PURE__ */ createProjectionNode({
9325
9449
  measureScroll: (instance) => ({
9326
9450
  x: instance.scrollLeft,
9327
9451
  y: instance.scrollTop,
@@ -9365,7 +9489,7 @@
9365
9489
  "whileDrag",
9366
9490
  "exit",
9367
9491
  ];
9368
- const variantProps = ["initial", ...variantPriorityOrder];
9492
+ const variantProps = /* @__PURE__ */ ["initial"].concat(variantPriorityOrder);
9369
9493
 
9370
9494
  function isControllingVariants(props) {
9371
9495
  if (isAnimationControls(props.animate))
@@ -10455,7 +10579,7 @@
10455
10579
  function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
10456
10580
  if (typeof scopeOrUpdateDom === "function") {
10457
10581
  return {
10458
- scope: document,
10582
+ scope: activeScope.current?.root || document,
10459
10583
  updateDom: scopeOrUpdateDom,
10460
10584
  defaultOptions: updateDomOrOptions,
10461
10585
  };
@@ -10591,7 +10715,7 @@
10591
10715
  }
10592
10716
 
10593
10717
  function resize(a, b) {
10594
- return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b);
10718
+ return addToScope(typeof a === "function" ? resizeWindow(a) : resizeElement(a, b));
10595
10719
  }
10596
10720
 
10597
10721
  const distance = (a, b) => Math.abs(a - b);
@@ -10656,6 +10780,12 @@
10656
10780
  onMove && onMove(this.lastMoveEvent, info);
10657
10781
  };
10658
10782
  this.handlePointerMove = (event, info) => {
10783
+ /**
10784
+ * This event's pageX/pageY already include any window scroll that
10785
+ * hasn't fired its scroll event yet. Settle it now, so that scroll
10786
+ * event doesn't add it to the new point a second time.
10787
+ */
10788
+ this.handleScroll(window);
10659
10789
  this.lastMoveEvent = event;
10660
10790
  this.lastRawMoveEventInfo = info;
10661
10791
  this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
@@ -11212,7 +11342,11 @@
11212
11342
  element: this.visualElement.current,
11213
11343
  });
11214
11344
  }
11215
- stop(event, panInfo) {
11345
+ /**
11346
+ * @param dragAxes - The axes to animate on release, if not the `drag`
11347
+ * prop's, e.g. when drag was disabled mid-gesture.
11348
+ */
11349
+ stop(event, panInfo, dragAxes) {
11216
11350
  const finalEvent = event || this.latestPointerEvent;
11217
11351
  const finalPanInfo = panInfo || this.latestPanInfo;
11218
11352
  const isDragging = this.isDragging;
@@ -11220,7 +11354,7 @@
11220
11354
  if (!isDragging || !finalPanInfo || !finalEvent)
11221
11355
  return;
11222
11356
  const { velocity } = finalPanInfo;
11223
- this.startAnimation(velocity);
11357
+ this.startAnimation(velocity, dragAxes);
11224
11358
  const { onDragEnd } = this.getProps();
11225
11359
  if (onDragEnd) {
11226
11360
  frame.postRender(() => onDragEnd(finalEvent, finalPanInfo));
@@ -11341,11 +11475,11 @@
11341
11475
  }
11342
11476
  return measuredConstraints;
11343
11477
  }
11344
- startAnimation(velocity) {
11478
+ startAnimation(velocity, dragAxes) {
11345
11479
  const { drag, dragMomentum, dragElastic, dragTransition, dragSnapToOrigin, onDragTransitionEnd, } = this.getProps();
11346
11480
  const constraints = this.constraints || {};
11347
11481
  const momentumAnimations = eachAxis((axis) => {
11348
- if (!shouldDrag(axis, drag, this.currentDirection)) {
11482
+ if (!shouldDrag(axis, dragAxes || drag, this.currentDirection)) {
11349
11483
  return;
11350
11484
  }
11351
11485
  let transition = (constraints && constraints[axis]) || {};
@@ -11548,9 +11682,15 @@
11548
11682
  const stopLayoutUpdateListener = projection.addEventListener("didUpdate", (({ delta, hasLayoutChanged }) => {
11549
11683
  if (this.isDragging && hasLayoutChanged) {
11550
11684
  eachAxis((axis) => {
11551
- const motionValue = this.getAxisMotionValue(axis);
11552
- if (!motionValue)
11685
+ /**
11686
+ * Only compensate axes this gesture drags. A
11687
+ * non-drag axis follows its layout instead, as
11688
+ * nothing would ever move it back (e.g. snapping
11689
+ * to origin only animates drag axes).
11690
+ */
11691
+ if (!shouldDrag(axis, this.getProps().drag, this.currentDirection))
11553
11692
  return;
11693
+ const motionValue = this.getAxisMotionValue(axis);
11554
11694
  this.originPoint[axis] += delta[axis].translate;
11555
11695
  motionValue.set(motionValue.get() + delta[axis].translate);
11556
11696
  });
@@ -11687,7 +11827,20 @@
11687
11827
  */
11688
11828
  function drag(elementOrSelector, options = {}) {
11689
11829
  const props = toProps(options);
11830
+ const scope = activeScope.current;
11831
+ /**
11832
+ * Run callbacks in the active scope, so work they start is collected.
11833
+ */
11834
+ if (scope) {
11835
+ for (const key in props) {
11836
+ const value = props[key];
11837
+ if (typeof value === "function") {
11838
+ props[key] = scope.bind(value);
11839
+ }
11840
+ }
11841
+ }
11690
11842
  const cleanups = resolveElements(elementOrSelector).map((element) => {
11843
+ scope?.track(element);
11691
11844
  const [node, release] = acquireProjectionNode(element);
11692
11845
  /**
11693
11846
  * Measure layout even when not animating it, as constraints and
@@ -11701,10 +11854,11 @@
11701
11854
  removeStyles();
11702
11855
  removeListeners?.();
11703
11856
  controls.cancel();
11857
+ controls.stopAnimation();
11704
11858
  release();
11705
11859
  };
11706
11860
  });
11707
- return () => cleanups.forEach((cleanup) => cleanup());
11861
+ return addToScope(() => cleanups.forEach((cleanup) => cleanup()));
11708
11862
  }
11709
11863
 
11710
11864
  function setupGesture(elementOrSelector, options) {
@@ -11715,7 +11869,7 @@
11715
11869
  ...options,
11716
11870
  signal: gestureAbortController.signal,
11717
11871
  };
11718
- const cancel = () => gestureAbortController.abort();
11872
+ const cancel = addToScope(() => gestureAbortController.abort());
11719
11873
  return [elements, eventOptions, cancel];
11720
11874
  }
11721
11875
 
@@ -11730,6 +11884,7 @@
11730
11884
  * @public
11731
11885
  */
11732
11886
  function hover(elementOrSelector, onHoverStart, options = {}) {
11887
+ onHoverStart = bindToScope(onHoverStart);
11733
11888
  const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
11734
11889
  elements.forEach((element) => {
11735
11890
  let isPressed = false;
@@ -11818,26 +11973,36 @@
11818
11973
  function firePointerEvent(target, type) {
11819
11974
  target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true }));
11820
11975
  }
11821
- const enableKeyboardPress = (focusEvent, eventOptions) => {
11822
- const element = focusEvent.currentTarget;
11823
- if (!element)
11824
- return;
11976
+ const enableKeyboardPress = (element, eventOptions) => {
11977
+ /**
11978
+ * Listeners are scoped to this focus and all released on blur, rather
11979
+ * than added per Enter press, so they can't accumulate and fire
11980
+ * duplicate pointerup/pointercancel events. See #3935.
11981
+ */
11982
+ let isKeyPressing = false;
11825
11983
  const handleKeydown = filterEvents(() => {
11826
11984
  if (isPressing.has(element))
11827
11985
  return;
11986
+ isKeyPressing = true;
11828
11987
  firePointerEvent(element, "down");
11829
- const handleKeyup = filterEvents(() => {
11830
- firePointerEvent(element, "up");
11831
- });
11832
- const handleBlur = () => firePointerEvent(element, "cancel");
11833
- element.addEventListener("keyup", handleKeyup, eventOptions);
11834
- element.addEventListener("blur", handleBlur, eventOptions);
11988
+ });
11989
+ const handleKeyup = filterEvents(() => {
11990
+ if (!isKeyPressing)
11991
+ return;
11992
+ isKeyPressing = false;
11993
+ firePointerEvent(element, "up");
11835
11994
  });
11836
11995
  element.addEventListener("keydown", handleKeydown, eventOptions);
11996
+ element.addEventListener("keyup", handleKeyup, eventOptions);
11837
11997
  /**
11838
- * Add an event listener that fires on blur to remove the keydown events.
11998
+ * On blur, remove the key listeners and cancel any keyboard press.
11839
11999
  */
11840
- element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions);
12000
+ element.addEventListener("blur", () => {
12001
+ element.removeEventListener("keydown", handleKeydown);
12002
+ element.removeEventListener("keyup", handleKeyup);
12003
+ if (isKeyPressing)
12004
+ firePointerEvent(element, "cancel");
12005
+ }, { ...eventOptions, once: true });
11841
12006
  };
11842
12007
 
11843
12008
  /**
@@ -11868,6 +12033,7 @@
11868
12033
  * @public
11869
12034
  */
11870
12035
  function press(targetOrSelector, onPressStart, options = {}) {
12036
+ onPressStart = bindToScope(onPressStart);
11871
12037
  const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options);
11872
12038
  const startPress = (startEvent) => {
11873
12039
  const target = startEvent.currentTarget;
@@ -11916,7 +12082,7 @@
11916
12082
  const pointerDownTarget = options.useGlobalTarget ? window : target;
11917
12083
  pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
11918
12084
  if (isHTMLElement(target)) {
11919
- target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
12085
+ target.addEventListener("focus", () => enableKeyboardPress(target, eventOptions), eventOptions);
11920
12086
  if (!isElementKeyboardAccessible(target) &&
11921
12087
  !target.hasAttribute("tabindex")) {
11922
12088
  target.tabIndex = 0;
@@ -11933,6 +12099,160 @@
11933
12099
  : computedStyle[name];
11934
12100
  }
11935
12101
 
12102
+ const keywords = {
12103
+ reducedMotion: "(prefers-reduced-motion: reduce)",
12104
+ motion: "(prefers-reduced-motion: no-preference)",
12105
+ dark: "(prefers-color-scheme: dark)",
12106
+ light: "(prefers-color-scheme: light)",
12107
+ hover: "(hover: hover) and (pointer: fine)",
12108
+ touch: "(pointer: coarse)",
12109
+ portrait: "(orientation: portrait)",
12110
+ landscape: "(orientation: landscape)",
12111
+ };
12112
+ /**
12113
+ * Turn a condition into a media query. Full media queries pass through
12114
+ * unchanged. Otherwise each space-separated token must match:
12115
+ *
12116
+ * - A width comparison: `">=800"`, `"<1200px"`, `"> 40em"`
12117
+ * - A keyword: `reducedMotion`, `motion`, `dark`, `light`, `hover`,
12118
+ * `touch`, `portrait`, `landscape`
12119
+ *
12120
+ * Prefix a token with `!` to negate it, for instance `">=800 !touch"`.
12121
+ */
12122
+ function toMediaQuery(condition) {
12123
+ if (condition.includes("("))
12124
+ return condition;
12125
+ return condition
12126
+ .replace(/([<>!]=?)\s+/g, "$1")
12127
+ .trim()
12128
+ .split(/\s+/)
12129
+ .map((token) => {
12130
+ const isNot = token[0] === "!";
12131
+ if (isNot)
12132
+ token = token.slice(1);
12133
+ const width = /^([<>]=?)([\d.]+)([a-z]*)$/.exec(token);
12134
+ const query = width
12135
+ ? `(width ${width[1]} ${width[2]}${width[3] || "px"})`
12136
+ : keywords[token] || token;
12137
+ return isNot ? `(not (${query}))` : query;
12138
+ })
12139
+ .join(" and ");
12140
+ }
12141
+
12142
+ function scopeMotion(...args) {
12143
+ const root = typeof args[0] === "function" ? undefined : args.shift();
12144
+ const [setup, options = {}] = args;
12145
+ const { transition, matchMedia } = options;
12146
+ const queries = [];
12147
+ const styles = new Map();
12148
+ let cleanups = [];
12149
+ let isPending = false;
12150
+ const active = {
12151
+ root: resolveElements(root)[0],
12152
+ transition,
12153
+ add(cleanup) {
12154
+ cleanups.push(cleanup);
12155
+ return () => removeItem(cleanups, cleanup);
12156
+ },
12157
+ bind: (callback) => ((...args) => {
12158
+ const result = scope.run(() => callback(...args));
12159
+ return typeof result === "function"
12160
+ ? active.bind(result)
12161
+ : result;
12162
+ }),
12163
+ track(element) {
12164
+ styles.has(element) ||
12165
+ styles.set(element, element.getAttribute("style"));
12166
+ },
12167
+ };
12168
+ const clear = (restore) => {
12169
+ const current = cleanups;
12170
+ cleanups = [];
12171
+ for (let i = current.length - 1; i >= 0; i--)
12172
+ current[i]();
12173
+ restore &&
12174
+ styles.forEach((style, element) => {
12175
+ releaseElement(element);
12176
+ style === null
12177
+ ? element.removeAttribute("style")
12178
+ : element.setAttribute("style", style);
12179
+ });
12180
+ styles.clear();
12181
+ };
12182
+ const start = () => {
12183
+ scope.state = "active";
12184
+ let matches = !queries.length;
12185
+ scope.media = {};
12186
+ for (const [name, query] of queries) {
12187
+ matches = (scope.media[name] = query.matches) || matches;
12188
+ }
12189
+ if (matches && setup) {
12190
+ const cleanup = scope.run(() => setup(scope));
12191
+ typeof cleanup === "function" && active.add(cleanup);
12192
+ }
12193
+ };
12194
+ const onChange = () => {
12195
+ if (isPending)
12196
+ return;
12197
+ isPending = true;
12198
+ /**
12199
+ * Several conditions can change together, for instance two
12200
+ * breakpoints crossed by one resize. Refresh once.
12201
+ */
12202
+ queueMicrotask(() => {
12203
+ isPending = false;
12204
+ queries.length && scope.refresh();
12205
+ });
12206
+ };
12207
+ const kill = (restore) => {
12208
+ scope.state = restore ? "cancelled" : "stopped";
12209
+ for (const [, query] of queries) {
12210
+ query.removeEventListener("change", onChange);
12211
+ }
12212
+ queries.length = 0;
12213
+ clear(restore);
12214
+ };
12215
+ const runIn = (target, callback) => {
12216
+ const prev = activeScope.current;
12217
+ activeScope.current = target;
12218
+ try {
12219
+ return callback();
12220
+ }
12221
+ finally {
12222
+ activeScope.current = prev;
12223
+ }
12224
+ };
12225
+ const scope = {
12226
+ state: "active",
12227
+ media: {},
12228
+ run: (callback) => runIn(active, callback),
12229
+ ignore: (callback) => runIn(undefined, callback),
12230
+ bind: active.bind,
12231
+ stop: () => kill(),
12232
+ cancel: () => kill(true),
12233
+ refresh() {
12234
+ clear(true);
12235
+ start();
12236
+ },
12237
+ };
12238
+ if (matchMedia && typeof window !== "undefined") {
12239
+ const conditions = typeof matchMedia === "string"
12240
+ ? { matches: matchMedia }
12241
+ : matchMedia;
12242
+ for (const name in conditions) {
12243
+ const query = window.matchMedia(toMediaQuery(conditions[name]));
12244
+ query.addEventListener("change", onChange);
12245
+ queries.push([name, query]);
12246
+ }
12247
+ }
12248
+ /**
12249
+ * A scope created inside another is cancelled with it.
12250
+ */
12251
+ addToScope(scope.cancel);
12252
+ start();
12253
+ return scope;
12254
+ }
12255
+
11936
12256
  function clearStatsBuffer() {
11937
12257
  statsBuffer.value = null;
11938
12258
  statsBuffer.addProjectionMetrics = null;
@@ -13549,7 +13869,9 @@
13549
13869
  }
13550
13870
  }
13551
13871
  function animateView(update, options = {}) {
13552
- return new ViewTransitionBuilder(update, options);
13872
+ const builder = new ViewTransitionBuilder(update, options);
13873
+ addBuilderToScope(builder);
13874
+ return builder;
13553
13875
  }
13554
13876
 
13555
13877
  /**
@@ -13843,8 +14165,7 @@
13843
14165
  const valueHasChanged = Array.isArray(next) && Array.isArray(prev)
13844
14166
  ? !shallowCompare(next, prev) || variantDidChange
13845
14167
  : next !== prev;
13846
- if (valueHasChanged &&
13847
- (next === undefined || next === null)) {
14168
+ if (valueHasChanged && (next === undefined || next === null)) {
13848
14169
  removedKeys.add(key);
13849
14170
  }
13850
14171
  else if (valueHasChanged ||
@@ -14058,7 +14379,12 @@
14058
14379
  */
14059
14380
  function animateLayout(scopeOrUpdateDom, updateDomOrOptions, options) {
14060
14381
  const { scope, updateDom, defaultOptions } = parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options);
14061
- return new LayoutAnimationBuilder(scope, updateDom, defaultOptions);
14382
+ const builder = new LayoutAnimationBuilder(scope, updateDom, {
14383
+ ...activeScope.current?.transition,
14384
+ ...defaultOptions,
14385
+ });
14386
+ addBuilderToScope(builder);
14387
+ return builder;
14062
14388
  }
14063
14389
 
14064
14390
  /**
@@ -14577,6 +14903,8 @@
14577
14903
  function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
14578
14904
  let animations = [];
14579
14905
  let animationOnComplete;
14906
+ const motionScope = activeScope.current;
14907
+ const scopeTransition = motionScope?.transition;
14580
14908
  const inherited = {};
14581
14909
  if (reduceMotion !== undefined)
14582
14910
  inherited.reduceMotion = reduceMotion;
@@ -14587,6 +14915,12 @@
14587
14915
  if (typeof onComplete === "function") {
14588
14916
  animationOnComplete = onComplete;
14589
14917
  }
14918
+ if (scopeTransition) {
14919
+ sequenceOptions.defaultTransition = {
14920
+ ...scopeTransition,
14921
+ ...sequenceOptions.defaultTransition,
14922
+ };
14923
+ }
14590
14924
  animations = animateSequence(subjectOrSequence, { ...inherited, ...sequenceOptions }, scope);
14591
14925
  }
14592
14926
  else {
@@ -14595,12 +14929,19 @@
14595
14929
  if (typeof onComplete === "function") {
14596
14930
  animationOnComplete = onComplete;
14597
14931
  }
14598
- animations = animateSubject(subjectOrSequence, optionsOrKeyframes, { ...inherited, ...rest }, scope);
14932
+ animations = animateSubject(subjectOrSequence, optionsOrKeyframes, {
14933
+ ...inherited,
14934
+ ...scopeTransition,
14935
+ ...rest,
14936
+ }, scope);
14599
14937
  }
14600
14938
  const animation = new GroupAnimationWithThen(animations);
14601
14939
  if (animationOnComplete) {
14602
14940
  animation.finished.then(animationOnComplete);
14603
14941
  }
14942
+ if (motionScope) {
14943
+ animation.finished.then(motionScope.add(() => animation.stop()));
14944
+ }
14604
14945
  if (scope) {
14605
14946
  scope.animations.push(animation);
14606
14947
  animation.finished.then(() => {
@@ -14611,7 +14952,8 @@
14611
14952
  }
14612
14953
  return scopedAnimate;
14613
14954
  }
14614
- const animate = Object.assign(createScopedAnimate(), {
14955
+ const animate = /* @__PURE__ */ Object.assign(
14956
+ /* @__PURE__ */ createScopedAnimate(), {
14615
14957
  /**
14616
14958
  * Register an effect so `animate()` can animate the subjects it
14617
14959
  * claims, for instance `animate.addEffect(threeEffect)`. The most
@@ -15328,9 +15670,11 @@
15328
15670
  container: source || container,
15329
15671
  ...options,
15330
15672
  };
15331
- return typeof onScroll === "function"
15332
- ? scrollInfo((info) => onScroll(info[axis].progress, info), optionsWithDefaults)
15333
- : attachToAnimation(onScroll, optionsWithDefaults);
15673
+ if (typeof onScroll === "function") {
15674
+ const callback = bindToScope(onScroll);
15675
+ return addToScope(scrollInfo((info) => callback(info[axis].progress, info), optionsWithDefaults));
15676
+ }
15677
+ return addToScope(attachToAnimation(onScroll, optionsWithDefaults));
15334
15678
  }
15335
15679
 
15336
15680
  const thresholds = {
@@ -15339,6 +15683,7 @@
15339
15683
  };
15340
15684
  function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) {
15341
15685
  const elements = resolveElements(elementOrSelector);
15686
+ onStart = bindToScope(onStart);
15342
15687
  const activeIntersections = new WeakMap();
15343
15688
  const onIntersectionChange = (entries) => {
15344
15689
  entries.forEach((entry) => {
@@ -15370,7 +15715,7 @@
15370
15715
  threshold: typeof amount === "number" ? amount : thresholds[amount],
15371
15716
  });
15372
15717
  elements.forEach((element) => observer.observe(element));
15373
- return () => observer.disconnect();
15718
+ return addToScope(() => observer.disconnect());
15374
15719
  }
15375
15720
 
15376
15721
  exports.AsyncMotionValueAnimation = AsyncMotionValueAnimation;
@@ -15401,7 +15746,9 @@
15401
15746
  exports.VisualElement = VisualElement;
15402
15747
  exports.VisualElementDragControls = VisualElementDragControls;
15403
15748
  exports.acceleratedValues = acceleratedValues;
15749
+ exports.activeScope = activeScope;
15404
15750
  exports.addAttrValue = addAttrValue;
15751
+ exports.addBuilderToScope = addBuilderToScope;
15405
15752
  exports.addDomEvent = addDomEvent;
15406
15753
  exports.addEffect = addEffect;
15407
15754
  exports.addPointerEvent = addPointerEvent;
@@ -15409,6 +15756,7 @@
15409
15756
  exports.addSVGValue = addSVGValue;
15410
15757
  exports.addScaleCorrector = addScaleCorrector;
15411
15758
  exports.addStyleValue = addStyleValue;
15759
+ exports.addToScope = addToScope;
15412
15760
  exports.addUniqueItem = addUniqueItem;
15413
15761
  exports.addValueToWillChange = addValueToWillChange;
15414
15762
  exports.alpha = alpha;
@@ -15445,6 +15793,7 @@
15445
15793
  exports.backIn = backIn;
15446
15794
  exports.backInOut = backInOut;
15447
15795
  exports.backOut = backOut;
15796
+ exports.bindToScope = bindToScope;
15448
15797
  exports.boxEquals = boxEquals;
15449
15798
  exports.boxEqualsRounded = boxEqualsRounded;
15450
15799
  exports.buildHTMLStyles = buildHTMLStyles;
@@ -15630,6 +15979,7 @@
15630
15979
  exports.positionalKeys = positionalKeys;
15631
15980
  exports.prefersReducedMotion = prefersReducedMotion;
15632
15981
  exports.press = press;
15982
+ exports.processFrame = processFrame;
15633
15983
  exports.progress = progress;
15634
15984
  exports.progressPercentage = progressPercentage;
15635
15985
  exports.propEffect = propEffect;
@@ -15639,6 +15989,7 @@
15639
15989
  exports.readStyleValue = readStyleValue;
15640
15990
  exports.readTransformValue = readTransformValue;
15641
15991
  exports.recordStats = recordStats;
15992
+ exports.releaseElement = releaseElement;
15642
15993
  exports.removeAxisDelta = removeAxisDelta;
15643
15994
  exports.removeAxisTransforms = removeAxisTransforms;
15644
15995
  exports.removeBoxTransforms = removeBoxTransforms;
@@ -15662,6 +16013,7 @@
15662
16013
  exports.scale = scale;
15663
16014
  exports.scaleCorrectors = scaleCorrectors;
15664
16015
  exports.scalePoint = scalePoint;
16016
+ exports.scopeMotion = scopeMotion;
15665
16017
  exports.scrapeHTMLMotionValuesFromProps = scrapeMotionValuesFromProps$1;
15666
16018
  exports.scrapeSVGMotionValuesFromProps = scrapeMotionValuesFromProps;
15667
16019
  exports.scroll = scroll;
@@ -15692,6 +16044,7 @@
15692
16044
  exports.sync = sync;
15693
16045
  exports.testValueType = testValueType;
15694
16046
  exports.time = time;
16047
+ exports.toMediaQuery = toMediaQuery;
15695
16048
  exports.transform = transform;
15696
16049
  exports.transformAxis = transformAxis;
15697
16050
  exports.transformBox = transformBox;