motion 12.27.0-alpha.6 → 12.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2963,6 +2963,20 @@
2963
2963
  return map;
2964
2964
  }
2965
2965
 
2966
+ function calcChildStagger(children, child, delayChildren, staggerChildren = 0, staggerDirection = 1) {
2967
+ const index = Array.from(children)
2968
+ .sort((a, b) => a.sortNodePosition(b))
2969
+ .indexOf(child);
2970
+ const numChildren = children.size;
2971
+ const maxStaggerDuration = (numChildren - 1) * staggerChildren;
2972
+ const delayIsFunction = typeof delayChildren === "function";
2973
+ return delayIsFunction
2974
+ ? delayChildren(index, numChildren)
2975
+ : staggerDirection === 1
2976
+ ? index * staggerChildren
2977
+ : maxStaggerDuration - index * staggerChildren;
2978
+ }
2979
+
2966
2980
  /**
2967
2981
  * Parse Framer's special CSS variable format into a CSS token and a fallback.
2968
2982
  *
@@ -3000,12 +3014,6 @@
3000
3014
  : fallback;
3001
3015
  }
3002
3016
 
3003
- function getValueTransition$1(transition, key) {
3004
- return (transition?.[key] ??
3005
- transition?.["default"] ??
3006
- transition);
3007
- }
3008
-
3009
3017
  const underDampedSpring = {
3010
3018
  type: "spring",
3011
3019
  stiffness: 500,
@@ -3043,15 +3051,6 @@
3043
3051
  return ease;
3044
3052
  };
3045
3053
 
3046
- /**
3047
- * Decide whether a transition is defined on a given Transition.
3048
- * This filters out orchestration options and returns true
3049
- * if any options are left.
3050
- */
3051
- function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
3052
- return !!Object.keys(transition).length;
3053
- }
3054
-
3055
3054
  const isNotNull = (value) => value !== null;
3056
3055
  function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
3057
3056
  const resolvedKeyframes = keyframes.filter(isNotNull);
@@ -3063,18 +3062,19 @@
3063
3062
  : finalKeyframe;
3064
3063
  }
3065
3064
 
3066
- function calcChildStagger(children, child, delayChildren, staggerChildren = 0, staggerDirection = 1) {
3067
- const index = Array.from(children)
3068
- .sort((a, b) => a.sortNodePosition(b))
3069
- .indexOf(child);
3070
- const numChildren = children.size;
3071
- const maxStaggerDuration = (numChildren - 1) * staggerChildren;
3072
- const delayIsFunction = typeof delayChildren === "function";
3073
- return delayIsFunction
3074
- ? delayChildren(index, numChildren)
3075
- : staggerDirection === 1
3076
- ? index * staggerChildren
3077
- : maxStaggerDuration - index * staggerChildren;
3065
+ function getValueTransition$1(transition, key) {
3066
+ return (transition?.[key] ??
3067
+ transition?.["default"] ??
3068
+ transition);
3069
+ }
3070
+
3071
+ /**
3072
+ * Decide whether a transition is defined on a given Transition.
3073
+ * This filters out orchestration options and returns true
3074
+ * if any options are left.
3075
+ */
3076
+ function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
3077
+ return !!Object.keys(transition).length;
3078
3078
  }
3079
3079
 
3080
3080
  const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
@@ -3168,6 +3168,46 @@
3168
3168
  : new AsyncMotionValueAnimation(options);
3169
3169
  };
3170
3170
 
3171
+ function getValueState(visualElement) {
3172
+ const state = [{}, {}];
3173
+ visualElement?.values.forEach((value, key) => {
3174
+ state[0][key] = value.get();
3175
+ state[1][key] = value.getVelocity();
3176
+ });
3177
+ return state;
3178
+ }
3179
+ function resolveVariantFromProps(props, definition, custom, visualElement) {
3180
+ /**
3181
+ * If the variant definition is a function, resolve.
3182
+ */
3183
+ if (typeof definition === "function") {
3184
+ const [current, velocity] = getValueState(visualElement);
3185
+ definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
3186
+ }
3187
+ /**
3188
+ * If the variant definition is a variant label, or
3189
+ * the function returned a variant label, resolve.
3190
+ */
3191
+ if (typeof definition === "string") {
3192
+ definition = props.variants && props.variants[definition];
3193
+ }
3194
+ /**
3195
+ * At this point we've resolved both functions and variant labels,
3196
+ * but the resolved variant label might itself have been a function.
3197
+ * If so, resolve. This can only have returned a valid target object.
3198
+ */
3199
+ if (typeof definition === "function") {
3200
+ const [current, velocity] = getValueState(visualElement);
3201
+ definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
3202
+ }
3203
+ return definition;
3204
+ }
3205
+
3206
+ function resolveVariant(visualElement, definition, custom) {
3207
+ const props = visualElement.getProps();
3208
+ return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
3209
+ }
3210
+
3171
3211
  const positionalKeys = new Set([
3172
3212
  "width",
3173
3213
  "height",
@@ -3496,46 +3536,6 @@
3496
3536
  return new MotionValue(init, options);
3497
3537
  }
3498
3538
 
3499
- function getValueState(visualElement) {
3500
- const state = [{}, {}];
3501
- visualElement?.values.forEach((value, key) => {
3502
- state[0][key] = value.get();
3503
- state[1][key] = value.getVelocity();
3504
- });
3505
- return state;
3506
- }
3507
- function resolveVariantFromProps(props, definition, custom, visualElement) {
3508
- /**
3509
- * If the variant definition is a function, resolve.
3510
- */
3511
- if (typeof definition === "function") {
3512
- const [current, velocity] = getValueState(visualElement);
3513
- definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
3514
- }
3515
- /**
3516
- * If the variant definition is a variant label, or
3517
- * the function returned a variant label, resolve.
3518
- */
3519
- if (typeof definition === "string") {
3520
- definition = props.variants && props.variants[definition];
3521
- }
3522
- /**
3523
- * At this point we've resolved both functions and variant labels,
3524
- * but the resolved variant label might itself have been a function.
3525
- * If so, resolve. This can only have returned a valid target object.
3526
- */
3527
- if (typeof definition === "function") {
3528
- const [current, velocity] = getValueState(visualElement);
3529
- definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
3530
- }
3531
- return definition;
3532
- }
3533
-
3534
- function resolveVariant(visualElement, definition, custom) {
3535
- const props = visualElement.getProps();
3536
- return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
3537
- }
3538
-
3539
3539
  const isKeyframesTarget = (v) => {
3540
3540
  return Array.isArray(v);
3541
3541
  };
@@ -6066,19 +6066,6 @@
6066
6066
  }
