motion 12.27.1 → 12.27.3-alpha.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.
@@ -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);
@@ -2491,7 +2491,7 @@
2491
2491
  this.animation.onfinish = null;
2492
2492
  if (timeline && supportsScrollTimeline()) {
2493
2493
  this.animation.timeline = timeline;
2494
- return noop;
2494
+ return noop$1;
2495
2495
  }
2496
2496
  else {
2497
2497
  return observe(this);
@@ -2783,7 +2783,7 @@
2783
2783
  : new JSAnimation(resolvedOptions);
2784
2784
  animation.finished.then(() => {
2785
2785
  this.notifyFinished();
2786
- }).catch(noop);
2786
+ }).catch(noop$1);
2787
2787
  if (this.pendingTimeline) {
2788
2788
  this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
2789
2789
  this.pendingTimeline = undefined;
@@ -4195,6 +4195,9 @@
4195
4195
  ]);
4196
4196
 
4197
4197
  function resolveElements(elementOrSelector, scope, selectorCache) {
4198
+ if (elementOrSelector == null) {
4199
+ return [];
4200
+ }
4198
4201
  if (elementOrSelector instanceof EventTarget) {
4199
4202
  return [elementOrSelector];
4200
4203
  }
@@ -4207,7 +4210,7 @@
4207
4210
  root.querySelectorAll(elementOrSelector);
4208
4211
  return elements ? Array.from(elements) : [];
4209
4212
  }
4210
- return Array.from(elementOrSelector);
4213
+ return Array.from(elementOrSelector).filter((element) => element != null);
4211
4214
  }
4212
4215
 
4213
4216
  function createSelectorEffect(subjectEffect) {
@@ -5399,7 +5402,7 @@
5399
5402
  constructor(update, options = {}) {
5400
5403
  this.currentSubject = "root";
5401
5404
  this.targets = new Map();
5402
- this.notifyReady = noop;
5405
+ this.notifyReady = noop$1;
5403
5406
  this.readyPromise = new Promise((resolve) => {
5404
5407
  this.notifyReady = resolve;
5405
5408
  });
@@ -7454,7 +7457,7 @@
7454
7457
  : values.borderRadius;
7455
7458
  }
7456
7459
  const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
7457
- const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
7460
+ const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop$1);
7458
7461
  function compress(min, max, easing) {
7459
7462
  return (p) => {
7460
7463
  // Could replace ifs with clamp
@@ -7680,7 +7683,7 @@
7680
7683
  cancelTreeOptimisedTransformAnimations(parent);
7681
7684
  }
7682
7685
  }
