framer-motion 12.40.0 → 12.41.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.
- package/README.md +1 -1
- package/dist/cjs/client.js +1 -1
- package/dist/cjs/{feature-bundle-frRcWALD.js → feature-bundle-riC1cckV.js} +6 -2
- package/dist/cjs/feature-bundle-riC1cckV.js.map +1 -0
- package/dist/cjs/index.js +15 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/dom.js +1 -1
- package/dist/es/components/AnimatePresence/PresenceChild.mjs +16 -2
- package/dist/es/components/AnimatePresence/PresenceChild.mjs.map +1 -1
- package/dist/es/gestures/pan/PanSession.mjs +5 -1
- package/dist/es/gestures/pan/PanSession.mjs.map +1 -1
- package/dist/framer-motion.dev.js +1059 -477
- package/dist/framer-motion.js +1 -1
- package/dist/size-rollup-animate.js +1 -1
- package/dist/size-rollup-animate.js.map +1 -1
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-rollup-motion.js.map +1 -1
- package/dist/size-rollup-scroll.js.map +1 -1
- package/dist/size-rollup-waapi-animate.js.map +1 -1
- package/package.json +3 -3
- package/dist/cjs/feature-bundle-frRcWALD.js.map +0 -1
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/*#__NO_SIDE_EFFECTS__*/
|
|
161
|
-
const noop
|
|
161
|
+
const noop = (any) => any;
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
164
|
* Pipe
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
296
296
|
// If this is a linear gradient, return linear easing
|
|
297
297
|
if (mX1 === mY1 && mX2 === mY2)
|
|
298
|
-
return noop
|
|
298
|
+
return noop;
|
|
299
299
|
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
300
300
|
// If animation is at start/end, return t without easing
|
|
301
301
|
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
@@ -356,7 +356,7 @@
|
|
|
356
356
|
const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
|
|
357
357
|
|
|
358
358
|
const easingLookup = {
|
|
359
|
-
linear: noop
|
|
359
|
+
linear: noop,
|
|
360
360
|
easeIn,
|
|
361
361
|
easeInOut,
|
|
362
362
|
easeOut,
|
|
@@ -397,12 +397,7 @@
|
|
|
397
397
|
"postRender", // Compute
|
|
398
398
|
];
|
|
399
399
|
|
|
400
|
-
|
|
401
|
-
value: null,
|
|
402
|
-
addProjectionMetrics: null,
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
function createRenderStep(runNextFrame, stepName) {
|
|
400
|
+
function createRenderStep(runNextFrame) {
|
|
406
401
|
/**
|
|
407
402
|
* We create and reuse two queues, one to queue jobs for the current frame
|
|
408
403
|
* and one for the next. We reuse to avoid triggering GC after x frames.
|
|
@@ -424,13 +419,11 @@
|
|
|
424
419
|
timestamp: 0.0,
|
|
425
420
|
isProcessing: false,
|
|
426
421
|
};
|
|
427
|
-
let numCalls = 0;
|
|
428
422
|
function triggerCallback(callback) {
|
|
429
423
|
if (toKeepAlive.has(callback)) {
|
|
430
424
|
step.schedule(callback);
|
|
431
425
|
runNextFrame();
|
|
432
426
|
}
|
|
433
|
-
numCalls++;
|
|
434
427
|
callback(latestFrameData);
|
|
435
428
|
}
|
|
436
429
|
const step = {
|
|
@@ -473,13 +466,6 @@
|
|
|
473
466
|
nextFrame = prevFrame;
|
|
474
467
|
// Execute this frame
|
|
475
468
|
thisFrame.forEach(triggerCallback);
|
|
476
|
-
/**
|
|
477
|
-
* If we're recording stats then
|
|
478
|
-
*/
|
|
479
|
-
if (stepName && statsBuffer.value) {
|
|
480
|
-
statsBuffer.value.frameloop[stepName].push(numCalls);
|
|
481
|
-
}
|
|
482
|
-
numCalls = 0;
|
|
483
469
|
// Clear the frame so no callbacks remain. This is to avoid
|
|
484
470
|
// memory leaks should this render step not run for a while.
|
|
485
471
|
thisFrame.clear();
|
|
@@ -504,7 +490,7 @@
|
|
|
504
490
|
};
|
|
505
491
|
const flagRunNextFrame = () => (runNextFrame = true);
|
|
506
492
|
const steps = stepsOrder.reduce((acc, key) => {
|
|
507
|
-
acc[key] = createRenderStep(flagRunNextFrame
|
|
493
|
+
acc[key] = createRenderStep(flagRunNextFrame);
|
|
508
494
|
return acc;
|
|
509
495
|
}, {});
|
|
510
496
|
const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
|
|
@@ -560,7 +546,7 @@
|
|
|
560
546
|
return { schedule, cancel, state, steps };
|
|
561
547
|
}
|
|
562
548
|
|
|
563
|
-
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop
|
|
549
|
+
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
|
|
564
550
|
|
|
565
551
|
let now;
|
|
566
552
|
function clearTime() {
|
|
@@ -589,12 +575,6 @@
|
|
|
589
575
|
},
|
|
590
576
|
};
|
|
591
577
|
|
|
592
|
-
const activeAnimations = {
|
|
593
|
-
layout: 0,
|
|
594
|
-
mainThread: 0,
|
|
595
|
-
waapi: 0,
|
|
596
|
-
};
|
|
597
|
-
|
|
598
578
|
const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
|
|
599
579
|
const isCSSVariableName =
|
|
600
580
|
/*@__PURE__*/ checkStringStartsWith("--");
|
|
@@ -1582,7 +1562,7 @@
|
|
|
1582
1562
|
for (let i = 0; i < numMixers; i++) {
|
|
1583
1563
|
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
1584
1564
|
if (ease) {
|
|
1585
|
-
const easingFunction = Array.isArray(ease) ? ease[i] || noop
|
|
1565
|
+
const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
|
|
1586
1566
|
mixer = pipe(easingFunction, mixer);
|
|
1587
1567
|
}
|
|
1588
1568
|
mixers.push(mixer);
|
|
@@ -1796,7 +1776,6 @@
|
|
|
1796
1776
|
this.teardown();
|
|
1797
1777
|
this.options.onStop?.();
|
|
1798
1778
|
};
|
|
1799
|
-
activeAnimations.mainThread++;
|
|
1800
1779
|
this.options = options;
|
|
1801
1780
|
this.initAnimation();
|
|
1802
1781
|
this.play();
|
|
@@ -2101,7 +2080,6 @@
|
|
|
2101
2080
|
this.state = "idle";
|
|
2102
2081
|
this.stopDriver();
|
|
2103
2082
|
this.startTime = this.holdTime = null;
|
|
2104
|
-
activeAnimations.mainThread--;
|
|
2105
2083
|
}
|
|
2106
2084
|
stopDriver() {
|
|
2107
2085
|
if (!this.driver)
|
|
@@ -2513,9 +2491,6 @@
|
|
|
2513
2491
|
*/
|
|
2514
2492
|
if (Array.isArray(easing))
|
|
2515
2493
|
keyframeOptions.easing = easing;
|
|
2516
|
-
if (statsBuffer.value) {
|
|
2517
|
-
activeAnimations.waapi++;
|
|
2518
|
-
}
|
|
2519
2494
|
const options = {
|
|
2520
2495
|
delay,
|
|
2521
2496
|
duration,
|
|
@@ -2526,13 +2501,7 @@
|
|
|
2526
2501
|
};
|
|
2527
2502
|
if (pseudoElement)
|
|
2528
2503
|
options.pseudoElement = pseudoElement;
|
|
2529
|
-
|
|
2530
|
-
if (statsBuffer.value) {
|
|
2531
|
-
animation.finished.finally(() => {
|
|
2532
|
-
activeAnimations.waapi--;
|
|
2533
|
-
});
|
|
2534
|
-
}
|
|
2535
|
-
return animation;
|
|
2504
|
+
return element.animate(keyframeOptions, options);
|
|
2536
2505
|
}
|
|
2537
2506
|
|
|
2538
2507
|
function isGenerator(type) {
|
|
@@ -2711,7 +2680,7 @@
|
|
|
2711
2680
|
this.animation.rangeStart = rangeStart;
|
|
2712
2681
|
if (rangeEnd)
|
|
2713
2682
|
this.animation.rangeEnd = rangeEnd;
|
|
2714
|
-
return noop
|
|
2683
|
+
return noop;
|
|
2715
2684
|
}
|
|
2716
2685
|
else {
|
|
2717
2686
|
return observe(this);
|
|
@@ -3064,7 +3033,7 @@
|
|
|
3064
3033
|
}
|
|
3065
3034
|
animation.finished.then(() => {
|
|
3066
3035
|
this.notifyFinished();
|
|
3067
|
-
}).catch(noop
|
|
3036
|
+
}).catch(noop);
|
|
3068
3037
|
if (this.pendingTimeline) {
|
|
3069
3038
|
this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
|
|
3070
3039
|
this.pendingTimeline = undefined;
|
|
@@ -5242,9 +5211,16 @@
|
|
|
5242
5211
|
claimedPointerDownEvents.add(startEvent);
|
|
5243
5212
|
}
|
|
5244
5213
|
const onPressEnd = onPressStart(target, startEvent);
|
|
5214
|
+
/**
|
|
5215
|
+
* End listeners run in the capture phase so a descendant calling
|
|
5216
|
+
* stopPropagation() in its own pointerup handler can't prevent the
|
|
5217
|
+
* press gesture from ending. This also keeps the gesture-end
|
|
5218
|
+
* ordering consistent with the drag gesture. See #2794.
|
|
5219
|
+
*/
|
|
5220
|
+
const endEventOptions = { ...eventOptions, capture: true };
|
|
5245
5221
|
const onPointerEnd = (endEvent, success) => {
|
|
5246
|
-
window.removeEventListener("pointerup", onPointerUp);
|
|
5247
|
-
window.removeEventListener("pointercancel", onPointerCancel);
|
|
5222
|
+
window.removeEventListener("pointerup", onPointerUp, endEventOptions);
|
|
5223
|
+
window.removeEventListener("pointercancel", onPointerCancel, endEventOptions);
|
|
5248
5224
|
if (isPressing.has(target)) {
|
|
5249
5225
|
isPressing.delete(target);
|
|
5250
5226
|
}
|
|
@@ -5264,8 +5240,8 @@
|
|
|
5264
5240
|
const onPointerCancel = (cancelEvent) => {
|
|
5265
5241
|
onPointerEnd(cancelEvent, false);
|
|
5266
5242
|
};
|
|
5267
|
-
window.addEventListener("pointerup", onPointerUp,
|
|
5268
|
-
window.addEventListener("pointercancel", onPointerCancel,
|
|
5243
|
+
window.addEventListener("pointerup", onPointerUp, endEventOptions);
|
|
5244
|
+
window.addEventListener("pointercancel", onPointerCancel, endEventOptions);
|
|
5269
5245
|
};
|
|
5270
5246
|
targets.forEach((target) => {
|
|
5271
5247
|
const pointerDownTarget = options.useGlobalTarget ? window : target;
|
|
@@ -5404,116 +5380,34 @@
|
|
|
5404
5380
|
return () => cancelFrame(onFrame);
|
|
5405
5381
|
}
|
|
5406
5382
|
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
}
|
|
5413
|
-
value.frameloop.rate.push(frameData.delta);
|
|
5414
|
-
value.animations.mainThread.push(activeAnimations.mainThread);
|
|
5415
|
-
value.animations.waapi.push(activeAnimations.waapi);
|
|
5416
|
-
value.animations.layout.push(activeAnimations.layout);
|
|
5417
|
-
}
|
|
5418
|
-
function mean(values) {
|
|
5419
|
-
return values.reduce((acc, value) => acc + value, 0) / values.length;
|
|
5420
|
-
}
|
|
5421
|
-
function summarise(values, calcAverage = mean) {
|
|
5422
|
-
if (values.length === 0) {
|
|
5423
|
-
return {
|
|
5424
|
-
min: 0,
|
|
5425
|
-
max: 0,
|
|
5426
|
-
avg: 0,
|
|
5427
|
-
};
|
|
5428
|
-
}
|
|
5429
|
-
return {
|
|
5430
|
-
min: Math.min(...values),
|
|
5431
|
-
max: Math.max(...values),
|
|
5432
|
-
avg: calcAverage(values),
|
|
5433
|
-
};
|
|
5434
|
-
}
|
|
5435
|
-
const msToFps = (ms) => Math.round(1000 / ms);
|
|
5383
|
+
const statsBuffer = {
|
|
5384
|
+
value: null,
|
|
5385
|
+
addProjectionMetrics: null,
|
|
5386
|
+
};
|
|
5387
|
+
|
|
5436
5388
|
function clearStatsBuffer() {
|
|
5437
5389
|
statsBuffer.value = null;
|
|
5438
5390
|
statsBuffer.addProjectionMetrics = null;
|
|
5439
5391
|
}
|
|
5440
|
-
function reportStats() {
|
|
5441
|
-
const { value } = statsBuffer;
|
|
5442
|
-
if (!value) {
|
|
5443
|
-
throw new Error("Stats are not being measured");
|
|
5444
|
-
}
|
|
5445
|
-
clearStatsBuffer();
|
|
5446
|
-
cancelFrame(record);
|
|
5447
|
-
const summary = {
|
|
5448
|
-
frameloop: {
|
|
5449
|
-
setup: summarise(value.frameloop.setup),
|
|
5450
|
-
rate: summarise(value.frameloop.rate),
|
|
5451
|
-
read: summarise(value.frameloop.read),
|
|
5452
|
-
resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
|
|
5453
|
-
preUpdate: summarise(value.frameloop.preUpdate),
|
|
5454
|
-
update: summarise(value.frameloop.update),
|
|
5455
|
-
preRender: summarise(value.frameloop.preRender),
|
|
5456
|
-
render: summarise(value.frameloop.render),
|
|
5457
|
-
postRender: summarise(value.frameloop.postRender),
|
|
5458
|
-
},
|
|
5459
|
-
animations: {
|
|
5460
|
-
mainThread: summarise(value.animations.mainThread),
|
|
5461
|
-
waapi: summarise(value.animations.waapi),
|
|
5462
|
-
layout: summarise(value.animations.layout),
|
|
5463
|
-
},
|
|
5464
|
-
layoutProjection: {
|
|
5465
|
-
nodes: summarise(value.layoutProjection.nodes),
|
|
5466
|
-
calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
|
|
5467
|
-
calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
|
|
5468
|
-
},
|
|
5469
|
-
};
|
|
5470
|
-
/**
|
|
5471
|
-
* Convert the rate to FPS
|
|
5472
|
-
*/
|
|
5473
|
-
const { rate } = summary.frameloop;
|
|
5474
|
-
rate.min = msToFps(rate.min);
|
|
5475
|
-
rate.max = msToFps(rate.max);
|
|
5476
|
-
rate.avg = msToFps(rate.avg);
|
|
5477
|
-
[rate.min, rate.max] = [rate.max, rate.min];
|
|
5478
|
-
return summary;
|
|
5479
|
-
}
|
|
5480
5392
|
function recordStats() {
|
|
5481
5393
|
if (statsBuffer.value) {
|
|
5482
5394
|
clearStatsBuffer();
|
|
5483
5395
|
throw new Error("Stats are already being measured");
|
|
5484
5396
|
}
|
|
5485
|
-
const
|
|
5486
|
-
|
|
5487
|
-
frameloop: {
|
|
5488
|
-
setup: [],
|
|
5489
|
-
rate: [],
|
|
5490
|
-
read: [],
|
|
5491
|
-
resolveKeyframes: [],
|
|
5492
|
-
preUpdate: [],
|
|
5493
|
-
update: [],
|
|
5494
|
-
preRender: [],
|
|
5495
|
-
render: [],
|
|
5496
|
-
postRender: [],
|
|
5497
|
-
},
|
|
5498
|
-
animations: {
|
|
5499
|
-
mainThread: [],
|
|
5500
|
-
waapi: [],
|
|
5501
|
-
layout: [],
|
|
5502
|
-
},
|
|
5397
|
+
const buffer = statsBuffer;
|
|
5398
|
+
buffer.value = {
|
|
5503
5399
|
layoutProjection: {
|
|
5504
5400
|
nodes: [],
|
|
5505
5401
|
calculatedTargetDeltas: [],
|
|
5506
5402
|
calculatedProjections: [],
|
|
5507
5403
|
},
|
|
5508
5404
|
};
|
|
5509
|
-
|
|
5510
|
-
const { layoutProjection } =
|
|
5405
|
+
buffer.addProjectionMetrics = (metrics) => {
|
|
5406
|
+
const { layoutProjection } = buffer.value;
|
|
5511
5407
|
layoutProjection.nodes.push(metrics.nodes);
|
|
5512
5408
|
layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
|
|
5513
5409
|
layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
|
|
5514
5410
|
};
|
|
5515
|
-
frame.postRender(record, true);
|
|
5516
|
-
return reportStats;
|
|
5517
5411
|
}
|
|
5518
5412
|
|
|
5519
5413
|
/**
|
|
@@ -5799,14 +5693,126 @@
|
|
|
5799
5693
|
*/
|
|
5800
5694
|
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
5801
5695
|
|
|
5696
|
+
let nameCount = 0;
|
|
5697
|
+
/**
|
|
5698
|
+
* Generated names live in their own namespace so we can tell a name we own
|
|
5699
|
+
* (and must clean up) from an author-defined one - and so a stale generated
|
|
5700
|
+
* name left behind by an interrupted transition is re-owned, not mistaken for
|
|
5701
|
+
* the author's and leaked.
|
|
5702
|
+
*/
|
|
5703
|
+
const generatedName = () => `motion-view-${nameCount++}`;
|
|
5704
|
+
const isGeneratedName = (name) => name.startsWith("motion-view-");
|
|
5705
|
+
/**
|
|
5706
|
+
* Tag a captured element with a `view-transition-class` so authors can target
|
|
5707
|
+
* its generated layer from CSS (e.g. `::view-transition-group(.hero)`) without
|
|
5708
|
+
* the opaque generated name. Tracked in `classed` - separate from the generated
|
|
5709
|
+
* names in `assigned` - so cleanup removes the class without ever stripping an
|
|
5710
|
+
* author's own inline `view-transition-name`.
|
|
5711
|
+
*/
|
|
5712
|
+
function tagClass(element, className, classed) {
|
|
5713
|
+
if (!className)
|
|
5714
|
+
return;
|
|
5715
|
+
element.style?.setProperty("view-transition-class", className);
|
|
5716
|
+
classed.push(element);
|
|
5717
|
+
}
|
|
5718
|
+
/**
|
|
5719
|
+
* Resolve a selector/Element to elements and ensure each one carries a
|
|
5720
|
+
* `view-transition-name` we can target from script.
|
|
5721
|
+
*
|
|
5722
|
+
* Author-defined names are reused as-is. Elements that are unnamed (or use
|
|
5723
|
+
* the browser's `auto`/`match-element`, whose generated name is not exposed
|
|
5724
|
+
* to script) are given a unique generated name, set inline so it's captured,
|
|
5725
|
+
* and tracked in `assigned` for later cleanup.
|
|
5726
|
+
*
|
|
5727
|
+
* `registry` maps each Element to its name so the same element keeps its name
|
|
5728
|
+
* across both captures (before and after the update), which is what allows a
|
|
5729
|
+
* persistent element to animate as a single `group` layer.
|
|
5730
|
+
*/
|
|
5731
|
+
function assignViewTransitionNames(definition, registry, assigned, forcedNames, className, classed = []) {
|
|
5732
|
+
const elements = resolveElements(definition);
|
|
5733
|
+
/**
|
|
5734
|
+
* The new end of a paired morph: give each element the matching name from
|
|
5735
|
+
* the old end (by index) so the two share one layer and morph. If the new
|
|
5736
|
+
* end resolves to *more* elements than the old end named, the extras have no
|
|
5737
|
+
* counterpart - give them a fresh name so they animate as newcomers rather
|
|
5738
|
+
* than being silently left unnamed. We return the names actually assigned
|
|
5739
|
+
* (sized to the resolved elements), not the raw `forcedNames`, so stagger
|
|
5740
|
+
* totals and the layer set stay in step with what's on the page.
|
|
5741
|
+
*/
|
|
5742
|
+
if (forcedNames) {
|
|
5743
|
+
return elements.map((element, i) => {
|
|
5744
|
+
const existing = registry.get(element);
|
|
5745
|
+
if (existing)
|
|
5746
|
+
return existing;
|
|
5747
|
+
const name = forcedNames[i] ?? generatedName();
|
|
5748
|
+
element.style?.setProperty("view-transition-name", name);
|
|
5749
|
+
assigned.push(element);
|
|
5750
|
+
registry.set(element, name);
|
|
5751
|
+
tagClass(element, className, classed);
|
|
5752
|
+
return name;
|
|
5753
|
+
});
|
|
5754
|
+
}
|
|
5755
|
+
/**
|
|
5756
|
+
* Read every current name up front, before assigning any. Interleaving the
|
|
5757
|
+
* reads with the inline `setProperty` writes below would dirty styles
|
|
5758
|
+
* between reads and force a style recalc per element; batching the reads
|
|
5759
|
+
* keeps it to one. Elements already in the registry keep their name and
|
|
5760
|
+
* need no read.
|
|
5761
|
+
*/
|
|
5762
|
+
const currentNames = elements.map((element) => registry.has(element)
|
|
5763
|
+
? undefined
|
|
5764
|
+
: getComputedStyle(element).getPropertyValue("view-transition-name"));
|
|
5765
|
+
return elements.map((element, i) => {
|
|
5766
|
+
const existing = registry.get(element);
|
|
5767
|
+
if (existing)
|
|
5768
|
+
return existing;
|
|
5769
|
+
const current = currentNames[i];
|
|
5770
|
+
let name;
|
|
5771
|
+
if (current &&
|
|
5772
|
+
current !== "none" &&
|
|
5773
|
+
current !== "auto" &&
|
|
5774
|
+
current !== "match-element" &&
|
|
5775
|
+
!isGeneratedName(current)) {
|
|
5776
|
+
/**
|
|
5777
|
+
* The author already named this layer - target it as-is and leave
|
|
5778
|
+
* it to them to clean up. `auto`/`match-element` are overridden
|
|
5779
|
+
* because their generated name is not exposed to script, and a
|
|
5780
|
+
* stale `motion-view-*` (e.g. left by an interrupted transition) is
|
|
5781
|
+
* re-owned rather than adopted as an author name and leaked.
|
|
5782
|
+
*/
|
|
5783
|
+
name = current;
|
|
5784
|
+
}
|
|
5785
|
+
else {
|
|
5786
|
+
name = generatedName();
|
|
5787
|
+
element.style?.setProperty("view-transition-name", name);
|
|
5788
|
+
assigned.push(element);
|
|
5789
|
+
}
|
|
5790
|
+
registry.set(element, name);
|
|
5791
|
+
tagClass(element, className, classed);
|
|
5792
|
+
return name;
|
|
5793
|
+
});
|
|
5794
|
+
}
|
|
5795
|
+
/**
|
|
5796
|
+
* Remove the `view-transition-name`s we generated and the
|
|
5797
|
+
* `view-transition-class`es we applied. Author-defined names are never touched
|
|
5798
|
+
* (they're not in `assigned`). Safe to call more than once (e.g. on both a
|
|
5799
|
+
* finished and an interrupted transition).
|
|
5800
|
+
*/
|
|
5801
|
+
function releaseViewTransitionNames(assigned, classed = []) {
|
|
5802
|
+
for (const element of assigned) {
|
|
5803
|
+
element.style?.removeProperty("view-transition-name");
|
|
5804
|
+
}
|
|
5805
|
+
for (const element of classed) {
|
|
5806
|
+
element.style?.removeProperty("view-transition-class");
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
|
|
5802
5810
|
function chooseLayerType(valueName) {
|
|
5803
5811
|
if (valueName === "layout")
|
|
5804
5812
|
return "group";
|
|
5805
5813
|
if (valueName === "enter" || valueName === "new")
|
|
5806
5814
|
return "new";
|
|
5807
|
-
|
|
5808
|
-
return "old";
|
|
5809
|
-
return "group";
|
|
5815
|
+
return "old";
|
|
5810
5816
|
}
|
|
5811
5817
|
|
|
5812
5818
|
let pendingRules = {};
|
|
@@ -5841,21 +5847,21 @@
|
|
|
5841
5847
|
};
|
|
5842
5848
|
|
|
5843
5849
|
function getViewAnimationLayerInfo(pseudoElement) {
|
|
5844
|
-
const match = pseudoElement.match(
|
|
5850
|
+
const match = pseudoElement.match(
|
|
5851
|
+
// `group-children` (nested transitions) before `group` so it wins.
|
|
5852
|
+
/::view-transition-(old|new|group-children|group|image-pair)\((.*?)\)/);
|
|
5845
5853
|
if (!match)
|
|
5846
5854
|
return null;
|
|
5847
5855
|
return { layer: match[2], type: match[1] };
|
|
5848
5856
|
}
|
|
5849
5857
|
|
|
5850
|
-
function filterViewAnimations(animation) {
|
|
5851
|
-
const { effect } = animation;
|
|
5852
|
-
if (!effect)
|
|
5853
|
-
return false;
|
|
5854
|
-
return (effect.target === document.documentElement &&
|
|
5855
|
-
effect.pseudoElement?.startsWith("::view-transition"));
|
|
5856
|
-
}
|
|
5857
5858
|
function getViewAnimations() {
|
|
5858
|
-
return document.getAnimations().filter(
|
|
5859
|
+
return document.getAnimations().filter((animation) => {
|
|
5860
|
+
const { effect } = animation;
|
|
5861
|
+
return (!!effect &&
|
|
5862
|
+
effect.target === document.documentElement &&
|
|
5863
|
+
effect.pseudoElement?.startsWith("::view-transition"));
|
|
5864
|
+
});
|
|
5859
5865
|
}
|
|
5860
5866
|
|
|
5861
5867
|
function hasTarget(target, targets) {
|
|
@@ -5863,87 +5869,370 @@
|
|
|
5863
5869
|
}
|
|
5864
5870
|
|
|
5865
5871
|
const definitionNames = ["layout", "enter", "exit", "new", "old"];
|
|
5872
|
+
/**
|
|
5873
|
+
* The `ViewTransitionTarget` buckets driving each generated layer type, in
|
|
5874
|
+
* priority order - the inverse of `chooseLayerType`. The new view is driven by
|
|
5875
|
+
* `new`/`enter`, the old by `old`/`exit`. `group-children`/`image-pair` have no
|
|
5876
|
+
* bucket; they follow the default layout timing.
|
|
5877
|
+
*/
|
|
5878
|
+
const typeBuckets = {
|
|
5879
|
+
group: ["layout"],
|
|
5880
|
+
new: ["new", "enter"],
|
|
5881
|
+
old: ["old", "exit"],
|
|
5882
|
+
};
|
|
5883
|
+
/**
|
|
5884
|
+
* Default "absent" origin for a single-value keyframe, by pseudo type, so e.g.
|
|
5885
|
+
* `enter({ scale: 1 })` grows in from 0.85 and `exit({ opacity: 0 })` fades
|
|
5886
|
+
* from 1. `enter` prefers the matching `exit` value over these (see below).
|
|
5887
|
+
*/
|
|
5888
|
+
const ORIGIN_DEFAULTS = {
|
|
5889
|
+
new: { opacity: 0, scale: 0.85 },
|
|
5890
|
+
old: { opacity: 1, scale: 1 },
|
|
5891
|
+
};
|
|
5892
|
+
const cornerProps = [
|
|
5893
|
+
"borderTopLeftRadius",
|
|
5894
|
+
"borderTopRightRadius",
|
|
5895
|
+
"borderBottomRightRadius",
|
|
5896
|
+
"borderBottomLeftRadius",
|
|
5897
|
+
];
|
|
5866
5898
|
function startViewAnimation(builder) {
|
|
5867
|
-
const { update, targets, options: defaultOptions } = builder;
|
|
5899
|
+
const { update, targets, resolveDefs, noCrop, pairs, classNames, options: defaultOptions, } = builder;
|
|
5868
5900
|
if (!document.startViewTransition) {
|
|
5869
|
-
|
|
5901
|
+
// An async IIFE (not `new Promise(async …)`) so a throwing/rejecting
|
|
5902
|
+
// update rejects this promise rather than leaving it unsettled.
|
|
5903
|
+
return (async () => {
|
|
5870
5904
|
await update();
|
|
5871
|
-
|
|
5872
|
-
});
|
|
5905
|
+
return new GroupAnimation([]);
|
|
5906
|
+
})();
|
|
5873
5907
|
}
|
|
5874
|
-
// TODO: Go over existing targets and ensure they all have ids
|
|
5875
5908
|
/**
|
|
5876
|
-
*
|
|
5877
|
-
*
|
|
5909
|
+
* Resolve any selector/Element targets to layer names, assigning a
|
|
5910
|
+
* `view-transition-name` to each element as we go. We run this before the
|
|
5911
|
+
* update (so the elements are captured in the old snapshot) and again
|
|
5912
|
+
* after it (for the new snapshot). An element present in both keeps the
|
|
5913
|
+
* same name and animates as a single `group` layer.
|
|
5914
|
+
*/
|
|
5915
|
+
const nameRegistry = new Map();
|
|
5916
|
+
const assigned = [];
|
|
5917
|
+
/**
|
|
5918
|
+
* Elements we tagged with a `view-transition-class` (via `.class()`),
|
|
5919
|
+
* tracked separately from `assigned` so cleanup removes the class without
|
|
5920
|
+
* ever stripping an author's own inline `view-transition-name`.
|
|
5921
|
+
*/
|
|
5922
|
+
const classed = [];
|
|
5923
|
+
const layerTargets = new Map();
|
|
5924
|
+
const croppedNames = new Set();
|
|
5925
|
+
/**
|
|
5926
|
+
* Each layer's stagger position (index + total) within its subject, per
|
|
5927
|
+
* snapshot. Resolving against the snapshot the layer belongs to keeps
|
|
5928
|
+
* stagger correct when `update()` replaces the matched elements, and lets
|
|
5929
|
+
* us skip a layer that's absent from a snapshot (e.g. an exited element
|
|
5930
|
+
* has no `new` pseudo-element).
|
|
5878
5931
|
*/
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5932
|
+
const layerStagger = new Map();
|
|
5933
|
+
/**
|
|
5934
|
+
* Names allocated for a paired subject in the old snapshot, replayed onto
|
|
5935
|
+
* its new-snapshot target so both ends share a layer and morph.
|
|
5936
|
+
*/
|
|
5937
|
+
const pairNames = new Map();
|
|
5938
|
+
/**
|
|
5939
|
+
* The old (`from`) elements of each paired subject, so their names can be
|
|
5940
|
+
* transferred off before the new (`to`) elements inherit them.
|
|
5941
|
+
*/
|
|
5942
|
+
const pairFrom = new Map();
|
|
5943
|
+
const resolveLayers = (phase) => {
|
|
5944
|
+
targets.forEach((target, definition) => {
|
|
5945
|
+
const className = classNames.get(definition);
|
|
5946
|
+
let names;
|
|
5947
|
+
if (definition === "root" || !resolveDefs.has(definition)) {
|
|
5948
|
+
names = [definition];
|
|
5949
|
+
}
|
|
5950
|
+
else if (pairs.has(definition)) {
|
|
5951
|
+
/**
|
|
5952
|
+
* Paired morph: name the old target in the old snapshot, then
|
|
5953
|
+
* force the same name(s) onto the new target in the new one, so
|
|
5954
|
+
* two different elements morph as a single layer.
|
|
5955
|
+
*/
|
|
5956
|
+
if (phase === "old") {
|
|
5957
|
+
pairFrom.set(definition, resolveElements(definition));
|
|
5958
|
+
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed);
|
|
5959
|
+
pairNames.set(definition, names);
|
|
5960
|
+
}
|
|
5961
|
+
else {
|
|
5962
|
+
/**
|
|
5963
|
+
* Transfer the name(s) off the `from` elements before the
|
|
5964
|
+
* `to` elements inherit them. A `from` that survives into
|
|
5965
|
+
* the new snapshot (e.g. hidden with `visibility: hidden`
|
|
5966
|
+
* rather than removed) would otherwise keep the name and
|
|
5967
|
+
* collide - "duplicate view-transition-name".
|
|
5968
|
+
*/
|
|
5969
|
+
for (const el of pairFrom.get(definition) ?? []) {
|
|
5970
|
+
el.style?.removeProperty("view-transition-name");
|
|
5971
|
+
/**
|
|
5972
|
+
* Drop the old end from the registry too, so the new
|
|
5973
|
+
* end alone supplies this name's `new` crop radii - we
|
|
5974
|
+
* neither re-measure nor get ordered by a stale element.
|
|
5975
|
+
*/
|
|
5976
|
+
nameRegistry.delete(el);
|
|
5977
|
+
}
|
|
5978
|
+
names = assignViewTransitionNames(pairs.get(definition), nameRegistry, assigned, pairNames.get(definition), className, classed);
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
5981
|
+
else {
|
|
5982
|
+
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed);
|
|
5983
|
+
}
|
|
5984
|
+
const cropped = definition !== "root" && !noCrop.has(definition);
|
|
5985
|
+
names.forEach((name, index) => {
|
|
5986
|
+
/**
|
|
5987
|
+
* If two subjects resolve to the same element, merge their
|
|
5988
|
+
* definitions so neither subject's animations are dropped.
|
|
5989
|
+
*/
|
|
5990
|
+
const existing = layerTargets.get(name);
|
|
5991
|
+
layerTargets.set(name, existing && existing !== target
|
|
5992
|
+
? { ...existing, ...target }
|
|
5993
|
+
: target);
|
|
5994
|
+
if (cropped)
|
|
5995
|
+
croppedNames.add(name);
|
|
5996
|
+
const stagger = layerStagger.get(name) ?? {};
|
|
5997
|
+
stagger[phase] = [index, names.length];
|
|
5998
|
+
layerStagger.set(name, stagger);
|
|
5999
|
+
});
|
|
5882
6000
|
});
|
|
5883
|
-
}
|
|
6001
|
+
};
|
|
5884
6002
|
/**
|
|
5885
|
-
*
|
|
5886
|
-
*
|
|
5887
|
-
*
|
|
6003
|
+
* The stagger index/total for a layer, resolved against the snapshot it
|
|
6004
|
+
* belongs to. Returns index -1 when the layer is absent from that snapshot
|
|
6005
|
+
* so the caller can skip a pseudo-element that doesn't exist.
|
|
6006
|
+
*/
|
|
6007
|
+
const staggerPosition = (name, type) => {
|
|
6008
|
+
const stagger = layerStagger.get(name);
|
|
6009
|
+
const position = type === "old"
|
|
6010
|
+
? stagger?.old
|
|
6011
|
+
: type === "new"
|
|
6012
|
+
? stagger?.new
|
|
6013
|
+
: // group / group-children / image-pair persist across both.
|
|
6014
|
+
stagger?.new ?? stagger?.old;
|
|
6015
|
+
return position ?? [-1, 1];
|
|
6016
|
+
};
|
|
6017
|
+
/**
|
|
6018
|
+
* Merge default + per-layer transition options for a generated layer and
|
|
6019
|
+
* resolve any stagger/delay function against this element's position. Used
|
|
6020
|
+
* by both the morph-retiming and crop corner-radius passes.
|
|
6021
|
+
*/
|
|
6022
|
+
const resolveLayerTransition = (target, type, transitionName, index, total) => {
|
|
6023
|
+
const transition = mergeTransition(getValueTransition$1(defaultOptions, transitionName), getValueTransition$1((layerOptions(target, type) ?? {}), transitionName));
|
|
6024
|
+
if (typeof transition.delay === "function") {
|
|
6025
|
+
transition.delay = transition.delay(index, total);
|
|
6026
|
+
}
|
|
6027
|
+
return transition;
|
|
6028
|
+
};
|
|
6029
|
+
/**
|
|
6030
|
+
* Measured corner radii per cropped layer, so the clip can animate each
|
|
6031
|
+
* corner between the old and new elements. Per-corner (rather than the
|
|
6032
|
+
* shorthand) so mismatched/individual radii interpolate cleanly.
|
|
6033
|
+
*/
|
|
6034
|
+
const cropRadii = new Map();
|
|
6035
|
+
const recordRadii = (style, name, phase) => {
|
|
6036
|
+
const corners = {};
|
|
6037
|
+
for (const corner of cornerProps)
|
|
6038
|
+
corners[corner] = style[corner];
|
|
6039
|
+
const entry = cropRadii.get(name) ?? {};
|
|
6040
|
+
entry[phase] = corners;
|
|
6041
|
+
cropRadii.set(name, entry);
|
|
6042
|
+
};
|
|
6043
|
+
/**
|
|
6044
|
+
* Cropped layers all come from `.add()`, so their elements are in the
|
|
6045
|
+
* registry - read each one's corner radii directly. For a paired morph both
|
|
6046
|
+
* ends share a name; the new-snapshot element is registered last, so it
|
|
6047
|
+
* wins the `new` reading (and the old end the `old` reading).
|
|
6048
|
+
*/
|
|
6049
|
+
const measureCrop = (phase) => {
|
|
6050
|
+
if (!croppedNames.size)
|
|
6051
|
+
return;
|
|
6052
|
+
nameRegistry.forEach((name, element) => {
|
|
6053
|
+
if (croppedNames.has(name)) {
|
|
6054
|
+
recordRadii(getComputedStyle(element), name, phase);
|
|
6055
|
+
}
|
|
6056
|
+
});
|
|
6057
|
+
};
|
|
6058
|
+
/**
|
|
6059
|
+
* Write the persistent view-transition CSS: suppress root capture when the
|
|
6060
|
+
* root has no animations of its own; force linear timing (baked into the
|
|
6061
|
+
* keyframes, so we can retime later via updateTiming); and clip +
|
|
6062
|
+
* object-fit: cover every cropped morph (the UA default overflows on
|
|
6063
|
+
* aspect-ratio change), with an animated border-radius added below.
|
|
5888
6064
|
*
|
|
5889
|
-
*
|
|
6065
|
+
* `css.commit` replaces rather than appends, so we re-set the full rule set
|
|
6066
|
+
* each call - the second call (in the update callback) then picks up cropped
|
|
6067
|
+
* layers that only exist in the new snapshot.
|
|
5890
6068
|
*/
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
6069
|
+
const commitViewCSS = () => {
|
|
6070
|
+
if (!hasTarget("root", targets)) {
|
|
6071
|
+
css.set(":root", { "view-transition-name": "none" });
|
|
6072
|
+
}
|
|
6073
|
+
css.set("::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*)", { "animation-timing-function": "linear !important" });
|
|
6074
|
+
croppedNames.forEach((name) => {
|
|
6075
|
+
css.set(`::view-transition-group(${name})`, { overflow: "clip" });
|
|
6076
|
+
css.set(`::view-transition-old(${name}), ::view-transition-new(${name})`, { width: "100%", height: "100%", "object-fit": "cover" });
|
|
6077
|
+
});
|
|
6078
|
+
css.commit(); // Write
|
|
6079
|
+
};
|
|
6080
|
+
const cleanup = () => {
|
|
6081
|
+
releaseViewTransitionNames(assigned, classed);
|
|
5898
6082
|
css.remove(); // Write
|
|
5899
|
-
}
|
|
5900
|
-
|
|
5901
|
-
|
|
6083
|
+
};
|
|
6084
|
+
const callback = async () => {
|
|
6085
|
+
await update();
|
|
6086
|
+
/**
|
|
6087
|
+
* Re-resolve so elements created by the update are named for the new
|
|
6088
|
+
* snapshot, then measure the cropped layers' new border-radius.
|
|
6089
|
+
*/
|
|
6090
|
+
const croppedBefore = croppedNames.size;
|
|
6091
|
+
resolveLayers("new");
|
|
6092
|
+
measureCrop("new");
|
|
6093
|
+
/**
|
|
6094
|
+
* Re-commit the crop CSS only if the new snapshot introduced cropped
|
|
6095
|
+
* layers, so a layer that exists only in the new snapshot is clipped
|
|
6096
|
+
* too - without forcing a redundant style write on the common path.
|
|
6097
|
+
*/
|
|
6098
|
+
if (croppedNames.size > croppedBefore)
|
|
6099
|
+
commitViewCSS();
|
|
6100
|
+
};
|
|
6101
|
+
let transition;
|
|
6102
|
+
try {
|
|
6103
|
+
resolveLayers("old");
|
|
6104
|
+
measureCrop("old");
|
|
6105
|
+
commitViewCSS();
|
|
6106
|
+
transition = document.startViewTransition(callback);
|
|
6107
|
+
}
|
|
6108
|
+
catch (error) {
|
|
6109
|
+
/**
|
|
6110
|
+
* The prelude writes inline names before the transition exists. If it
|
|
6111
|
+
* throws (e.g. startViewTransition rejects in a bad UA state), release
|
|
6112
|
+
* them so we neither leak DOM state nor stall the queue on a promise
|
|
6113
|
+
* that never settles - hand back a rejection it can advance past.
|
|
6114
|
+
*/
|
|
6115
|
+
cleanup();
|
|
6116
|
+
return Promise.reject(error);
|
|
6117
|
+
}
|
|
6118
|
+
transition.finished.finally(cleanup);
|
|
6119
|
+
return new Promise((resolve, reject) => {
|
|
6120
|
+
transition.ready
|
|
6121
|
+
.then(() => {
|
|
5902
6122
|
const generatedViewAnimations = getViewAnimations();
|
|
5903
6123
|
const animations = [];
|
|
5904
6124
|
/**
|
|
5905
6125
|
* Create animations for each of our explicitly-defined subjects.
|
|
6126
|
+
* `opacityAnimated` additionally tracks which `${name}:${type}`
|
|
6127
|
+
* we faded, so we can keep the UA `plus-lighter` blend only for a
|
|
6128
|
+
* genuine opacity crossfade (both sides fading) and drop it for a
|
|
6129
|
+
* slide/transform, where additive compositing would flash bright.
|
|
5906
6130
|
*/
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
6131
|
+
const explicitlyAnimated = new Set();
|
|
6132
|
+
const opacityAnimated = new Set();
|
|
6133
|
+
layerTargets.forEach((target, name) => {
|
|
6134
|
+
const stagger = layerStagger.get(name);
|
|
6135
|
+
/**
|
|
6136
|
+
* Presence: `enter` only fires for a pure newcomer (a new
|
|
6137
|
+
* view with no old), `exit` only for a pure leaver. A
|
|
6138
|
+
* survivor (both) gets neither - it just morphs.
|
|
6139
|
+
*/
|
|
6140
|
+
const enterApplies = !!stagger?.new && !stagger?.old;
|
|
6141
|
+
const exitApplies = !!stagger?.old && !stagger?.new;
|
|
5910
6142
|
for (const key of definitionNames) {
|
|
5911
|
-
if (!
|
|
6143
|
+
if (!target[key])
|
|
5912
6144
|
continue;
|
|
5913
|
-
|
|
6145
|
+
if (key === "enter" && !enterApplies)
|
|
6146
|
+
continue;
|
|
6147
|
+
if (key === "exit" && !exitApplies)
|
|
6148
|
+
continue;
|
|
6149
|
+
const type = chooseLayerType(key);
|
|
6150
|
+
const [index, total] = staggerPosition(name, type);
|
|
6151
|
+
// Skip a layer absent from its snapshot.
|
|
6152
|
+
if (index === -1)
|
|
6153
|
+
continue;
|
|
6154
|
+
const { keyframes, options } = target[key];
|
|
5914
6155
|
for (let [valueName, valueKeyframes] of Object.entries(keyframes)) {
|
|
5915
|
-
|
|
6156
|
+
// Skip only missing values - `0` (e.g. opacity: 0)
|
|
6157
|
+
// is valid and must reach the from-value inference.
|
|
6158
|
+
if (valueKeyframes == null)
|
|
5916
6159
|
continue;
|
|
5917
|
-
const valueOptions = {
|
|
5918
|
-
...getValueTransition$1(defaultOptions, valueName),
|
|
5919
|
-
...getValueTransition$1(options, valueName),
|
|
5920
|
-
};
|
|
5921
|
-
const type = chooseLayerType(key);
|
|
5922
6160
|
/**
|
|
5923
|
-
*
|
|
5924
|
-
*
|
|
6161
|
+
* The view path hands keyframes straight to WAAPI,
|
|
6162
|
+
* so Motion's `x`/`y` shorthands (compiled to
|
|
6163
|
+
* `transform` only via the value pipeline) have no
|
|
6164
|
+
* effect. Warn and skip - use `transform`/`translate`.
|
|
6165
|
+
*/
|
|
6166
|
+
if (valueName === "x" || valueName === "y") {
|
|
6167
|
+
warnOnce(false, `animateView() animates view-transition layers with CSS properties; the "${valueName}" shorthand has no effect - use transform, e.g. { transform: "translateX(40px)" }.`);
|
|
6168
|
+
continue;
|
|
6169
|
+
}
|
|
6170
|
+
/**
|
|
6171
|
+
* enter/exit win over new/old on a shared property -
|
|
6172
|
+
* skip it here when the gated bucket also defines it.
|
|
6173
|
+
*/
|
|
6174
|
+
if (key === "new" &&
|
|
6175
|
+
enterApplies &&
|
|
6176
|
+
target.enter?.keyframes[valueName] != null) {
|
|
6177
|
+
continue;
|
|
6178
|
+
}
|
|
6179
|
+
if (key === "old" &&
|
|
6180
|
+
exitApplies &&
|
|
6181
|
+
target.exit?.keyframes[valueName] != null) {
|
|
6182
|
+
continue;
|
|
6183
|
+
}
|
|
6184
|
+
const valueOptions = mergeTransition(getValueTransition$1(defaultOptions, valueName), getValueTransition$1(options, valueName));
|
|
6185
|
+
/**
|
|
6186
|
+
* Infer an origin for a single-value keyframe. An
|
|
6187
|
+
* `enter` mirrors the matching `exit` value (a
|
|
6188
|
+
* defined exit reverses into the enter for free);
|
|
6189
|
+
* otherwise the per-type default (opacity 0/1, scale
|
|
6190
|
+
* 0.85). No default -> left as-is (animates from the
|
|
6191
|
+
* live value).
|
|
6192
|
+
*
|
|
6193
|
+
* `new`/`old` fire for survivors too, where only the
|
|
6194
|
+
* opacity crossfade default applies - a transform
|
|
6195
|
+
* default like scale 0.85 would pop a persisting
|
|
6196
|
+
* element, so gate it on the layer actually
|
|
6197
|
+
* entering/leaving.
|
|
5925
6198
|
*/
|
|
5926
|
-
if (
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
6199
|
+
if (!Array.isArray(valueKeyframes)) {
|
|
6200
|
+
const exitValue = key === "enter"
|
|
6201
|
+
? target.exit?.keyframes[valueName]
|
|
6202
|
+
: undefined;
|
|
6203
|
+
const allowDefault = valueName === "opacity" ||
|
|
6204
|
+
(type === "new" ? enterApplies : exitApplies);
|
|
6205
|
+
const from = exitValue != null
|
|
6206
|
+
? Array.isArray(exitValue)
|
|
6207
|
+
? exitValue[exitValue.length - 1]
|
|
6208
|
+
: exitValue
|
|
6209
|
+
: allowDefault
|
|
6210
|
+
? ORIGIN_DEFAULTS[type]?.[valueName]
|
|
6211
|
+
: undefined;
|
|
6212
|
+
if (from !== undefined) {
|
|
6213
|
+
valueKeyframes = [from, valueKeyframes];
|
|
6214
|
+
}
|
|
5930
6215
|
}
|
|
5931
6216
|
/**
|
|
5932
|
-
* Resolve stagger function if provided
|
|
6217
|
+
* Resolve stagger function if provided, per element
|
|
6218
|
+
* across this subject's resolved layers.
|
|
5933
6219
|
*/
|
|
5934
6220
|
if (typeof valueOptions.delay === "function") {
|
|
5935
|
-
valueOptions.delay = valueOptions.delay(
|
|
6221
|
+
valueOptions.delay = valueOptions.delay(index, total);
|
|
5936
6222
|
}
|
|
5937
6223
|
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
|
|
5938
6224
|
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
|
|
5939
|
-
|
|
6225
|
+
animations.push(new NativeAnimation({
|
|
5940
6226
|
...valueOptions,
|
|
5941
6227
|
element: document.documentElement,
|
|
5942
6228
|
name: valueName,
|
|
5943
|
-
pseudoElement: `::view-transition-${type}(${
|
|
6229
|
+
pseudoElement: `::view-transition-${type}(${name})`,
|
|
5944
6230
|
keyframes: valueKeyframes,
|
|
5945
|
-
});
|
|
5946
|
-
|
|
6231
|
+
}));
|
|
6232
|
+
explicitlyAnimated.add(`${name}:${type}`);
|
|
6233
|
+
if (valueName === "opacity") {
|
|
6234
|
+
opacityAnimated.add(`${name}:${type}`);
|
|
6235
|
+
}
|
|
5947
6236
|
}
|
|
5948
6237
|
}
|
|
5949
6238
|
});
|
|
@@ -5962,45 +6251,161 @@
|
|
|
5962
6251
|
const name = getViewAnimationLayerInfo(pseudoElement);
|
|
5963
6252
|
if (!name)
|
|
5964
6253
|
continue;
|
|
5965
|
-
const targetDefinition =
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
}
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
effect
|
|
5990
|
-
.getKeyframes()
|
|
5991
|
-
.some((keyframe) => keyframe.mixBlendMode)) {
|
|
5992
|
-
animations.push(new NativeAnimationWrapper(animation));
|
|
5993
|
-
}
|
|
5994
|
-
else {
|
|
5995
|
-
animation.cancel();
|
|
6254
|
+
const targetDefinition = layerTargets.get(name.layer);
|
|
6255
|
+
/**
|
|
6256
|
+
* We built our own animation for this layer, so drop the
|
|
6257
|
+
* browser-generated fade we're replacing. The UA
|
|
6258
|
+
* `plus-lighter` blend is a *separate* generated animation on
|
|
6259
|
+
* the same pseudo (it sets `mix-blend-mode` in its keyframes):
|
|
6260
|
+
* keep it *only* for a true opacity crossfade - both sides
|
|
6261
|
+
* fading - so a symmetric crossfade composites without
|
|
6262
|
+
* darkening, but a slide/transform (where both layers stay
|
|
6263
|
+
* opaque and overlap) doesn't flash bright from the addition.
|
|
6264
|
+
*/
|
|
6265
|
+
if (explicitlyAnimated.has(`${name.layer}:${name.type}`)) {
|
|
6266
|
+
const isCrossfade = opacityAnimated.has(`${name.layer}:new`) &&
|
|
6267
|
+
opacityAnimated.has(`${name.layer}:old`);
|
|
6268
|
+
if (isCrossfade &&
|
|
6269
|
+
effect
|
|
6270
|
+
.getKeyframes()
|
|
6271
|
+
.some((keyframe) => keyframe.mixBlendMode)) {
|
|
6272
|
+
animations.push(new NativeAnimationWrapper(animation));
|
|
6273
|
+
}
|
|
6274
|
+
else {
|
|
6275
|
+
animation.cancel();
|
|
6276
|
+
}
|
|
6277
|
+
continue;
|
|
5996
6278
|
}
|
|
6279
|
+
/**
|
|
6280
|
+
* Otherwise retime the browser-generated animation to
|
|
6281
|
+
* Motion's timing. This auto-enables the layout (group)
|
|
6282
|
+
* morph for any resolved/named target, and applies the
|
|
6283
|
+
* default timing to old/new layers we haven't explicitly
|
|
6284
|
+
* overridden.
|
|
6285
|
+
*
|
|
6286
|
+
* group + group-children both follow the layout timing so
|
|
6287
|
+
* the nesting container stays in sync with the morph.
|
|
6288
|
+
*/
|
|
6289
|
+
/**
|
|
6290
|
+
* A survivor's old + new are the two halves of one
|
|
6291
|
+
* `plus-lighter` crossfade. They must share identical timing
|
|
6292
|
+
* (so their opacities stay mirrored and sum to 1 - else the
|
|
6293
|
+
* additive blend flashes bright wherever both are partly
|
|
6294
|
+
* visible) and fade linearly (the bounce belongs on the
|
|
6295
|
+
* group's geometry, not the opacity). So time them as the
|
|
6296
|
+
* group, rather than via their own - potentially staggered,
|
|
6297
|
+
* or enter/exit-derived - old/new options.
|
|
6298
|
+
*/
|
|
6299
|
+
const stagger = layerStagger.get(name.layer);
|
|
6300
|
+
const isMorphCrossfade = (name.type === "old" || name.type === "new") &&
|
|
6301
|
+
!!stagger?.old &&
|
|
6302
|
+
!!stagger?.new;
|
|
6303
|
+
const timingType = name.type.startsWith("group") || isMorphCrossfade
|
|
6304
|
+
? "group"
|
|
6305
|
+
: name.type;
|
|
6306
|
+
const [index, total] = staggerPosition(name.layer, timingType);
|
|
6307
|
+
const transitionName = timingType === "group" ? "layout" : "";
|
|
6308
|
+
let animationTransition = resolveLayerTransition(targetDefinition, timingType, transitionName, index === -1 ? 0 : index, total);
|
|
6309
|
+
/**
|
|
6310
|
+
* The crossfade should resolve at the spring's *perceptual*
|
|
6311
|
+
* (visual) duration - the geometry can keep bouncing, but the
|
|
6312
|
+
* opacity shouldn't drag through the settle. So capture
|
|
6313
|
+
* `visualDuration` before `applyGeneratorOptions` replaces it
|
|
6314
|
+
* with the full overshoot duration, and use it for the fade.
|
|
6315
|
+
*/
|
|
6316
|
+
const visualDuration = animationTransition.visualDuration;
|
|
6317
|
+
animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
|
|
6318
|
+
animationTransition =
|
|
6319
|
+
applyGeneratorOptions(animationTransition);
|
|
6320
|
+
const duration = isMorphCrossfade && visualDuration !== undefined
|
|
6321
|
+
? secondsToMilliseconds(visualDuration)
|
|
6322
|
+
: animationTransition.duration;
|
|
6323
|
+
const easing = isMorphCrossfade
|
|
6324
|
+
? "linear"
|
|
6325
|
+
: mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration);
|
|
6326
|
+
effect.updateTiming({
|
|
6327
|
+
delay: secondsToMilliseconds(animationTransition.delay ?? 0),
|
|
6328
|
+
duration,
|
|
6329
|
+
easing,
|
|
6330
|
+
});
|
|
6331
|
+
animations.push(new NativeAnimationWrapper(animation));
|
|
5997
6332
|
}
|
|
6333
|
+
/**
|
|
6334
|
+
* Animate each cropped layer's clip corners between the old and
|
|
6335
|
+
* new elements, so a cropped morph keeps rounded corners
|
|
6336
|
+
* (handling individual per-corner radii).
|
|
6337
|
+
*/
|
|
6338
|
+
cropRadii.forEach((radii, name) => {
|
|
6339
|
+
if (!radii.old && !radii.new)
|
|
6340
|
+
return;
|
|
6341
|
+
const target = layerTargets.get(name);
|
|
6342
|
+
const [index, total] = staggerPosition(name, "group");
|
|
6343
|
+
const radiusOptions = resolveLayerTransition(target, "group", "layout", index === -1 ? 0 : index, total);
|
|
6344
|
+
radiusOptions.duration && (radiusOptions.duration = secondsToMilliseconds(radiusOptions.duration));
|
|
6345
|
+
radiusOptions.delay && (radiusOptions.delay = secondsToMilliseconds(radiusOptions.delay));
|
|
6346
|
+
for (const corner of cornerProps) {
|
|
6347
|
+
// `||` (not `??`) so an empty measurement (e.g. an
|
|
6348
|
+
// un-rendered element) falls back rather than producing
|
|
6349
|
+
// an invalid keyframe.
|
|
6350
|
+
const from = radii.old?.[corner] || radii.new?.[corner] || "0px";
|
|
6351
|
+
const to = radii.new?.[corner] || radii.old?.[corner] || "0px";
|
|
6352
|
+
// Skip square corners - nothing to round.
|
|
6353
|
+
if (parseFloat(from) === 0 && parseFloat(to) === 0) {
|
|
6354
|
+
continue;
|
|
6355
|
+
}
|
|
6356
|
+
animations.push(new NativeAnimation({
|
|
6357
|
+
...radiusOptions,
|
|
6358
|
+
element: document.documentElement,
|
|
6359
|
+
name: corner,
|
|
6360
|
+
pseudoElement: `::view-transition-group(${name})`,
|
|
6361
|
+
keyframes: [from, to],
|
|
6362
|
+
}));
|
|
6363
|
+
}
|
|
6364
|
+
});
|
|
5998
6365
|
resolve(new GroupAnimation(animations));
|
|
5999
|
-
})
|
|
6366
|
+
})
|
|
6367
|
+
.catch(() =>
|
|
6368
|
+
/**
|
|
6369
|
+
* `ready` rejects when the transition is skipped - no visual
|
|
6370
|
+
* change, or superseded by an interrupting transition. The DOM
|
|
6371
|
+
* update still applied, so settle with no animations rather than
|
|
6372
|
+
* surfacing it as an error to an awaiting caller. A genuine
|
|
6373
|
+
* failure in `update()` rejects `updateCallbackDone` (already
|
|
6374
|
+
* settled by now), so propagate that instead.
|
|
6375
|
+
*/
|
|
6376
|
+
transition.updateCallbackDone.then(() => resolve(new GroupAnimation([])), reject));
|
|
6000
6377
|
});
|
|
6001
6378
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6379
|
+
/**
|
|
6380
|
+
* The options that should time a given generated layer type, so a retimed
|
|
6381
|
+
* group/old/new picks up any per-target transition the user provided. Checks
|
|
6382
|
+
* the type's buckets in priority order (e.g. `new` before `enter`).
|
|
6383
|
+
*/
|
|
6384
|
+
function layerOptions(target, type) {
|
|
6385
|
+
for (const bucket of typeBuckets[type] ?? []) {
|
|
6386
|
+
const options = target?.[bucket]?.options;
|
|
6387
|
+
if (options)
|
|
6388
|
+
return options;
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
/**
|
|
6392
|
+
* Merge a base transition (e.g. the default `options`) with a per-layer/value
|
|
6393
|
+
* override. An explicit `duration` on the override must win over an inherited
|
|
6394
|
+
* generator's own timing: a spring prefers `visualDuration`, and
|
|
6395
|
+
* `spring.applyToOptions` overwrites `duration` with the computed settle time -
|
|
6396
|
+
* so without this the override is silently discarded. Dropping the inherited
|
|
6397
|
+
* `type`/`visualDuration` makes the layer a plain tween of that duration, unless
|
|
6398
|
+
* it asked for its own generator `type`/`visualDuration`.
|
|
6399
|
+
*/
|
|
6400
|
+
function mergeTransition(base, override) {
|
|
6401
|
+
const merged = { ...base, ...override };
|
|
6402
|
+
if (override.duration !== undefined) {
|
|
6403
|
+
if (override.visualDuration === undefined)
|
|
6404
|
+
delete merged.visualDuration;
|
|
6405
|
+
if (override.type === undefined)
|
|
6406
|
+
delete merged.type;
|
|
6407
|
+
}
|
|
6408
|
+
return merged;
|
|
6004
6409
|
}
|
|
6005
6410
|
|
|
6006
6411
|
let builders = [];
|
|
@@ -6014,10 +6419,16 @@
|
|
|
6014
6419
|
function start(builder) {
|
|
6015
6420
|
removeItem(builders, builder);
|
|
6016
6421
|
current = builder;
|
|
6017
|
-
startViewAnimation(builder)
|
|
6422
|
+
startViewAnimation(builder)
|
|
6423
|
+
.then((animation) => {
|
|
6018
6424
|
builder.notifyReady(animation);
|
|
6019
|
-
animation.finished
|
|
6020
|
-
})
|
|
6425
|
+
return animation.finished;
|
|
6426
|
+
})
|
|
6427
|
+
// A genuinely failed transition (a throwing update) rejects the
|
|
6428
|
+
// builder; a skipped/interrupted one resolves with no animations (see
|
|
6429
|
+
// start.ts). Either way, advance the queue - else later transitions hang.
|
|
6430
|
+
.catch((error) => builder.notifyReject(error))
|
|
6431
|
+
.finally(next);
|
|
6021
6432
|
}
|
|
6022
6433
|
function processQueue() {
|
|
6023
6434
|
/**
|
|
@@ -6054,31 +6465,96 @@
|
|
|
6054
6465
|
constructor(update, options = {}) {
|
|
6055
6466
|
this.currentSubject = "root";
|
|
6056
6467
|
this.targets = new Map();
|
|
6057
|
-
|
|
6058
|
-
|
|
6468
|
+
/**
|
|
6469
|
+
* Definitions that must be resolved to elements (and assigned a
|
|
6470
|
+
* `view-transition-name`) rather than treated as pre-named layers.
|
|
6471
|
+
*/
|
|
6472
|
+
this.resolveDefs = new Set();
|
|
6473
|
+
/**
|
|
6474
|
+
* Subjects opted out of the default crop (clip + object-fit: cover +
|
|
6475
|
+
* animated corner radii) via `.crop(false)`.
|
|
6476
|
+
*/
|
|
6477
|
+
this.noCrop = new Set();
|
|
6478
|
+
/**
|
|
6479
|
+
* Subjects paired with a different new-snapshot target (the second `.add()`
|
|
6480
|
+
* argument), so two distinct elements share one name and morph into each
|
|
6481
|
+
* other - a shared-element transition.
|
|
6482
|
+
*/
|
|
6483
|
+
this.pairs = new Map();
|
|
6484
|
+
/**
|
|
6485
|
+
* A `view-transition-class` to apply to each subject's resolved elements,
|
|
6486
|
+
* so authors can target the generated layers from CSS by class rather than
|
|
6487
|
+
* the opaque generated name.
|
|
6488
|
+
*/
|
|
6489
|
+
this.classNames = new Map();
|
|
6490
|
+
this.notifyReady = noop;
|
|
6491
|
+
this.notifyReject = noop;
|
|
6492
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
6059
6493
|
this.notifyReady = resolve;
|
|
6494
|
+
this.notifyReject = reject;
|
|
6060
6495
|
});
|
|
6061
6496
|
this.update = update;
|
|
6062
6497
|
this.options = {
|
|
6063
6498
|
interrupt: "wait",
|
|
6064
6499
|
...options,
|
|
6065
6500
|
};
|
|
6501
|
+
// Avoid an unhandled rejection when a failed transition has no
|
|
6502
|
+
// `.then(_, reject)` handler attached (e.g. fire-and-forget).
|
|
6503
|
+
this.readyPromise.catch(noop);
|
|
6066
6504
|
addToQueue(this);
|
|
6067
6505
|
}
|
|
6068
|
-
|
|
6506
|
+
/**
|
|
6507
|
+
* Target elements resolved from a selector or Element, each assigned a
|
|
6508
|
+
* `view-transition-name` automatically.
|
|
6509
|
+
*
|
|
6510
|
+
* Passing a second target pairs them: the first is resolved in the old
|
|
6511
|
+
* snapshot and the second in the new, sharing one name so two *different*
|
|
6512
|
+
* elements morph into each other (e.g. `.add(card, ".modal")`). Symmetric -
|
|
6513
|
+
* pass them the other way round to morph back.
|
|
6514
|
+
*/
|
|
6515
|
+
add(subject, newSubject) {
|
|
6069
6516
|
this.currentSubject = subject;
|
|
6517
|
+
this.resolveDefs.add(subject);
|
|
6518
|
+
if (newSubject !== undefined)
|
|
6519
|
+
this.pairs.set(subject, newSubject);
|
|
6520
|
+
// Register the subject so it participates (and gets an automatic
|
|
6521
|
+
// layout/morph animation) even without an explicit enter/exit/layout.
|
|
6522
|
+
if (!this.targets.has(subject))
|
|
6523
|
+
this.targets.set(subject, {});
|
|
6070
6524
|
return this;
|
|
6071
6525
|
}
|
|
6072
|
-
|
|
6073
|
-
|
|
6526
|
+
/**
|
|
6527
|
+
* Morphs are clipped + `object-fit: cover` (and their corners animate)
|
|
6528
|
+
* by default. Call `.crop(false)` to opt this subject out and fall back
|
|
6529
|
+
* to the browser default (which overflows on aspect-ratio change).
|
|
6530
|
+
*/
|
|
6531
|
+
crop(enabled = true) {
|
|
6532
|
+
enabled
|
|
6533
|
+
? this.noCrop.delete(this.currentSubject)
|
|
6534
|
+
: this.noCrop.add(this.currentSubject);
|
|
6074
6535
|
return this;
|
|
6075
6536
|
}
|
|
6076
|
-
|
|
6077
|
-
|
|
6537
|
+
/**
|
|
6538
|
+
* Tag this subject's generated layers with a `view-transition-class`, so
|
|
6539
|
+
* they can be targeted from CSS - `::view-transition-group(.name)`,
|
|
6540
|
+
* `::view-transition-old/new(.name)`, `::view-transition-image-pair(.name)`
|
|
6541
|
+
* - without the opaque generated `view-transition-name`. Because `.add()`
|
|
6542
|
+
* can match many elements, a shared class targets them all at once (and,
|
|
6543
|
+
* for a pair, both ends). The escape hatch for z-index / custom keyframes
|
|
6544
|
+
* on a morph layer.
|
|
6545
|
+
*/
|
|
6546
|
+
class(name) {
|
|
6547
|
+
this.classNames.set(this.currentSubject, name);
|
|
6078
6548
|
return this;
|
|
6079
6549
|
}
|
|
6080
|
-
|
|
6081
|
-
|
|
6550
|
+
/**
|
|
6551
|
+
* Set the transition for this subject's morph. The morph is enabled
|
|
6552
|
+
* automatically by `.add()`; this just customises its timing (duration,
|
|
6553
|
+
* easing, a `delay`/`stagger`, …). On the implicit `root` subject it also
|
|
6554
|
+
* opts the page into the transition (the root crossfade).
|
|
6555
|
+
*/
|
|
6556
|
+
layout(options = {}) {
|
|
6557
|
+
this.updateTarget("layout", {}, options);
|
|
6082
6558
|
return this;
|
|
6083
6559
|
}
|
|
6084
6560
|
enter(keyframes, options) {
|
|
@@ -6089,9 +6565,21 @@
|
|
|
6089
6565
|
this.updateTarget("exit", keyframes, options);
|
|
6090
6566
|
return this;
|
|
6091
6567
|
}
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6568
|
+
/**
|
|
6569
|
+
* Animate the new view directly, whether the element is appearing or
|
|
6570
|
+
* persisting (unlike `.enter()`, which only fires for a pure newcomer).
|
|
6571
|
+
* Pair with `.old()` for a crossfade or slide-through.
|
|
6572
|
+
*/
|
|
6573
|
+
new(keyframes, options) {
|
|
6574
|
+
this.updateTarget("new", keyframes, options);
|
|
6575
|
+
return this;
|
|
6576
|
+
}
|
|
6577
|
+
/**
|
|
6578
|
+
* Animate the old view directly, whether the element is leaving or
|
|
6579
|
+
* persisting (unlike `.exit()`, which only fires for a pure leaver).
|
|
6580
|
+
*/
|
|
6581
|
+
old(keyframes, options) {
|
|
6582
|
+
this.updateTarget("old", keyframes, options);
|
|
6095
6583
|
return this;
|
|
6096
6584
|
}
|
|
6097
6585
|
updateTarget(target, keyframes, options = {}) {
|
|
@@ -6106,8 +6594,8 @@
|
|
|
6106
6594
|
return this.readyPromise.then(resolve, reject);
|
|
6107
6595
|
}
|
|
6108
6596
|
}
|
|
6109
|
-
function animateView(update,
|
|
6110
|
-
return new ViewTransitionBuilder(update,
|
|
6597
|
+
function animateView(update, options = {}) {
|
|
6598
|
+
return new ViewTransitionBuilder(update, options);
|
|
6111
6599
|
}
|
|
6112
6600
|
|
|
6113
6601
|
const createAxisDelta = () => ({
|
|
@@ -8207,7 +8695,7 @@
|
|
|
8207
8695
|
: values.borderRadius;
|
|
8208
8696
|
}
|
|
8209
8697
|
const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
|
|
8210
|
-
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop
|
|
8698
|
+
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
|
|
8211
8699
|
function compress(min, max, easing) {
|
|
8212
8700
|
return (p) => {
|
|
8213
8701
|
// Could replace ifs with clamp
|
|
@@ -8227,7 +8715,7 @@
|
|
|
8227
8715
|
|
|
8228
8716
|
function addDomEvent(target, eventName, handler, options = { passive: true }) {
|
|
8229
8717
|
target.addEventListener(eventName, handler, options);
|
|
8230
|
-
return () => target.removeEventListener(eventName, handler);
|
|
8718
|
+
return () => target.removeEventListener(eventName, handler, options);
|
|
8231
8719
|
}
|
|
8232
8720
|
|
|
8233
8721
|
const compareByDepth = (a, b) => a.depth - b.depth;
|
|
@@ -9497,7 +9985,6 @@
|
|
|
9497
9985
|
*/
|
|
9498
9986
|
this.pendingAnimation = frame.update(() => {
|
|
9499
9987
|
globalProjectionState.hasAnimatedSinceResize = true;
|
|
9500
|
-
activeAnimations.layout++;
|
|
9501
9988
|
this.motionValue || (this.motionValue = motionValue(0));
|
|
9502
9989
|
this.motionValue.jump(0, false);
|
|
9503
9990
|
this.currentAnimation = animateSingleValue(this.motionValue, [0, 1000], {
|
|
@@ -9508,11 +9995,7 @@
|
|
|
9508
9995
|
this.mixTargetDelta(latest);
|
|
9509
9996
|
options.onUpdate && options.onUpdate(latest);
|
|
9510
9997
|
},
|
|
9511
|
-
onStop: () => {
|
|
9512
|
-
activeAnimations.layout--;
|
|
9513
|
-
},
|
|
9514
9998
|
onComplete: () => {
|
|
9515
|
-
activeAnimations.layout--;
|
|
9516
9999
|
options.onComplete && options.onComplete();
|
|
9517
10000
|
this.completeAnimation();
|
|
9518
10001
|
},
|
|
@@ -10027,7 +10510,7 @@
|
|
|
10027
10510
|
*/
|
|
10028
10511
|
const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
|
|
10029
10512
|
? Math.round
|
|
10030
|
-
: noop
|
|
10513
|
+
: noop;
|
|
10031
10514
|
function roundAxis(axis) {
|
|
10032
10515
|
// Round to the nearest .5 pixels to support subpixel layouts
|
|
10033
10516
|
axis.min = roundPoint(axis.min);
|
|
@@ -10101,39 +10584,221 @@
|
|
|
10101
10584
|
checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
|
|
10102
10585
|
});
|
|
10103
10586
|
|
|
10104
|
-
const layoutSelector = "[data-layout],
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10587
|
+
const layoutSelector = "[data-layout],[data-layout-id]";
|
|
10588
|
+
/**
|
|
10589
|
+
* All imperatively-created projection nodes live in one persistent tree,
|
|
10590
|
+
* shared across animateLayout() calls (and with any React-created nodes,
|
|
10591
|
+
* via the singleton document root). Keyed by element for reuse.
|
|
10592
|
+
*/
|
|
10593
|
+
const layoutNodes = new WeakMap();
|
|
10594
|
+
/**
|
|
10595
|
+
* Builders created within the same synchronous tick are flushed together
|
|
10596
|
+
* as a single "commit": every node is snapshotted before any updateDom
|
|
10597
|
+
* runs, mirroring React batching renders from different parts of the tree.
|
|
10598
|
+
*/
|
|
10599
|
+
let pendingBuilders;
|
|
10600
|
+
function collectLayoutElements(scope) {
|
|
10601
|
+
const elements = [];
|
|
10602
|
+
if (scope instanceof HTMLElement && scope.matches(layoutSelector)) {
|
|
10603
|
+
elements.push(scope);
|
|
10604
|
+
}
|
|
10605
|
+
scope.querySelectorAll(layoutSelector).forEach((element) => {
|
|
10606
|
+
if (element instanceof HTMLElement)
|
|
10607
|
+
elements.push(element);
|
|
10608
|
+
});
|
|
10609
|
+
return elements;
|
|
10610
|
+
}
|
|
10611
|
+
/**
|
|
10612
|
+
* Process any work scheduled on the frameloop now. A previous animation
|
|
10613
|
+
* may have been seeked while paused (controls.time = x) without a frame
|
|
10614
|
+
* having rendered it - we must materialise that state into the DOM
|
|
10615
|
+
* before taking snapshots.
|
|
10616
|
+
*/
|
|
10617
|
+
function flushPendingFrame() {
|
|
10618
|
+
if (frameData.isProcessing)
|
|
10619
|
+
return;
|
|
10620
|
+
const now = time.now();
|
|
10621
|
+
frameData.delta = clamp(0, 1000 / 60, now - frameData.timestamp);
|
|
10622
|
+
frameData.timestamp = now;
|
|
10623
|
+
frameData.isProcessing = true;
|
|
10624
|
+
frameSteps.update.process(frameData);
|
|
10625
|
+
frameSteps.preRender.process(frameData);
|
|
10626
|
+
frameSteps.render.process(frameData);
|
|
10627
|
+
frameData.isProcessing = false;
|
|
10628
|
+
}
|
|
10629
|
+
function getProjectionParent(element) {
|
|
10630
|
+
let ancestor = element.parentElement;
|
|
10631
|
+
while (ancestor) {
|
|
10632
|
+
const node = layoutNodes.get(ancestor);
|
|
10633
|
+
if (node && node.instance)
|
|
10634
|
+
return node;
|
|
10635
|
+
ancestor = ancestor.parentElement;
|
|
10636
|
+
}
|
|
10637
|
+
return undefined;
|
|
10638
|
+
}
|
|
10639
|
+
function createVisualElement() {
|
|
10640
|
+
return new HTMLVisualElement({
|
|
10641
|
+
props: {},
|
|
10642
|
+
presenceContext: null,
|
|
10643
|
+
visualState: {
|
|
10644
|
+
latestValues: {},
|
|
10645
|
+
renderState: {
|
|
10646
|
+
transform: {},
|
|
10647
|
+
transformOrigin: {},
|
|
10648
|
+
style: {},
|
|
10649
|
+
vars: {},
|
|
10650
|
+
},
|
|
10651
|
+
},
|
|
10652
|
+
}, { allowProjection: true });
|
|
10653
|
+
}
|
|
10654
|
+
function readNodeOptions(element, transition) {
|
|
10655
|
+
const layoutAttr = element.getAttribute("data-layout");
|
|
10656
|
+
const layoutId = element.getAttribute("data-layout-id") ?? undefined;
|
|
10114
10657
|
return {
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10658
|
+
layoutId,
|
|
10659
|
+
layout: layoutAttr !== null ? true : undefined,
|
|
10660
|
+
animationType: (!layoutAttr || layoutAttr === "true"
|
|
10661
|
+
? "both"
|
|
10662
|
+
: layoutAttr),
|
|
10663
|
+
transition,
|
|
10664
|
+
};
|
|
10665
|
+
}
|
|
10666
|
+
function prepareNode(element, transition) {
|
|
10667
|
+
let node = layoutNodes.get(element);
|
|
10668
|
+
if (!node) {
|
|
10669
|
+
let visualElement = visualElementStore.get(element);
|
|
10670
|
+
if (!visualElement)
|
|
10671
|
+
visualElement = createVisualElement();
|
|
10672
|
+
/**
|
|
10673
|
+
* A first-time element may carry a projection transform in its
|
|
10674
|
+
* inline style (e.g. it was cloned from an element mid-animation).
|
|
10675
|
+
* That transform isn't tracked in latestValues so the engine can't
|
|
10676
|
+
* reset it before measuring - clear it now so the first layout
|
|
10677
|
+
* measurement isn't inflated.
|
|
10678
|
+
*/
|
|
10679
|
+
if (element.style.transform &&
|
|
10680
|
+
!hasTransform(visualElement.latestValues)) {
|
|
10681
|
+
element.style.transform = "";
|
|
10682
|
+
}
|
|
10683
|
+
node = new HTMLProjectionNode(visualElement.latestValues, getProjectionParent(element));
|
|
10684
|
+
visualElement.projection = node;
|
|
10685
|
+
node.setOptions({
|
|
10686
|
+
...readNodeOptions(element, transition),
|
|
10687
|
+
visualElement,
|
|
10688
|
+
});
|
|
10689
|
+
node.mount(element);
|
|
10690
|
+
layoutNodes.set(element, node);
|
|
10691
|
+
}
|
|
10692
|
+
else {
|
|
10693
|
+
node.setOptions(readNodeOptions(element, transition));
|
|
10694
|
+
}
|
|
10695
|
+
node.isPresent = true;
|
|
10696
|
+
if (node.options.onExitComplete) {
|
|
10697
|
+
node.setOptions({ onExitComplete: undefined });
|
|
10698
|
+
}
|
|
10699
|
+
return node;
|
|
10700
|
+
}
|
|
10701
|
+
function sortDocumentOrder(elements) {
|
|
10702
|
+
return [...elements].sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
|
10703
|
+
}
|
|
10704
|
+
function dropNode(element, node) {
|
|
10705
|
+
node.setOptions({ onExitComplete: undefined });
|
|
10706
|
+
/**
|
|
10707
|
+
* Stop any lingering animation so it can't leak into future updates.
|
|
10708
|
+
* A follow node can share its currentAnimation with a surviving lead
|
|
10709
|
+
* (via resumingFrom), in which case it isn't ours to stop.
|
|
10710
|
+
*/
|
|
10711
|
+
const stack = node.getStack();
|
|
10712
|
+
if (!stack || node.isLead())
|
|
10713
|
+
node.currentAnimation?.stop();
|
|
10714
|
+
node.unmount();
|
|
10715
|
+
layoutNodes.delete(element);
|
|
10716
|
+
}
|
|
10717
|
+
function flushPendingBuilders() {
|
|
10718
|
+
const builders = pendingBuilders;
|
|
10719
|
+
pendingBuilders = undefined;
|
|
10720
|
+
flushPendingFrame();
|
|
10721
|
+
/**
|
|
10722
|
+
* Discover and mount every node across all builders before snapshotting
|
|
10723
|
+
* any of them. Mounting during an active update flags isLayoutDirty,
|
|
10724
|
+
* which would make that node's own willUpdate skip its snapshot.
|
|
10725
|
+
* Document order guarantees ancestors mount before descendants, even
|
|
10726
|
+
* when they're discovered by different builders.
|
|
10727
|
+
*/
|
|
10728
|
+
const targets = new Map();
|
|
10729
|
+
for (const builder of builders) {
|
|
10730
|
+
for (const element of builder.collectTargets()) {
|
|
10731
|
+
const owners = targets.get(element);
|
|
10732
|
+
owners ? owners.push(builder) : targets.set(element, [builder]);
|
|
10733
|
+
}
|
|
10734
|
+
}
|
|
10735
|
+
const union = new Map();
|
|
10736
|
+
for (const element of sortDocumentOrder(targets.keys())) {
|
|
10737
|
+
const owners = targets.get(element);
|
|
10738
|
+
const node = prepareNode(element, owners[owners.length - 1].transitionFor(element));
|
|
10739
|
+
for (const owner of owners)
|
|
10740
|
+
owner.adopt(element, node);
|
|
10741
|
+
union.set(element, node);
|
|
10742
|
+
}
|
|
10743
|
+
union.forEach((node) => {
|
|
10744
|
+
node.isLayoutDirty = false;
|
|
10745
|
+
node.willUpdate();
|
|
10746
|
+
});
|
|
10747
|
+
const updatePromises = [];
|
|
10748
|
+
for (const builder of builders) {
|
|
10749
|
+
const result = builder.runUpdate();
|
|
10750
|
+
if (result)
|
|
10751
|
+
updatePromises.push(result);
|
|
10752
|
+
}
|
|
10753
|
+
const commit = () => {
|
|
10754
|
+
/**
|
|
10755
|
+
* Process all additions before any removals so that, even across
|
|
10756
|
+
* builders, a removed member knows whether a replacement with the
|
|
10757
|
+
* same layoutId was added in this commit.
|
|
10758
|
+
*/
|
|
10759
|
+
const newMemberIds = new Set();
|
|
10760
|
+
for (const builder of builders) {
|
|
10761
|
+
builder.reconcileAdditions(newMemberIds);
|
|
10762
|
+
}
|
|
10763
|
+
for (const builder of builders) {
|
|
10764
|
+
builder.reconcileRemovals(newMemberIds);
|
|
10765
|
+
}
|
|
10766
|
+
let root;
|
|
10767
|
+
union.forEach((node) => (root || (root = node.root)));
|
|
10768
|
+
for (const builder of builders)
|
|
10769
|
+
root || (root = builder.getRoot());
|
|
10770
|
+
root?.didUpdate();
|
|
10771
|
+
/**
|
|
10772
|
+
* The root flushes the update on a microtask, synchronously
|
|
10773
|
+
* processing the frame that creates the layout animations. Collect
|
|
10774
|
+
* them in a later microtask step of the same pass.
|
|
10775
|
+
*/
|
|
10776
|
+
microtask.render(() => {
|
|
10777
|
+
for (const builder of builders)
|
|
10778
|
+
builder.finalize();
|
|
10779
|
+
});
|
|
10120
10780
|
};
|
|
10781
|
+
updatePromises.length ? Promise.all(updatePromises).then(commit) : commit();
|
|
10121
10782
|
}
|
|
10122
10783
|
class LayoutAnimationBuilder {
|
|
10123
10784
|
constructor(scope, updateDom, defaultOptions) {
|
|
10124
|
-
this.sharedTransitions = new Map();
|
|
10125
|
-
this.notifyReady = noop;
|
|
10126
|
-
this.rejectReady = noop;
|
|
10127
10785
|
this.scope = scope;
|
|
10128
10786
|
this.updateDom = updateDom;
|
|
10129
10787
|
this.defaultOptions = defaultOptions;
|
|
10788
|
+
this.sharedTransitions = new Map();
|
|
10789
|
+
this.notifyReady = () => { };
|
|
10790
|
+
this.rejectReady = () => { };
|
|
10791
|
+
this.tracked = new Map();
|
|
10792
|
+
this.restorePoints = new Map();
|
|
10130
10793
|
this.readyPromise = new Promise((resolve, reject) => {
|
|
10131
10794
|
this.notifyReady = resolve;
|
|
10132
10795
|
this.rejectReady = reject;
|
|
10133
10796
|
});
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10797
|
+
if (!pendingBuilders) {
|
|
10798
|
+
pendingBuilders = [];
|
|
10799
|
+
queueMicrotask(flushPendingBuilders);
|
|
10800
|
+
}
|
|
10801
|
+
pendingBuilders.push(this);
|
|
10137
10802
|
}
|
|
10138
10803
|
shared(id, transition) {
|
|
10139
10804
|
this.sharedTransitions.set(id, transition);
|
|
@@ -10142,114 +10807,107 @@
|
|
|
10142
10807
|
then(resolve, reject) {
|
|
10143
10808
|
return this.readyPromise.then(resolve, reject);
|
|
10144
10809
|
}
|
|
10145
|
-
|
|
10146
|
-
const
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
}
|
|
10156
|
-
else if (projection.snapshot) {
|
|
10157
|
-
projection.snapshot = undefined;
|
|
10158
|
-
}
|
|
10159
|
-
}
|
|
10160
|
-
else if (projection.snapshot &&
|
|
10161
|
-
(projection.currentAnimation || projection.isProjecting())) {
|
|
10162
|
-
projection.snapshot = undefined;
|
|
10163
|
-
}
|
|
10164
|
-
projection.isPresent = true;
|
|
10165
|
-
projection.willUpdate();
|
|
10810
|
+
transitionFor(element) {
|
|
10811
|
+
const layoutId = element.getAttribute("data-layout-id");
|
|
10812
|
+
return ((layoutId && this.sharedTransitions.get(layoutId)) ||
|
|
10813
|
+
this.defaultOptions);
|
|
10814
|
+
}
|
|
10815
|
+
adopt(element, node) {
|
|
10816
|
+
this.tracked.set(element, node);
|
|
10817
|
+
this.restorePoints.set(element, {
|
|
10818
|
+
parent: element.parentElement,
|
|
10819
|
+
next: element.nextSibling,
|
|
10166
10820
|
});
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
const
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
return;
|
|
10179
|
-
const currentTransform = instance.style.transform;
|
|
10180
|
-
const resumeFromTransform = resumeFromInstance.style.transform;
|
|
10181
|
-
if (currentTransform &&
|
|
10182
|
-
resumeFromTransform &&
|
|
10183
|
-
currentTransform === resumeFromTransform) {
|
|
10184
|
-
instance.style.transform = "";
|
|
10185
|
-
instance.style.transformOrigin = "";
|
|
10821
|
+
}
|
|
10822
|
+
collectTargets() {
|
|
10823
|
+
return collectLayoutElements(this.scope);
|
|
10824
|
+
}
|
|
10825
|
+
runUpdate() {
|
|
10826
|
+
try {
|
|
10827
|
+
const result = this.updateDom();
|
|
10828
|
+
if (result && typeof result.then === "function") {
|
|
10829
|
+
return result.then(undefined, (error) => {
|
|
10830
|
+
this.updateError = error;
|
|
10831
|
+
});
|
|
10186
10832
|
}
|
|
10187
|
-
}
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
}
|
|
10191
|
-
|
|
10192
|
-
root?.didUpdate();
|
|
10193
|
-
await new Promise((resolve) => {
|
|
10194
|
-
frame.postRender(() => resolve());
|
|
10195
|
-
});
|
|
10196
|
-
const animations = collectAnimations(afterRecords);
|
|
10197
|
-
const animation = new GroupAnimation(animations);
|
|
10198
|
-
return animation;
|
|
10833
|
+
}
|
|
10834
|
+
catch (error) {
|
|
10835
|
+
this.updateError = error;
|
|
10836
|
+
}
|
|
10837
|
+
return undefined;
|
|
10199
10838
|
}
|
|
10200
|
-
|
|
10201
|
-
const
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
const
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
? this.sharedTransitions.get(layoutId)
|
|
10208
|
-
: undefined;
|
|
10209
|
-
const transition = override || this.defaultOptions;
|
|
10210
|
-
const record = getOrCreateRecord(element, parentRecord?.projection, {
|
|
10211
|
-
layout,
|
|
10212
|
-
layoutId,
|
|
10213
|
-
animationType: typeof layout === "string" ? layout : "both",
|
|
10214
|
-
transition: transition,
|
|
10215
|
-
});
|
|
10216
|
-
recordMap.set(element, record);
|
|
10217
|
-
records.push(record);
|
|
10839
|
+
reconcileAdditions(newMemberIds) {
|
|
10840
|
+
for (const element of collectLayoutElements(this.scope)) {
|
|
10841
|
+
if (this.tracked.has(element))
|
|
10842
|
+
continue;
|
|
10843
|
+
const node = prepareNode(element, this.transitionFor(element));
|
|
10844
|
+
this.adopt(element, node);
|
|
10845
|
+
node.options.layoutId && newMemberIds.add(node.options.layoutId);
|
|
10218
10846
|
}
|
|
10219
|
-
return records;
|
|
10220
10847
|
}
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
if (afterElementsSet.has(record.element))
|
|
10848
|
+
reconcileRemovals(newMemberIds) {
|
|
10849
|
+
this.tracked.forEach((node, element) => {
|
|
10850
|
+
if (element.isConnected)
|
|
10225
10851
|
return;
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10852
|
+
const restore = this.restorePoints.get(element);
|
|
10853
|
+
this.restorePoints.delete(element);
|
|
10854
|
+
const { layoutId } = node.options;
|
|
10855
|
+
const stack = node.getStack();
|
|
10856
|
+
const hasSurvivor = stack &&
|
|
10857
|
+
stack.members.some((member) => member !== node &&
|
|
10858
|
+
member.instance
|
|
10859
|
+
?.isConnected);
|
|
10860
|
+
/**
|
|
10861
|
+
* A removed lead with a surviving stack member - and no
|
|
10862
|
+
* replacement member added this commit - runs an exit
|
|
10863
|
+
* crossfade: restore the element to its old position in the
|
|
10864
|
+
* DOM, relegate it and let the survivor take over. It's
|
|
10865
|
+
* removed again once the animation completes.
|
|
10866
|
+
*/
|
|
10867
|
+
if (layoutId &&
|
|
10868
|
+
node.isLead() &&
|
|
10869
|
+
hasSurvivor &&
|
|
10870
|
+
!newMemberIds.has(layoutId)) {
|
|
10871
|
+
if (restore && restore.parent.isConnected) {
|
|
10872
|
+
restore.parent.insertBefore(element, restore.next && restore.next.parentNode === restore.parent
|
|
10873
|
+
? restore.next
|
|
10874
|
+
: null);
|
|
10875
|
+
node.isPresent = false;
|
|
10876
|
+
node.setOptions({
|
|
10877
|
+
onExitComplete: () => {
|
|
10878
|
+
element.remove();
|
|
10879
|
+
dropNode(element, node);
|
|
10880
|
+
},
|
|
10881
|
+
});
|
|
10882
|
+
if (node.relegate())
|
|
10883
|
+
return;
|
|
10884
|
+
element.remove();
|
|
10885
|
+
}
|
|
10231
10886
|
}
|
|
10232
|
-
|
|
10233
|
-
|
|
10887
|
+
dropNode(element, node);
|
|
10888
|
+
this.tracked.delete(element);
|
|
10234
10889
|
});
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10890
|
+
}
|
|
10891
|
+
getRoot() {
|
|
10892
|
+
let root;
|
|
10893
|
+
this.tracked.forEach((node) => (root || (root = node.root)));
|
|
10894
|
+
return root;
|
|
10895
|
+
}
|
|
10896
|
+
finalize() {
|
|
10897
|
+
if (this.updateError) {
|
|
10898
|
+
this.rejectReady(this.updateError);
|
|
10899
|
+
return;
|
|
10900
|
+
}
|
|
10901
|
+
const animations = new Set();
|
|
10902
|
+
this.tracked.forEach((node) => {
|
|
10903
|
+
if (node.instance && node.currentAnimation) {
|
|
10904
|
+
animations.add(node.currentAnimation);
|
|
10247
10905
|
}
|
|
10248
10906
|
});
|
|
10907
|
+
this.notifyReady(new GroupAnimation([...animations]));
|
|
10249
10908
|
}
|
|
10250
10909
|
}
|
|
10251
10910
|
function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
|
|
10252
|
-
// animateLayout(updateDom)
|
|
10253
10911
|
if (typeof scopeOrUpdateDom === "function") {
|
|
10254
10912
|
return {
|
|
10255
10913
|
scope: document,
|
|
@@ -10257,107 +10915,15 @@
|
|
|
10257
10915
|
defaultOptions: updateDomOrOptions,
|
|
10258
10916
|
};
|
|
10259
10917
|
}
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10918
|
+
const scope = scopeOrUpdateDom instanceof Document
|
|
10919
|
+
? scopeOrUpdateDom
|
|
10920
|
+
: resolveElements(scopeOrUpdateDom)[0] ?? document;
|
|
10263
10921
|
return {
|
|
10264
10922
|
scope,
|
|
10265
10923
|
updateDom: updateDomOrOptions,
|
|
10266
10924
|
defaultOptions: options,
|
|
10267
10925
|
};
|
|
10268
10926
|
}
|
|
10269
|
-
function collectLayoutElements(scope) {
|
|
10270
|
-
const elements = Array.from(scope.querySelectorAll(layoutSelector));
|
|
10271
|
-
if (scope instanceof Element && scope.matches(layoutSelector)) {
|
|
10272
|
-
if (!elements.includes(scope)) {
|
|
10273
|
-
elements.unshift(scope);
|
|
10274
|
-
}
|
|
10275
|
-
}
|
|
10276
|
-
return elements;
|
|
10277
|
-
}
|
|
10278
|
-
function readLayoutAttributes(element) {
|
|
10279
|
-
const layoutId = element.getAttribute("data-layout-id") || undefined;
|
|
10280
|
-
const rawLayout = element.getAttribute("data-layout");
|
|
10281
|
-
let layout;
|
|
10282
|
-
if (rawLayout === "" || rawLayout === "true") {
|
|
10283
|
-
layout = true;
|
|
10284
|
-
}
|
|
10285
|
-
else if (rawLayout) {
|
|
10286
|
-
layout = rawLayout;
|
|
10287
|
-
}
|
|
10288
|
-
return {
|
|
10289
|
-
layout,
|
|
10290
|
-
layoutId,
|
|
10291
|
-
};
|
|
10292
|
-
}
|
|
10293
|
-
function createVisualState() {
|
|
10294
|
-
return {
|
|
10295
|
-
latestValues: {},
|
|
10296
|
-
renderState: {
|
|
10297
|
-
transform: {},
|
|
10298
|
-
transformOrigin: {},
|
|
10299
|
-
style: {},
|
|
10300
|
-
vars: {},
|
|
10301
|
-
},
|
|
10302
|
-
};
|
|
10303
|
-
}
|
|
10304
|
-
function getOrCreateRecord(element, parentProjection, projectionOptions) {
|
|
10305
|
-
const existing = visualElementStore.get(element);
|
|
10306
|
-
const visualElement = existing ??
|
|
10307
|
-
new HTMLVisualElement({
|
|
10308
|
-
props: {},
|
|
10309
|
-
presenceContext: null,
|
|
10310
|
-
visualState: createVisualState(),
|
|
10311
|
-
}, { allowProjection: true });
|
|
10312
|
-
if (!existing || !visualElement.projection) {
|
|
10313
|
-
visualElement.projection = new HTMLProjectionNode(visualElement.latestValues, parentProjection);
|
|
10314
|
-
}
|
|
10315
|
-
visualElement.projection.setOptions({
|
|
10316
|
-
...projectionOptions,
|
|
10317
|
-
visualElement,
|
|
10318
|
-
});
|
|
10319
|
-
if (!visualElement.current) {
|
|
10320
|
-
visualElement.mount(element);
|
|
10321
|
-
}
|
|
10322
|
-
else if (!visualElement.projection.instance) {
|
|
10323
|
-
// Mount projection if VisualElement is already mounted but projection isn't
|
|
10324
|
-
// This happens when animate() was called before animateLayout()
|
|
10325
|
-
visualElement.projection.mount(element);
|
|
10326
|
-
}
|
|
10327
|
-
if (!existing) {
|
|
10328
|
-
visualElementStore.set(element, visualElement);
|
|
10329
|
-
}
|
|
10330
|
-
return {
|
|
10331
|
-
element,
|
|
10332
|
-
visualElement,
|
|
10333
|
-
projection: visualElement.projection,
|
|
10334
|
-
};
|
|
10335
|
-
}
|
|
10336
|
-
function findParentRecord(element, recordMap, scope) {
|
|
10337
|
-
let parent = element.parentElement;
|
|
10338
|
-
while (parent) {
|
|
10339
|
-
const record = recordMap.get(parent);
|
|
10340
|
-
if (record)
|
|
10341
|
-
return record;
|
|
10342
|
-
if (parent === scope)
|
|
10343
|
-
break;
|
|
10344
|
-
parent = parent.parentElement;
|
|
10345
|
-
}
|
|
10346
|
-
return undefined;
|
|
10347
|
-
}
|
|
10348
|
-
function getProjectionRoot(afterRecords, beforeRecords) {
|
|
10349
|
-
const record = afterRecords[0] || beforeRecords[0];
|
|
10350
|
-
return record?.projection.root;
|
|
10351
|
-
}
|
|
10352
|
-
function collectAnimations(afterRecords) {
|
|
10353
|
-
const animations = new Set();
|
|
10354
|
-
afterRecords.forEach((record) => {
|
|
10355
|
-
const animation = record.projection.currentAnimation;
|
|
10356
|
-
if (animation)
|
|
10357
|
-
animations.add(animation);
|
|
10358
|
-
});
|
|
10359
|
-
return Array.from(animations);
|
|
10360
|
-
}
|
|
10361
10927
|
|
|
10362
10928
|
/**
|
|
10363
10929
|
* @deprecated
|
|
@@ -10546,6 +11112,14 @@
|
|
|
10546
11112
|
const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, anchorY, root }) => {
|
|
10547
11113
|
const presenceChildren = useConstant(newChildrenMap);
|
|
10548
11114
|
const id = React$1.useId();
|
|
11115
|
+
// Written in a layout effect (not render) so discarded concurrent
|
|
11116
|
+
// renders can't leave the refs pointing at uncommitted state.
|
|
11117
|
+
const isPresentRef = React$1.useRef(isPresent);
|
|
11118
|
+
const onExitCompleteRef = React$1.useRef(onExitComplete);
|
|
11119
|
+
useIsomorphicLayoutEffect(() => {
|
|
11120
|
+
isPresentRef.current = isPresent;
|
|
11121
|
+
onExitCompleteRef.current = onExitComplete;
|
|
11122
|
+
});
|
|
10549
11123
|
let isReusedContext = true;
|
|
10550
11124
|
let context = React$1.useMemo(() => {
|
|
10551
11125
|
isReusedContext = false;
|
|
@@ -10564,7 +11138,12 @@
|
|
|
10564
11138
|
},
|
|
10565
11139
|
register: (childId) => {
|
|
10566
11140
|
presenceChildren.set(childId, false);
|
|
10567
|
-
return () =>
|
|
11141
|
+
return () => {
|
|
11142
|
+
presenceChildren.delete(childId);
|
|
11143
|
+
!isPresentRef.current &&
|
|
11144
|
+
!presenceChildren.size &&
|
|
11145
|
+
onExitCompleteRef.current?.();
|
|
11146
|
+
};
|
|
10568
11147
|
},
|
|
10569
11148
|
};
|
|
10570
11149
|
}, [isPresent, presenceChildren, onExitComplete]);
|
|
@@ -12041,7 +12620,11 @@
|
|
|
12041
12620
|
const { onSessionStart } = handlers;
|
|
12042
12621
|
onSessionStart &&
|
|
12043
12622
|
onSessionStart(event, getPanInfo(initialInfo, this.history));
|
|
12044
|
-
|
|
12623
|
+
// Listen in the capture phase so a descendant calling
|
|
12624
|
+
// stopPropagation() (e.g. in its own pointerup handler) can't
|
|
12625
|
+
// prevent the gesture from ending. See #2794.
|
|
12626
|
+
const eventOptions = { passive: true, capture: true };
|
|
12627
|
+
this.removeListeners = pipe(addPointerEvent(this.contextWindow, "pointermove", this.handlePointerMove, eventOptions), addPointerEvent(this.contextWindow, "pointerup", this.handlePointerUp, eventOptions), addPointerEvent(this.contextWindow, "pointercancel", this.handlePointerUp, eventOptions));
|
|
12045
12628
|
// Start scroll tracking if element provided
|
|
12046
12629
|
if (element) {
|
|
12047
12630
|
this.startScrollTracking(element);
|
|
@@ -12885,8 +13468,8 @@
|
|
|
12885
13468
|
class DragGesture extends Feature {
|
|
12886
13469
|
constructor(node) {
|
|
12887
13470
|
super(node);
|
|
12888
|
-
this.removeGroupControls = noop
|
|
12889
|
-
this.removeListeners = noop
|
|
13471
|
+
this.removeGroupControls = noop;
|
|
13472
|
+
this.removeListeners = noop;
|
|
12890
13473
|
this.controls = new VisualElementDragControls(node);
|
|
12891
13474
|
}
|
|
12892
13475
|
mount() {
|
|
@@ -12896,7 +13479,7 @@
|
|
|
12896
13479
|
if (dragControls) {
|
|
12897
13480
|
this.removeGroupControls = dragControls.subscribe(this.controls);
|
|
12898
13481
|
}
|
|
12899
|
-
this.removeListeners = this.controls.addListeners() || noop
|
|
13482
|
+
this.removeListeners = this.controls.addListeners() || noop;
|
|
12900
13483
|
}
|
|
12901
13484
|
update() {
|
|
12902
13485
|
const { dragControls } = this.node.getProps();
|
|
@@ -12935,7 +13518,7 @@
|
|
|
12935
13518
|
class PanGesture extends Feature {
|
|
12936
13519
|
constructor() {
|
|
12937
13520
|
super(...arguments);
|
|
12938
|
-
this.removePointerDownListener = noop
|
|
13521
|
+
this.removePointerDownListener = noop;
|
|
12939
13522
|
}
|
|
12940
13523
|
onPointerDown(pointerDownEvent) {
|
|
12941
13524
|
this.session = new PanSession(pointerDownEvent, this.createPanHandlers(), {
|
|
@@ -14722,7 +15305,7 @@
|
|
|
14722
15305
|
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
|
|
14723
15306
|
function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
|
|
14724
15307
|
if (!container)
|
|
14725
|
-
return noop
|
|
15308
|
+
return noop;
|
|
14726
15309
|
let containerHandlers = onScrollHandlers.get(container);
|
|
14727
15310
|
/**
|
|
14728
15311
|
* Get the onScroll handlers for this container.
|
|
@@ -15006,7 +15589,7 @@
|
|
|
15006
15589
|
|
|
15007
15590
|
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
|
|
15008
15591
|
if (!container)
|
|
15009
|
-
return noop
|
|
15592
|
+
return noop;
|
|
15010
15593
|
const optionsWithDefaults = { axis, container, ...options };
|
|
15011
15594
|
return typeof onScroll === "function"
|
|
15012
15595
|
? attachToFunction(onScroll, optionsWithDefaults)
|
|
@@ -16177,7 +16760,7 @@
|
|
|
16177
16760
|
};
|
|
16178
16761
|
appearComplete.set(id, false);
|
|
16179
16762
|
if (readyAnimation.ready) {
|
|
16180
|
-
readyAnimation.ready.then(startAnimation).catch(noop
|
|
16763
|
+
readyAnimation.ready.then(startAnimation).catch(noop);
|
|
16181
16764
|
}
|
|
16182
16765
|
else {
|
|
16183
16766
|
startAnimation();
|
|
@@ -16334,7 +16917,6 @@
|
|
|
16334
16917
|
exports.VisualElement = VisualElement;
|
|
16335
16918
|
exports.WillChangeMotionValue = WillChangeMotionValue;
|
|
16336
16919
|
exports.acceleratedValues = acceleratedValues;
|
|
16337
|
-
exports.activeAnimations = activeAnimations;
|
|
16338
16920
|
exports.addAttrValue = addAttrValue;
|
|
16339
16921
|
exports.addDomEvent = addDomEvent;
|
|
16340
16922
|
exports.addPointerEvent = addPointerEvent;
|
|
@@ -16549,7 +17131,7 @@
|
|
|
16549
17131
|
exports.motionValue = motionValue;
|
|
16550
17132
|
exports.moveItem = moveItem;
|
|
16551
17133
|
exports.nodeGroup = nodeGroup;
|
|
16552
|
-
exports.noop = noop
|
|
17134
|
+
exports.noop = noop;
|
|
16553
17135
|
exports.number = number;
|
|
16554
17136
|
exports.numberValueTypes = numberValueTypes;
|
|
16555
17137
|
exports.observeTimeline = observeTimeline;
|