6067
6067
  }
6068
6068
 
6069
- /**
6070
- * Feature base class for extending VisualElement functionality.
6071
- * Features are plugins that can be mounted/unmounted to add behavior
6072
- * like gestures, animations, or layout tracking.
6073
- */
6074
- class Feature {
6075
- constructor(node) {
6076
- this.isMounted = false;
6077
- this.node = node;
6078
- }
6079
- update() { }
6080
- }
6081
-
6082
6069
  class DOMVisualElement extends VisualElement {
6083
6070
  constructor() {
6084
6071
  super(...arguments);
@@ -6116,6 +6103,19 @@
6116
6103
  }
6117
6104
  }
6118
6105
 
6106
+ /**
6107
+ * Feature base class for extending VisualElement functionality.
6108
+ * Features are plugins that can be mounted/unmounted to add behavior
6109
+ * like gestures, animations, or layout tracking.
6110
+ */
6111
+ class Feature {
6112
+ constructor(node) {
6113
+ this.isMounted = false;
6114
+ this.node = node;
6115
+ }
6116
+ update() { }
6117
+ }
6118
+
6119
6119
  /**
6120
6120
  * Bounding boxes tend to be defined as top, left, right, bottom. For various operations
6121
6121
  * it's easier to consider each axis individually. This function returns a bounding box
@@ -6584,6 +6584,43 @@
6584
6584
  }
6585
6585
  }
6586
6586
 
6587
+ function isObjectKey(key, object) {
6588
+ return key in object;
6589
+ }
6590
+ class ObjectVisualElement extends VisualElement {
6591
+ constructor() {
6592
+ super(...arguments);
6593
+ this.type = "object";
6594
+ }
6595
+ readValueFromInstance(instance, key) {
6596
+ if (isObjectKey(key, instance)) {
6597
+ const value = instance[key];
6598
+ if (typeof value === "string" || typeof value === "number") {
6599
+ return value;
6600
+ }
6601
+ }
6602
+ return undefined;
6603
+ }
6604
+ getBaseTargetFromProps() {
6605
+ return undefined;
6606
+ }
6607
+ removeValueFromRenderState(key, renderState) {
6608
+ delete renderState.output[key];
6609
+ }
6610
+ measureInstanceViewportBox() {
6611
+ return createBox();
6612
+ }
6613
+ build(renderState, latestValues) {
6614
+ Object.assign(renderState.output, latestValues);
6615
+ }
6616
+ renderInstance(instance, { output }) {
6617
+ Object.assign(instance, output);
6618
+ }
6619
+ sortInstanceNodePosition() {
6620
+ return 0;
6621
+ }
6622
+ }
6623
+
6587
6624
  const dashKeys = {
6588
6625
  offset: "stroke-dashoffset",
6589
6626
  array: "stroke-dasharray",
@@ -6766,43 +6803,6 @@
6766
6803
  }
6767
6804
  }
6768
6805
 
6769
- function isObjectKey(key, object) {
6770
- return key in object;
6771
- }
6772
- class ObjectVisualElement extends VisualElement {
6773
- constructor() {
6774
- super(...arguments);
6775
- this.type = "object";
6776
- }
6777
- readValueFromInstance(instance, key) {
6778
- if (isObjectKey(key, instance)) {
6779
- const value = instance[key];
6780
- if (typeof value === "string" || typeof value === "number") {
6781
- return value;
6782
- }
6783
- }
6784
- return undefined;
6785
- }
6786
- getBaseTargetFromProps() {
6787
- return undefined;
6788
- }
6789
- removeValueFromRenderState(key, renderState) {
6790
- delete renderState.output[key];
6791
- }
6792
- measureInstanceViewportBox() {
6793
- return createBox();
6794
- }
6795
- build(renderState, latestValues) {
6796
- Object.assign(renderState.output, latestValues);
6797
- }
6798
- renderInstance(instance, { output }) {
6799
- Object.assign(instance, output);
6800
- }
6801
- sortInstanceNodePosition() {
6802
- return 0;
6803
- }
6804
- }
6805
-
6806
6806
  const numVariantProps = variantProps.length;
6807
6807
  /**
6808
6808
  * Get variant context from a visual element's parent chain.
@@ -7197,6 +7197,36 @@
7197
7197
  };
7198
7198
  }
7199
7199
 
7200
+ /**
7201
+ * Reset an axis to the provided origin box.
7202
+ *
7203
+ * This is a mutative operation.
7204
+ */
7205
+ function copyAxisInto(axis, originAxis) {
7206
+ axis.min = originAxis.min;
7207
+ axis.max = originAxis.max;
7208
+ }
7209
+ /**
7210
+ * Reset a box to the provided origin box.
7211
+ *
7212
+ * This is a mutative operation.
7213
+ */
7214
+ function copyBoxInto(box, originBox) {
7215
+ copyAxisInto(box.x, originBox.x);
7216
+ copyAxisInto(box.y, originBox.y);
7217
+ }
7218
+ /**
7219
+ * Reset a delta to the provided origin box.
7220
+ *
7221
+ * This is a mutative operation.
7222
+ */
7223
+ function copyAxisDeltaInto(delta, originDelta) {
7224
+ delta.translate = originDelta.translate;
7225
+ delta.scale = originDelta.scale;
7226
+ delta.originPoint = originDelta.originPoint;
7227
+ delta.origin = originDelta.origin;
7228
+ }
7229
+
7200
7230
  const SCALE_PRECISION = 0.0001;
