motion 12.29.1-alpha.0 → 12.29.2

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.
@@ -81,7 +81,7 @@
81
81
  }
82
82
 
83
83
  /*#__NO_SIDE_EFFECTS__*/
84
- const noop = (any) => any;
84
+ const noop$1 = (any) => any;
85
85
 
86
86
  /**
87
87
  * Pipe
@@ -227,7 +227,7 @@
227
227
  function cubicBezier(mX1, mY1, mX2, mY2) {
228
228
  // If this is a linear gradient, return linear easing
229
229
  if (mX1 === mY1 && mX2 === mY2)
230
- return noop;
230
+ return noop$1;
231
231
  const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
232
232
  // If animation is at start/end, return t without easing
233
233
  return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
@@ -278,7 +278,7 @@
278
278
  const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
279
279
 
280
280
  const easingLookup = {
281
- linear: noop,
281
+ linear: noop$1,
282
282
  easeIn,
283
283
  easeInOut,
284
284
  easeOut,
@@ -479,7 +479,7 @@
479
479
  return { schedule, cancel, state, steps };
480
480
  }
481
481
 
482
- const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
482
+ const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop$1, true);
483
483
 
484
484
  let now;
485
485
  function clearTime() {
@@ -1429,7 +1429,7 @@
1429
1429
  for (let i = 0; i < numMixers; i++) {
1430
1430
  let mixer = mixerFactory(output[i], output[i + 1]);
1431
1431
  if (ease) {
1432
- const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
1432
+ const easingFunction = Array.isArray(ease) ? ease[i] || noop$1 : ease;
1433
1433
  mixer = pipe(easingFunction, mixer);
1434
1434
  }
1435
1435
  mixers.push(mixer);
@@ -1921,30 +1921,6 @@
1921
1921
  this.startTime = 0;
1922
1922
  return this.tick(sampleTime, true);
1923
1923
  }
1924
- /**
1925
- * Returns whether this animation can provide accurate velocity sampling.
1926
- * This is false for animations using mixKeyframes (non-numeric values)
1927
- * or animations that aren't currently running.
1928
- */
1929
- get canSampleVelocity() {
1930
- return (this.state === "running" &&
1931
- !this.isStopped &&
1932
- !this.mixKeyframes &&
1933
- !!this.generator);
1934
- }
1935
- /**
1936
- * Sample the animation's value at a specific time in milliseconds.
1937
- * Used for velocity calculations via finite differencing.
1938
- */
1939
- sampleAt(t) {
1940
- return this.generator.next(t).value;
1941
- }
1942
- /**
1943
- * The current elapsed time of the animation in milliseconds.
1944
- */
1945
- get elapsed() {
1946
- return this.currentTime;
1947
- }
1948
1924
  attachTimeline(timeline) {
1949
1925
  if (this.options.allowFlatten) {
1950
1926
  this.options.type = "keyframes";
@@ -2515,7 +2491,7 @@
2515
2491
  this.animation.onfinish = null;
2516
2492
  if (timeline && supportsScrollTimeline()) {
2517
2493
  this.animation.timeline = timeline;
2518
- return noop;
2494
+ return noop$1;
2519
2495
  }
2520
2496
  else {
2521
2497
  return observe(this);
@@ -2807,7 +2783,7 @@
2807
2783
  : new JSAnimation(resolvedOptions);
2808
2784
  animation.finished.then(() => {
2809
2785
  this.notifyFinished();
2810
- }).catch(noop);
2786
+ }).catch(noop$1);
2811
2787
  if (this.pendingTimeline) {
2812
2788
  this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
2813
2789
  this.pendingTimeline = undefined;
@@ -5063,25 +5039,16 @@
5063
5039
  }
5064
5040
  };
