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/react.js +35 -11
- package/dist/es/react.mjs +16 -1
- package/dist/motion.dev.js +1036 -471
- package/dist/motion.js +1 -1
- package/dist/react.d.ts +6 -1
- package/package.json +3 -3
package/dist/motion.dev.js
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/*#__NO_SIDE_EFFECTS__*/
|
|
82
|
-
const noop
|
|
82
|
+
const noop = (any) => any;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Pipe
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
217
217
|
// If this is a linear gradient, return linear easing
|
|
218
218
|
if (mX1 === mY1 && mX2 === mY2)
|
|
219
|
-
return noop
|
|
219
|
+
return noop;
|
|
220
220
|
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
221
221
|
// If animation is at start/end, return t without easing
|
|
222
222
|
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
@@ -277,7 +277,7 @@
|
|
|
277
277
|
const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
|
|
278
278
|
|
|
279
279
|
const easingLookup = {
|
|
280
|
-
linear: noop
|
|
280
|
+
linear: noop,
|
|
281
281
|
easeIn,
|
|
282
282
|
easeInOut,
|
|
283
283
|
easeOut,
|
|
@@ -318,12 +318,7 @@
|
|
|
318
318
|
"postRender", // Compute
|
|
319
319
|
];
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
value: null,
|
|
323
|
-
addProjectionMetrics: null,
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
function createRenderStep(runNextFrame, stepName) {
|
|
321
|
+
function createRenderStep(runNextFrame) {
|
|
327
322
|
/**
|
|
328
323
|
* We create and reuse two queues, one to queue jobs for the current frame
|
|
329
324
|
* and one for the next. We reuse to avoid triggering GC after x frames.
|
|
@@ -345,13 +340,11 @@
|
|
|
345
340
|
timestamp: 0.0,
|
|
346
341
|
isProcessing: false,
|
|
347
342
|
};
|
|
348
|
-
let numCalls = 0;
|
|
349
343
|
function triggerCallback(callback) {
|
|
350
344
|
if (toKeepAlive.has(callback)) {
|
|
351
345
|
step.schedule(callback);
|
|
352
346
|
runNextFrame();
|
|
353
347
|
}
|
|
354
|
-
numCalls++;
|
|
355
348
|
callback(latestFrameData);
|
|
356
349
|
}
|
|
357
350
|
const step = {
|
|
@@ -394,13 +387,6 @@
|
|
|
394
387
|
nextFrame = prevFrame;
|
|
395
388
|
// Execute this frame
|
|
396
389
|
thisFrame.forEach(triggerCallback);
|
|
397
|
-
/**
|
|
398
|
-
* If we're recording stats then
|
|
399
|
-
*/
|
|
400
|
-
if (stepName && statsBuffer.value) {
|
|
401
|
-
statsBuffer.value.frameloop[stepName].push(numCalls);
|
|
402
|
-
}
|
|
403
|
-
numCalls = 0;
|
|
404
390
|
// Clear the frame so no callbacks remain. This is to avoid
|
|
405
391
|
// memory leaks should this render step not run for a while.
|
|
406
392
|
thisFrame.clear();
|
|
@@ -425,7 +411,7 @@
|
|
|
425
411
|
};
|
|
426
412
|
const flagRunNextFrame = () => (runNextFrame = true);
|
|
427
413
|
const steps = stepsOrder.reduce((acc, key) => {
|
|
428
|
-
acc[key] = createRenderStep(flagRunNextFrame
|
|
414
|
+
acc[key] = createRenderStep(flagRunNextFrame);
|
|
429
415
|
return acc;
|
|
430
416
|
}, {});
|
|
431
417
|
const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
|
|
@@ -481,7 +467,7 @@
|
|
|
481
467
|
return { schedule, cancel, state, steps };
|
|
482
468
|
}
|
|
483
469
|
|
|
484
|
-
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop
|
|
470
|
+
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
|
|
485
471
|
|
|
486
472
|
let now;
|
|
487
473
|
function clearTime() {
|
|
@@ -510,12 +496,6 @@
|
|
|
510
496
|
},
|
|
511
497
|
};
|
|
512
498
|
|
|
513
|
-
const activeAnimations = {
|
|
514
|
-
layout: 0,
|
|
515
|
-
mainThread: 0,
|
|
516
|
-
waapi: 0,
|
|
517
|
-
};
|
|
518
|
-
|
|
519
499
|
const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
|
|
520
500
|
const isCSSVariableName =
|
|
521
501
|
/*@__PURE__*/ checkStringStartsWith("--");
|
|
@@ -1503,7 +1483,7 @@
|
|
|
1503
1483
|
for (let i = 0; i < numMixers; i++) {
|
|
1504
1484
|
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
1505
1485
|
if (ease) {
|
|
1506
|
-
const easingFunction = Array.isArray(ease) ? ease[i] || noop
|
|
1486
|
+
const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
|
|
1507
1487
|
mixer = pipe(easingFunction, mixer);
|
|
1508
1488
|
}
|
|
1509
1489
|
mixers.push(mixer);
|
|
@@ -1717,7 +1697,6 @@
|
|
|
1717
1697
|
this.teardown();
|
|
1718
1698
|
this.options.onStop?.();
|
|
1719
1699
|
};
|
|
1720
|
-
activeAnimations.mainThread++;
|
|
1721
1700
|
this.options = options;
|
|
1722
1701
|
this.initAnimation();
|
|
1723
1702
|
this.play();
|
|
@@ -2022,7 +2001,6 @@
|
|
|
2022
2001
|
this.state = "idle";
|
|
2023
2002
|
this.stopDriver();
|
|
2024
2003
|
this.startTime = this.holdTime = null;
|
|
2025
|
-
activeAnimations.mainThread--;
|
|
2026
2004
|
}
|
|
2027
2005
|
stopDriver() {
|
|
2028
2006
|
if (!this.driver)
|
|
@@ -2434,9 +2412,6 @@
|
|
|
2434
2412
|
*/
|
|
2435
2413
|
if (Array.isArray(easing))
|
|
2436
2414
|
keyframeOptions.easing = easing;
|
|
2437
|
-
if (statsBuffer.value) {
|
|
2438
|
-
activeAnimations.waapi++;
|
|
2439
|
-
}
|
|
2440
2415
|
const options = {
|
|
2441
2416
|
delay,
|
|
2442
2417
|
duration,
|
|
@@ -2447,13 +2422,7 @@
|
|
|
2447
2422
|
};
|
|
2448
2423
|
if (pseudoElement)
|
|
2449
2424
|
options.pseudoElement = pseudoElement;
|
|
2450
|
-
|
|
2451
|
-
if (statsBuffer.value) {
|
|
2452
|
-
animation.finished.finally(() => {
|
|
2453
|
-
activeAnimations.waapi--;
|
|
2454
|
-
});
|
|
2455
|
-
}
|
|
2456
|
-
return animation;
|
|
2425
|
+
return element.animate(keyframeOptions, options);
|
|
2457
2426
|
}
|
|
2458
2427
|
|
|
2459
2428
|
function isGenerator(type) {
|
|
@@ -2632,7 +2601,7 @@
|
|
|
2632
2601
|
this.animation.rangeStart = rangeStart;
|
|
2633
2602
|
if (rangeEnd)
|
|
2634
2603
|
this.animation.rangeEnd = rangeEnd;
|
|
2635
|
-
return noop
|
|
2604
|
+
return noop;
|
|
2636
2605
|
}
|
|
2637
2606
|
else {
|
|
2638
2607
|
return observe(this);
|
|
@@ -2985,7 +2954,7 @@
|
|
|
2985
2954
|
}
|
|
2986
2955
|
animation.finished.then(() => {
|
|
2987
2956
|
this.notifyFinished();
|
|
2988
|
-
}).catch(noop
|
|
2957
|
+
}).catch(noop);
|
|
2989
2958
|
if (this.pendingTimeline) {
|
|
2990
2959
|
this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
|
|
2991
2960
|
this.pendingTimeline = undefined;
|
|
@@ -5163,9 +5132,16 @@
|
|
|
5163
5132
|
claimedPointerDownEvents.add(startEvent);
|
|
5164
5133
|
}
|
|
5165
5134
|
const onPressEnd = onPressStart(target, startEvent);
|
|
5135
|
+
/**
|
|
5136
|
+
* End listeners run in the capture phase so a descendant calling
|
|
5137
|
+
* stopPropagation() in its own pointerup handler can't prevent the
|
|
5138
|
+
* press gesture from ending. This also keeps the gesture-end
|
|
5139
|
+
* ordering consistent with the drag gesture. See #2794.
|
|
5140
|
+
*/
|
|
5141
|
+
const endEventOptions = { ...eventOptions, capture: true };
|
|
5166
5142
|
const onPointerEnd = (endEvent, success) => {
|
|
5167
|
-
window.removeEventListener("pointerup", onPointerUp);
|
|
5168
|
-
window.removeEventListener("pointercancel", onPointerCancel);
|
|
5143
|
+
window.removeEventListener("pointerup", onPointerUp, endEventOptions);
|
|
5144
|
+
window.removeEventListener("pointercancel", onPointerCancel, endEventOptions);
|
|
5169
5145
|
if (isPressing.has(target)) {
|
|
5170
5146
|
isPressing.delete(target);
|
|
5171
5147
|
}
|
|
@@ -5185,8 +5161,8 @@
|
|
|
5185
5161
|
const onPointerCancel = (cancelEvent) => {
|
|
5186
5162
|
onPointerEnd(cancelEvent, false);
|
|
5187
5163
|
};
|
|
5188
|
-
window.addEventListener("pointerup", onPointerUp,
|
|
5189
|
-
window.addEventListener("pointercancel", onPointerCancel,
|
|
5164
|
+
window.addEventListener("pointerup", onPointerUp, endEventOptions);
|
|
5165
|
+
window.addEventListener("pointercancel", onPointerCancel, endEventOptions);
|
|
5190
5166
|
};
|
|
5191
5167
|
targets.forEach((target) => {
|
|
5192
5168
|
const pointerDownTarget = options.useGlobalTarget ? window : target;
|
|
@@ -5325,116 +5301,34 @@
|
|
|
5325
5301
|
return () => cancelFrame(onFrame);
|
|
5326
5302
|
}
|
|
5327
5303
|
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
}
|
|
5334
|
-
value.frameloop.rate.push(frameData.delta);
|
|
5335
|
-
value.animations.mainThread.push(activeAnimations.mainThread);
|
|
5336
|
-
value.animations.waapi.push(activeAnimations.waapi);
|
|
5337
|
-
value.animations.layout.push(activeAnimations.layout);
|
|
5338
|
-
}
|
|
5339
|
-
function mean(values) {
|
|
5340
|
-
return values.reduce((acc, value) => acc + value, 0) / values.length;
|
|
5341
|
-
}
|
|
5342
|
-
function summarise(values, calcAverage = mean) {
|
|
5343
|
-
if (values.length === 0) {
|
|
5344
|
-
return {
|
|
5345
|
-
min: 0,
|
|
5346
|
-
max: 0,
|
|
5347
|
-
avg: 0,
|
|
5348
|
-
};
|
|
5349
|
-
}
|
|
5350
|
-
return {
|
|
5351
|
-
min: Math.min(...values),
|
|
5352
|
-
max: Math.max(...values),
|
|
5353
|
-
avg: calcAverage(values),
|
|
5354
|
-
};
|
|
5355
|
-
}
|
|
5356
|
-
const msToFps = (ms) => Math.round(1000 / ms);
|
|
5304
|
+
const statsBuffer = {
|
|
5305
|
+
value: null,
|
|
5306
|
+
addProjectionMetrics: null,
|
|
5307
|
+
};
|
|
5308
|
+
|
|
5357
5309
|
function clearStatsBuffer() {
|
|
5358
5310
|
statsBuffer.value = null;
|
|
5359
5311
|
statsBuffer.addProjectionMetrics = null;
|
|
5360
5312
|
}
|
|
5361
|
-
function reportStats() {
|
|
5362
|
-
const { value } = statsBuffer;
|
|
5363
|
-
if (!value) {
|
|
5364
|
-
throw new Error("Stats are not being measured");
|
|
5365
|
-
}
|
|
5366
|
-
clearStatsBuffer();
|
|
5367
|
-
cancelFrame(record);
|
|
5368
|
-
const summary = {
|
|
5369
|
-
frameloop: {
|
|
5370
|
-
setup: summarise(value.frameloop.setup),
|
|
5371
|
-
rate: summarise(value.frameloop.rate),
|
|
5372
|
-
read: summarise(value.frameloop.read),
|
|
5373
|
-
resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
|
|
5374
|
-
preUpdate: summarise(value.frameloop.preUpdate),
|
|
5375
|
-
update: summarise(value.frameloop.update),
|
|
5376
|
-
preRender: summarise(value.frameloop.preRender),
|
|
5377
|
-
render: summarise(value.frameloop.render),
|
|
5378
|
-
postRender: summarise(value.frameloop.postRender),
|
|
5379
|
-
},
|
|
5380
|
-
animations: {
|
|
5381
|
-
mainThread: summarise(value.animations.mainThread),
|
|
5382
|
-
waapi: summarise(value.animations.waapi),
|
|
5383
|
-
layout: summarise(value.animations.layout),
|
|
5384
|
-
},
|
|
5385
|
-
layoutProjection: {
|
|
5386
|
-
nodes: summarise(value.layoutProjection.nodes),
|
|
5387
|
-
calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
|
|
5388
|
-
calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
|
|
5389
|
-
},
|
|
5390
|
-
};
|
|
5391
|
-
/**
|
|
5392
|
-
* Convert the rate to FPS
|
|
5393
|
-
*/
|
|
5394
|
-
const { rate } = summary.frameloop;
|
|
5395
|
-
rate.min = msToFps(rate.min);
|
|
5396
|
-
rate.max = msToFps(rate.max);
|
|
5397
|
-
rate.avg = msToFps(rate.avg);
|
|
5398
|
-
[rate.min, rate.max] = [rate.max, rate.min];
|
|
5399
|
-
return summary;
|
|
5400
|
-
}
|
|
5401
5313
|
function recordStats() {
|
|
5402
5314
|
if (statsBuffer.value) {
|
|
5403
5315
|
clearStatsBuffer();
|
|
5404
5316
|
throw new Error("Stats are already being measured");
|
|
5405
5317
|
}
|
|
5406
|
-
const
|
|
5407
|
-
|
|
5408
|
-
frameloop: {
|
|
5409
|
-
setup: [],
|
|
5410
|
-
rate: [],
|
|
5411
|
-
read: [],
|
|
5412
|
-
resolveKeyframes: [],
|
|
5413
|
-
preUpdate: [],
|
|
5414
|
-
update: [],
|
|
5415
|
-
preRender: [],
|
|
5416
|
-
render: [],
|
|
5417
|
-
postRender: [],
|
|
5418
|
-
},
|
|
5419
|
-
animations: {
|
|
5420
|
-
mainThread: [],
|
|
5421
|
-
waapi: [],
|
|
5422
|
-
layout: [],
|
|
5423
|
-
},
|
|
5318
|
+
const buffer = statsBuffer;
|
|
5319
|
+
buffer.value = {
|
|
5424
5320
|
layoutProjection: {
|
|
5425
5321
|
nodes: [],
|
|
5426
5322
|
calculatedTargetDeltas: [],
|
|
5427
5323
|
calculatedProjections: [],
|
|
5428
5324
|
},
|
|
5429
5325
|
};
|
|
5430
|
-
|
|
5431
|
-
const { layoutProjection } =
|
|
5326
|
+
buffer.addProjectionMetrics = (metrics) => {
|
|
5327
|
+
const { layoutProjection } = buffer.value;
|
|
5432
5328
|
layoutProjection.nodes.push(metrics.nodes);
|
|
5433
5329
|
layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
|
|
5434
5330
|
layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
|
|
5435
5331
|
};
|
|
5436
|
-
frame.postRender(record, true);
|
|
5437
|
-
return reportStats;
|
|
5438
5332
|
}
|
|
5439
5333
|
|
|
5440
5334
|
/**
|
|
@@ -5720,14 +5614,126 @@
|
|
|
5720
5614
|
*/
|
|
5721
5615
|
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
5722
5616
|
|
|
5617
|
+
let nameCount = 0;
|
|
5618
|
+
/**
|
|
5619
|
+
* Generated names live in their own namespace so we can tell a name we own
|
|
5620
|
+
* (and must clean up) from an author-defined one - and so a stale generated
|
|
5621
|
+
* name left behind by an interrupted transition is re-owned, not mistaken for
|
|
5622
|
+
* the author's and leaked.
|
|
5623
|
+
*/
|
|
5624
|
+
const generatedName = () => `motion-view-${nameCount++}`;
|
|
5625
|
+
const isGeneratedName = (name) => name.startsWith("motion-view-");
|
|
5626
|
+
/**
|
|
5627
|
+
* Tag a captured element with a `view-transition-class` so authors can target
|
|
5628
|
+
* its generated layer from CSS (e.g. `::view-transition-group(.hero)`) without
|
|
5629
|
+
* the opaque generated name. Tracked in `classed` - separate from the generated
|
|
5630
|
+
* names in `assigned` - so cleanup removes the class without ever stripping an
|
|
5631
|
+
* author's own inline `view-transition-name`.
|
|
5632
|
+
*/
|
|
5633
|
+
function tagClass(element, className, classed) {
|
|
5634
|
+
if (!className)
|
|
5635
|
+
return;
|
|
5636
|
+
element.style?.setProperty("view-transition-class", className);
|
|
5637
|
+
classed.push(element);
|
|
5638
|
+
}
|
|
5639
|
+
/**
|
|
5640
|
+
* Resolve a selector/Element to elements and ensure each one carries a
|
|
5641
|
+
* `view-transition-name` we can target from script.
|
|
5642
|
+
*
|
|
5643
|
+
* Author-defined names are reused as-is. Elements that are unnamed (or use
|
|
5644
|
+
* the browser's `auto`/`match-element`, whose generated name is not exposed
|
|
5645
|
+
* to script) are given a unique generated name, set inline so it's captured,
|
|
5646
|
+
* and tracked in `assigned` for later cleanup.
|
|
5647
|
+
*
|
|
5648
|
+
* `registry` maps each Element to its name so the same element keeps its name
|
|
5649
|
+
* across both captures (before and after the update), which is what allows a
|
|
5650
|
+
* persistent element to animate as a single `group` layer.
|
|
5651
|
+
*/
|
|
5652
|
+
function assignViewTransitionNames(definition, registry, assigned, forcedNames, className, classed = []) {
|
|
5653
|
+
const elements = resolveElements(definition);
|
|
5654
|
+
/**
|
|
5655
|
+
* The new end of a paired morph: give each element the matching name from
|
|
5656
|
+
* the old end (by index) so the two share one layer and morph. If the new
|
|
5657
|
+
* end resolves to *more* elements than the old end named, the extras have no
|
|
5658
|
+
* counterpart - give them a fresh name so they animate as newcomers rather
|
|
5659
|
+
* than being silently left unnamed. We return the names actually assigned
|
|
5660
|
+
* (sized to the resolved elements), not the raw `forcedNames`, so stagger
|
|
5661
|
+
* totals and the layer set stay in step with what's on the page.
|
|
5662
|
+
*/
|
|
5663
|
+
if (forcedNames) {
|
|
5664
|
+
return elements.map((element, i) => {
|
|
5665
|
+
const existing = registry.get(element);
|
|
5666
|
+
if (existing)
|
|
5667
|
+
return existing;
|
|
5668
|
+
const name = forcedNames[i] ?? generatedName();
|
|
5669
|
+
element.style?.setProperty("view-transition-name", name);
|
|
5670
|
+
assigned.push(element);
|
|
5671
|
+
registry.set(element, name);
|
|
5672
|
+
tagClass(element, className, classed);
|
|
5673
|
+
return name;
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
5676
|
+
/**
|
|
5677
|
+
* Read every current name up front, before assigning any. Interleaving the
|
|
5678
|
+
* reads with the inline `setProperty` writes below would dirty styles
|
|
5679
|
+
* between reads and force a style recalc per element; batching the reads
|
|
5680
|
+
* keeps it to one. Elements already in the registry keep their name and
|
|
5681
|
+
* need no read.
|
|
5682
|
+
*/
|
|
5683
|
+
const currentNames = elements.map((element) => registry.has(element)
|
|
5684
|
+
? undefined
|
|
5685
|
+
: getComputedStyle(element).getPropertyValue("view-transition-name"));
|
|
5686
|
+
return elements.map((element, i) => {
|
|
5687
|
+
const existing = registry.get(element);
|
|
5688
|
+
if (existing)
|
|
5689
|
+
return existing;
|
|
5690
|
+
const current = currentNames[i];
|
|
5691
|
+
let name;
|
|
5692
|
+
if (current &&
|
|
5693
|
+
current !== "none" &&
|
|
5694
|
+
current !== "auto" &&
|
|
5695
|
+
current !== "match-element" &&
|
|
5696
|
+
!isGeneratedName(current)) {
|
|
5697
|
+
/**
|
|
5698
|
+
* The author already named this layer - target it as-is and leave
|
|
5699
|
+
* it to them to clean up. `auto`/`match-element` are overridden
|
|
5700
|
+
* because their generated name is not exposed to script, and a
|
|
5701
|
+
* stale `motion-view-*` (e.g. left by an interrupted transition) is
|
|
5702
|
+
* re-owned rather than adopted as an author name and leaked.
|
|
5703
|
+
*/
|
|
5704
|
+
name = current;
|
|
5705
|
+
}
|
|
5706
|
+
else {
|
|
5707
|
+
name = generatedName();
|
|
5708
|
+
element.style?.setProperty("view-transition-name", name);
|
|
5709
|
+
assigned.push(element);
|
|
5710
|
+
}
|
|
5711
|
+
registry.set(element, name);
|
|
5712
|
+
tagClass(element, className, classed);
|
|
5713
|
+
return name;
|
|
5714
|
+
});
|
|
5715
|
+
}
|
|
5716
|
+
/**
|
|
5717
|
+
* Remove the `view-transition-name`s we generated and the
|
|
5718
|
+
* `view-transition-class`es we applied. Author-defined names are never touched
|
|
5719
|
+
* (they're not in `assigned`). Safe to call more than once (e.g. on both a
|
|
5720
|
+
* finished and an interrupted transition).
|
|
5721
|
+
*/
|
|
5722
|
+
function releaseViewTransitionNames(assigned, classed = []) {
|
|
5723
|
+
for (const element of assigned) {
|
|
5724
|
+
element.style?.removeProperty("view-transition-name");
|
|
5725
|
+
}
|
|
5726
|
+
for (const element of classed) {
|
|
5727
|
+
element.style?.removeProperty("view-transition-class");
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5723
5731
|
function chooseLayerType(valueName) {
|
|
5724
5732
|
if (valueName === "layout")
|
|
5725
5733
|
return "group";
|
|
5726
5734
|
if (valueName === "enter" || valueName === "new")
|
|
5727
5735
|
return "new";
|
|
5728
|
-
|
|
5729
|
-
return "old";
|
|
5730
|
-
return "group";
|
|
5736
|
+
return "old";
|
|
5731
5737
|
}
|
|
5732
5738
|
|
|
5733
5739
|
let pendingRules = {};
|
|
@@ -5762,21 +5768,21 @@
|
|
|
5762
5768
|
};
|
|
5763
5769
|
|
|
5764
5770
|
function getViewAnimationLayerInfo(pseudoElement) {
|
|
5765
|
-
const match = pseudoElement.match(
|
|
5771
|
+
const match = pseudoElement.match(
|
|
5772
|
+
// `group-children` (nested transitions) before `group` so it wins.
|
|
5773
|
+
/::view-transition-(old|new|group-children|group|image-pair)\((.*?)\)/);
|
|
5766
5774
|
if (!match)
|
|
5767
5775
|
return null;
|
|
5768
5776
|
return { layer: match[2], type: match[1] };
|
|
5769
5777
|
}
|
|
5770
5778
|
|
|
5771
|
-
function filterViewAnimations(animation) {
|
|
5772
|
-
const { effect } = animation;
|
|
5773
|
-
if (!effect)
|
|
5774
|
-
return false;
|
|
5775
|
-
return (effect.target === document.documentElement &&
|
|
5776
|
-
effect.pseudoElement?.startsWith("::view-transition"));
|
|
5777
|
-
}
|
|
5778
5779
|
function getViewAnimations() {
|
|
5779
|
-
return document.getAnimations().filter(
|
|
5780
|
+
return document.getAnimations().filter((animation) => {
|
|
5781
|
+
const { effect } = animation;
|
|
5782
|
+
return (!!effect &&
|
|
5783
|
+
effect.target === document.documentElement &&
|
|
5784
|
+
effect.pseudoElement?.startsWith("::view-transition"));
|
|
5785
|
+
});
|
|
5780
5786
|
}
|
|
5781
5787
|
|
|
5782
5788
|
function hasTarget(target, targets) {
|
|
@@ -5784,87 +5790,370 @@
|
|
|
5784
5790
|
}
|
|
5785
5791
|
|
|
5786
5792
|
const definitionNames = ["layout", "enter", "exit", "new", "old"];
|
|
5793
|
+
/**
|
|
5794
|
+
* The `ViewTransitionTarget` buckets driving each generated layer type, in
|
|
5795
|
+
* priority order - the inverse of `chooseLayerType`. The new view is driven by
|
|
5796
|
+
* `new`/`enter`, the old by `old`/`exit`. `group-children`/`image-pair` have no
|
|
5797
|
+
* bucket; they follow the default layout timing.
|
|
5798
|
+
*/
|
|
5799
|
+
const typeBuckets = {
|
|
5800
|
+
group: ["layout"],
|
|
5801
|
+
new: ["new", "enter"],
|
|
5802
|
+
old: ["old", "exit"],
|
|
5803
|
+
};
|
|
5804
|
+
/**
|
|
5805
|
+
* Default "absent" origin for a single-value keyframe, by pseudo type, so e.g.
|
|
5806
|
+
* `enter({ scale: 1 })` grows in from 0.85 and `exit({ opacity: 0 })` fades
|
|
5807
|
+
* from 1. `enter` prefers the matching `exit` value over these (see below).
|
|
5808
|
+
*/
|
|
5809
|
+
const ORIGIN_DEFAULTS = {
|
|
5810
|
+
new: { opacity: 0, scale: 0.85 },
|
|
5811
|
+
old: { opacity: 1, scale: 1 },
|
|
5812
|
+
};
|
|
5813
|
+
const cornerProps = [
|
|
5814
|
+
"borderTopLeftRadius",
|
|
5815
|
+
"borderTopRightRadius",
|
|
5816
|
+
"borderBottomRightRadius",
|
|
5817
|
+
"borderBottomLeftRadius",
|
|
5818
|
+
];
|
|
5787
5819
|
function startViewAnimation(builder) {
|
|
5788
|
-
const { update, targets, options: defaultOptions } = builder;
|
|
5820
|
+
const { update, targets, resolveDefs, noCrop, pairs, classNames, options: defaultOptions, } = builder;
|
|
5789
5821
|
if (!document.startViewTransition) {
|
|
5790
|
-
|
|
5822
|
+
// An async IIFE (not `new Promise(async …)`) so a throwing/rejecting
|
|
5823
|
+
// update rejects this promise rather than leaving it unsettled.
|
|
5824
|
+
return (async () => {
|
|
5791
5825
|
await update();
|
|
5792
|
-
|
|
5793
|
-
});
|
|
5826
|
+
return new GroupAnimation([]);
|
|
5827
|
+
})();
|
|
5794
5828
|
}
|
|
5795
|
-
// TODO: Go over existing targets and ensure they all have ids
|
|
5796
5829
|
/**
|
|
5797
|
-
*
|
|
5798
|
-
*
|
|
5830
|
+
* Resolve any selector/Element targets to layer names, assigning a
|
|
5831
|
+
* `view-transition-name` to each element as we go. We run this before the
|
|
5832
|
+
* update (so the elements are captured in the old snapshot) and again
|
|
5833
|
+
* after it (for the new snapshot). An element present in both keeps the
|
|
5834
|
+
* same name and animates as a single `group` layer.
|
|
5835
|
+
*/
|
|
5836
|
+
const nameRegistry = new Map();
|
|
5837
|
+
const assigned = [];
|
|
5838
|
+
/**
|
|
5839
|
+
* Elements we tagged with a `view-transition-class` (via `.class()`),
|
|
5840
|
+
* tracked separately from `assigned` so cleanup removes the class without
|
|
5841
|
+
* ever stripping an author's own inline `view-transition-name`.
|
|
5799
5842
|
*/
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5843
|
+
const classed = [];
|
|
5844
|
+
const layerTargets = new Map();
|
|
5845
|
+
const croppedNames = new Set();
|
|
5846
|
+
/**
|
|
5847
|
+
* Each layer's stagger position (index + total) within its subject, per
|
|
5848
|
+
* snapshot. Resolving against the snapshot the layer belongs to keeps
|
|
5849
|
+
* stagger correct when `update()` replaces the matched elements, and lets
|
|
5850
|
+
* us skip a layer that's absent from a snapshot (e.g. an exited element
|
|
5851
|
+
* has no `new` pseudo-element).
|
|
5852
|
+
*/
|
|
5853
|
+
const layerStagger = new Map();
|
|
5854
|
+
/**
|
|
5855
|
+
* Names allocated for a paired subject in the old snapshot, replayed onto
|
|
5856
|
+
* its new-snapshot target so both ends share a layer and morph.
|
|
5857
|
+
*/
|
|
5858
|
+
const pairNames = new Map();
|
|
5859
|
+
/**
|
|
5860
|
+
* The old (`from`) elements of each paired subject, so their names can be
|
|
5861
|
+
* transferred off before the new (`to`) elements inherit them.
|
|
5862
|
+
*/
|
|
5863
|
+
const pairFrom = new Map();
|
|
5864
|
+
const resolveLayers = (phase) => {
|
|
5865
|
+
targets.forEach((target, definition) => {
|
|
5866
|
+
const className = classNames.get(definition);
|
|
5867
|
+
let names;
|
|
5868
|
+
if (definition === "root" || !resolveDefs.has(definition)) {
|
|
5869
|
+
names = [definition];
|
|
5870
|
+
}
|
|
5871
|
+
else if (pairs.has(definition)) {
|
|
5872
|
+
/**
|
|
5873
|
+
* Paired morph: name the old target in the old snapshot, then
|
|
5874
|
+
* force the same name(s) onto the new target in the new one, so
|
|
5875
|
+
* two different elements morph as a single layer.
|
|
5876
|
+
*/
|
|
5877
|
+
if (phase === "old") {
|
|
5878
|
+
pairFrom.set(definition, resolveElements(definition));
|
|
5879
|
+
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed);
|
|
5880
|
+
pairNames.set(definition, names);
|
|
5881
|
+
}
|
|
5882
|
+
else {
|
|
5883
|
+
/**
|
|
5884
|
+
* Transfer the name(s) off the `from` elements before the
|
|
5885
|
+
* `to` elements inherit them. A `from` that survives into
|
|
5886
|
+
* the new snapshot (e.g. hidden with `visibility: hidden`
|
|
5887
|
+
* rather than removed) would otherwise keep the name and
|
|
5888
|
+
* collide - "duplicate view-transition-name".
|
|
5889
|
+
*/
|
|
5890
|
+
for (const el of pairFrom.get(definition) ?? []) {
|
|
5891
|
+
el.style?.removeProperty("view-transition-name");
|
|
5892
|
+
/**
|
|
5893
|
+
* Drop the old end from the registry too, so the new
|
|
5894
|
+
* end alone supplies this name's `new` crop radii - we
|
|
5895
|
+
* neither re-measure nor get ordered by a stale element.
|
|
5896
|
+
*/
|
|
5897
|
+
nameRegistry.delete(el);
|
|
5898
|
+
}
|
|
5899
|
+
names = assignViewTransitionNames(pairs.get(definition), nameRegistry, assigned, pairNames.get(definition), className, classed);
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
else {
|
|
5903
|
+
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed);
|
|
5904
|
+
}
|
|
5905
|
+
const cropped = definition !== "root" && !noCrop.has(definition);
|
|
5906
|
+
names.forEach((name, index) => {
|
|
5907
|
+
/**
|
|
5908
|
+
* If two subjects resolve to the same element, merge their
|
|
5909
|
+
* definitions so neither subject's animations are dropped.
|
|
5910
|
+
*/
|
|
5911
|
+
const existing = layerTargets.get(name);
|
|
5912
|
+
layerTargets.set(name, existing && existing !== target
|
|
5913
|
+
? { ...existing, ...target }
|
|
5914
|
+
: target);
|
|
5915
|
+
if (cropped)
|
|
5916
|
+
croppedNames.add(name);
|
|
5917
|
+
const stagger = layerStagger.get(name) ?? {};
|
|
5918
|
+
stagger[phase] = [index, names.length];
|
|
5919
|
+
layerStagger.set(name, stagger);
|
|
5920
|
+
});
|
|
5803
5921
|
});
|
|
5804
|
-
}
|
|
5922
|
+
};
|
|
5923
|
+
/**
|
|
5924
|
+
* The stagger index/total for a layer, resolved against the snapshot it
|
|
5925
|
+
* belongs to. Returns index -1 when the layer is absent from that snapshot
|
|
5926
|
+
* so the caller can skip a pseudo-element that doesn't exist.
|
|
5927
|
+
*/
|
|
5928
|
+
const staggerPosition = (name, type) => {
|
|
5929
|
+
const stagger = layerStagger.get(name);
|
|
5930
|
+
const position = type === "old"
|
|
5931
|
+
? stagger?.old
|
|
5932
|
+
: type === "new"
|
|
5933
|
+
? stagger?.new
|
|
5934
|
+
: // group / group-children / image-pair persist across both.
|
|
5935
|
+
stagger?.new ?? stagger?.old;
|
|
5936
|
+
return position ?? [-1, 1];
|
|
5937
|
+
};
|
|
5938
|
+
/**
|
|
5939
|
+
* Merge default + per-layer transition options for a generated layer and
|
|
5940
|
+
* resolve any stagger/delay function against this element's position. Used
|
|
5941
|
+
* by both the morph-retiming and crop corner-radius passes.
|
|
5942
|
+
*/
|
|
5943
|
+
const resolveLayerTransition = (target, type, transitionName, index, total) => {
|
|
5944
|
+
const transition = mergeTransition(getValueTransition$1(defaultOptions, transitionName), getValueTransition$1((layerOptions(target, type) ?? {}), transitionName));
|
|
5945
|
+
if (typeof transition.delay === "function") {
|
|
5946
|
+
transition.delay = transition.delay(index, total);
|
|
5947
|
+
}
|
|
5948
|
+
return transition;
|
|
5949
|
+
};
|
|
5950
|
+
/**
|
|
5951
|
+
* Measured corner radii per cropped layer, so the clip can animate each
|
|
5952
|
+
* corner between the old and new elements. Per-corner (rather than the
|
|
5953
|
+
* shorthand) so mismatched/individual radii interpolate cleanly.
|
|
5954
|
+
*/
|
|
5955
|
+
const cropRadii = new Map();
|
|
5956
|
+
const recordRadii = (style, name, phase) => {
|
|
5957
|
+
const corners = {};
|
|
5958
|
+
for (const corner of cornerProps)
|
|
5959
|
+
corners[corner] = style[corner];
|
|
5960
|
+
const entry = cropRadii.get(name) ?? {};
|
|
5961
|
+
entry[phase] = corners;
|
|
5962
|
+
cropRadii.set(name, entry);
|
|
5963
|
+
};
|
|
5805
5964
|
/**
|
|
5806
|
-
*
|
|
5807
|
-
*
|
|
5808
|
-
*
|
|
5965
|
+
* Cropped layers all come from `.add()`, so their elements are in the
|
|
5966
|
+
* registry - read each one's corner radii directly. For a paired morph both
|
|
5967
|
+
* ends share a name; the new-snapshot element is registered last, so it
|
|
5968
|
+
* wins the `new` reading (and the old end the `old` reading).
|
|
5969
|
+
*/
|
|
5970
|
+
const measureCrop = (phase) => {
|
|
5971
|
+
if (!croppedNames.size)
|
|
5972
|
+
return;
|
|
5973
|
+
nameRegistry.forEach((name, element) => {
|
|
5974
|
+
if (croppedNames.has(name)) {
|
|
5975
|
+
recordRadii(getComputedStyle(element), name, phase);
|
|
5976
|
+
}
|
|
5977
|
+
});
|
|
5978
|
+
};
|
|
5979
|
+
/**
|
|
5980
|
+
* Write the persistent view-transition CSS: suppress root capture when the
|
|
5981
|
+
* root has no animations of its own; force linear timing (baked into the
|
|
5982
|
+
* keyframes, so we can retime later via updateTiming); and clip +
|
|
5983
|
+
* object-fit: cover every cropped morph (the UA default overflows on
|
|
5984
|
+
* aspect-ratio change), with an animated border-radius added below.
|
|
5809
5985
|
*
|
|
5810
|
-
*
|
|
5986
|
+
* `css.commit` replaces rather than appends, so we re-set the full rule set
|
|
5987
|
+
* each call - the second call (in the update callback) then picks up cropped
|
|
5988
|
+
* layers that only exist in the new snapshot.
|
|
5811
5989
|
*/
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5990
|
+
const commitViewCSS = () => {
|
|
5991
|
+
if (!hasTarget("root", targets)) {
|
|
5992
|
+
css.set(":root", { "view-transition-name": "none" });
|
|
5993
|
+
}
|
|
5994
|
+
css.set("::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*)", { "animation-timing-function": "linear !important" });
|
|
5995
|
+
croppedNames.forEach((name) => {
|
|
5996
|
+
css.set(`::view-transition-group(${name})`, { overflow: "clip" });
|
|
5997
|
+
css.set(`::view-transition-old(${name}), ::view-transition-new(${name})`, { width: "100%", height: "100%", "object-fit": "cover" });
|
|
5998
|
+
});
|
|
5999
|
+
css.commit(); // Write
|
|
6000
|
+
};
|
|
6001
|
+
const cleanup = () => {
|
|
6002
|
+
releaseViewTransitionNames(assigned, classed);
|
|
5819
6003
|
css.remove(); // Write
|
|
5820
|
-
}
|
|
5821
|
-
|
|
5822
|
-
|
|
6004
|
+
};
|
|
6005
|
+
const callback = async () => {
|
|
6006
|
+
await update();
|
|
6007
|
+
/**
|
|
6008
|
+
* Re-resolve so elements created by the update are named for the new
|
|
6009
|
+
* snapshot, then measure the cropped layers' new border-radius.
|
|
6010
|
+
*/
|
|
6011
|
+
const croppedBefore = croppedNames.size;
|
|
6012
|
+
resolveLayers("new");
|
|
6013
|
+
measureCrop("new");
|
|
6014
|
+
/**
|
|
6015
|
+
* Re-commit the crop CSS only if the new snapshot introduced cropped
|
|
6016
|
+
* layers, so a layer that exists only in the new snapshot is clipped
|
|
6017
|
+
* too - without forcing a redundant style write on the common path.
|
|
6018
|
+
*/
|
|
6019
|
+
if (croppedNames.size > croppedBefore)
|
|
6020
|
+
commitViewCSS();
|
|
6021
|
+
};
|
|
6022
|
+
let transition;
|
|
6023
|
+
try {
|
|
6024
|
+
resolveLayers("old");
|
|
6025
|
+
measureCrop("old");
|
|
6026
|
+
commitViewCSS();
|
|
6027
|
+
transition = document.startViewTransition(callback);
|
|
6028
|
+
}
|
|
6029
|
+
catch (error) {
|
|
6030
|
+
/**
|
|
6031
|
+
* The prelude writes inline names before the transition exists. If it
|
|
6032
|
+
* throws (e.g. startViewTransition rejects in a bad UA state), release
|
|
6033
|
+
* them so we neither leak DOM state nor stall the queue on a promise
|
|
6034
|
+
* that never settles - hand back a rejection it can advance past.
|
|
6035
|
+
*/
|
|
6036
|
+
cleanup();
|
|
6037
|
+
return Promise.reject(error);
|
|
6038
|
+
}
|
|
6039
|
+
transition.finished.finally(cleanup);
|
|
6040
|
+
return new Promise((resolve, reject) => {
|
|
6041
|
+
transition.ready
|
|
6042
|
+
.then(() => {
|
|
5823
6043
|
const generatedViewAnimations = getViewAnimations();
|
|
5824
6044
|
const animations = [];
|
|
5825
6045
|
/**
|
|
5826
6046
|
* Create animations for each of our explicitly-defined subjects.
|
|
6047
|
+
* `opacityAnimated` additionally tracks which `${name}:${type}`
|
|
6048
|
+
* we faded, so we can keep the UA `plus-lighter` blend only for a
|
|
6049
|
+
* genuine opacity crossfade (both sides fading) and drop it for a
|
|
6050
|
+
* slide/transform, where additive compositing would flash bright.
|
|
5827
6051
|
*/
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
6052
|
+
const explicitlyAnimated = new Set();
|
|
6053
|
+
const opacityAnimated = new Set();
|
|
6054
|
+
layerTargets.forEach((target, name) => {
|
|
6055
|
+
const stagger = layerStagger.get(name);
|
|
6056
|
+
/**
|
|
6057
|
+
* Presence: `enter` only fires for a pure newcomer (a new
|
|
6058
|
+
* view with no old), `exit` only for a pure leaver. A
|
|
6059
|
+
* survivor (both) gets neither - it just morphs.
|
|
6060
|
+
*/
|
|
6061
|
+
const enterApplies = !!stagger?.new && !stagger?.old;
|
|
6062
|
+
const exitApplies = !!stagger?.old && !stagger?.new;
|
|
5831
6063
|
for (const key of definitionNames) {
|
|
5832
|
-
if (!
|
|
6064
|
+
if (!target[key])
|
|
6065
|
+
continue;
|
|
6066
|
+
if (key === "enter" && !enterApplies)
|
|
6067
|
+
continue;
|
|
6068
|
+
if (key === "exit" && !exitApplies)
|
|
6069
|
+
continue;
|
|
6070
|
+
const type = chooseLayerType(key);
|
|
6071
|
+
const [index, total] = staggerPosition(name, type);
|
|
6072
|
+
// Skip a layer absent from its snapshot.
|
|
6073
|
+
if (index === -1)
|
|
5833
6074
|
continue;
|
|
5834
|
-
const { keyframes, options } =
|
|
6075
|
+
const { keyframes, options } = target[key];
|
|
5835
6076
|
for (let [valueName, valueKeyframes] of Object.entries(keyframes)) {
|
|
5836
|
-
|
|
6077
|
+
// Skip only missing values - `0` (e.g. opacity: 0)
|
|
6078
|
+
// is valid and must reach the from-value inference.
|
|
6079
|
+
if (valueKeyframes == null)
|
|
5837
6080
|
continue;
|
|
5838
|
-
const valueOptions = {
|
|
5839
|
-
...getValueTransition$1(defaultOptions, valueName),
|
|
5840
|
-
...getValueTransition$1(options, valueName),
|
|
5841
|
-
};
|
|
5842
|
-
const type = chooseLayerType(key);
|
|
5843
6081
|
/**
|
|
5844
|
-
*
|
|
5845
|
-
*
|
|
6082
|
+
* The view path hands keyframes straight to WAAPI,
|
|
6083
|
+
* so Motion's `x`/`y` shorthands (compiled to
|
|
6084
|
+
* `transform` only via the value pipeline) have no
|
|
6085
|
+
* effect. Warn and skip - use `transform`/`translate`.
|
|
6086
|
+
*/
|
|
6087
|
+
if (valueName === "x" || valueName === "y") {
|
|
6088
|
+
warnOnce(false, `animateView() animates view-transition layers with CSS properties; the "${valueName}" shorthand has no effect - use transform, e.g. { transform: "translateX(40px)" }.`);
|
|
6089
|
+
continue;
|
|
6090
|
+
}
|
|
6091
|
+
/**
|
|
6092
|
+
* enter/exit win over new/old on a shared property -
|
|
6093
|
+
* skip it here when the gated bucket also defines it.
|
|
5846
6094
|
*/
|
|
5847
|
-
if (
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
6095
|
+
if (key === "new" &&
|
|
6096
|
+
enterApplies &&
|
|
6097
|
+
target.enter?.keyframes[valueName] != null) {
|
|
6098
|
+
continue;
|
|
6099
|
+
}
|
|
6100
|
+
if (key === "old" &&
|
|
6101
|
+
exitApplies &&
|
|
6102
|
+
target.exit?.keyframes[valueName] != null) {
|
|
6103
|
+
continue;
|
|
6104
|
+
}
|
|
6105
|
+
const valueOptions = mergeTransition(getValueTransition$1(defaultOptions, valueName), getValueTransition$1(options, valueName));
|
|
6106
|
+
/**
|
|
6107
|
+
* Infer an origin for a single-value keyframe. An
|
|
6108
|
+
* `enter` mirrors the matching `exit` value (a
|
|
6109
|
+
* defined exit reverses into the enter for free);
|
|
6110
|
+
* otherwise the per-type default (opacity 0/1, scale
|
|
6111
|
+
* 0.85). No default -> left as-is (animates from the
|
|
6112
|
+
* live value).
|
|
6113
|
+
*
|
|
6114
|
+
* `new`/`old` fire for survivors too, where only the
|
|
6115
|
+
* opacity crossfade default applies - a transform
|
|
6116
|
+
* default like scale 0.85 would pop a persisting
|
|
6117
|
+
* element, so gate it on the layer actually
|
|
6118
|
+
* entering/leaving.
|
|
6119
|
+
*/
|
|
6120
|
+
if (!Array.isArray(valueKeyframes)) {
|
|
6121
|
+
const exitValue = key === "enter"
|
|
6122
|
+
? target.exit?.keyframes[valueName]
|
|
6123
|
+
: undefined;
|
|
6124
|
+
const allowDefault = valueName === "opacity" ||
|
|
6125
|
+
(type === "new" ? enterApplies : exitApplies);
|
|
6126
|
+
const from = exitValue != null
|
|
6127
|
+
? Array.isArray(exitValue)
|
|
6128
|
+
? exitValue[exitValue.length - 1]
|
|
6129
|
+
: exitValue
|
|
6130
|
+
: allowDefault
|
|
6131
|
+
? ORIGIN_DEFAULTS[type]?.[valueName]
|
|
6132
|
+
: undefined;
|
|
6133
|
+
if (from !== undefined) {
|
|
6134
|
+
valueKeyframes = [from, valueKeyframes];
|
|
6135
|
+
}
|
|
5851
6136
|
}
|
|
5852
6137
|
/**
|
|
5853
|
-
* Resolve stagger function if provided
|
|
6138
|
+
* Resolve stagger function if provided, per element
|
|
6139
|
+
* across this subject's resolved layers.
|
|
5854
6140
|
*/
|
|
5855
6141
|
if (typeof valueOptions.delay === "function") {
|
|
5856
|
-
valueOptions.delay = valueOptions.delay(
|
|
6142
|
+
valueOptions.delay = valueOptions.delay(index, total);
|
|
5857
6143
|
}
|
|
5858
6144
|
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
|
|
5859
6145
|
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
|
|
5860
|
-
|
|
6146
|
+
animations.push(new NativeAnimation({
|
|
5861
6147
|
...valueOptions,
|
|
5862
6148
|
element: document.documentElement,
|
|
5863
6149
|
name: valueName,
|
|
5864
|
-
pseudoElement: `::view-transition-${type}(${
|
|
6150
|
+
pseudoElement: `::view-transition-${type}(${name})`,
|
|
5865
6151
|
keyframes: valueKeyframes,
|
|
5866
|
-
});
|
|
5867
|
-
|
|
6152
|
+
}));
|
|
6153
|
+
explicitlyAnimated.add(`${name}:${type}`);
|
|
6154
|
+
if (valueName === "opacity") {
|
|
6155
|
+
opacityAnimated.add(`${name}:${type}`);
|
|
6156
|
+
}
|
|
5868
6157
|
}
|
|
5869
6158
|
}
|
|
5870
6159
|
});
|
|
@@ -5883,45 +6172,161 @@
|
|
|
5883
6172
|
const name = getViewAnimationLayerInfo(pseudoElement);
|
|
5884
6173
|
if (!name)
|
|
5885
6174
|
continue;
|
|
5886
|
-
const targetDefinition =
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
}
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
effect
|
|
5911
|
-
.getKeyframes()
|
|
5912
|
-
.some((keyframe) => keyframe.mixBlendMode)) {
|
|
5913
|
-
animations.push(new NativeAnimationWrapper(animation));
|
|
5914
|
-
}
|
|
5915
|
-
else {
|
|
5916
|
-
animation.cancel();
|
|
6175
|
+
const targetDefinition = layerTargets.get(name.layer);
|
|
6176
|
+
/**
|
|
6177
|
+
* We built our own animation for this layer, so drop the
|
|
6178
|
+
* browser-generated fade we're replacing. The UA
|
|
6179
|
+
* `plus-lighter` blend is a *separate* generated animation on
|
|
6180
|
+
* the same pseudo (it sets `mix-blend-mode` in its keyframes):
|
|
6181
|
+
* keep it *only* for a true opacity crossfade - both sides
|
|
6182
|
+
* fading - so a symmetric crossfade composites without
|
|
6183
|
+
* darkening, but a slide/transform (where both layers stay
|
|
6184
|
+
* opaque and overlap) doesn't flash bright from the addition.
|
|
6185
|
+
*/
|
|
6186
|
+
if (explicitlyAnimated.has(`${name.layer}:${name.type}`)) {
|
|
6187
|
+
const isCrossfade = opacityAnimated.has(`${name.layer}:new`) &&
|
|
6188
|
+
opacityAnimated.has(`${name.layer}:old`);
|
|
6189
|
+
if (isCrossfade &&
|
|
6190
|
+
effect
|
|
6191
|
+
.getKeyframes()
|
|
6192
|
+
.some((keyframe) => keyframe.mixBlendMode)) {
|
|
6193
|
+
animations.push(new NativeAnimationWrapper(animation));
|
|
6194
|
+
}
|
|
6195
|
+
else {
|
|
6196
|
+
animation.cancel();
|
|
6197
|
+
}
|
|
6198
|
+
continue;
|
|
5917
6199
|
}
|
|
6200
|
+
/**
|
|
6201
|
+
* Otherwise retime the browser-generated animation to
|
|
6202
|
+
* Motion's timing. This auto-enables the layout (group)
|
|
6203
|
+
* morph for any resolved/named target, and applies the
|
|
6204
|
+
* default timing to old/new layers we haven't explicitly
|
|
6205
|
+
* overridden.
|
|
6206
|
+
*
|
|
6207
|
+
* group + group-children both follow the layout timing so
|
|
6208
|
+
* the nesting container stays in sync with the morph.
|
|
6209
|
+
*/
|
|
6210
|
+
/**
|
|
6211
|
+
* A survivor's old + new are the two halves of one
|
|
6212
|
+
* `plus-lighter` crossfade. They must share identical timing
|
|
6213
|
+
* (so their opacities stay mirrored and sum to 1 - else the
|
|
6214
|
+
* additive blend flashes bright wherever both are partly
|
|
6215
|
+
* visible) and fade linearly (the bounce belongs on the
|
|
6216
|
+
* group's geometry, not the opacity). So time them as the
|
|
6217
|
+
* group, rather than via their own - potentially staggered,
|
|
6218
|
+
* or enter/exit-derived - old/new options.
|
|
6219
|
+
*/
|
|
6220
|
+
const stagger = layerStagger.get(name.layer);
|
|
6221
|
+
const isMorphCrossfade = (name.type === "old" || name.type === "new") &&
|
|
6222
|
+
!!stagger?.old &&
|
|
6223
|
+
!!stagger?.new;
|
|
6224
|
+
const timingType = name.type.startsWith("group") || isMorphCrossfade
|
|
6225
|
+
? "group"
|
|
6226
|
+
: name.type;
|
|
6227
|
+
const [index, total] = staggerPosition(name.layer, timingType);
|
|
6228
|
+
const transitionName = timingType === "group" ? "layout" : "";
|
|
6229
|
+
let animationTransition = resolveLayerTransition(targetDefinition, timingType, transitionName, index === -1 ? 0 : index, total);
|
|
6230
|
+
/**
|
|
6231
|
+
* The crossfade should resolve at the spring's *perceptual*
|
|
6232
|
+
* (visual) duration - the geometry can keep bouncing, but the
|
|
6233
|
+
* opacity shouldn't drag through the settle. So capture
|
|
6234
|
+
* `visualDuration` before `applyGeneratorOptions` replaces it
|
|
6235
|
+
* with the full overshoot duration, and use it for the fade.
|
|
6236
|
+
*/
|
|
6237
|
+
const visualDuration = animationTransition.visualDuration;
|
|
6238
|
+
animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
|
|
6239
|
+
animationTransition =
|
|
6240
|
+
applyGeneratorOptions(animationTransition);
|
|
6241
|
+
const duration = isMorphCrossfade && visualDuration !== undefined
|
|
6242
|
+
? secondsToMilliseconds(visualDuration)
|
|
6243
|
+
: animationTransition.duration;
|
|
6244
|
+
const easing = isMorphCrossfade
|
|
6245
|
+
? "linear"
|
|
6246
|
+
: mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration);
|
|
6247
|
+
effect.updateTiming({
|
|
6248
|
+
delay: secondsToMilliseconds(animationTransition.delay ?? 0),
|
|
6249
|
+
duration,
|
|
6250
|
+
easing,
|
|
6251
|
+
});
|
|
6252
|
+
animations.push(new NativeAnimationWrapper(animation));
|
|
5918
6253
|
}
|
|
6254
|
+
/**
|
|
6255
|
+
* Animate each cropped layer's clip corners between the old and
|
|
6256
|
+
* new elements, so a cropped morph keeps rounded corners
|
|
6257
|
+
* (handling individual per-corner radii).
|
|
6258
|
+
*/
|
|
6259
|
+
cropRadii.forEach((radii, name) => {
|
|
6260
|
+
if (!radii.old && !radii.new)
|
|
6261
|
+
return;
|
|
6262
|
+
const target = layerTargets.get(name);
|
|
6263
|
+
const [index, total] = staggerPosition(name, "group");
|
|
6264
|
+
const radiusOptions = resolveLayerTransition(target, "group", "layout", index === -1 ? 0 : index, total);
|
|
6265
|
+
radiusOptions.duration && (radiusOptions.duration = secondsToMilliseconds(radiusOptions.duration));
|
|
6266
|
+
radiusOptions.delay && (radiusOptions.delay = secondsToMilliseconds(radiusOptions.delay));
|
|
6267
|
+
for (const corner of cornerProps) {
|
|
6268
|
+
// `||` (not `??`) so an empty measurement (e.g. an
|
|
6269
|
+
// un-rendered element) falls back rather than producing
|
|
6270
|
+
// an invalid keyframe.
|
|
6271
|
+
const from = radii.old?.[corner] || radii.new?.[corner] || "0px";
|
|
6272
|
+
const to = radii.new?.[corner] || radii.old?.[corner] || "0px";
|
|
6273
|
+
// Skip square corners - nothing to round.
|
|
6274
|
+
if (parseFloat(from) === 0 && parseFloat(to) === 0) {
|
|
6275
|
+
continue;
|
|
6276
|
+
}
|
|
6277
|
+
animations.push(new NativeAnimation({
|
|
6278
|
+
...radiusOptions,
|
|
6279
|
+
element: document.documentElement,
|
|
6280
|
+
name: corner,
|
|
6281
|
+
pseudoElement: `::view-transition-group(${name})`,
|
|
6282
|
+
keyframes: [from, to],
|
|
6283
|
+
}));
|
|
6284
|
+
}
|
|
6285
|
+
});
|
|
5919
6286
|
resolve(new GroupAnimation(animations));
|
|
5920
|
-
})
|
|
6287
|
+
})
|
|
6288
|
+
.catch(() =>
|
|
6289
|
+
/**
|
|
6290
|
+
* `ready` rejects when the transition is skipped - no visual
|
|
6291
|
+
* change, or superseded by an interrupting transition. The DOM
|
|
6292
|
+
* update still applied, so settle with no animations rather than
|
|
6293
|
+
* surfacing it as an error to an awaiting caller. A genuine
|
|
6294
|
+
* failure in `update()` rejects `updateCallbackDone` (already
|
|
6295
|
+
* settled by now), so propagate that instead.
|
|
6296
|
+
*/
|
|
6297
|
+
transition.updateCallbackDone.then(() => resolve(new GroupAnimation([])), reject));
|
|
5921
6298
|
});
|
|
5922
6299
|
}
|
|
5923
|
-
|
|
5924
|
-
|
|
6300
|
+
/**
|
|
6301
|
+
* The options that should time a given generated layer type, so a retimed
|
|
6302
|
+
* group/old/new picks up any per-target transition the user provided. Checks
|
|
6303
|
+
* the type's buckets in priority order (e.g. `new` before `enter`).
|
|
6304
|
+
*/
|
|
6305
|
+
function layerOptions(target, type) {
|
|
6306
|
+
for (const bucket of typeBuckets[type] ?? []) {
|
|
6307
|
+
const options = target?.[bucket]?.options;
|
|
6308
|
+
if (options)
|
|
6309
|
+
return options;
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6312
|
+
/**
|
|
6313
|
+
* Merge a base transition (e.g. the default `options`) with a per-layer/value
|
|
6314
|
+
* override. An explicit `duration` on the override must win over an inherited
|
|
6315
|
+
* generator's own timing: a spring prefers `visualDuration`, and
|
|
6316
|
+
* `spring.applyToOptions` overwrites `duration` with the computed settle time -
|
|
6317
|
+
* so without this the override is silently discarded. Dropping the inherited
|
|
6318
|
+
* `type`/`visualDuration` makes the layer a plain tween of that duration, unless
|
|
6319
|
+
* it asked for its own generator `type`/`visualDuration`.
|
|
6320
|
+
*/
|
|
6321
|
+
function mergeTransition(base, override) {
|
|
6322
|
+
const merged = { ...base, ...override };
|
|
6323
|
+
if (override.duration !== undefined) {
|
|
6324
|
+
if (override.visualDuration === undefined)
|
|
6325
|
+
delete merged.visualDuration;
|
|
6326
|
+
if (override.type === undefined)
|
|
6327
|
+
delete merged.type;
|
|
6328
|
+
}
|
|
6329
|
+
return merged;
|
|
5925
6330
|
}
|
|
5926
6331
|
|
|
5927
6332
|
let builders = [];
|
|
@@ -5935,10 +6340,16 @@
|
|
|
5935
6340
|
function start(builder) {
|
|
5936
6341
|
removeItem(builders, builder);
|
|
5937
6342
|
current = builder;
|
|
5938
|
-
startViewAnimation(builder)
|
|
6343
|
+
startViewAnimation(builder)
|
|
6344
|
+
.then((animation) => {
|
|
5939
6345
|
builder.notifyReady(animation);
|
|
5940
|
-
animation.finished
|
|
5941
|
-
})
|
|
6346
|
+
return animation.finished;
|
|
6347
|
+
})
|
|
6348
|
+
// A genuinely failed transition (a throwing update) rejects the
|
|
6349
|
+
// builder; a skipped/interrupted one resolves with no animations (see
|
|
6350
|
+
// start.ts). Either way, advance the queue - else later transitions hang.
|
|
6351
|
+
.catch((error) => builder.notifyReject(error))
|
|
6352
|
+
.finally(next);
|
|
5942
6353
|
}
|
|
5943
6354
|
function processQueue() {
|
|
5944
6355
|
/**
|
|
@@ -5975,31 +6386,96 @@
|
|
|
5975
6386
|
constructor(update, options = {}) {
|
|
5976
6387
|
this.currentSubject = "root";
|
|
5977
6388
|
this.targets = new Map();
|
|
5978
|
-
|
|
5979
|
-
|
|
6389
|
+
/**
|
|
6390
|
+
* Definitions that must be resolved to elements (and assigned a
|
|
6391
|
+
* `view-transition-name`) rather than treated as pre-named layers.
|
|
6392
|
+
*/
|
|
6393
|
+
this.resolveDefs = new Set();
|
|
6394
|
+
/**
|
|
6395
|
+
* Subjects opted out of the default crop (clip + object-fit: cover +
|
|
6396
|
+
* animated corner radii) via `.crop(false)`.
|
|
6397
|
+
*/
|
|
6398
|
+
this.noCrop = new Set();
|
|
6399
|
+
/**
|
|
6400
|
+
* Subjects paired with a different new-snapshot target (the second `.add()`
|
|
6401
|
+
* argument), so two distinct elements share one name and morph into each
|
|
6402
|
+
* other - a shared-element transition.
|
|
6403
|
+
*/
|
|
6404
|
+
this.pairs = new Map();
|
|
6405
|
+
/**
|
|
6406
|
+
* A `view-transition-class` to apply to each subject's resolved elements,
|
|
6407
|
+
* so authors can target the generated layers from CSS by class rather than
|
|
6408
|
+
* the opaque generated name.
|
|
6409
|
+
*/
|
|
6410
|
+
this.classNames = new Map();
|
|
6411
|
+
this.notifyReady = noop;
|
|
6412
|
+
this.notifyReject = noop;
|
|
6413
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
5980
6414
|
this.notifyReady = resolve;
|
|
6415
|
+
this.notifyReject = reject;
|
|
5981
6416
|
});
|
|
5982
6417
|
this.update = update;
|
|
5983
6418
|
this.options = {
|
|
5984
6419
|
interrupt: "wait",
|
|
5985
6420
|
...options,
|
|
5986
6421
|
};
|
|
6422
|
+
// Avoid an unhandled rejection when a failed transition has no
|
|
6423
|
+
// `.then(_, reject)` handler attached (e.g. fire-and-forget).
|
|
6424
|
+
this.readyPromise.catch(noop);
|
|
5987
6425
|
addToQueue(this);
|
|
5988
6426
|
}
|
|
5989
|
-
|
|
6427
|
+
/**
|
|
6428
|
+
* Target elements resolved from a selector or Element, each assigned a
|
|
6429
|
+
* `view-transition-name` automatically.
|
|
6430
|
+
*
|
|
6431
|
+
* Passing a second target pairs them: the first is resolved in the old
|
|
6432
|
+
* snapshot and the second in the new, sharing one name so two *different*
|
|
6433
|
+
* elements morph into each other (e.g. `.add(card, ".modal")`). Symmetric -
|
|
6434
|
+
* pass them the other way round to morph back.
|
|
6435
|
+
*/
|
|
6436
|
+
add(subject, newSubject) {
|
|
5990
6437
|
this.currentSubject = subject;
|
|
6438
|
+
this.resolveDefs.add(subject);
|
|
6439
|
+
if (newSubject !== undefined)
|
|
6440
|
+
this.pairs.set(subject, newSubject);
|
|
6441
|
+
// Register the subject so it participates (and gets an automatic
|
|
6442
|
+
// layout/morph animation) even without an explicit enter/exit/layout.
|
|
6443
|
+
if (!this.targets.has(subject))
|
|
6444
|
+
this.targets.set(subject, {});
|
|
5991
6445
|
return this;
|
|
5992
6446
|
}
|
|
5993
|
-
|
|
5994
|
-
|
|
6447
|
+
/**
|
|
6448
|
+
* Morphs are clipped + `object-fit: cover` (and their corners animate)
|
|
6449
|
+
* by default. Call `.crop(false)` to opt this subject out and fall back
|
|
6450
|
+
* to the browser default (which overflows on aspect-ratio change).
|
|
6451
|
+
*/
|
|
6452
|
+
crop(enabled = true) {
|
|
6453
|
+
enabled
|
|
6454
|
+
? this.noCrop.delete(this.currentSubject)
|
|
6455
|
+
: this.noCrop.add(this.currentSubject);
|
|
5995
6456
|
return this;
|
|
5996
6457
|
}
|
|
5997
|
-
|
|
5998
|
-
|
|
6458
|
+
/**
|
|
6459
|
+
* Tag this subject's generated layers with a `view-transition-class`, so
|
|
6460
|
+
* they can be targeted from CSS - `::view-transition-group(.name)`,
|
|
6461
|
+
* `::view-transition-old/new(.name)`, `::view-transition-image-pair(.name)`
|
|
6462
|
+
* - without the opaque generated `view-transition-name`. Because `.add()`
|
|
6463
|
+
* can match many elements, a shared class targets them all at once (and,
|
|
6464
|
+
* for a pair, both ends). The escape hatch for z-index / custom keyframes
|
|
6465
|
+
* on a morph layer.
|
|
6466
|
+
*/
|
|
6467
|
+
class(name) {
|
|
6468
|
+
this.classNames.set(this.currentSubject, name);
|
|
5999
6469
|
return this;
|
|
6000
6470
|
}
|
|
6001
|
-
|
|
6002
|
-
|
|
6471
|
+
/**
|
|
6472
|
+
* Set the transition for this subject's morph. The morph is enabled
|
|
6473
|
+
* automatically by `.add()`; this just customises its timing (duration,
|
|
6474
|
+
* easing, a `delay`/`stagger`, …). On the implicit `root` subject it also
|
|
6475
|
+
* opts the page into the transition (the root crossfade).
|
|
6476
|
+
*/
|
|
6477
|
+
layout(options = {}) {
|
|
6478
|
+
this.updateTarget("layout", {}, options);
|
|
6003
6479
|
return this;
|
|
6004
6480
|
}
|
|
6005
6481
|
enter(keyframes, options) {
|
|
@@ -6010,9 +6486,21 @@
|
|
|
6010
6486
|
this.updateTarget("exit", keyframes, options);
|
|
6011
6487
|
return this;
|
|
6012
6488
|
}
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6489
|
+
/**
|
|
6490
|
+
* Animate the new view directly, whether the element is appearing or
|
|
6491
|
+
* persisting (unlike `.enter()`, which only fires for a pure newcomer).
|
|
6492
|
+
* Pair with `.old()` for a crossfade or slide-through.
|
|
6493
|
+
*/
|
|
6494
|
+
new(keyframes, options) {
|
|
6495
|
+
this.updateTarget("new", keyframes, options);
|
|
6496
|
+
return this;
|
|
6497
|
+
}
|
|
6498
|
+
/**
|
|
6499
|
+
* Animate the old view directly, whether the element is leaving or
|
|
6500
|
+
* persisting (unlike `.exit()`, which only fires for a pure leaver).
|
|
6501
|
+
*/
|
|
6502
|
+
old(keyframes, options) {
|
|
6503
|
+
this.updateTarget("old", keyframes, options);
|
|
6016
6504
|
return this;
|
|
6017
6505
|
}
|
|
6018
6506
|
updateTarget(target, keyframes, options = {}) {
|
|
@@ -6027,8 +6515,8 @@
|
|
|
6027
6515
|
return this.readyPromise.then(resolve, reject);
|
|
6028
6516
|
}
|
|
6029
6517
|
}
|
|
6030
|
-
function animateView(update,
|
|
6031
|
-
return new ViewTransitionBuilder(update,
|
|
6518
|
+
function animateView(update, options = {}) {
|
|
6519
|
+
return new ViewTransitionBuilder(update, options);
|
|
6032
6520
|
}
|
|
6033
6521
|
|
|
6034
6522
|
const createAxisDelta = () => ({
|
|
@@ -8128,7 +8616,7 @@
|
|
|
8128
8616
|
: values.borderRadius;
|
|
8129
8617
|
}
|
|
8130
8618
|
const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
|
|
8131
|
-
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop
|
|
8619
|
+
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
|
|
8132
8620
|
function compress(min, max, easing) {
|
|
8133
8621
|
return (p) => {
|
|
8134
8622
|
// Could replace ifs with clamp
|
|
@@ -8148,7 +8636,7 @@
|
|
|
8148
8636
|
|
|
8149
8637
|
function addDomEvent(target, eventName, handler, options = { passive: true }) {
|
|
8150
8638
|
target.addEventListener(eventName, handler, options);
|
|
8151
|
-
return () => target.removeEventListener(eventName, handler);
|
|
8639
|
+
return () => target.removeEventListener(eventName, handler, options);
|
|
8152
8640
|
}
|
|
8153
8641
|
|
|
8154
8642
|
const compareByDepth = (a, b) => a.depth - b.depth;
|
|
@@ -9418,7 +9906,6 @@
|
|
|
9418
9906
|
*/
|
|
9419
9907
|
this.pendingAnimation = frame.update(() => {
|
|
9420
9908
|
globalProjectionState.hasAnimatedSinceResize = true;
|
|
9421
|
-
activeAnimations.layout++;
|
|
9422
9909
|
this.motionValue || (this.motionValue = motionValue(0));
|
|
9423
9910
|
this.motionValue.jump(0, false);
|
|
9424
9911
|
this.currentAnimation = animateSingleValue(this.motionValue, [0, 1000], {
|
|
@@ -9429,11 +9916,7 @@
|
|
|
9429
9916
|
this.mixTargetDelta(latest);
|
|
9430
9917
|
options.onUpdate && options.onUpdate(latest);
|
|
9431
9918
|
},
|
|
9432
|
-
onStop: () => {
|
|
9433
|
-
activeAnimations.layout--;
|
|
9434
|
-
},
|
|
9435
9919
|
onComplete: () => {
|
|
9436
|
-
activeAnimations.layout--;
|
|
9437
9920
|
options.onComplete && options.onComplete();
|
|
9438
9921
|
this.completeAnimation();
|
|
9439
9922
|
},
|
|
@@ -9948,7 +10431,7 @@
|
|
|
9948
10431
|
*/
|
|
9949
10432
|
const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
|
|
9950
10433
|
? Math.round
|
|
9951
|
-
: noop
|
|
10434
|
+
: noop;
|
|
9952
10435
|
function roundAxis(axis) {
|
|
9953
10436
|
// Round to the nearest .5 pixels to support subpixel layouts
|
|
9954
10437
|
axis.min = roundPoint(axis.min);
|
|
@@ -10022,39 +10505,221 @@
|
|
|
10022
10505
|
checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
|
|
10023
10506
|
});
|
|
10024
10507
|
|
|
10025
|
-
const layoutSelector = "[data-layout],
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10508
|
+
const layoutSelector = "[data-layout],[data-layout-id]";
|
|
10509
|
+
/**
|
|
10510
|
+
* All imperatively-created projection nodes live in one persistent tree,
|
|
10511
|
+
* shared across animateLayout() calls (and with any React-created nodes,
|
|
10512
|
+
* via the singleton document root). Keyed by element for reuse.
|
|
10513
|
+
*/
|
|
10514
|
+
const layoutNodes = new WeakMap();
|
|
10515
|
+
/**
|
|
10516
|
+
* Builders created within the same synchronous tick are flushed together
|
|
10517
|
+
* as a single "commit": every node is snapshotted before any updateDom
|
|
10518
|
+
* runs, mirroring React batching renders from different parts of the tree.
|
|
10519
|
+
*/
|
|
10520
|
+
let pendingBuilders;
|
|
10521
|
+
function collectLayoutElements(scope) {
|
|
10522
|
+
const elements = [];
|
|
10523
|
+
if (scope instanceof HTMLElement && scope.matches(layoutSelector)) {
|
|
10524
|
+
elements.push(scope);
|
|
10525
|
+
}
|
|
10526
|
+
scope.querySelectorAll(layoutSelector).forEach((element) => {
|
|
10527
|
+
if (element instanceof HTMLElement)
|
|
10528
|
+
elements.push(element);
|
|
10529
|
+
});
|
|
10530
|
+
return elements;
|
|
10531
|
+
}
|
|
10532
|
+
/**
|
|
10533
|
+
* Process any work scheduled on the frameloop now. A previous animation
|
|
10534
|
+
* may have been seeked while paused (controls.time = x) without a frame
|
|
10535
|
+
* having rendered it - we must materialise that state into the DOM
|
|
10536
|
+
* before taking snapshots.
|
|
10537
|
+
*/
|
|
10538
|
+
function flushPendingFrame() {
|
|
10539
|
+
if (frameData.isProcessing)
|
|
10540
|
+
return;
|
|
10541
|
+
const now = time.now();
|
|
10542
|
+
frameData.delta = clamp(0, 1000 / 60, now - frameData.timestamp);
|
|
10543
|
+
frameData.timestamp = now;
|
|
10544
|
+
frameData.isProcessing = true;
|
|
10545
|
+
frameSteps.update.process(frameData);
|
|
10546
|
+
frameSteps.preRender.process(frameData);
|
|
10547
|
+
frameSteps.render.process(frameData);
|
|
10548
|
+
frameData.isProcessing = false;
|
|
10549
|
+
}
|
|
10550
|
+
function getProjectionParent(element) {
|
|
10551
|
+
let ancestor = element.parentElement;
|
|
10552
|
+
while (ancestor) {
|
|
10553
|
+
const node = layoutNodes.get(ancestor);
|
|
10554
|
+
if (node && node.instance)
|
|
10555
|
+
return node;
|
|
10556
|
+
ancestor = ancestor.parentElement;
|
|
10557
|
+
}
|
|
10558
|
+
return undefined;
|
|
10559
|
+
}
|
|
10560
|
+
function createVisualElement() {
|
|
10561
|
+
return new HTMLVisualElement({
|
|
10562
|
+
props: {},
|
|
10563
|
+
presenceContext: null,
|
|
10564
|
+
visualState: {
|
|
10565
|
+
latestValues: {},
|
|
10566
|
+
renderState: {
|
|
10567
|
+
transform: {},
|
|
10568
|
+
transformOrigin: {},
|
|
10569
|
+
style: {},
|
|
10570
|
+
vars: {},
|
|
10571
|
+
},
|
|
10572
|
+
},
|
|
10573
|
+
}, { allowProjection: true });
|
|
10574
|
+
}
|
|
10575
|
+
function readNodeOptions(element, transition) {
|
|
10576
|
+
const layoutAttr = element.getAttribute("data-layout");
|
|
10577
|
+
const layoutId = element.getAttribute("data-layout-id") ?? undefined;
|
|
10035
10578
|
return {
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10579
|
+
layoutId,
|
|
10580
|
+
layout: layoutAttr !== null ? true : undefined,
|
|
10581
|
+
animationType: (!layoutAttr || layoutAttr === "true"
|
|
10582
|
+
? "both"
|
|
10583
|
+
: layoutAttr),
|
|
10584
|
+
transition,
|
|
10041
10585
|
};
|
|
10042
10586
|
}
|
|
10587
|
+
function prepareNode(element, transition) {
|
|
10588
|
+
let node = layoutNodes.get(element);
|
|
10589
|
+
if (!node) {
|
|
10590
|
+
let visualElement = visualElementStore.get(element);
|
|
10591
|
+
if (!visualElement)
|
|
10592
|
+
visualElement = createVisualElement();
|
|
10593
|
+
/**
|
|
10594
|
+
* A first-time element may carry a projection transform in its
|
|
10595
|
+
* inline style (e.g. it was cloned from an element mid-animation).
|
|
10596
|
+
* That transform isn't tracked in latestValues so the engine can't
|
|
10597
|
+
* reset it before measuring - clear it now so the first layout
|
|
10598
|
+
* measurement isn't inflated.
|
|
10599
|
+
*/
|
|
10600
|
+
if (element.style.transform &&
|
|
10601
|
+
!hasTransform(visualElement.latestValues)) {
|
|
10602
|
+
element.style.transform = "";
|
|
10603
|
+
}
|
|
10604
|
+
node = new HTMLProjectionNode(visualElement.latestValues, getProjectionParent(element));
|
|
10605
|
+
visualElement.projection = node;
|
|
10606
|
+
node.setOptions({
|
|
10607
|
+
...readNodeOptions(element, transition),
|
|
10608
|
+
visualElement,
|
|
10609
|
+
});
|
|
10610
|
+
node.mount(element);
|
|
10611
|
+
layoutNodes.set(element, node);
|
|
10612
|
+
}
|
|
10613
|
+
else {
|
|
10614
|
+
node.setOptions(readNodeOptions(element, transition));
|
|
10615
|
+
}
|
|
10616
|
+
node.isPresent = true;
|
|
10617
|
+
if (node.options.onExitComplete) {
|
|
10618
|
+
node.setOptions({ onExitComplete: undefined });
|
|
10619
|
+
}
|
|
10620
|
+
return node;
|
|
10621
|
+
}
|
|
10622
|
+
function sortDocumentOrder(elements) {
|
|
10623
|
+
return [...elements].sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
|
10624
|
+
}
|
|
10625
|
+
function dropNode(element, node) {
|
|
10626
|
+
node.setOptions({ onExitComplete: undefined });
|
|
10627
|
+
/**
|
|
10628
|
+
* Stop any lingering animation so it can't leak into future updates.
|
|
10629
|
+
* A follow node can share its currentAnimation with a surviving lead
|
|
10630
|
+
* (via resumingFrom), in which case it isn't ours to stop.
|
|
10631
|
+
*/
|
|
10632
|
+
const stack = node.getStack();
|
|
10633
|
+
if (!stack || node.isLead())
|
|
10634
|
+
node.currentAnimation?.stop();
|
|
10635
|
+
node.unmount();
|
|
10636
|
+
layoutNodes.delete(element);
|
|
10637
|
+
}
|
|
10638
|
+
function flushPendingBuilders() {
|
|
10639
|
+
const builders = pendingBuilders;
|
|
10640
|
+
pendingBuilders = undefined;
|
|
10641
|
+
flushPendingFrame();
|
|
10642
|
+
/**
|
|
10643
|
+
* Discover and mount every node across all builders before snapshotting
|
|
10644
|
+
* any of them. Mounting during an active update flags isLayoutDirty,
|
|
10645
|
+
* which would make that node's own willUpdate skip its snapshot.
|
|
10646
|
+
* Document order guarantees ancestors mount before descendants, even
|
|
10647
|
+
* when they're discovered by different builders.
|
|
10648
|
+
*/
|
|
10649
|
+
const targets = new Map();
|
|
10650
|
+
for (const builder of builders) {
|
|
10651
|
+
for (const element of builder.collectTargets()) {
|
|
10652
|
+
const owners = targets.get(element);
|
|
10653
|
+
owners ? owners.push(builder) : targets.set(element, [builder]);
|
|
10654
|
+
}
|
|
10655
|
+
}
|
|
10656
|
+
const union = new Map();
|
|
10657
|
+
for (const element of sortDocumentOrder(targets.keys())) {
|
|
10658
|
+
const owners = targets.get(element);
|
|
10659
|
+
const node = prepareNode(element, owners[owners.length - 1].transitionFor(element));
|
|
10660
|
+
for (const owner of owners)
|
|
10661
|
+
owner.adopt(element, node);
|
|
10662
|
+
union.set(element, node);
|
|
10663
|
+
}
|
|
10664
|
+
union.forEach((node) => {
|
|
10665
|
+
node.isLayoutDirty = false;
|
|
10666
|
+
node.willUpdate();
|
|
10667
|
+
});
|
|
10668
|
+
const updatePromises = [];
|
|
10669
|
+
for (const builder of builders) {
|
|
10670
|
+
const result = builder.runUpdate();
|
|
10671
|
+
if (result)
|
|
10672
|
+
updatePromises.push(result);
|
|
10673
|
+
}
|
|
10674
|
+
const commit = () => {
|
|
10675
|
+
/**
|
|
10676
|
+
* Process all additions before any removals so that, even across
|
|
10677
|
+
* builders, a removed member knows whether a replacement with the
|
|
10678
|
+
* same layoutId was added in this commit.
|
|
10679
|
+
*/
|
|
10680
|
+
const newMemberIds = new Set();
|
|
10681
|
+
for (const builder of builders) {
|
|
10682
|
+
builder.reconcileAdditions(newMemberIds);
|
|
10683
|
+
}
|
|
10684
|
+
for (const builder of builders) {
|
|
10685
|
+
builder.reconcileRemovals(newMemberIds);
|
|
10686
|
+
}
|
|
10687
|
+
let root;
|
|
10688
|
+
union.forEach((node) => (root || (root = node.root)));
|
|
10689
|
+
for (const builder of builders)
|
|
10690
|
+
root || (root = builder.getRoot());
|
|
10691
|
+
root?.didUpdate();
|
|
10692
|
+
/**
|
|
10693
|
+
* The root flushes the update on a microtask, synchronously
|
|
10694
|
+
* processing the frame that creates the layout animations. Collect
|
|
10695
|
+
* them in a later microtask step of the same pass.
|
|
10696
|
+
*/
|
|
10697
|
+
microtask.render(() => {
|
|
10698
|
+
for (const builder of builders)
|
|
10699
|
+
builder.finalize();
|
|
10700
|
+
});
|
|
10701
|
+
};
|
|
10702
|
+
updatePromises.length ? Promise.all(updatePromises).then(commit) : commit();
|
|
10703
|
+
}
|
|
10043
10704
|
class LayoutAnimationBuilder {
|
|
10044
10705
|
constructor(scope, updateDom, defaultOptions) {
|
|
10045
|
-
this.sharedTransitions = new Map();
|
|
10046
|
-
this.notifyReady = noop;
|
|
10047
|
-
this.rejectReady = noop;
|
|
10048
10706
|
this.scope = scope;
|
|
10049
10707
|
this.updateDom = updateDom;
|
|
10050
10708
|
this.defaultOptions = defaultOptions;
|
|
10709
|
+
this.sharedTransitions = new Map();
|
|
10710
|
+
this.notifyReady = () => { };
|
|
10711
|
+
this.rejectReady = () => { };
|
|
10712
|
+
this.tracked = new Map();
|
|
10713
|
+
this.restorePoints = new Map();
|
|
10051
10714
|
this.readyPromise = new Promise((resolve, reject) => {
|
|
10052
10715
|
this.notifyReady = resolve;
|
|
10053
10716
|
this.rejectReady = reject;
|
|
10054
10717
|
});
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10718
|
+
if (!pendingBuilders) {
|
|
10719
|
+
pendingBuilders = [];
|
|
10720
|
+
queueMicrotask(flushPendingBuilders);
|
|
10721
|
+
}
|
|
10722
|
+
pendingBuilders.push(this);
|
|
10058
10723
|
}
|
|
10059
10724
|
shared(id, transition) {
|
|
10060
10725
|
this.sharedTransitions.set(id, transition);
|
|
@@ -10063,114 +10728,107 @@
|
|
|
10063
10728
|
then(resolve, reject) {
|
|
10064
10729
|
return this.readyPromise.then(resolve, reject);
|
|
10065
10730
|
}
|
|
10066
|
-
|
|
10067
|
-
const
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
}
|
|
10077
|
-
else if (projection.snapshot) {
|
|
10078
|
-
projection.snapshot = undefined;
|
|
10079
|
-
}
|
|
10080
|
-
}
|
|
10081
|
-
else if (projection.snapshot &&
|
|
10082
|
-
(projection.currentAnimation || projection.isProjecting())) {
|
|
10083
|
-
projection.snapshot = undefined;
|
|
10084
|
-
}
|
|
10085
|
-
projection.isPresent = true;
|
|
10086
|
-
projection.willUpdate();
|
|
10731
|
+
transitionFor(element) {
|
|
10732
|
+
const layoutId = element.getAttribute("data-layout-id");
|
|
10733
|
+
return ((layoutId && this.sharedTransitions.get(layoutId)) ||
|
|
10734
|
+
this.defaultOptions);
|
|
10735
|
+
}
|
|
10736
|
+
adopt(element, node) {
|
|
10737
|
+
this.tracked.set(element, node);
|
|
10738
|
+
this.restorePoints.set(element, {
|
|
10739
|
+
parent: element.parentElement,
|
|
10740
|
+
next: element.nextSibling,
|
|
10087
10741
|
});
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
const
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
return;
|
|
10100
|
-
const currentTransform = instance.style.transform;
|
|
10101
|
-
const resumeFromTransform = resumeFromInstance.style.transform;
|
|
10102
|
-
if (currentTransform &&
|
|
10103
|
-
resumeFromTransform &&
|
|
10104
|
-
currentTransform === resumeFromTransform) {
|
|
10105
|
-
instance.style.transform = "";
|
|
10106
|
-
instance.style.transformOrigin = "";
|
|
10742
|
+
}
|
|
10743
|
+
collectTargets() {
|
|
10744
|
+
return collectLayoutElements(this.scope);
|
|
10745
|
+
}
|
|
10746
|
+
runUpdate() {
|
|
10747
|
+
try {
|
|
10748
|
+
const result = this.updateDom();
|
|
10749
|
+
if (result && typeof result.then === "function") {
|
|
10750
|
+
return result.then(undefined, (error) => {
|
|
10751
|
+
this.updateError = error;
|
|
10752
|
+
});
|
|
10107
10753
|
}
|
|
10108
|
-
}
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
}
|
|
10112
|
-
|
|
10113
|
-
root?.didUpdate();
|
|
10114
|
-
await new Promise((resolve) => {
|
|
10115
|
-
frame.postRender(() => resolve());
|
|
10116
|
-
});
|
|
10117
|
-
const animations = collectAnimations(afterRecords);
|
|
10118
|
-
const animation = new GroupAnimation(animations);
|
|
10119
|
-
return animation;
|
|
10754
|
+
}
|
|
10755
|
+
catch (error) {
|
|
10756
|
+
this.updateError = error;
|
|
10757
|
+
}
|
|
10758
|
+
return undefined;
|
|
10120
10759
|
}
|
|
10121
|
-
|
|
10122
|
-
const
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
const
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
? this.sharedTransitions.get(layoutId)
|
|
10129
|
-
: undefined;
|
|
10130
|
-
const transition = override || this.defaultOptions;
|
|
10131
|
-
const record = getOrCreateRecord(element, parentRecord?.projection, {
|
|
10132
|
-
layout,
|
|
10133
|
-
layoutId,
|
|
10134
|
-
animationType: typeof layout === "string" ? layout : "both",
|
|
10135
|
-
transition: transition,
|
|
10136
|
-
});
|
|
10137
|
-
recordMap.set(element, record);
|
|
10138
|
-
records.push(record);
|
|
10760
|
+
reconcileAdditions(newMemberIds) {
|
|
10761
|
+
for (const element of collectLayoutElements(this.scope)) {
|
|
10762
|
+
if (this.tracked.has(element))
|
|
10763
|
+
continue;
|
|
10764
|
+
const node = prepareNode(element, this.transitionFor(element));
|
|
10765
|
+
this.adopt(element, node);
|
|
10766
|
+
node.options.layoutId && newMemberIds.add(node.options.layoutId);
|
|
10139
10767
|
}
|
|
10140
|
-
return records;
|
|
10141
10768
|
}
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
if (afterElementsSet.has(record.element))
|
|
10769
|
+
reconcileRemovals(newMemberIds) {
|
|
10770
|
+
this.tracked.forEach((node, element) => {
|
|
10771
|
+
if (element.isConnected)
|
|
10146
10772
|
return;
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10773
|
+
const restore = this.restorePoints.get(element);
|
|
10774
|
+
this.restorePoints.delete(element);
|
|
10775
|
+
const { layoutId } = node.options;
|
|
10776
|
+
const stack = node.getStack();
|
|
10777
|
+
const hasSurvivor = stack &&
|
|
10778
|
+
stack.members.some((member) => member !== node &&
|
|
10779
|
+
member.instance
|
|
10780
|
+
?.isConnected);
|
|
10781
|
+
/**
|
|
10782
|
+
* A removed lead with a surviving stack member - and no
|
|
10783
|
+
* replacement member added this commit - runs an exit
|
|
10784
|
+
* crossfade: restore the element to its old position in the
|
|
10785
|
+
* DOM, relegate it and let the survivor take over. It's
|
|
10786
|
+
* removed again once the animation completes.
|
|
10787
|
+
*/
|
|
10788
|
+
if (layoutId &&
|
|
10789
|
+
node.isLead() &&
|
|
10790
|
+
hasSurvivor &&
|
|
10791
|
+
!newMemberIds.has(layoutId)) {
|
|
10792
|
+
if (restore && restore.parent.isConnected) {
|
|
10793
|
+
restore.parent.insertBefore(element, restore.next && restore.next.parentNode === restore.parent
|
|
10794
|
+
? restore.next
|
|
10795
|
+
: null);
|
|
10796
|
+
node.isPresent = false;
|
|
10797
|
+
node.setOptions({
|
|
10798
|
+
onExitComplete: () => {
|
|
10799
|
+
element.remove();
|
|
10800
|
+
dropNode(element, node);
|
|
10801
|
+
},
|
|
10802
|
+
});
|
|
10803
|
+
if (node.relegate())
|
|
10804
|
+
return;
|
|
10805
|
+
element.remove();
|
|
10806
|
+
}
|
|
10807
|
+
}
|
|
10808
|
+
dropNode(element, node);
|
|
10809
|
+
this.tracked.delete(element);
|
|
10155
10810
|
});
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10811
|
+
}
|
|
10812
|
+
getRoot() {
|
|
10813
|
+
let root;
|
|
10814
|
+
this.tracked.forEach((node) => (root || (root = node.root)));
|
|
10815
|
+
return root;
|
|
10816
|
+
}
|
|
10817
|
+
finalize() {
|
|
10818
|
+
if (this.updateError) {
|
|
10819
|
+
this.rejectReady(this.updateError);
|
|
10820
|
+
return;
|
|
10821
|
+
}
|
|
10822
|
+
const animations = new Set();
|
|
10823
|
+
this.tracked.forEach((node) => {
|
|
10824
|
+
if (node.instance && node.currentAnimation) {
|
|
10825
|
+
animations.add(node.currentAnimation);
|
|
10168
10826
|
}
|
|
10169
10827
|
});
|
|
10828
|
+
this.notifyReady(new GroupAnimation([...animations]));
|
|
10170
10829
|
}
|
|
10171
10830
|
}
|
|
10172
10831
|
function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
|
|
10173
|
-
// animateLayout(updateDom)
|
|
10174
10832
|
if (typeof scopeOrUpdateDom === "function") {
|
|
10175
10833
|
return {
|
|
10176
10834
|
scope: document,
|
|
@@ -10178,107 +10836,15 @@
|
|
|
10178
10836
|
defaultOptions: updateDomOrOptions,
|
|
10179
10837
|
};
|
|
10180
10838
|
}
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10839
|
+
const scope = scopeOrUpdateDom instanceof Document
|
|
10840
|
+
? scopeOrUpdateDom
|
|
10841
|
+
: resolveElements(scopeOrUpdateDom)[0] ?? document;
|
|
10184
10842
|
return {
|
|
10185
10843
|
scope,
|
|
10186
10844
|
updateDom: updateDomOrOptions,
|
|
10187
10845
|
defaultOptions: options,
|
|
10188
10846
|
};
|
|
10189
10847
|
}
|
|
10190
|
-
function collectLayoutElements(scope) {
|
|
10191
|
-
const elements = Array.from(scope.querySelectorAll(layoutSelector));
|
|
10192
|
-
if (scope instanceof Element && scope.matches(layoutSelector)) {
|
|
10193
|
-
if (!elements.includes(scope)) {
|
|
10194
|
-
elements.unshift(scope);
|
|
10195
|
-
}
|
|
10196
|
-
}
|
|
10197
|
-
return elements;
|
|
10198
|
-
}
|
|
10199
|
-
function readLayoutAttributes(element) {
|
|
10200
|
-
const layoutId = element.getAttribute("data-layout-id") || undefined;
|
|
10201
|
-
const rawLayout = element.getAttribute("data-layout");
|
|
10202
|
-
let layout;
|
|
10203
|
-
if (rawLayout === "" || rawLayout === "true") {
|
|
10204
|
-
layout = true;
|
|
10205
|
-
}
|
|
10206
|
-
else if (rawLayout) {
|
|
10207
|
-
layout = rawLayout;
|
|
10208
|
-
}
|
|
10209
|
-
return {
|
|
10210
|
-
layout,
|
|
10211
|
-
layoutId,
|
|
10212
|
-
};
|
|
10213
|
-
}
|
|
10214
|
-
function createVisualState() {
|
|
10215
|
-
return {
|
|
10216
|
-
latestValues: {},
|
|
10217
|
-
renderState: {
|
|
10218
|
-
transform: {},
|
|
10219
|
-
transformOrigin: {},
|
|
10220
|
-
style: {},
|
|
10221
|
-
vars: {},
|
|
10222
|
-
},
|
|
10223
|
-
};
|
|
10224
|
-
}
|
|
10225
|
-
function getOrCreateRecord(element, parentProjection, projectionOptions) {
|
|
10226
|
-
const existing = visualElementStore.get(element);
|
|
10227
|
-
const visualElement = existing ??
|
|
10228
|
-
new HTMLVisualElement({
|
|
10229
|
-
props: {},
|
|
10230
|
-
presenceContext: null,
|
|
10231
|
-
visualState: createVisualState(),
|
|
10232
|
-
}, { allowProjection: true });
|
|
10233
|
-
if (!existing || !visualElement.projection) {
|
|
10234
|
-
visualElement.projection = new HTMLProjectionNode(visualElement.latestValues, parentProjection);
|
|
10235
|
-
}
|
|
10236
|
-
visualElement.projection.setOptions({
|
|
10237
|
-
...projectionOptions,
|
|
10238
|
-
visualElement,
|
|
10239
|
-
});
|
|
10240
|
-
if (!visualElement.current) {
|
|
10241
|
-
visualElement.mount(element);
|
|
10242
|
-
}
|
|
10243
|
-
else if (!visualElement.projection.instance) {
|
|
10244
|
-
// Mount projection if VisualElement is already mounted but projection isn't
|
|
10245
|
-
// This happens when animate() was called before animateLayout()
|
|
10246
|
-
visualElement.projection.mount(element);
|
|
10247
|
-
}
|
|
10248
|
-
if (!existing) {
|
|
10249
|
-
visualElementStore.set(element, visualElement);
|
|
10250
|
-
}
|
|
10251
|
-
return {
|
|
10252
|
-
element,
|
|
10253
|
-
visualElement,
|
|
10254
|
-
projection: visualElement.projection,
|
|
10255
|
-
};
|
|
10256
|
-
}
|
|
10257
|
-
function findParentRecord(element, recordMap, scope) {
|
|
10258
|
-
let parent = element.parentElement;
|
|
10259
|
-
while (parent) {
|
|
10260
|
-
const record = recordMap.get(parent);
|
|
10261
|
-
if (record)
|
|
10262
|
-
return record;
|
|
10263
|
-
if (parent === scope)
|
|
10264
|
-
break;
|
|
10265
|
-
parent = parent.parentElement;
|
|
10266
|
-
}
|
|
10267
|
-
return undefined;
|
|
10268
|
-
}
|
|
10269
|
-
function getProjectionRoot(afterRecords, beforeRecords) {
|
|
10270
|
-
const record = afterRecords[0] || beforeRecords[0];
|
|
10271
|
-
return record?.projection.root;
|
|
10272
|
-
}
|
|
10273
|
-
function collectAnimations(afterRecords) {
|
|
10274
|
-
const animations = new Set();
|
|
10275
|
-
afterRecords.forEach((record) => {
|
|
10276
|
-
const animation = record.projection.currentAnimation;
|
|
10277
|
-
if (animation)
|
|
10278
|
-
animations.add(animation);
|
|
10279
|
-
});
|
|
10280
|
-
return Array.from(animations);
|
|
10281
|
-
}
|
|
10282
10848
|
|
|
10283
10849
|
/**
|
|
10284
10850
|
* @deprecated
|
|
@@ -11279,7 +11845,7 @@
|
|
|
11279
11845
|
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
|
|
11280
11846
|
function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
|
|
11281
11847
|
if (!container)
|
|
11282
|
-
return noop
|
|
11848
|
+
return noop;
|
|
11283
11849
|
let containerHandlers = onScrollHandlers.get(container);
|
|
11284
11850
|
/**
|
|
11285
11851
|
* Get the onScroll handlers for this container.
|
|
@@ -11563,7 +12129,7 @@
|
|
|
11563
12129
|
|
|
11564
12130
|
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
|
|
11565
12131
|
if (!container)
|
|
11566
|
-
return noop
|
|
12132
|
+
return noop;
|
|
11567
12133
|
const optionsWithDefaults = { axis, container, ...options };
|
|
11568
12134
|
return typeof onScroll === "function"
|
|
11569
12135
|
? attachToFunction(onScroll, optionsWithDefaults)
|
|
@@ -11643,7 +12209,6 @@
|
|
|
11643
12209
|
exports.ViewTransitionBuilder = ViewTransitionBuilder;
|
|
11644
12210
|
exports.VisualElement = VisualElement;
|
|
11645
12211
|
exports.acceleratedValues = acceleratedValues;
|
|
11646
|
-
exports.activeAnimations = activeAnimations;
|
|
11647
12212
|
exports.addAttrValue = addAttrValue;
|
|
11648
12213
|
exports.addDomEvent = addDomEvent;
|
|
11649
12214
|
exports.addScaleCorrector = addScaleCorrector;
|
|
@@ -11842,7 +12407,7 @@
|
|
|
11842
12407
|
exports.motionValue = motionValue;
|
|
11843
12408
|
exports.moveItem = moveItem;
|
|
11844
12409
|
exports.nodeGroup = nodeGroup;
|
|
11845
|
-
exports.noop = noop
|
|
12410
|
+
exports.noop = noop;
|
|
11846
12411
|
exports.number = number;
|
|
11847
12412
|
exports.numberValueTypes = numberValueTypes;
|
|
11848
12413
|
exports.observeTimeline = observeTimeline;
|