7201
7231
  const SCALE_MIN = 1 - SCALE_PRECISION;
7202
7232
  const SCALE_MAX = 1 + SCALE_PRECISION;
@@ -7295,36 +7325,6 @@
7295
7325
  removeAxisTransforms(box.y, transforms, yKeys, originBox ? originBox.y : undefined, sourceBox ? sourceBox.y : undefined);
7296
7326
  }
7297
7327
 
7298
- /**
7299
- * Reset an axis to the provided origin box.
7300
- *
7301
- * This is a mutative operation.
7302
- */
7303
- function copyAxisInto(axis, originAxis) {
7304
- axis.min = originAxis.min;
7305
- axis.max = originAxis.max;
7306
- }
7307
- /**
7308
- * Reset a box to the provided origin box.
7309
- *
7310
- * This is a mutative operation.
7311
- */
7312
- function copyBoxInto(box, originBox) {
7313
- copyAxisInto(box.x, originBox.x);
7314
- copyAxisInto(box.y, originBox.y);
7315
- }
7316
- /**
7317
- * Reset a delta to the provided origin box.
7318
- *
7319
- * This is a mutative operation.
7320
- */
7321
- function copyAxisDeltaInto(delta, originDelta) {
7322
- delta.translate = originDelta.translate;
7323
- delta.scale = originDelta.scale;
7324
- delta.originPoint = originDelta.originPoint;
7325
- delta.origin = originDelta.origin;
7326
- }
7327
-
7328
7328
  function isAxisDeltaZero(delta) {
7329
7329
  return delta.translate === 0 && delta.scale === 1;
7330
7330
  }
@@ -7466,23 +7466,10 @@
7466
7466
  };
7467
7467
  }
7468
7468
 
7469
- /**
7470
- * Timeout defined in ms
7471
- */
7472
- function delay(callback, timeout) {
7473
- const start = time.now();
7474
- const checkElapsed = ({ timestamp }) => {
7475
- const elapsed = timestamp - start;
7476
- if (elapsed >= timeout) {
7477
- cancelFrame(checkElapsed);
7478
- callback(elapsed - timeout);
7479
- }
7480
- };
7481
- frame.setup(checkElapsed, true);
7482
- return () => cancelFrame(checkElapsed);
7483
- }
7484
- function delayInSeconds(callback, timeout) {
7485
- return delay(callback, secondsToMilliseconds(timeout));
7469
+ function animateSingleValue(value, keyframes, options) {
7470
+ const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
7471
+ motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
7472
+ return motionValue$1.animation;
7486
7473
  }
7487
7474
 
7488
7475
  function addDomEvent(target, eventName, handler, options = { passive: true }) {
@@ -7490,19 +7477,6 @@
7490
7477
  return () => target.removeEventListener(eventName, handler);
7491
7478
  }
7492
7479
 
7493
- /**
7494
- * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
7495
- */
7496
- function resolveMotionValue(value) {
7497
- return isMotionValue(value) ? value.get() : value;
7498
- }
7499
-
7500
- function animateSingleValue(value, keyframes, options) {
7501
- const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
7502
- motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
7503
- return motionValue$1.animation;
7504
- }
7505
-
7506
7480
  const compareByDepth = (a, b) => a.depth - b.depth;
7507
7481
 
7508
7482
  class FlatTree {
@@ -7525,6 +7499,32 @@
7525
7499
  }
7526
7500
  }
