motion 12.27.3-alpha.2 → 12.27.4
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/dist/motion.dev.js +319 -280
- package/dist/motion.js +1 -1
- package/package.json +3 -3
package/dist/motion.dev.js
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/*#__NO_SIDE_EFFECTS__*/
|
|
84
|
-
const noop
|
|
84
|
+
const noop = (any) => any;
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Pipe
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
228
228
|
// If this is a linear gradient, return linear easing
|
|
229
229
|
if (mX1 === mY1 && mX2 === mY2)
|
|
230
|
-
return noop
|
|
230
|
+
return noop;
|
|
231
231
|
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
232
232
|
// If animation is at start/end, return t without easing
|
|
233
233
|
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
|
|
279
279
|
|
|
280
280
|
const easingLookup = {
|
|
281
|
-
linear: noop
|
|
281
|
+
linear: noop,
|
|
282
282
|
easeIn,
|
|
283
283
|
easeInOut,
|
|
284
284
|
easeOut,
|
|
@@ -479,7 +479,7 @@
|
|
|
479
479
|
return { schedule, cancel, state, steps };
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop
|
|
482
|
+
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
|
|
483
483
|
|
|
484
484
|
let now;
|
|
485
485
|
function clearTime() {
|
|
@@ -1429,7 +1429,7 @@
|
|
|
1429
1429
|
for (let i = 0; i < numMixers; i++) {
|
|
1430
1430
|
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
1431
1431
|
if (ease) {
|
|
1432
|
-
const easingFunction = Array.isArray(ease) ? ease[i] || noop
|
|
1432
|
+
const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
|
|
1433
1433
|
mixer = pipe(easingFunction, mixer);
|
|
1434
1434
|
}
|
|
1435
1435
|
mixers.push(mixer);
|
|
@@ -2491,7 +2491,7 @@
|
|
|
2491
2491
|
this.animation.onfinish = null;
|
|
2492
2492
|
if (timeline && supportsScrollTimeline()) {
|
|
2493
2493
|
this.animation.timeline = timeline;
|
|
2494
|
-
return noop
|
|
2494
|
+
return noop;
|
|
2495
2495
|
}
|
|
2496
2496
|
else {
|
|
2497
2497
|
return observe(this);
|
|
@@ -2783,7 +2783,7 @@
|
|
|
2783
2783
|
: new JSAnimation(resolvedOptions);
|
|
2784
2784
|
animation.finished.then(() => {
|
|
2785
2785
|
this.notifyFinished();
|
|
2786
|
-
}).catch(noop
|
|
2786
|
+
}).catch(noop);
|
|
2787
2787
|
if (this.pendingTimeline) {
|
|
2788
2788
|
this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
|
|
2789
2789
|
this.pendingTimeline = undefined;
|
|
@@ -4425,17 +4425,21 @@
|
|
|
4425
4425
|
const styleEffect = /*@__PURE__*/ createSelectorEffect(
|
|
4426
4426
|
/*@__PURE__*/ createEffect(addStyleValue));
|
|
4427
4427
|
|
|
4428
|
-
const toPx = px.transform;
|
|
4429
4428
|
function addSVGPathValue(element, state, key, value) {
|
|
4430
4429
|
frame.render(() => element.setAttribute("pathLength", "1"));
|
|
4431
4430
|
if (key === "pathOffset") {
|
|
4432
|
-
return state.set(key, value, () =>
|
|
4431
|
+
return state.set(key, value, () => {
|
|
4432
|
+
// Use unitless value to avoid Safari zoom bug
|
|
4433
|
+
const offset = state.latest[key];
|
|
4434
|
+
element.setAttribute("stroke-dashoffset", `${-offset}`);
|
|
4435
|
+
});
|
|
4433
4436
|
}
|
|
4434
4437
|
else {
|
|
4435
4438
|
if (!state.get("stroke-dasharray")) {
|
|
4436
4439
|
state.set("stroke-dasharray", new MotionValue("1 1"), () => {
|
|
4437
4440
|
const { pathLength = 1, pathSpacing } = state.latest;
|
|
4438
|
-
|
|
4441
|
+
// Use unitless values to avoid Safari zoom bug
|
|
4442
|
+
element.setAttribute("stroke-dasharray", `${pathLength} ${pathSpacing ?? 1 - Number(pathLength)}`);
|
|
4439
4443
|
});
|
|
4440
4444
|
}
|
|
4441
4445
|
return state.set(key, value, undefined, state.get("stroke-dasharray"));
|
|
@@ -5402,7 +5406,7 @@
|
|
|
5402
5406
|
constructor(update, options = {}) {
|
|
5403
5407
|
this.currentSubject = "root";
|
|
5404
5408
|
this.targets = new Map();
|
|
5405
|
-
this.notifyReady = noop
|
|
5409
|
+
this.notifyReady = noop;
|
|
5406
5410
|
this.readyPromise = new Promise((resolve) => {
|
|
5407
5411
|
this.notifyReady = resolve;
|
|
5408
5412
|
});
|
|
@@ -6638,6 +6642,9 @@
|
|
|
6638
6642
|
* and stroke-dasharray attributes.
|
|
6639
6643
|
*
|
|
6640
6644
|
* This function is mutative to reduce per-frame GC.
|
|
6645
|
+
*
|
|
6646
|
+
* Note: We use unitless values for stroke-dasharray and stroke-dashoffset
|
|
6647
|
+
* because Safari incorrectly scales px values when the page is zoomed.
|
|
6641
6648
|
*/
|
|
6642
6649
|
function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) {
|
|
6643
6650
|
// Normalise path length by setting SVG attribute pathLength to 1
|
|
@@ -6645,12 +6652,10 @@
|
|
|
6645
6652
|
// We use dash case when setting attributes directly to the DOM node and camel case
|
|
6646
6653
|
// when defining props on a React component.
|
|
6647
6654
|
const keys = useDashCase ? dashKeys : camelKeys;
|
|
6648
|
-
// Build the dash offset
|
|
6649
|
-
attrs[keys.offset] =
|
|
6650
|
-
// Build the dash array
|
|
6651
|
-
|
|
6652
|
-
const pathSpacing = px.transform(spacing);
|
|
6653
|
-
attrs[keys.array] = `${pathLength} ${pathSpacing}`;
|
|
6655
|
+
// Build the dash offset (unitless to avoid Safari zoom bug)
|
|
6656
|
+
attrs[keys.offset] = `${-offset}`;
|
|
6657
|
+
// Build the dash array (unitless to avoid Safari zoom bug)
|
|
6658
|
+
attrs[keys.array] = `${length} ${spacing}`;
|
|
6654
6659
|
}
|
|
6655
6660
|
|
|
6656
6661
|
/**
|
|
@@ -7457,7 +7462,7 @@
|
|
|
7457
7462
|
: values.borderRadius;
|
|
7458
7463
|
}
|
|
7459
7464
|
const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
|
|
7460
|
-
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop
|
|
7465
|
+
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
|
|
7461
7466
|
function compress(min, max, easing) {
|
|
7462
7467
|
return (p) => {
|
|
7463
7468
|
// Could replace ifs with clamp
|
|
@@ -7575,13 +7580,6 @@
|
|
|
7575
7580
|
const prevLead = this.lead;
|
|
7576
7581
|
if (node === prevLead)
|
|
7577
7582
|
return;
|
|
7578
|
-
console.log("[projection] promote", {
|
|
7579
|
-
layoutId: node.options.layoutId,
|
|
7580
|
-
node: node.id,
|
|
7581
|
-
prevLead: prevLead?.id,
|
|
7582
|
-
prevLeadSnapshot: Boolean(prevLead?.snapshot),
|
|
7583
|
-
prevLeadResumeFrom: Boolean(prevLead?.resumeFrom),
|
|
7584
|
-
});
|
|
7585
7583
|
this.prevLead = prevLead;
|
|
7586
7584
|
this.lead = node;
|
|
7587
7585
|
node.show();
|
|
@@ -7690,7 +7688,7 @@
|
|
|
7690
7688
|
cancelTreeOptimisedTransformAnimations(parent);
|
|
7691
7689
|
}
|
|
7692
7690
|
}
|
|
7693
|
-
function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
|
|
7691
|
+
function createProjectionNode$1({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
|
|
7694
7692
|
return class ProjectionNode {
|
|
7695
7693
|
constructor(latestValues = {}, parent = defaultParent?.()) {
|
|
7696
7694
|
/**
|
|
@@ -9228,7 +9226,7 @@
|
|
|
9228
9226
|
*/
|
|
9229
9227
|
const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
|
|
9230
9228
|
? Math.round
|
|
9231
|
-
: noop
|
|
9229
|
+
: noop;
|
|
9232
9230
|
function roundAxis(axis) {
|
|
9233
9231
|
// Round to the nearest .5 pixels to support subpixel layouts
|
|
9234
9232
|
axis.min = roundPoint(axis.min);
|
|
@@ -9247,7 +9245,7 @@
|
|
|
9247
9245
|
return node !== node.root && node.scroll?.wasRoot;
|
|
9248
9246
|
}
|
|
9249
9247
|
|
|
9250
|
-
const DocumentProjectionNode = createProjectionNode({
|
|
9248
|
+
const DocumentProjectionNode = createProjectionNode$1({
|
|
9251
9249
|
attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
|
|
9252
9250
|
measureScroll: () => ({
|
|
9253
9251
|
x: document.documentElement.scrollLeft || document.body.scrollLeft,
|
|
@@ -9282,7 +9280,7 @@
|
|
|
9282
9280
|
const rootProjectionNode = {
|
|
9283
9281
|
current: undefined,
|
|
9284
9282
|
};
|
|
9285
|
-
const HTMLProjectionNode = createProjectionNode({
|
|
9283
|
+
const HTMLProjectionNode = createProjectionNode$1({
|
|
9286
9284
|
measureScroll: (instance) => ({
|
|
9287
9285
|
x: instance.scrollLeft,
|
|
9288
9286
|
y: instance.scrollTop,
|
|
@@ -9302,160 +9300,315 @@
|
|
|
9302
9300
|
checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
|
|
9303
9301
|
});
|
|
9304
9302
|
|
|
9305
|
-
const
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
if (
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9303
|
+
const LAYOUT_SELECTOR = "[data-layout], [data-layout-id]";
|
|
9304
|
+
function getLayoutElements(scope) {
|
|
9305
|
+
const elements = Array.from(scope.querySelectorAll(LAYOUT_SELECTOR));
|
|
9306
|
+
// Include scope itself if it's an Element (not Document) and has layout attributes
|
|
9307
|
+
if (scope instanceof Element && hasLayout(scope)) {
|
|
9308
|
+
elements.unshift(scope);
|
|
9309
|
+
}
|
|
9310
|
+
return elements;
|
|
9311
|
+
}
|
|
9312
|
+
function getLayoutId(element) {
|
|
9313
|
+
return element.getAttribute("data-layout-id");
|
|
9314
|
+
}
|
|
9315
|
+
function hasLayout(element) {
|
|
9316
|
+
return (element.hasAttribute("data-layout") ||
|
|
9317
|
+
element.hasAttribute("data-layout-id"));
|
|
9318
|
+
}
|
|
9319
|
+
|
|
9320
|
+
let scaleCorrectorAdded = false;
|
|
9321
|
+
/**
|
|
9322
|
+
* Track active projection nodes per element to handle animation interruption.
|
|
9323
|
+
* When a new animation starts on an element that already has an active animation,
|
|
9324
|
+
* we need to stop the old animation so the new one can start from the current
|
|
9325
|
+
* visual position.
|
|
9326
|
+
*/
|
|
9327
|
+
const activeProjectionNodes = new WeakMap();
|
|
9328
|
+
function ensureScaleCorrectors() {
|
|
9329
|
+
if (scaleCorrectorAdded)
|
|
9330
|
+
return;
|
|
9331
|
+
scaleCorrectorAdded = true;
|
|
9332
|
+
addScaleCorrector({
|
|
9333
|
+
borderRadius: {
|
|
9334
|
+
...correctBorderRadius,
|
|
9335
|
+
applyTo: [
|
|
9336
|
+
"borderTopLeftRadius",
|
|
9337
|
+
"borderTopRightRadius",
|
|
9338
|
+
"borderBottomLeftRadius",
|
|
9339
|
+
"borderBottomRightRadius",
|
|
9340
|
+
],
|
|
9341
|
+
},
|
|
9342
|
+
borderTopLeftRadius: correctBorderRadius,
|
|
9343
|
+
borderTopRightRadius: correctBorderRadius,
|
|
9344
|
+
borderBottomLeftRadius: correctBorderRadius,
|
|
9345
|
+
borderBottomRightRadius: correctBorderRadius,
|
|
9346
|
+
boxShadow: correctBoxShadow,
|
|
9347
|
+
});
|
|
9348
|
+
}
|
|
9349
|
+
/**
|
|
9350
|
+
* Get DOM depth of an element
|
|
9351
|
+
*/
|
|
9352
|
+
function getDepth(element) {
|
|
9353
|
+
let depth = 0;
|
|
9354
|
+
let current = element.parentElement;
|
|
9355
|
+
while (current) {
|
|
9356
|
+
depth++;
|
|
9357
|
+
current = current.parentElement;
|
|
9358
|
+
}
|
|
9359
|
+
return depth;
|
|
9360
|
+
}
|
|
9361
|
+
/**
|
|
9362
|
+
* Find the closest projection parent for an element
|
|
9363
|
+
*/
|
|
9364
|
+
function findProjectionParent(element, nodeCache) {
|
|
9365
|
+
let parent = element.parentElement;
|
|
9366
|
+
while (parent) {
|
|
9367
|
+
const node = nodeCache.get(parent);
|
|
9368
|
+
if (node)
|
|
9369
|
+
return node;
|
|
9370
|
+
parent = parent.parentElement;
|
|
9371
|
+
}
|
|
9372
|
+
return undefined;
|
|
9373
|
+
}
|
|
9374
|
+
/**
|
|
9375
|
+
* Create or reuse a projection node for an element
|
|
9376
|
+
*/
|
|
9377
|
+
function createProjectionNode(element, parent, options, transition) {
|
|
9378
|
+
// Check for existing active node - reuse it to preserve animation state
|
|
9379
|
+
const existingNode = activeProjectionNodes.get(element);
|
|
9380
|
+
if (existingNode) {
|
|
9381
|
+
const visualElement = existingNode.options.visualElement;
|
|
9382
|
+
// Update transition options for the new animation
|
|
9383
|
+
const nodeTransition = transition
|
|
9384
|
+
? { duration: transition.duration, ease: transition.ease }
|
|
9385
|
+
: { duration: 0.3, ease: "easeOut" };
|
|
9386
|
+
existingNode.setOptions({
|
|
9387
|
+
...existingNode.options,
|
|
9388
|
+
animate: true,
|
|
9389
|
+
transition: nodeTransition,
|
|
9390
|
+
...options,
|
|
9391
|
+
});
|
|
9392
|
+
// Re-mount the node if it was previously unmounted
|
|
9393
|
+
// This re-adds it to root.nodes so didUpdate() will process it
|
|
9394
|
+
if (!existingNode.instance) {
|
|
9395
|
+
existingNode.mount(element);
|
|
9396
|
+
}
|
|
9397
|
+
return { node: existingNode, visualElement };
|
|
9398
|
+
}
|
|
9399
|
+
// No existing node - create a new one
|
|
9400
|
+
const latestValues = {};
|
|
9401
|
+
const visualElement = new HTMLVisualElement({
|
|
9402
|
+
visualState: {
|
|
9403
|
+
latestValues,
|
|
9404
|
+
renderState: {
|
|
9405
|
+
transformOrigin: {},
|
|
9406
|
+
transform: {},
|
|
9407
|
+
style: {},
|
|
9408
|
+
vars: {},
|
|
9409
|
+
},
|
|
9410
|
+
},
|
|
9411
|
+
presenceContext: null,
|
|
9412
|
+
props: {},
|
|
9413
|
+
});
|
|
9414
|
+
const node = new HTMLProjectionNode(latestValues, parent);
|
|
9415
|
+
// Convert AnimationOptions to transition format for the projection system
|
|
9416
|
+
const nodeTransition = transition
|
|
9417
|
+
? { duration: transition.duration, ease: transition.ease }
|
|
9418
|
+
: { duration: 0.3, ease: "easeOut" };
|
|
9419
|
+
node.setOptions({
|
|
9420
|
+
visualElement,
|
|
9421
|
+
layout: true,
|
|
9422
|
+
animate: true,
|
|
9423
|
+
transition: nodeTransition,
|
|
9424
|
+
...options,
|
|
9425
|
+
});
|
|
9426
|
+
node.mount(element);
|
|
9427
|
+
visualElement.projection = node;
|
|
9428
|
+
// Track this node as the active one for this element
|
|
9429
|
+
activeProjectionNodes.set(element, node);
|
|
9430
|
+
return { node, visualElement };
|
|
9431
|
+
}
|
|
9432
|
+
/**
|
|
9433
|
+
* Build a projection tree from a list of elements
|
|
9434
|
+
*/
|
|
9435
|
+
function buildProjectionTree(elements, existingContext, options) {
|
|
9436
|
+
ensureScaleCorrectors();
|
|
9437
|
+
const nodes = existingContext?.nodes ?? new Map();
|
|
9438
|
+
const visualElements = existingContext?.visualElements ?? new Map();
|
|
9439
|
+
const group = existingContext?.group ?? nodeGroup();
|
|
9440
|
+
const defaultTransition = options?.defaultTransition;
|
|
9441
|
+
const sharedTransitions = options?.sharedTransitions;
|
|
9442
|
+
// Sort elements by DOM depth (parents before children)
|
|
9443
|
+
const sorted = [...elements].sort((a, b) => getDepth(a) - getDepth(b));
|
|
9444
|
+
let root = existingContext?.root;
|
|
9445
|
+
for (const element of sorted) {
|
|
9446
|
+
// Skip if already has a node
|
|
9447
|
+
if (nodes.has(element))
|
|
9448
|
+
continue;
|
|
9449
|
+
const parent = findProjectionParent(element, nodes);
|
|
9450
|
+
const layoutId = getLayoutId(element);
|
|
9451
|
+
const layoutMode = element.getAttribute("data-layout");
|
|
9452
|
+
const nodeOptions = {
|
|
9453
|
+
layoutId: layoutId ?? undefined,
|
|
9454
|
+
animationType: parseLayoutMode(layoutMode),
|
|
9455
|
+
};
|
|
9456
|
+
// Use layoutId-specific transition if available, otherwise use default
|
|
9457
|
+
const transition = layoutId && sharedTransitions?.get(layoutId)
|
|
9458
|
+
? sharedTransitions.get(layoutId)
|
|
9459
|
+
: defaultTransition;
|
|
9460
|
+
const { node, visualElement } = createProjectionNode(element, parent, nodeOptions, transition);
|
|
9461
|
+
nodes.set(element, node);
|
|
9462
|
+
visualElements.set(element, visualElement);
|
|
9463
|
+
group.add(node);
|
|
9464
|
+
if (!root) {
|
|
9465
|
+
root = node.root;
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9315
9468
|
return {
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
source: projection.id,
|
|
9469
|
+
nodes,
|
|
9470
|
+
visualElements,
|
|
9471
|
+
group,
|
|
9472
|
+
root: root,
|
|
9321
9473
|
};
|
|
9322
9474
|
}
|
|
9475
|
+
/**
|
|
9476
|
+
* Parse the data-layout attribute value
|
|
9477
|
+
*/
|
|
9478
|
+
function parseLayoutMode(value) {
|
|
9479
|
+
if (value === "position")
|
|
9480
|
+
return "position";
|
|
9481
|
+
if (value === "size")
|
|
9482
|
+
return "size";
|
|
9483
|
+
if (value === "preserve-aspect")
|
|
9484
|
+
return "preserve-aspect";
|
|
9485
|
+
return "both";
|
|
9486
|
+
}
|
|
9487
|
+
/**
|
|
9488
|
+
* Clean up projection nodes for specific elements.
|
|
9489
|
+
* If elementsToCleanup is provided, only those elements are cleaned up.
|
|
9490
|
+
* If not provided, all nodes are cleaned up.
|
|
9491
|
+
*
|
|
9492
|
+
* This allows persisting elements to keep their nodes between animations,
|
|
9493
|
+
* matching React's behavior where nodes persist for elements that remain in the DOM.
|
|
9494
|
+
*/
|
|
9495
|
+
function cleanupProjectionTree(context, elementsToCleanup) {
|
|
9496
|
+
const elementsToProcess = elementsToCleanup
|
|
9497
|
+
? [...context.nodes.entries()].filter(([el]) => elementsToCleanup.has(el))
|
|
9498
|
+
: [...context.nodes.entries()];
|
|
9499
|
+
for (const [element, node] of elementsToProcess) {
|
|
9500
|
+
context.group.remove(node);
|
|
9501
|
+
node.unmount();
|
|
9502
|
+
// Only clear from activeProjectionNodes if this is still the active node.
|
|
9503
|
+
// A newer animation might have already taken over.
|
|
9504
|
+
if (activeProjectionNodes.get(element) === node) {
|
|
9505
|
+
activeProjectionNodes.delete(element);
|
|
9506
|
+
}
|
|
9507
|
+
context.nodes.delete(element);
|
|
9508
|
+
context.visualElements.delete(element);
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
|
|
9323
9512
|
class LayoutAnimationBuilder {
|
|
9324
9513
|
constructor(scope, updateDom, defaultOptions) {
|
|
9325
9514
|
this.sharedTransitions = new Map();
|
|
9326
9515
|
this.notifyReady = noop;
|
|
9327
|
-
this.
|
|
9516
|
+
this.executed = false;
|
|
9328
9517
|
this.scope = scope;
|
|
9329
9518
|
this.updateDom = updateDom;
|
|
9330
9519
|
this.defaultOptions = defaultOptions;
|
|
9331
|
-
this.readyPromise = new Promise((resolve
|
|
9520
|
+
this.readyPromise = new Promise((resolve) => {
|
|
9332
9521
|
this.notifyReady = resolve;
|
|
9333
|
-
this.rejectReady = reject;
|
|
9334
|
-
});
|
|
9335
|
-
frame.postRender(() => {
|
|
9336
|
-
this.start().then(this.notifyReady).catch(this.rejectReady);
|
|
9337
9522
|
});
|
|
9523
|
+
// Queue execution on microtask to allow builder methods to be called
|
|
9524
|
+
queueMicrotask(() => this.execute());
|
|
9338
9525
|
}
|
|
9339
|
-
shared(id,
|
|
9340
|
-
this.sharedTransitions.set(id,
|
|
9526
|
+
shared(id, options) {
|
|
9527
|
+
this.sharedTransitions.set(id, options);
|
|
9341
9528
|
return this;
|
|
9342
9529
|
}
|
|
9343
|
-
then(
|
|
9344
|
-
return this.readyPromise.then(
|
|
9530
|
+
then(onfulfilled, onrejected) {
|
|
9531
|
+
return this.readyPromise.then(onfulfilled, onrejected);
|
|
9345
9532
|
}
|
|
9346
|
-
async
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
if (!instance || !resumeFromInstance)
|
|
9378
|
-
return;
|
|
9379
|
-
if (!("style" in instance))
|
|
9380
|
-
return;
|
|
9381
|
-
const currentTransform = instance.style.transform;
|
|
9382
|
-
const resumeFromTransform = resumeFromInstance.style.transform;
|
|
9383
|
-
if (currentTransform &&
|
|
9384
|
-
resumeFromTransform &&
|
|
9385
|
-
currentTransform === resumeFromTransform) {
|
|
9386
|
-
instance.style.transform = "";
|
|
9387
|
-
instance.style.transformOrigin = "";
|
|
9388
|
-
}
|
|
9389
|
-
});
|
|
9390
|
-
afterRecords.forEach(({ projection }) => {
|
|
9391
|
-
projection.isPresent = true;
|
|
9392
|
-
});
|
|
9393
|
-
const root = getProjectionRoot(afterRecords, beforeRecords);
|
|
9394
|
-
root?.didUpdate();
|
|
9395
|
-
await new Promise((resolve) => {
|
|
9396
|
-
frame.postRender(() => resolve());
|
|
9397
|
-
});
|
|
9398
|
-
const animations = collectAnimations(afterRecords, exitRecords);
|
|
9399
|
-
const animation = new GroupAnimation(animations);
|
|
9400
|
-
if (exitRecords.length) {
|
|
9401
|
-
const cleanup = () => {
|
|
9402
|
-
exitRecords.forEach(({ element, visualElement }) => {
|
|
9403
|
-
if (element.isConnected) {
|
|
9404
|
-
element.remove();
|
|
9405
|
-
}
|
|
9406
|
-
visualElement.unmount();
|
|
9407
|
-
visualElementStore.delete(element);
|
|
9408
|
-
});
|
|
9409
|
-
};
|
|
9410
|
-
animation.finished.then(cleanup, cleanup);
|
|
9533
|
+
async execute() {
|
|
9534
|
+
if (this.executed)
|
|
9535
|
+
return;
|
|
9536
|
+
this.executed = true;
|
|
9537
|
+
let context;
|
|
9538
|
+
// Phase 1: Pre-mutation - Build projection tree and take snapshots
|
|
9539
|
+
const beforeElements = getLayoutElements(this.scope);
|
|
9540
|
+
if (beforeElements.length > 0) {
|
|
9541
|
+
context = buildProjectionTree(beforeElements, undefined, this.getBuildOptions());
|
|
9542
|
+
context.root.startUpdate();
|
|
9543
|
+
for (const node of context.nodes.values()) {
|
|
9544
|
+
node.isLayoutDirty = false;
|
|
9545
|
+
node.willUpdate();
|
|
9546
|
+
}
|
|
9547
|
+
}
|
|
9548
|
+
// Phase 2: Execute DOM update
|
|
9549
|
+
this.updateDom();
|
|
9550
|
+
// Phase 3: Post-mutation - Compare before/after elements
|
|
9551
|
+
const afterElements = getLayoutElements(this.scope);
|
|
9552
|
+
const beforeSet = new Set(beforeElements);
|
|
9553
|
+
const afterSet = new Set(afterElements);
|
|
9554
|
+
const entering = afterElements.filter((el) => !beforeSet.has(el));
|
|
9555
|
+
const exiting = beforeElements.filter((el) => !afterSet.has(el));
|
|
9556
|
+
// Build projection nodes for entering elements
|
|
9557
|
+
if (entering.length > 0) {
|
|
9558
|
+
context = buildProjectionTree(entering, context, this.getBuildOptions());
|
|
9559
|
+
}
|
|
9560
|
+
// No layout elements - return empty animation
|
|
9561
|
+
if (!context) {
|
|
9562
|
+
this.notifyReady(new GroupAnimation([]));
|
|
9563
|
+
return;
|
|
9411
9564
|
}
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
const recordMap = new Map();
|
|
9417
|
-
for (const element of elements) {
|
|
9418
|
-
const parentRecord = findParentRecord(element, recordMap, this.scope);
|
|
9419
|
-
const { layout, layoutId } = readLayoutAttributes(element);
|
|
9420
|
-
const override = layoutId
|
|
9421
|
-
? this.sharedTransitions.get(layoutId)
|
|
9422
|
-
: undefined;
|
|
9423
|
-
const transition = override || this.defaultOptions;
|
|
9424
|
-
const record = getOrCreateRecord(element, parentRecord?.projection, {
|
|
9425
|
-
layout,
|
|
9426
|
-
layoutId,
|
|
9427
|
-
animationType: typeof layout === "string" ? layout : "both",
|
|
9428
|
-
transition: transition,
|
|
9429
|
-
});
|
|
9430
|
-
recordMap.set(element, record);
|
|
9431
|
-
records.push(record);
|
|
9565
|
+
// Handle shared elements
|
|
9566
|
+
for (const element of exiting) {
|
|
9567
|
+
const node = context.nodes.get(element);
|
|
9568
|
+
node?.getStack()?.remove(node);
|
|
9432
9569
|
}
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9570
|
+
for (const element of entering) {
|
|
9571
|
+
context.nodes.get(element)?.promote();
|
|
9572
|
+
}
|
|
9573
|
+
// Phase 4: Animate
|
|
9574
|
+
context.root.didUpdate();
|
|
9575
|
+
await new Promise((resolve) => frame.postRender(() => resolve()));
|
|
9576
|
+
const animations = [];
|
|
9577
|
+
for (const node of context.nodes.values()) {
|
|
9578
|
+
if (node.currentAnimation) {
|
|
9579
|
+
animations.push(node.currentAnimation);
|
|
9580
|
+
}
|
|
9581
|
+
}
|
|
9582
|
+
const groupAnimation = new GroupAnimation(animations);
|
|
9583
|
+
groupAnimation.finished.then(() => {
|
|
9584
|
+
// Only clean up nodes for elements no longer in the document.
|
|
9585
|
+
// Elements still in DOM keep their nodes so subsequent animations
|
|
9586
|
+
// can use the stored position snapshots (A→B→A pattern).
|
|
9587
|
+
const elementsToCleanup = new Set();
|
|
9588
|
+
for (const element of context.nodes.keys()) {
|
|
9589
|
+
if (!document.contains(element)) {
|
|
9590
|
+
elementsToCleanup.add(element);
|
|
9591
|
+
}
|
|
9453
9592
|
}
|
|
9454
|
-
|
|
9593
|
+
cleanupProjectionTree(context, elementsToCleanup);
|
|
9455
9594
|
});
|
|
9456
|
-
|
|
9595
|
+
this.notifyReady(groupAnimation);
|
|
9596
|
+
}
|
|
9597
|
+
getBuildOptions() {
|
|
9598
|
+
return {
|
|
9599
|
+
defaultTransition: this.defaultOptions || {
|
|
9600
|
+
duration: 0.3,
|
|
9601
|
+
ease: "easeOut",
|
|
9602
|
+
},
|
|
9603
|
+
sharedTransitions: this.sharedTransitions.size > 0
|
|
9604
|
+
? this.sharedTransitions
|
|
9605
|
+
: undefined,
|
|
9606
|
+
};
|
|
9457
9607
|
}
|
|
9458
9608
|
}
|
|
9609
|
+
/**
|
|
9610
|
+
* Parse arguments for animateLayout overloads
|
|
9611
|
+
*/
|
|
9459
9612
|
function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
|
|
9460
9613
|
// animateLayout(updateDom)
|
|
9461
9614
|
if (typeof scopeOrUpdateDom === "function") {
|
|
@@ -9469,125 +9622,11 @@
|
|
|
9469
9622
|
const elements = resolveElements(scopeOrUpdateDom);
|
|
9470
9623
|
const scope = elements[0] || document;
|
|
9471
9624
|
return {
|
|
9472
|
-
scope,
|
|
9625
|
+
scope: scope instanceof Document ? scope : scope,
|
|
9473
9626
|
updateDom: updateDomOrOptions,
|
|
9474
9627
|
defaultOptions: options,
|
|
9475
9628
|
};
|
|
9476
9629
|
}
|
|
9477
|
-
function collectLayoutElements(scope) {
|
|
9478
|
-
const elements = Array.from(scope.querySelectorAll(layoutSelector));
|
|
9479
|
-
if (scope instanceof Element && scope.matches(layoutSelector)) {
|
|
9480
|
-
if (!elements.includes(scope)) {
|
|
9481
|
-
elements.unshift(scope);
|
|
9482
|
-
}
|
|
9483
|
-
}
|
|
9484
|
-
return elements;
|
|
9485
|
-
}
|
|
9486
|
-
function readLayoutAttributes(element) {
|
|
9487
|
-
const layoutId = element.getAttribute("data-layout-id") || undefined;
|
|
9488
|
-
const rawLayout = element.getAttribute("data-layout");
|
|
9489
|
-
let layout;
|
|
9490
|
-
if (rawLayout === "" || rawLayout === "true") {
|
|
9491
|
-
layout = true;
|
|
9492
|
-
}
|
|
9493
|
-
else if (rawLayout) {
|
|
9494
|
-
layout = rawLayout;
|
|
9495
|
-
}
|
|
9496
|
-
return {
|
|
9497
|
-
layout,
|
|
9498
|
-
layoutId,
|
|
9499
|
-
layoutExit: element.hasAttribute("data-layout-exit"),
|
|
9500
|
-
};
|
|
9501
|
-
}
|
|
9502
|
-
function createVisualState() {
|
|
9503
|
-
return {
|
|
9504
|
-
latestValues: {},
|
|
9505
|
-
renderState: {
|
|
9506
|
-
transform: {},
|
|
9507
|
-
transformOrigin: {},
|
|
9508
|
-
style: {},
|
|
9509
|
-
vars: {},
|
|
9510
|
-
},
|
|
9511
|
-
};
|
|
9512
|
-
}
|
|
9513
|
-
function getOrCreateRecord(element, parentProjection, projectionOptions) {
|
|
9514
|
-
const existing = visualElementStore.get(element);
|
|
9515
|
-
const visualElement = existing ??
|
|
9516
|
-
new HTMLVisualElement({
|
|
9517
|
-
props: {},
|
|
9518
|
-
presenceContext: null,
|
|
9519
|
-
visualState: createVisualState(),
|
|
9520
|
-
}, { allowProjection: true });
|
|
9521
|
-
if (!existing || !visualElement.projection) {
|
|
9522
|
-
visualElement.projection = new HTMLProjectionNode(visualElement.latestValues, parentProjection);
|
|
9523
|
-
}
|
|
9524
|
-
visualElement.projection.setOptions({
|
|
9525
|
-
...projectionOptions,
|
|
9526
|
-
visualElement,
|
|
9527
|
-
});
|
|
9528
|
-
if (!visualElement.current) {
|
|
9529
|
-
visualElement.mount(element);
|
|
9530
|
-
}
|
|
9531
|
-
if (!existing) {
|
|
9532
|
-
visualElementStore.set(element, visualElement);
|
|
9533
|
-
}
|
|
9534
|
-
return {
|
|
9535
|
-
element,
|
|
9536
|
-
visualElement,
|
|
9537
|
-
projection: visualElement.projection,
|
|
9538
|
-
};
|
|
9539
|
-
}
|
|
9540
|
-
function findParentRecord(element, recordMap, scope) {
|
|
9541
|
-
let parent = element.parentElement;
|
|
9542
|
-
while (parent) {
|
|
9543
|
-
const record = recordMap.get(parent);
|
|
9544
|
-
if (record)
|
|
9545
|
-
return record;
|
|
9546
|
-
if (parent === scope)
|
|
9547
|
-
break;
|
|
9548
|
-
parent = parent.parentElement;
|
|
9549
|
-
}
|
|
9550
|
-
return undefined;
|
|
9551
|
-
}
|
|
9552
|
-
function collectExitCandidates(records) {
|
|
9553
|
-
const exitCandidates = new Map();
|
|
9554
|
-
records.forEach((record) => {
|
|
9555
|
-
const { layoutExit } = readLayoutAttributes(record.element);
|
|
9556
|
-
if (!layoutExit)
|
|
9557
|
-
return;
|
|
9558
|
-
exitCandidates.set(record.element, {
|
|
9559
|
-
...record,
|
|
9560
|
-
parent: record.element.parentNode,
|
|
9561
|
-
nextSibling: record.element.nextSibling,
|
|
9562
|
-
});
|
|
9563
|
-
});
|
|
9564
|
-
return exitCandidates;
|
|
9565
|
-
}
|
|
9566
|
-
function reinstateExitElement(record) {
|
|
9567
|
-
if (!record.parent)
|
|
9568
|
-
return;
|
|
9569
|
-
if (record.nextSibling && record.nextSibling.parentNode === record.parent) {
|
|
9570
|
-
record.parent.insertBefore(record.element, record.nextSibling);
|
|
9571
|
-
}
|
|
9572
|
-
else {
|
|
9573
|
-
record.parent.appendChild(record.element);
|
|
9574
|
-
}
|
|
9575
|
-
}
|
|
9576
|
-
function getProjectionRoot(afterRecords, beforeRecords) {
|
|
9577
|
-
const record = afterRecords[0] || beforeRecords[0];
|
|
9578
|
-
return record?.projection.root;
|
|
9579
|
-
}
|
|
9580
|
-
function collectAnimations(afterRecords, exitRecords) {
|
|
9581
|
-
const animations = new Set();
|
|
9582
|
-
const addAnimation = (record) => {
|
|
9583
|
-
const animation = record.projection.currentAnimation;
|
|
9584
|
-
if (animation)
|
|
9585
|
-
animations.add(animation);
|
|
9586
|
-
};
|
|
9587
|
-
afterRecords.forEach(addAnimation);
|
|
9588
|
-
exitRecords.forEach(addAnimation);
|
|
9589
|
-
return Array.from(animations);
|
|
9590
|
-
}
|
|
9591
9630
|
|
|
9592
9631
|
/**
|
|
9593
9632
|
* @deprecated
|
|
@@ -10481,7 +10520,7 @@
|
|
|
10481
10520
|
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
|
|
10482
10521
|
function scrollInfo(onScroll, { container = document.scrollingElement, ...options } = {}) {
|
|
10483
10522
|
if (!container)
|
|
10484
|
-
return noop
|
|
10523
|
+
return noop;
|
|
10485
10524
|
let containerHandlers = onScrollHandlers.get(container);
|
|
10486
10525
|
/**
|
|
10487
10526
|
* Get the onScroll handlers for this container.
|
|
@@ -10609,7 +10648,7 @@
|
|
|
10609
10648
|
|
|
10610
10649
|
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
|
|
10611
10650
|
if (!container)
|
|
10612
|
-
return noop
|
|
10651
|
+
return noop;
|
|
10613
10652
|
const optionsWithDefaults = { axis, container, ...options };
|
|
10614
10653
|
return typeof onScroll === "function"
|
|
10615
10654
|
? attachToFunction(onScroll, optionsWithDefaults)
|
|
@@ -10770,7 +10809,7 @@
|
|
|
10770
10809
|
exports.createBox = createBox;
|
|
10771
10810
|
exports.createDelta = createDelta;
|
|
10772
10811
|
exports.createGeneratorEasing = createGeneratorEasing;
|
|
10773
|
-
exports.createProjectionNode = createProjectionNode;
|
|
10812
|
+
exports.createProjectionNode = createProjectionNode$1;
|
|
10774
10813
|
exports.createRenderBatcher = createRenderBatcher;
|
|
10775
10814
|
exports.createScopedAnimate = createScopedAnimate;
|
|
10776
10815
|
exports.cubicBezier = cubicBezier;
|
|
@@ -10884,7 +10923,7 @@
|
|
|
10884
10923
|
exports.motionValue = motionValue;
|
|
10885
10924
|
exports.moveItem = moveItem;
|
|
10886
10925
|
exports.nodeGroup = nodeGroup;
|
|
10887
|
-
exports.noop = noop
|
|
10926
|
+
exports.noop = noop;
|
|
10888
10927
|
exports.number = number;
|
|
10889
10928
|
exports.numberValueTypes = numberValueTypes;
|
|
10890
10929
|
exports.observeTimeline = observeTimeline;
|