5065
5041
  const startAnimation = () => {
5042
+ stopAnimation();
5066
5043
  const currentValue = asNumber$1(value.get());
5067
5044
  const targetValue = asNumber$1(latestValue);
5068
5045
  // Don't animate if we're already at the target
5069
5046
  if (currentValue === targetValue) {
5070
- stopAnimation();
5071
5047
  return;
5072
5048
  }
5073
- // Get velocity from the running animation before stopping it.
5074
- // This provides the instantaneous velocity from the spring physics,
5075
- // which is more accurate than the motion value's finite difference.
5076
- let velocity = value.getVelocity();
5077
- if (activeAnimation?.canSampleVelocity) {
5078
- const elapsed = activeAnimation.elapsed;
5079
- velocity = calcGeneratorVelocity((t) => activeAnimation.sampleAt(t), elapsed, activeAnimation.sampleAt(elapsed));
5080
- }
5081
- stopAnimation();
5082
5049
  activeAnimation = new JSAnimation({
5083
5050
  keyframes: [currentValue, targetValue],
5084
- velocity,
5051
+ velocity: value.getVelocity(),
5085
5052
  // Default to spring if no type specified (matches useSpring behavior)
5086
5053
  type: "spring",
5087
5054
  restDelta: 0.001,
@@ -5497,7 +5464,7 @@
5497
5464
  constructor(update, options = {}) {
5498
5465
  this.currentSubject = "root";
5499
5466
  this.targets = new Map();
5500
- this.notifyReady = noop;
5467
+ this.notifyReady = noop$1;
5501
5468
  this.readyPromise = new Promise((resolve) => {
5502
5469
  this.notifyReady = resolve;
5503
5470
  });
@@ -7554,7 +7521,7 @@
7554
7521
  : values.borderRadius;
7555
7522
  }
7556
7523
  const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
7557
- const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
7524
+ const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop$1);
7558
7525
  function compress(min, max, easing) {
7559
7526
  return (p) => {
7560
7527
  // Could replace ifs with clamp
@@ -7793,7 +7760,7 @@
7793
7760
  cancelTreeOptimisedTransformAnimations(parent);
7794
7761
  }
7795
7762
  }
7796
- function createProjectionNode$1({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
7763
+ function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
7797
7764
  return class ProjectionNode {
7798
7765
  constructor(latestValues = {}, parent = defaultParent?.()) {
7799
7766
  /**
@@ -9331,7 +9298,7 @@
9331
9298
  */
9332
9299
  const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
9333
9300
  ? Math.round
9334
- : noop;
9301
+ : noop$1;
9335
9302
  function roundAxis(axis) {
9336
9303
  // Round to the nearest .5 pixels to support subpixel layouts
9337
9304
  axis.min = roundPoint(axis.min);
@@ -9350,7 +9317,7 @@
9350
9317
  return node !== node.root && node.scroll?.wasRoot;
9351
9318
  }
9352
9319
 
9353
- const DocumentProjectionNode = createProjectionNode$1({
9320
+ const DocumentProjectionNode = createProjectionNode({
9354
9321
  attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
9355
9322
  measureScroll: () => ({
9356
9323
  x: document.documentElement.scrollLeft || document.body?.scrollLeft || 0,
@@ -9385,7 +9352,7 @@
9385
9352
  const rootProjectionNode = {
9386
9353
  current: undefined,
9387
9354
  };
9388
- const HTMLProjectionNode = createProjectionNode$1({
9355
+ const HTMLProjectionNode = createProjectionNode({
9389
9356
  measureScroll: (instance) => ({
9390
9357
  x: instance.scrollLeft,
9391
9358
  y: instance.scrollTop,
@@ -9405,315 +9372,153 @@
9405
9372
  checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
9406
9373
  });
9407
9374
 
9408
- const LAYOUT_SELECTOR = "[data-layout], [data-layout-id]";
9409
- function getLayoutElements(scope) {
9410
- const elements = Array.from(scope.querySelectorAll(LAYOUT_SELECTOR));
9411
- // Include scope itself if it's an Element (not Document) and has layout attributes
9412
- if (scope instanceof Element && hasLayout(scope)) {
9413
- elements.unshift(scope);
9414
- }
9415
- return elements;
9416
- }
9417
- function getLayoutId(element) {
9418
- return element.getAttribute("data-layout-id");
9419
- }
9420
- function hasLayout(element) {
9421
- return (element.hasAttribute("data-layout") ||
9422
- element.hasAttribute("data-layout-id"));
9423
- }
9424
-
9425
- let scaleCorrectorAdded = false;
9426
- /**
9427
- * Track active projection nodes per element to handle animation interruption.
9428
- * When a new animation starts on an element that already has an active animation,
9429
- * we need to stop the old animation so the new one can start from the current
9430
- * visual position.
9431
- */
9432
- const activeProjectionNodes = new WeakMap();
9433
- function ensureScaleCorrectors() {
9434
- if (scaleCorrectorAdded)
9435
- return;
9436
- scaleCorrectorAdded = true;
9437
- addScaleCorrector({
9438
- borderRadius: {
9439
- ...correctBorderRadius,
9440
- applyTo: [
9441
- "borderTopLeftRadius",
9442
- "borderTopRightRadius",
9443
- "borderBottomLeftRadius",
9444
- "borderBottomRightRadius",
9445
- ],
9446
- },
9447
- borderTopLeftRadius: correctBorderRadius,
9448
- borderTopRightRadius: correctBorderRadius,
9449
- borderBottomLeftRadius: correctBorderRadius,
9450
- borderBottomRightRadius: correctBorderRadius,
9451
- boxShadow: correctBoxShadow,
9452
- });
9453
- }
9454
- /**
9455
- * Get DOM depth of an element
9456
- */
9457
- function getDepth(element) {
9458
- let depth = 0;
9459
- let current = element.parentElement;
9460
- while (current) {
9461
- depth++;
9462
- current = current.parentElement;
9463
- }
9464
- return depth;
9465
- }
9466
- /**
9467
- * Find the closest projection parent for an element
9468
- */
9469
- function findProjectionParent(element, nodeCache) {
9470
- let parent = element.parentElement;
9471
- while (parent) {
9472
- const node = nodeCache.get(parent);
9473
- if (node)
9474
- return node;
9475
- parent = parent.parentElement;
9476
- }
9477
- return undefined;
9478
- }
9479
- /**
9480
- * Create or reuse a projection node for an element
9481
- */
9482
- function createProjectionNode(element, parent, options, transition) {
9483
- // Check for existing active node - reuse it to preserve animation state
9484
- const existingNode = activeProjectionNodes.get(element);
9485
- if (existingNode) {
9486
- const visualElement = existingNode.options.visualElement;
9487
- // Update transition options for the new animation
9488
- const nodeTransition = transition
9489
- ? { duration: transition.duration, ease: transition.ease }
9490
- : { duration: 0.3, ease: "easeOut" };
9491
- existingNode.setOptions({
9492
- ...existingNode.options,
9493
- animate: true,
9494
- transition: nodeTransition,
9495
- ...options,
9496
- });
9497
- // Re-mount the node if it was previously unmounted
9498
- // This re-adds it to root.nodes so didUpdate() will process it
9499
- if (!existingNode.instance) {
9500
- existingNode.mount(element);
9501
- }
9502
- return { node: existingNode, visualElement };
9503
- }
9504
- // No existing node - create a new one
9505
- const latestValues = {};
9506
- const visualElement = new HTMLVisualElement({
9507
- visualState: {
9508
- latestValues,
9509
- renderState: {
9510
- transformOrigin: {},
9511
- transform: {},
9512
- style: {},
9513
- vars: {},
9514
- },
9515
- },
9516
- presenceContext: null,
9517
- props: {},
9518
- });
9519
- const node = new HTMLProjectionNode(latestValues, parent);
9520
- // Convert AnimationOptions to transition format for the projection system
9521
- const nodeTransition = transition
9522
- ? { duration: transition.duration, ease: transition.ease }
9523
- : { duration: 0.3, ease: "easeOut" };
9524
- node.setOptions({
9525
- visualElement,
9526
- layout: true,
9527
- animate: true,
9528
- transition: nodeTransition,
9529
- ...options,
9530
- });
9531
- node.mount(element);
9532
- visualElement.projection = node;
9533
- // Track this node as the active one for this element
9534
- activeProjectionNodes.set(element, node);
9535
- return { node, visualElement };
9536
- }
9537
- /**
9538
- * Build a projection tree from a list of elements
9539
- */
9540
- function buildProjectionTree(elements, existingContext, options) {
9541
- ensureScaleCorrectors();
9542
- const nodes = existingContext?.nodes ?? new Map();
9543
- const visualElements = existingContext?.visualElements ?? new Map();
9544
- const group = existingContext?.group ?? nodeGroup();
9545
- const defaultTransition = options?.defaultTransition;
9546
- const sharedTransitions = options?.sharedTransitions;
9547
- // Sort elements by DOM depth (parents before children)
9548
- const sorted = [...elements].sort((a, b) => getDepth(a) - getDepth(b));
9549
- let root = existingContext?.root;
9550
- for (const element of sorted) {
9551
- // Skip if already has a node
9552
- if (nodes.has(element))
9553
- continue;
9554
- const parent = findProjectionParent(element, nodes);
9555
- const layoutId = getLayoutId(element);
9556
- const layoutMode = element.getAttribute("data-layout");
9557
- const nodeOptions = {
9558
- layoutId: layoutId ?? undefined,
9559
- animationType: parseLayoutMode(layoutMode),
9560
- };
9561
- // Use layoutId-specific transition if available, otherwise use default
9562
- const transition = layoutId && sharedTransitions?.get(layoutId)
9563
- ? sharedTransitions.get(layoutId)
9564
- : defaultTransition;
9565
- const { node, visualElement } = createProjectionNode(element, parent, nodeOptions, transition);
9566
- nodes.set(element, node);
9567
- visualElements.set(element, visualElement);
9568
- group.add(node);
9569
- if (!root) {
9570
- root = node.root;
9571
- }
9572
- }
9375
+ const layoutSelector = "[data-layout], [data-layout-id]";
9376
+ const noop = () => { };
9377
+ function snapshotFromTarget(projection) {
9378
+ const target = projection.targetWithTransforms || projection.target;
9379
+ if (!target)
9380
+ return undefined;
9381
+ const measuredBox = createBox();
9382
+ const layoutBox = createBox();
9383
+ copyBoxInto(measuredBox, target);
9384
+ copyBoxInto(layoutBox, target);
9573
9385
  return {
9574
- nodes,
9575
- visualElements,
9576
- group,
9577
- root: root,
9386
+ animationId: projection.root?.animationId ?? 0,
9387
+ measuredBox,
9388
+ layoutBox,
9389
+ latestValues: projection.animationValues || projection.latestValues || {},
9390
+ source: projection.id,
9578
9391
  };
9579
9392
  }
9580
- /**
9581
- * Parse the data-layout attribute value
9582
- */
9583
- function parseLayoutMode(value) {
9584
- if (value === "position")
9585
- return "position";
9586
- if (value === "size")
9587
- return "size";
9588
- if (value === "preserve-aspect")
9589
- return "preserve-aspect";
9590
- return "both";
9591
- }
9592
- /**
9593
- * Clean up projection nodes for specific elements.
9594
- * If elementsToCleanup is provided, only those elements are cleaned up.
9595
- * If not provided, all nodes are cleaned up.
9596
- *
9597
- * This allows persisting elements to keep their nodes between animations,
9598
- * matching React's behavior where nodes persist for elements that remain in the DOM.
9599
- */
9600
- function cleanupProjectionTree(context, elementsToCleanup) {
9601
- const elementsToProcess = elementsToCleanup
9602
- ? [...context.nodes.entries()].filter(([el]) => elementsToCleanup.has(el))
9603
- : [...context.nodes.entries()];
9604
- for (const [element, node] of elementsToProcess) {
9605
- context.group.remove(node);
9606
- node.unmount();
9607
- // Only clear from activeProjectionNodes if this is still the active node.
9608
- // A newer animation might have already taken over.
9609
- if (activeProjectionNodes.get(element) === node) {
9610
- activeProjectionNodes.delete(element);
9611
- }
9612
- context.nodes.delete(element);
9613
- context.visualElements.delete(element);
9614
- }
9615
- }
9616
-
9617
9393
  class LayoutAnimationBuilder {
9618
9394
  constructor(scope, updateDom, defaultOptions) {
9619
9395
  this.sharedTransitions = new Map();
9620
9396
  this.notifyReady = noop;
9621
- this.executed = false;
9397
+ this.rejectReady = noop;
9622
9398
  this.scope = scope;
9623
9399
  this.updateDom = updateDom;
9624
9400
  this.defaultOptions = defaultOptions;
9625
- this.readyPromise = new Promise((resolve) => {
9401
+ this.readyPromise = new Promise((resolve, reject) => {
9626
9402
  this.notifyReady = resolve;
9403
+ this.rejectReady = reject;
9404
+ });
9405
+ frame.postRender(() => {
9406
+ this.start().then(this.notifyReady).catch(this.rejectReady);
9627
9407
  });
9628
- // Queue execution on microtask to allow builder methods to be called
9629
- queueMicrotask(() => this.execute());
9630
9408
  }
9631
- shared(id, options) {
9632
- this.sharedTransitions.set(id, options);
9409
+ shared(id, transition) {
9410
+ this.sharedTransitions.set(id, transition);
9633
9411
  return this;
9634
9412
  }
9635
- then(onfulfilled, onrejected) {
9636
- return this.readyPromise.then(onfulfilled, onrejected);
9413
+ then(resolve, reject) {
9414
+ return this.readyPromise.then(resolve, reject);
9637
9415
  }
9638
- async execute() {
9639
- if (this.executed)
9640
- return;
9641
- this.executed = true;
9642
- let context;
9643
- // Phase 1: Pre-mutation - Build projection tree and take snapshots
9644
- const beforeElements = getLayoutElements(this.scope);
9645
- if (beforeElements.length > 0) {
9646
- context = buildProjectionTree(beforeElements, undefined, this.getBuildOptions());
9647
- context.root.startUpdate();
9648
- for (const node of context.nodes.values()) {
9649
- node.isLayoutDirty = false;
9650
- node.willUpdate();
9651
- }
9652
- }
9653
- // Phase 2: Execute DOM update
9654
- this.updateDom();
9655
- // Phase 3: Post-mutation - Compare before/after elements
9656
- const afterElements = getLayoutElements(this.scope);
9657
- const beforeSet = new Set(beforeElements);
9658
- const afterSet = new Set(afterElements);
9659
- const entering = afterElements.filter((el) => !beforeSet.has(el));
9660
- const exiting = beforeElements.filter((el) => !afterSet.has(el));
9661
- // Build projection nodes for entering elements
9662
- if (entering.length > 0) {
9663
- context = buildProjectionTree(entering, context, this.getBuildOptions());
9664
- }
9665
- // No layout elements - return empty animation
9666
- if (!context) {
9667
- this.notifyReady(new GroupAnimation([]));
9668
- return;
9669
- }
9670
- // Handle shared elements
9671
- for (const element of exiting) {
9672
- const node = context.nodes.get(element);
9673
- node?.getStack()?.remove(node);
9674
- }
9675
- for (const element of entering) {
9676
- context.nodes.get(element)?.promote();
9677
- }
9678
- // Phase 4: Animate
9679
- context.root.didUpdate();
9680
- await new Promise((resolve) => frame.postRender(() => resolve()));
9681
- const animations = [];
9682
- for (const node of context.nodes.values()) {
9683
- if (node.currentAnimation) {
9684
- animations.push(node.currentAnimation);
9685
- }
9686
- }
9687
- const groupAnimation = new GroupAnimation(animations);
9688
- groupAnimation.finished.then(() => {
9689
- // Only clean up nodes for elements no longer in the document.
9690
- // Elements still in DOM keep their nodes so subsequent animations
9691
- // can use the stored position snapshots (A→B→A pattern).
9692
- const elementsToCleanup = new Set();
9693
- for (const element of context.nodes.keys()) {
9694
- if (!document.contains(element)) {
9695
- elementsToCleanup.add(element);
9416
+ async start() {
9417
+ const beforeElements = collectLayoutElements(this.scope);
9418
+ const beforeRecords = this.buildRecords(beforeElements);
9419
+ beforeRecords.forEach(({ projection }) => {
9420
+ const hasCurrentAnimation = Boolean(projection.currentAnimation);
9421
+ const isSharedLayout = Boolean(projection.options.layoutId);
9422
+ if (hasCurrentAnimation && isSharedLayout) {
9423
+ const snapshot = snapshotFromTarget(projection);
9424
+ if (snapshot) {
9425
+ projection.snapshot = snapshot;
9696
9426
  }
9427
+ else if (projection.snapshot) {
9428
+ projection.snapshot = undefined;
9429
+ }
9430
+ }
9431
+ else if (projection.snapshot &&
9432
+ (projection.currentAnimation || projection.isProjecting())) {
9433
+ projection.snapshot = undefined;
9697
9434
  }
9698
- cleanupProjectionTree(context, elementsToCleanup);
9435
+ projection.isPresent = true;
9436
+ projection.willUpdate();
9699
9437
  });
9700
- this.notifyReady(groupAnimation);
9438
+ await this.updateDom();
9439
+ const afterElements = collectLayoutElements(this.scope);
9440
+ const afterRecords = this.buildRecords(afterElements);
9441
+ this.handleExitingElements(beforeRecords, afterRecords);
9442
+ afterRecords.forEach(({ projection }) => {
9443
+ const instance = projection.instance;
9444
+ const resumeFromInstance = projection.resumeFrom
9445
+ ?.instance;
9446
+ if (!instance || !resumeFromInstance)
9447
+ return;
9448
+ if (!("style" in instance))
9449
+ return;
9450
+ const currentTransform = instance.style.transform;
9451
+ const resumeFromTransform = resumeFromInstance.style.transform;
9452
+ if (currentTransform &&
9453
+ resumeFromTransform &&
9454
+ currentTransform === resumeFromTransform) {
9455
+ instance.style.transform = "";
9456
+ instance.style.transformOrigin = "";
9457
+ }
9458
+ });
9459
+ afterRecords.forEach(({ projection }) => {
9460
+ projection.isPresent = true;
9461
+ });
9462
+ const root = getProjectionRoot(afterRecords, beforeRecords);
9463
+ root?.didUpdate();
9464
+ await new Promise((resolve) => {
9465
+ frame.postRender(() => resolve());
9466
+ });
9467
+ const animations = collectAnimations(afterRecords);
9468
+ const animation = new GroupAnimation(animations);
9469
+ return animation;
9701
9470
  }
9702
- getBuildOptions() {
9703
- return {
9704
- defaultTransition: this.defaultOptions || {
9705
- duration: 0.3,
9706
- ease: "easeOut",
9707
- },
9708
- sharedTransitions: this.sharedTransitions.size > 0
9709
- ? this.sharedTransitions
9710
- : undefined,
9711
- };
9471
+ buildRecords(elements) {
9472
+ const records = [];
9473
+ const recordMap = new Map();
9474
+ for (const element of elements) {
9475
+ const parentRecord = findParentRecord(element, recordMap, this.scope);
9476
+ const { layout, layoutId } = readLayoutAttributes(element);
9477
+ const override = layoutId
9478
+ ? this.sharedTransitions.get(layoutId)
9479
+ : undefined;
9480
+ const transition = override || this.defaultOptions;
9481
+ const record = getOrCreateRecord(element, parentRecord?.projection, {
9482
+ layout,
9483
+ layoutId,
9484
+ animationType: typeof layout === "string" ? layout : "both",
9485
+ transition: transition,
9486
+ });
9487
+ recordMap.set(element, record);
9488
+ records.push(record);
9489
+ }
9490
+ return records;
9491
+ }
9492
+ handleExitingElements(beforeRecords, afterRecords) {
9493
+ const afterElementsSet = new Set(afterRecords.map((record) => record.element));
9494
+ beforeRecords.forEach((record) => {
9495
+ if (afterElementsSet.has(record.element))
9496
+ return;
9497
+ // For shared layout elements, relegate to set up resumeFrom
9498
+ // so the remaining element animates from this position
9499
+ if (record.projection.options.layoutId) {
9500
+ record.projection.isPresent = false;
9501
+ record.projection.relegate();
9502
+ }
9503
+ record.visualElement.unmount();
9504
+ visualElementStore.delete(record.element);
9505
+ });
9506
+ // Clear resumeFrom on EXISTING nodes that point to unmounted projections
9507
+ // This prevents crossfade animation when the source element was removed entirely
9508
+ // But preserve resumeFrom for NEW nodes so they can animate from the old position
9509
+ // Also preserve resumeFrom for lead nodes that were just promoted via relegate
9510
+ const beforeElementsSet = new Set(beforeRecords.map((record) => record.element));
9511
+ afterRecords.forEach(({ element, projection }) => {
9512
+ if (beforeElementsSet.has(element) &&
9513
+ projection.resumeFrom &&
9514
+ !projection.resumeFrom.instance &&
9515
+ !projection.isLead()) {
9516
+ projection.resumeFrom = undefined;
9517
+ projection.snapshot = undefined;
9518
+ }
9519
+ });
9712
9520
  }
9713
9521
  }
9714
- /**
9715
- * Parse arguments for animateLayout overloads
9716
- */
9717
9522
  function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
9718
9523
  // animateLayout(updateDom)
9719
9524
  if (typeof scopeOrUpdateDom === "function") {
@@ -9727,11 +9532,98 @@
9727
9532
  const elements = resolveElements(scopeOrUpdateDom);
9728
9533
  const scope = elements[0] || document;
9729
9534
  return {
9730
- scope: scope instanceof Document ? scope : scope,
9535
+ scope,
9731
9536
  updateDom: updateDomOrOptions,
9732
9537
  defaultOptions: options,
9733
9538
  };
9734
9539
  }
9540
+ function collectLayoutElements(scope) {
9541
+ const elements = Array.from(scope.querySelectorAll(layoutSelector));
9542
+ if (scope instanceof Element && scope.matches(layoutSelector)) {
9543
+ if (!elements.includes(scope)) {
9544
+ elements.unshift(scope);
9545
+ }
9546
+ }
9547
+ return elements;
9548
+ }
9549
+ function readLayoutAttributes(element) {
9550
+ const layoutId = element.getAttribute("data-layout-id") || undefined;
9551
+ const rawLayout = element.getAttribute("data-layout");
9552
+ let layout;
9553
+ if (rawLayout === "" || rawLayout === "true") {
9554
+ layout = true;
9555
+ }
9556
+ else if (rawLayout) {
9557
+ layout = rawLayout;
9558
+ }
9559
+ return {
9560
+ layout,
9561
+ layoutId,
9562
+ };
9563
+ }
9564
+ function createVisualState() {
9565
+ return {
9566
+ latestValues: {},
9567
+ renderState: {
9568
+ transform: {},
9569
+ transformOrigin: {},
9570
+ style: {},
9571
+ vars: {},
9572
+ },
9573
+ };
9574
+ }
9575
+ function getOrCreateRecord(element, parentProjection, projectionOptions) {
9576
+ const existing = visualElementStore.get(element);
9577
+ const visualElement = existing ??
9578
+ new HTMLVisualElement({
9579
+ props: {},
9580
+ presenceContext: null,
9581
+ visualState: createVisualState(),
9582
+ }, { allowProjection: true });
9583
+ if (!existing || !visualElement.projection) {
9584
+ visualElement.projection = new HTMLProjectionNode(visualElement.latestValues, parentProjection);
9585
+ }
9586
+ visualElement.projection.setOptions({
9587
+ ...projectionOptions,
9588
+ visualElement,
9589
+ });
9590
+ if (!visualElement.current) {
9591
+ visualElement.mount(element);
9592
+ }
9593
+ if (!existing) {
9594
+ visualElementStore.set(element, visualElement);
9595
+ }
9596
+ return {
9597
+ element,
9598
+ visualElement,
9599
+ projection: visualElement.projection,
9600
+ };
9601
+ }
9602
+ function findParentRecord(element, recordMap, scope) {
9603
+ let parent = element.parentElement;
9604
+ while (parent) {
9605
+ const record = recordMap.get(parent);
9606
+ if (record)
9607
+ return record;
9608
+ if (parent === scope)
9609
+ break;
9610
+ parent = parent.parentElement;
9611
+ }
9612
+ return undefined;
9613
+ }
9614
+ function getProjectionRoot(afterRecords, beforeRecords) {
9615
+ const record = afterRecords[0] || beforeRecords[0];
9616
+ return record?.projection.root;
9617
+ }
9618
+ function collectAnimations(afterRecords) {
9619
+ const animations = new Set();
9620
+ afterRecords.forEach((record) => {
9621
+ const animation = record.projection.currentAnimation;
9622
+ if (animation)
9623
+ animations.add(animation);
9624
+ });
9625
+ return Array.from(animations);
9626
+ }
9735
9627
 
9736
9628
  /**
9737
9629
  * @deprecated
@@ -10642,7 +10534,7 @@
10642
10534
  const getEventTarget = (element) => element === document.scrollingElement ? window : element;
10643
10535
  function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
10644
10536
  if (!container)
10645
- return noop;
10537
+ return noop$1;
10646
10538
  let containerHandlers = onScrollHandlers.get(container);
10647
10539
  /**
10648
10540
  * Get the onScroll handlers for this container.
@@ -10802,7 +10694,7 @@
10802
10694
 
10803
10695
  function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
10804
10696
  if (!container)
10805
- return noop;
10697
+ return noop$1;
10806
10698
  const optionsWithDefaults = { axis, container, ...options };
10807
10699
  return typeof onScroll === "function"
10808
10700
  ? attachToFunction(onScroll, optionsWithDefaults)
@@ -10964,7 +10856,7 @@
10964
10856
  exports.createBox = createBox;
10965
10857
  exports.createDelta = createDelta;
10966
10858
  exports.createGeneratorEasing = createGeneratorEasing;
10967
- exports.createProjectionNode = createProjectionNode$1;
10859
+ exports.createProjectionNode = createProjectionNode;
10968
10860
  exports.createRenderBatcher = createRenderBatcher;
10969
10861
  exports.createScopedAnimate = createScopedAnimate;
10970
10862
  exports.cubicBezier = cubicBezier;
@@ -11079,7 +10971,7 @@
11079
10971
  exports.motionValue = motionValue;
11080
10972
  exports.moveItem = moveItem;
11081
10973
  exports.nodeGroup = nodeGroup;
11082
- exports.noop = noop;
10974
+ exports.noop = noop$1;
11083
10975
  exports.number = number;
11084
10976
  exports.numberValueTypes = numberValueTypes;
11085
10977
  exports.observeTimeline = observeTimeline;