7527
7501
 
7502
+ /**
7503
+ * Timeout defined in ms
7504
+ */
7505
+ function delay(callback, timeout) {
7506
+ const start = time.now();
7507
+ const checkElapsed = ({ timestamp }) => {
7508
+ const elapsed = timestamp - start;
7509
+ if (elapsed >= timeout) {
7510
+ cancelFrame(checkElapsed);
7511
+ callback(elapsed - timeout);
7512
+ }
7513
+ };
7514
+ frame.setup(checkElapsed, true);
7515
+ return () => cancelFrame(checkElapsed);
7516
+ }
7517
+ function delayInSeconds(callback, timeout) {
7518
+ return delay(callback, secondsToMilliseconds(timeout));
7519
+ }
7520
+
7521
+ /**
7522
+ * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
7523
+ */
7524
+ function resolveMotionValue(value) {
7525
+ return isMotionValue(value) ? value.get() : value;
7526
+ }
7527
+
7528
7528
  class NodeStack {
7529
7529
  constructor() {
7530
7530
  this.members = [];
@@ -7902,13 +7902,6 @@
7902
7902
  this.relativeTarget = undefined;
7903
7903
  return;
7904
7904
  }
7905
- /**
7906
- * Check if this is a follow element in a crossfade (exiting element
7907
- * whose lead has resumeFrom pointing to it). These elements should
7908
- * NOT create their own animation - they get the lead's animation via
7909
- * resumingFrom.currentAnimation in startAnimation().
7910
- */
7911
- const isFollowInCrossfade = this.isPresent === false && !this.isLead();
7912
7905
  // TODO: Check here if an animation exists
7913
7906
  const layoutTransition = this.options.transition ||
7914
7907
  visualElement.getDefaultTransition() ||
@@ -7932,51 +7925,43 @@
7932
7925
  * relative to its parent has indeed changed. So here we check for that.
7933
7926
  */
7934
7927
  const hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeLayoutChanged;
7935
- /**
7936
- * Skip animation handling for follow elements in a crossfade.
7937
- * These elements get the lead's animation via resumingFrom.currentAnimation
7938
- * in startAnimation(), so they shouldn't create their own animation.
7939
- * They also shouldn't call finishAnimation(), which would reset their state.
7940
- */
7941
- if (!isFollowInCrossfade) {
7942
- if (this.options.layoutRoot ||
7943
- this.resumeFrom ||
7944
- hasOnlyRelativeTargetChanged ||
7945
- (hasLayoutChanged &&
7946
- (hasTargetChanged || !this.currentAnimation))) {
7947
- if (this.resumeFrom) {
7948
- this.resumingFrom = this.resumeFrom;
7949
- this.resumingFrom.resumingFrom = undefined;
7950
- }
7951
- const animationOptions = {
7952
- ...getValueTransition$1(layoutTransition, "layout"),
7953
- onPlay: onLayoutAnimationStart,
7954
- onComplete: onLayoutAnimationComplete,
7955
- };
7956
- if (visualElement.shouldReduceMotion ||
7957
- this.options.layoutRoot) {
7958
- animationOptions.delay = 0;
7959
- animationOptions.type = false;
7960
- }
7961
- this.startAnimation(animationOptions);
7962
- /**
7963
- * Set animation origin after starting animation to avoid layout jump
7964
- * caused by stopping previous layout animation
7965
- */
7966
- this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged);
7928
+ if (this.options.layoutRoot ||
7929
+ this.resumeFrom ||
7930
+ hasOnlyRelativeTargetChanged ||
7931
+ (hasLayoutChanged &&
7932
+ (hasTargetChanged || !this.currentAnimation))) {
7933
+ if (this.resumeFrom) {
7934
+ this.resumingFrom = this.resumeFrom;
7935
+ this.resumingFrom.resumingFrom = undefined;
7936
+ }
7937
+ const animationOptions = {
7938
+ ...getValueTransition$1(layoutTransition, "layout"),
7939
+ onPlay: onLayoutAnimationStart,
7940
+ onComplete: onLayoutAnimationComplete,
7941
+ };
7942
+ if (visualElement.shouldReduceMotion ||
7943
+ this.options.layoutRoot) {
7944
+ animationOptions.delay = 0;
7945
+ animationOptions.type = false;
7946
+ }
7947
+ this.startAnimation(animationOptions);
7948
+ /**
7949
+ * Set animation origin after starting animation to avoid layout jump
7950
+ * caused by stopping previous layout animation
7951
+ */
7952
+ this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged);
7953
+ }
7954
+ else {
7955
+ /**
7956
+ * If the layout hasn't changed and we have an animation that hasn't started yet,
7957
+ * finish it immediately. Otherwise it will be animating from a location
7958
+ * that was probably never committed to screen and look like a jumpy box.
7959
+ */
7960
+ if (!hasLayoutChanged) {
7961
+ finishAnimation(this);
7967
7962
  }
7968
- else {
7969
- /**
7970
- * If the layout hasn't changed and we have an animation that hasn't started yet,
7971
- * finish it immediately. Otherwise it will be animating from a location
7972
- * that was probably never committed to screen and look like a jumpy box.
7973
- */
7974
- if (!hasLayoutChanged) {
7975
- finishAnimation(this);
7976
- }
7977
- if (this.isLead() && this.options.onExitComplete) {
7978
- this.options.onExitComplete();
7979
- }
7963
+ if (this.isLead() && this.options.onExitComplete) {
7964
+ this.options.onExitComplete();
7980
7965
  }
7981
7966
  }
7982
7967
  this.targetLayout = newLayout;
@@ -9261,29 +9246,6 @@
9261
9246
  checkIsScrollRoot: () => true,
9262
9247
  });