7683
- function createProjectionNode$1({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
7686
+ function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
7684
7687
  return class ProjectionNode {
7685
7688
  constructor(latestValues = {}, parent = defaultParent?.()) {
7686
7689
  /**
@@ -9218,7 +9221,7 @@
9218
9221
  */
9219
9222
  const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
9220
9223
  ? Math.round
9221
- : noop;
9224
+ : noop$1;
9222
9225
  function roundAxis(axis) {
9223
9226
  // Round to the nearest .5 pixels to support subpixel layouts
9224
9227
  axis.min = roundPoint(axis.min);
@@ -9237,7 +9240,7 @@
9237
9240
  return node !== node.root && node.scroll?.wasRoot;
9238
9241
  }
9239
9242
 
9240
- const DocumentProjectionNode = createProjectionNode$1({
9243
+ const DocumentProjectionNode = createProjectionNode({
9241
9244
  attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
9242
9245
  measureScroll: () => ({
9243
9246
  x: document.documentElement.scrollLeft || document.body.scrollLeft,
@@ -9272,7 +9275,7 @@
9272
9275
  const rootProjectionNode = {
9273
9276
  current: undefined,
9274
9277
  };
9275
- const HTMLProjectionNode = createProjectionNode$1({
9278
+ const HTMLProjectionNode = createProjectionNode({
9276
9279
  measureScroll: (instance) => ({
9277
9280
  x: instance.scrollLeft,
9278
9281
  y: instance.scrollTop,
@@ -9292,315 +9295,112 @@
9292
9295
  checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
9293
9296
  });
9294
9297
 
9295
- const LAYOUT_SELECTOR = "[data-layout], [data-layout-id]";
9296
- function getLayoutElements(scope) {
9297
- const elements = Array.from(scope.querySelectorAll(LAYOUT_SELECTOR));
9298
- // Include scope itself if it's an Element (not Document) and has layout attributes
9299
- if (scope instanceof Element && hasLayout(scope)) {
9300
- elements.unshift(scope);
9301
- }
9302
- return elements;
9303
- }
9304
- function getLayoutId(element) {
9305
- return element.getAttribute("data-layout-id");
9306
- }
9307
- function hasLayout(element) {
9308
- return (element.hasAttribute("data-layout") ||
9309
- element.hasAttribute("data-layout-id"));
9310
- }
9311
-
9312
- let scaleCorrectorAdded = false;
9313
- /**
9314
- * Track active projection nodes per element to handle animation interruption.
9315
- * When a new animation starts on an element that already has an active animation,
9316
- * we need to stop the old animation so the new one can start from the current
9317
- * visual position.
9318
- */
9319
- const activeProjectionNodes = new WeakMap();
9320
- function ensureScaleCorrectors() {
9321
- if (scaleCorrectorAdded)
9322
- return;
9323
- scaleCorrectorAdded = true;
9324
- addScaleCorrector({
9325
- borderRadius: {
9326
- ...correctBorderRadius,
9327
- applyTo: [
9328
- "borderTopLeftRadius",
9329
- "borderTopRightRadius",
9330
- "borderBottomLeftRadius",
9331
- "borderBottomRightRadius",
9332
- ],
9333
- },
9334
- borderTopLeftRadius: correctBorderRadius,
9335
- borderTopRightRadius: correctBorderRadius,
9336
- borderBottomLeftRadius: correctBorderRadius,
9337
- borderBottomRightRadius: correctBorderRadius,
9338
- boxShadow: correctBoxShadow,
9339
- });
9340
- }
9341
- /**
9342
- * Get DOM depth of an element
9343
- */
9344
- function getDepth(element) {
9345
- let depth = 0;
9346
- let current = element.parentElement;
9347
- while (current) {
9348
- depth++;
9349
- current = current.parentElement;
9350
- }
9351
- return depth;
9352
- }
9353
- /**
9354
- * Find the closest projection parent for an element
9355
- */
9356
- function findProjectionParent(element, nodeCache) {
9357
- let parent = element.parentElement;
9358
- while (parent) {
9359
- const node = nodeCache.get(parent);
9360
- if (node)
9361
- return node;
9362
- parent = parent.parentElement;
9363
- }
9364
- return undefined;
9365
- }
9366
- /**
9367
- * Create or reuse a projection node for an element
9368
- */
9369
- function createProjectionNode(element, parent, options, transition) {
9370
- // Check for existing active node - reuse it to preserve animation state
9371
- const existingNode = activeProjectionNodes.get(element);
9372
- if (existingNode) {
9373
- const visualElement = existingNode.options.visualElement;
9374
- // Update transition options for the new animation
9375
- const nodeTransition = transition
9376
- ? { duration: transition.duration, ease: transition.ease }
9377
- : { duration: 0.3, ease: "easeOut" };
9378
- existingNode.setOptions({
9379
- ...existingNode.options,
9380
- animate: true,
9381
- transition: nodeTransition,
9382
- ...options,
9383
- });
9384
- // Re-mount the node if it was previously unmounted
9385
- // This re-adds it to root.nodes so didUpdate() will process it
9386
- if (!existingNode.instance) {
9387
- existingNode.mount(element);
9388
- }
9389
- return { node: existingNode, visualElement };
9390
- }
9391
- // No existing node - create a new one
9392
- const latestValues = {};
9393
- const visualElement = new HTMLVisualElement({
9394
- visualState: {
9395
- latestValues,
9396
- renderState: {
9397
- transformOrigin: {},
9398
- transform: {},
9399
- style: {},
9400
- vars: {},
9401
- },
9402
- },
9403
- presenceContext: null,
9404
- props: {},
9405
- });
9406
- const node = new HTMLProjectionNode(latestValues, parent);
9407
- // Convert AnimationOptions to transition format for the projection system
9408
- const nodeTransition = transition
9409
- ? { duration: transition.duration, ease: transition.ease }
9410
- : { duration: 0.3, ease: "easeOut" };
9411
- node.setOptions({
9412
- visualElement,
9413
- layout: true,
9414
- animate: true,
9415
- transition: nodeTransition,
9416
- ...options,
9417
- });
9418
- node.mount(element);
9419
- visualElement.projection = node;
9420
- // Track this node as the active one for this element
9421
- activeProjectionNodes.set(element, node);
9422
- return { node, visualElement };
9423
- }
9424
- /**
9425
- * Build a projection tree from a list of elements
9426
- */
9427
- function buildProjectionTree(elements, existingContext, options) {
9428
- ensureScaleCorrectors();
9429
- const nodes = existingContext?.nodes ?? new Map();
9430
- const visualElements = existingContext?.visualElements ?? new Map();
9431
- const group = existingContext?.group ?? nodeGroup();
9432
- const defaultTransition = options?.defaultTransition;
9433
- const sharedTransitions = options?.sharedTransitions;
9434
- // Sort elements by DOM depth (parents before children)
9435
- const sorted = [...elements].sort((a, b) => getDepth(a) - getDepth(b));
9436
- let root = existingContext?.root;
9437
- for (const element of sorted) {
9438
- // Skip if already has a node
9439
- if (nodes.has(element))
9440
- continue;
9441
- const parent = findProjectionParent(element, nodes);
9442
- const layoutId = getLayoutId(element);
9443
- const layoutMode = element.getAttribute("data-layout");
9444
- const nodeOptions = {
9445
- layoutId: layoutId ?? undefined,
9446
- animationType: parseLayoutMode(layoutMode),
9447
- };
9448
- // Use layoutId-specific transition if available, otherwise use default
9449
- const transition = layoutId && sharedTransitions?.get(layoutId)
9450
- ? sharedTransitions.get(layoutId)
9451
- : defaultTransition;
9452
- const { node, visualElement } = createProjectionNode(element, parent, nodeOptions, transition);
9453
- nodes.set(element, node);
9454
- visualElements.set(element, visualElement);
9455
- group.add(node);
9456
- if (!root) {
9457
- root = node.root;
9458
- }
9459
- }
9460
- return {
9461
- nodes,
9462
- visualElements,
9463
- group,
9464
- root: root,
9465
- };
9466
- }
9467
- /**
9468
- * Parse the data-layout attribute value
9469
- */
9470
- function parseLayoutMode(value) {
9471
- if (value === "position")
9472
- return "position";
9473
- if (value === "size")
9474
- return "size";
9475
- if (value === "preserve-aspect")
9476
- return "preserve-aspect";
9477
- return "both";
9478
- }
9479
- /**
9480
- * Clean up projection nodes for specific elements.
9481
- * If elementsToCleanup is provided, only those elements are cleaned up.
9482
- * If not provided, all nodes are cleaned up.
9483
- *
9484
- * This allows persisting elements to keep their nodes between animations,
9485
- * matching React's behavior where nodes persist for elements that remain in the DOM.
9486
- */
9487
- function cleanupProjectionTree(context, elementsToCleanup) {
9488
- const elementsToProcess = elementsToCleanup
9489
- ? [...context.nodes.entries()].filter(([el]) => elementsToCleanup.has(el))
9490
- : [...context.nodes.entries()];
9491
- for (const [element, node] of elementsToProcess) {
9492
- context.group.remove(node);
9493
- node.unmount();
9494
- // Only clear from activeProjectionNodes if this is still the active node.
9495
- // A newer animation might have already taken over.
9496
- if (activeProjectionNodes.get(element) === node) {
9497
- activeProjectionNodes.delete(element);
9498
- }
9499
- context.nodes.delete(element);
9500
- context.visualElements.delete(element);
9501
- }
9502
- }
9503
-
9298
+ const layoutSelector = "[data-layout], [data-layout-id]";
9299
+ const noop = () => { };
9504
9300
  class LayoutAnimationBuilder {
9505
9301
  constructor(scope, updateDom, defaultOptions) {
9506
9302
  this.sharedTransitions = new Map();
9507
9303
  this.notifyReady = noop;
9508
- this.executed = false;
9304
+ this.rejectReady = noop;
9509
9305
  this.scope = scope;
9510
9306
  this.updateDom = updateDom;
9511
9307
  this.defaultOptions = defaultOptions;
9512
- this.readyPromise = new Promise((resolve) => {
9308
+ this.readyPromise = new Promise((resolve, reject) => {
9513
9309
  this.notifyReady = resolve;
9310
+ this.rejectReady = reject;
9311
+ });
9312
+ microtask.read((_frameData) => {
9313
+ this.start().then(this.notifyReady).catch(this.rejectReady);
9514
9314
  });
9515
- // Queue execution on microtask to allow builder methods to be called
9516
- queueMicrotask(() => this.execute());
9517
9315
  }
9518
- shared(id, options) {
9519
- this.sharedTransitions.set(id, options);
9316
+ shared(id, transition) {
9317
+ this.sharedTransitions.set(id, transition);
9520
9318
  return this;
9521
9319
  }
9522
- then(onfulfilled, onrejected) {
9523
- return this.readyPromise.then(onfulfilled, onrejected);
9320
+ then(resolve, reject) {
9321
+ return this.readyPromise.then(resolve, reject);
9524
9322
  }
9525
- async execute() {
9526
- if (this.executed)
9527
- return;
9528
- this.executed = true;
9529
- let context;
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;
9556
- }
9557
- // Handle shared elements
9558
- for (const element of exiting) {
9559
- const node = context.nodes.get(element);
9560
- node?.getStack()?.remove(node);
9323
+ async start() {
9324
+ const beforeElements = collectLayoutElements(this.scope);
9325
+ const beforeRecords = this.buildRecords(beforeElements);
9326
+ const exitCandidates = collectExitCandidates(beforeRecords);
9327
+ beforeRecords.forEach(({ projection }) => {
9328
+ projection.isPresent = true;
9329
+ projection.willUpdate();
9330
+ });
9331
+ await this.updateDom();
9332
+ const afterElements = collectLayoutElements(this.scope);
9333
+ const afterRecords = this.buildRecords(afterElements);
9334
+ const exitRecords = this.handleExitingElements(beforeRecords, afterRecords, exitCandidates);
9335
+ afterRecords.forEach(({ projection }) => {
9336
+ projection.isPresent = true;
9337
+ });
9338
+ const root = getProjectionRoot(afterRecords, beforeRecords);
9339
+ root?.didUpdate();
9340
+ await new Promise((resolve) => {
9341
+ frame.postRender(() => resolve());
9342
+ });
9343
+ const animations = collectAnimations(afterRecords, exitRecords);
9344
+ const animation = new GroupAnimation(animations);
9345
+ if (exitRecords.length) {
9346
+ const cleanup = () => {
9347
+ exitRecords.forEach(({ element, visualElement }) => {
9348
+ if (element.isConnected) {
9349
+ element.remove();
9350
+ }
9351
+ visualElement.unmount();
9352
+ visualElementStore.delete(element);
9353
+ });
9354
+ };
9355
+ animation.finished.then(cleanup, cleanup);
9561
9356
  }
9562
- for (const element of entering) {
9563
- context.nodes.get(element)?.promote();
9357
+ return animation;
9358
+ }
9359
+ buildRecords(elements) {
9360
+ const records = [];
9361
+ const recordMap = new Map();
9362
+ for (const element of elements) {
9363
+ const parentRecord = findParentRecord(element, recordMap, this.scope);
9364
+ const { layout, layoutId } = readLayoutAttributes(element);
9365
+ const override = layoutId
9366
+ ? this.sharedTransitions.get(layoutId)
9367
+ : undefined;
9368
+ const transition = override || this.defaultOptions;
9369
+ const record = getOrCreateRecord(element, parentRecord?.projection, {
9370
+ layout,
9371
+ layoutId,
9372
+ animationType: typeof layout === "string" ? layout : "both",
9373
+ transition: transition,
9374
+ });
9375
+ recordMap.set(element, record);
9376
+ records.push(record);
9564
9377
  }
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
- }
9378
+ return records;
9379
+ }
9380
+ handleExitingElements(beforeRecords, afterRecords, exitCandidates) {
9381
+ const afterElementsSet = new Set(afterRecords.map((record) => record.element));
9382
+ const exiting = [];
9383
+ beforeRecords.forEach((record) => {
9384
+ if (afterElementsSet.has(record.element))
9385
+ return;
9386
+ const exitRecord = exitCandidates.get(record.element);
9387
+ if (!exitRecord) {
9388
+ record.visualElement.unmount();
9389
+ visualElementStore.delete(record.element);
9390
+ return;
9391
+ }
9392
+ if (!exitRecord.element.isConnected) {
9393
+ reinstateExitElement(exitRecord);
9394
+ }
9395
+ record.projection.isPresent = false;
9396
+ if (record.projection.options.layoutId) {
9397
+ record.projection.relegate();
9584
9398
  }
9585
- cleanupProjectionTree(context, elementsToCleanup);
9399
+ exiting.push(record);
9586
9400
  });
9587
- this.notifyReady(groupAnimation);
9588
- }
9589
- getBuildOptions() {
9590
- return {
9591
- defaultTransition: this.defaultOptions || {
9592
- duration: 0.3,
9593
- ease: "easeOut",
9594
- },
9595
- sharedTransitions: this.sharedTransitions.size > 0
9596
- ? this.sharedTransitions
9597
- : undefined,
9598
- };
9401
+ return exiting;
9599
9402
  }
9600
9403
  }
9601
- /**
9602
- * Parse arguments for animateLayout overloads
9603
- */
9604
9404
  function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
9605
9405
  // animateLayout(updateDom)
9606
9406
  if (typeof scopeOrUpdateDom === "function") {
@@ -9614,11 +9414,125 @@
9614
9414
  const elements = resolveElements(scopeOrUpdateDom);
9615
9415
  const scope = elements[0] || document;
9616
9416
  return {
9617
- scope: scope instanceof Document ? scope : scope,
9417
+ scope,
9618
9418
  updateDom: updateDomOrOptions,
9619
9419
  defaultOptions: options,
9620
9420
  };
9621
9421
  }
9422
+ function collectLayoutElements(scope) {
9423
+ const elements = Array.from(scope.querySelectorAll(layoutSelector));
9424
+ if (scope instanceof Element && scope.matches(layoutSelector)) {
9425
+ if (!elements.includes(scope)) {
9426
+ elements.unshift(scope);
9427
+ }
9428
+ }
9429
+ return elements;
9430
+ }
9431
+ function readLayoutAttributes(element) {
9432
+ const layoutId = element.getAttribute("data-layout-id") || undefined;
9433
+ const rawLayout = element.getAttribute("data-layout");
9434
+ let layout;
9435
+ if (rawLayout === "" || rawLayout === "true") {
9436
+ layout = true;
9437
+ }
9438
+ else if (rawLayout) {
9439
+ layout = rawLayout;
9440
+ }
9441
+ return {
9442
+ layout,
9443
+ layoutId,
9444
+ layoutExit: element.hasAttribute("data-layout-exit"),
9445
+ };
9446
+ }
9447
+ function createVisualState() {
9448
+ return {
9449
+ latestValues: {},
9450
+ renderState: {
9451
+ transform: {},
9452
+ transformOrigin: {},
9453
+ style: {},
9454
+ vars: {},
9455
+ },
9456
+ };
9457
+ }
9458
+ function getOrCreateRecord(element, parentProjection, projectionOptions) {
9459
+ const existing = visualElementStore.get(element);
9460
+ const visualElement = existing ??
9461
+ new HTMLVisualElement({
9462
+ props: {},
9463
+ presenceContext: null,
9464
+ visualState: createVisualState(),
9465
+ }, { allowProjection: true });
9466
+ if (!existing || !visualElement.projection) {
9467
+ visualElement.projection = new HTMLProjectionNode(visualElement.latestValues, parentProjection);
9468
+ }
9469
+ visualElement.projection.setOptions({
9470
+ ...projectionOptions,
9471
+ visualElement,
9472
+ });
9473
+ if (!visualElement.current) {
9474
+ visualElement.mount(element);
9475
+ }
9476
+ if (!existing) {
9477
+ visualElementStore.set(element, visualElement);
9478
+ }
9479
+ return {
9480
+ element,
9481
+ visualElement,
9482
+ projection: visualElement.projection,
9483
+ };
9484
+ }
9485
+ function findParentRecord(element, recordMap, scope) {
9486
+ let parent = element.parentElement;
9487
+ while (parent) {
9488
+ const record = recordMap.get(parent);
9489
+ if (record)
9490
+ return record;
9491
+ if (parent === scope)
9492
+ break;
9493
+ parent = parent.parentElement;
9494
+ }
9495
+ return undefined;
9496
+ }
9497
+ function collectExitCandidates(records) {
9498
+ const exitCandidates = new Map();
9499
+ records.forEach((record) => {
9500
+ const { layoutExit } = readLayoutAttributes(record.element);
9501
+ if (!layoutExit)
9502
+ return;
9503
+ exitCandidates.set(record.element, {
9504
+ ...record,
9505
+ parent: record.element.parentNode,
9506
+ nextSibling: record.element.nextSibling,
9507
+ });
9508
+ });
9509
+ return exitCandidates;
9510
+ }
9511
+ function reinstateExitElement(record) {
9512
+ if (!record.parent)
9513
+ return;
9514
+ if (record.nextSibling && record.nextSibling.parentNode === record.parent) {
9515
+ record.parent.insertBefore(record.element, record.nextSibling);
9516
+ }
9517
+ else {
9518
+ record.parent.appendChild(record.element);
9519
+ }
9520
+ }
9521
+ function getProjectionRoot(afterRecords, beforeRecords) {
9522
+ const record = afterRecords[0] || beforeRecords[0];
9523
+ return record?.projection.root;
9524
+ }
9525
+ function collectAnimations(afterRecords, exitRecords) {
9526
+ const animations = new Set();
9527
+ const addAnimation = (record) => {
9528
+ const animation = record.projection.currentAnimation;
9529
+ if (animation)
9530
+ animations.add(animation);
9531
+ };
9532
+ afterRecords.forEach(addAnimation);
9533
+ exitRecords.forEach(addAnimation);
9534
+ return Array.from(animations);
9535
+ }
9622
9536
 
9623
9537
  /**
9624
9538
  * @deprecated
@@ -9641,6 +9555,9 @@
9641
9555
  }
9642
9556
 
9643
9557
  function resolveSubjects(subject, keyframes, scope, selectorCache) {
9558
+ if (subject == null) {
9559
+ return [];
9560
+ }
9644
9561
  if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
9645
9562
  return resolveElements(subject, scope, selectorCache);
9646
9563
  }
@@ -9648,7 +9565,7 @@
9648
9565
  return Array.from(subject);
9649
9566
  }
9650
9567
  else if (Array.isArray(subject)) {
9651
- return subject;
9568
+ return subject.filter((s) => s != null);
9652
9569
  }
9653
9570
  else {
9654
9571
  return [subject];
@@ -10024,12 +9941,15 @@
10024
9941
  : keyframes, options ? options.default || options : options));
10025
9942
  }
10026
9943
  else {
9944
+ // Gracefully handle null/undefined subjects (e.g., from querySelector returning null)
9945
+ if (subject == null) {
9946
+ return animations;
9947
+ }
10027
9948
  const subjects = resolveSubjects(subject, keyframes, scope);
10028
9949
  const numSubjects = subjects.length;
10029
9950
  exports.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
10030
9951
  for (let i = 0; i < numSubjects; i++) {
10031
9952
  const thisSubject = subjects[i];
10032
- exports.invariant(thisSubject !== null, "You're trying to perform an animation on null. Ensure that selectors are correctly finding elements and refs are correctly hydrated.", "animate-null");
10033
9953
  const createVisualElement = thisSubject instanceof Element
10034
9954
  ? createDOMVisualElement
10035
9955
  : createObjectVisualElement;
@@ -10102,6 +10022,10 @@
10102
10022
  const animate = createScopedAnimate();
10103
10023
 
10104
10024
  function animateElements(elementOrSelector, keyframes, options, scope) {
10025
+ // Gracefully handle null/undefined elements (e.g., from querySelector returning null)
10026
+ if (elementOrSelector == null) {
10027
+ return [];
10028
+ }
10105
10029
  const elements = resolveElements(elementOrSelector, scope);
10106
10030
  const numElements = elements.length;
10107
10031
  exports.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
@@ -10502,7 +10426,7 @@
10502
10426
  const getEventTarget = (element) => element === document.scrollingElement ? window : element;
10503
10427
  function scrollInfo(onScroll, { container = document.scrollingElement, ...options } = {}) {
10504
10428
  if (!container)
10505
- return noop;
10429
+ return noop$1;
10506
10430
  let containerHandlers = onScrollHandlers.get(container);
10507
10431
  /**
10508
10432
  * Get the onScroll handlers for this container.
@@ -10630,7 +10554,7 @@
10630
10554
 
10631
10555
  function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
10632
10556
  if (!container)
10633
- return noop;
10557
+ return noop$1;
10634
10558
  const optionsWithDefaults = { axis, container, ...options };
10635
10559
  return typeof onScroll === "function"
10636
10560
  ? attachToFunction(onScroll, optionsWithDefaults)
@@ -10791,7 +10715,7 @@
10791
10715
  exports.createBox = createBox;
10792
10716
  exports.createDelta = createDelta;
10793
10717
  exports.createGeneratorEasing = createGeneratorEasing;
10794
- exports.createProjectionNode = createProjectionNode$1;
10718
+ exports.createProjectionNode = createProjectionNode;
10795
10719
  exports.createRenderBatcher = createRenderBatcher;
10796
10720
  exports.createScopedAnimate = createScopedAnimate;
10797
10721
  exports.cubicBezier = cubicBezier;
@@ -10905,7 +10829,7 @@
10905
10829
  exports.motionValue = motionValue;
10906
10830
  exports.moveItem = moveItem;
10907
10831
  exports.nodeGroup = nodeGroup;
10908
- exports.noop = noop;
10832
+ exports.noop = noop$1;
10909
10833
  exports.number = number;
10910
10834
  exports.numberValueTypes = numberValueTypes;
10911
10835
  exports.observeTimeline = observeTimeline;