9263
9248
 
9264
- const rootProjectionNode = {
9265
- current: undefined,
9266
- };
9267
- const HTMLProjectionNode = createProjectionNode$1({
9268
- measureScroll: (instance) => ({
9269
- x: instance.scrollLeft,
9270
- y: instance.scrollTop,
9271
- }),
9272
- defaultParent: () => {
9273
- if (!rootProjectionNode.current) {
9274
- const documentNode = new DocumentProjectionNode({});
9275
- documentNode.mount(window);
9276
- documentNode.setOptions({ layoutScroll: true });
9277
- rootProjectionNode.current = documentNode;
9278
- }
9279
- return rootProjectionNode.current;
9280
- },
9281
- resetTransform: (instance, value) => {
9282
- instance.style.transform = value !== undefined ? value : "none";
9283
- },
9284
- checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
9285
- });
9286
-
9287
9249
  const notify = (node) => !node.isLayoutDirty && node.willUpdate(false);
9288
9250
  function nodeGroup() {
9289
9251
  const nodes = new Set();
@@ -9307,6 +9269,29 @@
9307
9269
  };
9308
9270
  }
9309
9271
 
9272
+ const rootProjectionNode = {
9273
+ current: undefined,
9274
+ };
9275
+ const HTMLProjectionNode = createProjectionNode$1({
9276
+ measureScroll: (instance) => ({
9277
+ x: instance.scrollLeft,
9278
+ y: instance.scrollTop,
9279
+ }),
9280
+ defaultParent: () => {
9281
+ if (!rootProjectionNode.current) {
9282
+ const documentNode = new DocumentProjectionNode({});
9283
+ documentNode.mount(window);
9284
+ documentNode.setOptions({ layoutScroll: true });
9285
+ rootProjectionNode.current = documentNode;
9286
+ }
9287
+ return rootProjectionNode.current;
9288
+ },
9289
+ resetTransform: (instance, value) => {
9290
+ instance.style.transform = value !== undefined ? value : "none";
9291
+ },
9292
+ checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
9293
+ });
9294
+
9310
9295
  const LAYOUT_SELECTOR = "[data-layout], [data-layout-id]";
9311
9296
  function getLayoutElements(scope) {
9312
9297
  const elements = Array.from(scope.querySelectorAll(LAYOUT_SELECTOR));
@@ -9323,79 +9308,6 @@
9323
9308
  return (element.hasAttribute("data-layout") ||
9324
9309
  element.hasAttribute("data-layout-id"));
9325
9310
  }
9326
- /**
9327
- * Track layout elements before mutation.
9328
- * Does NOT measure bounds - that's handled by the projection system via willUpdate().
9329
- */
9330
- function trackLayoutElements(scope) {
9331
- const elements = getLayoutElements(scope);
9332
- const records = new Map();
9333
- for (const element of elements) {
9334
- records.set(element, {
9335
- element,
9336
- parentElement: element.parentElement,
9337
- nextSibling: element.nextSibling,
9338
- layoutId: getLayoutId(element),
9339
- });
9340
- }
9341
- return records;
9342
- }
9343
- /**
9344
- * Compare before/after records to detect entering/exiting/persisting elements
9345
- */
9346
- function detectMutations(beforeRecords, scope) {
9347
- const afterElements = new Set(getLayoutElements(scope));
9348
- const beforeElements = new Set(beforeRecords.keys());
9349
- const entering = [];
9350
- const exiting = [];
9351
- const persisting = [];
9352
- const sharedEntering = new Map();
9353
- const sharedExiting = new Map();
9354
- const sharedPersisting = new Map();
9355
- // Find exiting elements (were in before, not in after)
9356
- for (const element of beforeElements) {
9357
- if (!afterElements.has(element)) {
9358
- const record = beforeRecords.get(element);
9359
- exiting.push({
9360
- element,
9361
- parentElement: record.parentElement,
9362
- nextSibling: record.nextSibling,
9363
- });
9364
- if (record.layoutId) {
9365
- sharedExiting.set(record.layoutId, element);
9366
- }
9367
- }
9368
- }
9369
- // Find entering and persisting elements
9370
- for (const element of afterElements) {
9371
- if (!beforeElements.has(element)) {
9372
- entering.push(element);
9373
- const layoutId = getLayoutId(element);
9374
- if (layoutId) {
9375
- sharedEntering.set(layoutId, element);
9376
- }
9377
- }
9378
- else {
9379
- persisting.push(element);
9380
- }
9381
- }
9382
- // Find persisting elements that share a layoutId with exiting elements
9383
- // This handles the A -> AB -> A pattern where A persists and B exits
9384
- for (const element of persisting) {
9385
- const layoutId = getLayoutId(element);
9386
- if (layoutId && sharedExiting.has(layoutId)) {
9387
- sharedPersisting.set(layoutId, element);
9388
- }
9389
- }
9390
- return {
9391
- entering,
9392
- exiting,
9393
- persisting,
9394
- sharedEntering,
9395
- sharedExiting,
9396
- sharedPersisting,
9397
- };
9398
- }
9399
9311
 
9400
9312
  let scaleCorrectorAdded = false;
9401
9313
  /**
@@ -9603,12 +9515,8 @@
9603
9515
  // Queue execution on microtask to allow builder methods to be called
9604
9516
  queueMicrotask(() => this.execute());
9605
9517
  }
9606
- shared(layoutIdOrOptions, options) {
9607
- if (typeof layoutIdOrOptions === "string") {
9608
- this.sharedTransitions.set(layoutIdOrOptions, options);
9609
- }
9610
- // For now, we ignore default shared options as the projection system
9611
- // handles shared transitions automatically
9518
+ shared(id, options) {
9519
+ this.sharedTransitions.set(id, options);
9612
9520
  return this;
9613
9521
  }
9614
9522
  then(onfulfilled, onrejected) {
@@ -9618,132 +9526,65 @@
9618
9526
  if (this.executed)
9619
9527
  return;
9620
9528
  this.executed = true;
9621
- const animations = [];
9622
9529
  let context;
9623
- try {
9624
- // Phase 1: Pre-mutation - Build projection tree and take snapshots
9625
- const existingElements = getLayoutElements(this.scope);
9626
- // Build projection tree for existing elements FIRST
9627
- // This allows the projection system to handle measurements correctly
9628
- if (existingElements.length > 0) {
9629
- context = buildProjectionTree(existingElements, undefined, this.getBuildOptions());
9630
- // Start update cycle
9631
- context.root.startUpdate();
9632
- // Call willUpdate on all nodes to capture snapshots via projection system
9633
- // This handles transforms, scroll, etc. correctly
9634
- for (const node of context.nodes.values()) {
9635
- // Reset isLayoutDirty so willUpdate can take a snapshot.
9636
- // When hasTreeAnimated is true on the global root, newly mounted nodes
9637
- // get isLayoutDirty=true, which causes willUpdate to skip snapshot capture.
9638
- node.isLayoutDirty = false;
9639
- node.willUpdate();
9640
- }
9641
- }
9642
- // Track DOM structure (parent, sibling) for detecting removals
9643
- // No bounds measurement here - projection system already handled that
9644
- const beforeRecords = trackLayoutElements(this.scope);
9645
- // Phase 2: Execute DOM update
9646
- this.updateDom();
9647
- // Phase 3: Post-mutation (Detect & Prepare)
9648
- const mutationResult = detectMutations(beforeRecords, this.scope);
9649
- // Determine which exiting elements should be reattached:
9650
- // Non-shared exiting elements (no layoutId or no matching entering/persisting element)
9651
- const exitingToReattach = mutationResult.exiting.filter(({ element }) => {
9652
- const layoutId = element.getAttribute("data-layout-id");
9653
- const isSharedWithEntering = layoutId && mutationResult.sharedEntering.has(layoutId);
9654
- const isSharedWithPersisting = layoutId && mutationResult.sharedPersisting.has(layoutId);
9655
- // Reattach if not a shared element
9656
- return !isSharedWithEntering && !isSharedWithPersisting;
9657
- });
9658
- this.reattachExitingElements(exitingToReattach, context);
9659
- // Build projection nodes for entering elements
9660
- if (mutationResult.entering.length > 0) {
9661
- context = buildProjectionTree(mutationResult.entering, context, this.getBuildOptions());
9662
- }
9663
- // Also ensure persisting elements have nodes if context didn't exist
9664
- if (!context && mutationResult.persisting.length > 0) {
9665
- context = buildProjectionTree(mutationResult.persisting, undefined, this.getBuildOptions());
9666
- }
9667
- // Build set of shared exiting elements (they don't animate separately)
9668
- const sharedExitingElements = new Set();
9669
- for (const [layoutId, enteringElement] of mutationResult.sharedEntering) {
9670
- const exitingElement = mutationResult.sharedExiting.get(layoutId);
9671
- if (exitingElement) {
9672
- sharedExitingElements.add(exitingElement);
9673
- const exitingNode = context?.nodes.get(exitingElement);
9674
- const enteringNode = context?.nodes.get(enteringElement);
9675
- if (exitingNode && enteringNode) {
9676
- // Remove exiting node from stack, no crossfade
9677
- const stack = exitingNode.getStack();
9678
- if (stack) {
9679
- stack.remove(exitingNode);
9680
- }
9681
- }
9682
- else if (exitingNode && !enteringNode) {
9683
- // Fallback: If entering node doesn't exist yet, just handle exiting
9684
- const stack = exitingNode.getStack();
9685
- if (stack) {
9686
- stack.remove(exitingNode);
9687
- }
9688
- }
9689
- }
9690
- }
9691
- // Handle A -> AB -> A pattern: persisting element should become lead again
9692
- // when exiting element with same layoutId is removed
9693
- for (const [layoutId, persistingElement] of mutationResult.sharedPersisting) {
9694
- const exitingElement = mutationResult.sharedExiting.get(layoutId);
9695
- if (!exitingElement)
9696
- continue;
9697
- sharedExitingElements.add(exitingElement);
9698
- const exitingNode = context?.nodes.get(exitingElement);
9699
- const persistingNode = context?.nodes.get(persistingElement);
9700
- if (exitingNode && persistingNode) {
9701
- // Remove exiting node from stack, no crossfade
9702
- const stack = exitingNode.getStack();
9703
- if (stack) {
9704
- stack.remove(exitingNode);
9705
- }
9706
- }
9707
- }
9708
- // Phase 4: Animate
9709
- if (context) {
9710
- // Trigger layout animations via didUpdate
9711
- context.root.didUpdate();
9712
- // Wait for animations to be created (they're scheduled via frame.update)
9713
- await new Promise((resolve) => frame.postRender(() => resolve()));
9714
- // Collect layout animations from projection nodes
9715
- // Skip shared exiting elements (they don't animate)
9716
- for (const [element, node] of context.nodes.entries()) {
9717
- if (sharedExitingElements.has(element))
9718
- continue;
9719
- if (node.currentAnimation) {
9720
- animations.push(node.currentAnimation);
9721
- }
9722
- }
9723
- }
9724
- // Create and return group animation
9725
- const groupAnimation = new GroupAnimation(animations);
9726
- // Phase 5: Setup cleanup on complete
9727
- // Only cleanup exiting elements - persisting elements keep their nodes
9728
- // This matches React's behavior where nodes persist for elements in the DOM
9729
- const exitingElements = new Set(mutationResult.exiting.map(({ element }) => element));
9730
- groupAnimation.finished.then(() => {
9731
- // Clean up all reattached exiting elements (remove from DOM)
9732
- this.cleanupExitingElements(exitingToReattach);
9733
- if (context) {
9734
- // Only cleanup projection nodes for exiting elements
9735
- cleanupProjectionTree(context, exitingElements);
9736
- }
9737
- });
9738
- this.notifyReady(groupAnimation);
9530
+ // Phase 1: Pre-mutation - Build projection tree and take snapshots
9531
+ const beforeElements = getLayoutElements(this.scope);
9532
+ if (beforeElements.length > 0) {
9533
+ context = buildProjectionTree(beforeElements, undefined, this.getBuildOptions());
9534
+ context.root.startUpdate();
9535
+ for (const node of context.nodes.values()) {
9536
+ node.isLayoutDirty = false;
9537
+ node.willUpdate();
9538
+ }
9539
+ }
9540
+ // Phase 2: Execute DOM update
9541
+ this.updateDom();
9542
+ // Phase 3: Post-mutation - Compare before/after elements
9543
+ const afterElements = getLayoutElements(this.scope);
9544
+ const beforeSet = new Set(beforeElements);
9545
+ const afterSet = new Set(afterElements);
9546
+ const entering = afterElements.filter((el) => !beforeSet.has(el));
9547
+ const exiting = beforeElements.filter((el) => !afterSet.has(el));
9548
+ // Build projection nodes for entering elements
9549
+ if (entering.length > 0) {
9550
+ context = buildProjectionTree(entering, context, this.getBuildOptions());
9551
+ }
9552
+ // No layout elements - return empty animation
9553
+ if (!context) {
9554
+ this.notifyReady(new GroupAnimation([]));
9555
+ return;
9739
9556
  }
9740
- catch (error) {
9741
- // Cleanup on error - cleanup all nodes since animation failed
9742
- if (context) {
9743
- cleanupProjectionTree(context);
9744
- }
9745
- throw error;
9557
+ // Handle shared elements
9558
+ for (const element of exiting) {
9559
+ const node = context.nodes.get(element);
9560
+ node?.getStack()?.remove(node);
9746
9561
  }
9562
+ for (const element of entering) {
9563
+ context.nodes.get(element)?.promote();
9564
+ }
9565
+ // Phase 4: Animate
9566
+ context.root.didUpdate();
9567
+ await new Promise((resolve) => frame.postRender(() => resolve()));
9568
+ const animations = [];
9569
+ for (const node of context.nodes.values()) {
9570
+ if (node.currentAnimation) {
9571
+ animations.push(node.currentAnimation);
9572
+ }
9573
+ }
9574
+ const groupAnimation = new GroupAnimation(animations);
9575
+ groupAnimation.finished.then(() => {
9576
+ // Only clean up nodes for elements no longer in the document.
9577
+ // Elements still in DOM keep their nodes so subsequent animations
9578
+ // can use the stored position snapshots (A→B→A pattern).
9579
+ const elementsToCleanup = new Set();
9580
+ for (const element of context.nodes.keys()) {
9581
+ if (!document.contains(element)) {
9582
+ elementsToCleanup.add(element);
9583
+ }
9584
+ }
9585
+ cleanupProjectionTree(context, elementsToCleanup);
9586
+ });
9587
+ this.notifyReady(groupAnimation);
9747
9588
  }
9748
9589
  getBuildOptions() {
9749
9590
  return {
@@ -9756,43 +9597,6 @@
9756
9597
  : undefined,
9757
9598
  };
9758
9599
  }
9759
- reattachExitingElements(exiting, context) {
9760
- for (const { element, parentElement, nextSibling } of exiting) {
9761
- // Check if parent still exists in DOM
9762
- if (!parentElement.isConnected)
9763
- continue;
9764
- // Get bounds from projection node snapshot (measured correctly via projection system)
9765
- const node = context?.nodes.get(element);
9766
- const snapshot = node?.snapshot;
9767
- if (!snapshot)
9768
- continue;
9769
- const { layoutBox } = snapshot;
9770
- // Reattach element
9771
- if (nextSibling && nextSibling.parentNode === parentElement) {
9772
- parentElement.insertBefore(element, nextSibling);
9773
- }
9774
- else {
9775
- parentElement.appendChild(element);
9776
- }
9777
- // Apply absolute positioning to prevent layout shift
9778
- // Use layoutBox from projection system which has transform-free measurements
9779
- const htmlElement = element;
9780
- htmlElement.style.position = "absolute";
9781
- htmlElement.style.top = `${layoutBox.y.min}px`;
9782
- htmlElement.style.left = `${layoutBox.x.min}px`;
9783
- htmlElement.style.width = `${layoutBox.x.max - layoutBox.x.min}px`;
9784
- htmlElement.style.height = `${layoutBox.y.max - layoutBox.y.min}px`;
9785
- htmlElement.style.margin = "0";
9786
- htmlElement.style.pointerEvents = "none";
9787
- }
9788
- }
9789
- cleanupExitingElements(exiting) {
9790
- for (const { element } of exiting) {
9791
- if (element.parentElement) {
9792
- element.parentElement.removeChild(element);
9793
- }
9794
- }
9795
- }
9796
9600
  }
9797
9601
  /**
9798
9602
  * Parse arguments for animateLayout overloads
@@ -9816,45 +9620,6 @@
9816
9620
  };
9817
9621
  }
9818
9622
 
9819
- /**
9820
- * Animate layout changes within a DOM tree.
9821
- *
9822
- * @example
9823
- * ```typescript
9824
- * // Basic usage - animates all elements with data-layout or data-layout-id
9825
- * await animateLayout(() => {
9826
- * container.innerHTML = newContent
9827
- * })
9828
- *
9829
- * // With scope - only animates within the container
9830
- * await animateLayout(".container", () => {
9831
- * updateContent()
9832
- * })
9833
- *
9834
- * // With options
9835
- * await animateLayout(() => update(), { duration: 0.5 })
9836
- *
9837
- * // Configure shared element transitions
9838
- * animateLayout(".cards", () => {
9839
- * container.innerHTML = newCards
9840
- * }, { duration: 0.3 })
9841
- * .shared("hero", { type: "spring" })
9842
- * ```
9843
- *
9844
- * Elements are animated if they have:
9845
- * - `data-layout` attribute (layout animation only)
9846
- * - `data-layout-id` attribute (shared element transitions)
9847
- *
9848
- * @param scopeOrUpdateDom - Either a scope selector/element, or the DOM update function
9849
- * @param updateDomOrOptions - Either the DOM update function or animation options
9850
- * @param options - Animation options (when scope is provided)
9851
- * @returns A builder that resolves to animation controls
9852
- */
9853
- function unstable_animateLayout(scopeOrUpdateDom, updateDomOrOptions, options) {
9854
- const { scope, updateDom, defaultOptions } = parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options);
9855
- return new LayoutAnimationBuilder(scope, updateDom, defaultOptions);
9856
- }
9857
-
9858
9623
  /**
9859
9624
  * @deprecated
9860
9625
  *
@@ -11146,6 +10911,7 @@
11146
10911
  exports.observeTimeline = observeTimeline;
11147
10912
  exports.optimizedAppearDataAttribute = optimizedAppearDataAttribute;
11148
10913
  exports.optimizedAppearDataId = optimizedAppearDataId;
10914
+ exports.parseAnimateLayoutArgs = parseAnimateLayoutArgs;
11149
10915
  exports.parseCSSVariable = parseCSSVariable;
11150
10916
  exports.parseValueFromTransform = parseValueFromTransform;
11151
10917
  exports.percent = percent;
@@ -11215,7 +10981,6 @@
11215
10981
  exports.transformValue = transformValue;
11216
10982
  exports.transformValueTypes = transformValueTypes;
11217
10983
  exports.translateAxis = translateAxis;
11218
- exports.unstable_animateLayout = unstable_animateLayout;
11219
10984
  exports.updateMotionValuesFromProps = updateMotionValuesFromProps;
11220
10985
  exports.variantPriorityOrder = variantPriorityOrder;
11221
10986
  exports.variantProps = variantProps;