Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions dev/html/public/animate-layout/shared-element-spa-navigation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<html>
<head>
<style>
body {
padding: 0;
margin: 0;
}

#container {
width: 400px;
height: 400px;
background-color: #f0f0f0;
position: relative;
}

.box-a {
width: 100px;
height: 100px;
background-color: #00cc88;
}

.box-b {
position: absolute;
top: 200px;
left: 200px;
width: 150px;
height: 150px;
background-color: #09f;
}

[data-layout-correct="false"] {
background: #dd1144 !important;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="container">
<div class="box-a" data-layout-id="hero"></div>
</div>

<script type="module" src="/src/imports/animate-layout.js"></script>
<script type="module" src="/src/imports/script-assert.js"></script>
<script type="module">
const { animateLayout, frame } = window.AnimateLayout
const { matchViewportBox } = window.Assert
const container = document.getElementById("container")

async function nextFrame() {
return new Promise((resolve) => frame.postRender(resolve))
}

async function runTest() {
// Simulate SPA navigation: externally remove the old element
// (as a router would), then use animateLayout to add a new
// element with the same layoutId.
container.innerHTML = ""

const controls1 = await animateLayout(
container,
() => {
container.innerHTML = `
<div class="box-b" data-layout-id="hero"></div>
`
},
{ duration: 0.01 }
)

await controls1

// Second navigation: repeat to verify no zombie accumulation
container.innerHTML = ""

const controls2 = await animateLayout(
container,
() => {
container.innerHTML = `
<div class="box-a" data-layout-id="hero"></div>
`
},
{ duration: 0.01 }
)

await controls2

await nextFrame()

// Verify no elements are marked as incorrectly laid out
const incorrect = document.querySelectorAll(
'[data-layout-correct="false"]'
)
if (incorrect.length > 0) {
throw new Error(
`Found ${incorrect.length} elements with data-layout-correct="false"`
)
}
}

runTest().catch(console.error)
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion dev/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"motion": "^12.33.1",
"next": "15.4.10",
"next": "15.5.10",
"react": "19.0.0",
"react-dom": "19.0.0"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["app-store-a-b-a.html","basic-position-change.html","interrupt-animation.html","modal-open-after-animate.html","modal-open-close-interrupt.html","modal-open-close-open-interrupt.html","modal-open-close-open.html","modal-open-close.html","modal-open-opacity.html","modal-open.html","repeat-animation.html","scale-correction.html","scope-with-data-layout.html","shared-element-a-ab-a.html","shared-element-a-b-a-replace.html","shared-element-a-b-a-reuse.html","shared-element-basic.html","shared-element-configured.html","shared-element-crossfade.html","shared-element-nested-children-bottom.html","shared-element-nested-children.html","shared-element-no-crossfade.html","shared-multiple-elements.html"]
["app-store-a-b-a.html","basic-position-change.html","interrupt-animation.html","modal-open-after-animate.html","modal-open-close-interrupt.html","modal-open-close-open-interrupt.html","modal-open-close-open.html","modal-open-close.html","modal-open-opacity.html","modal-open.html","repeat-animation.html","scale-correction.html","scope-with-data-layout.html","shared-element-a-ab-a.html","shared-element-a-b-a-replace.html","shared-element-a-b-a-reuse.html","shared-element-basic.html","shared-element-configured.html","shared-element-crossfade.html","shared-element-nested-children-bottom.html","shared-element-nested-children.html","shared-element-no-crossfade.html","shared-element-spa-navigation.html","shared-multiple-elements.html"]
40 changes: 28 additions & 12 deletions packages/motion-dom/src/projection/shared/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ export class NodeStack {

add(node: IProjectionNode) {
addUniqueItem(this.members, node)

for (let i = this.members.length - 1; i >= 0; i--) {
const m = this.members[i]
if (m === node || m === this.lead || m === this.prevLead) continue
const inst = m.instance as HTMLElement | undefined
if (inst && inst.isConnected === false && m.isPresent !== false && !m.snapshot) {
removeItem(this.members, m)
}
}

node.scheduleRender()
}

Expand All @@ -34,7 +44,8 @@ export class NodeStack {
let prevLead: IProjectionNode | undefined
for (let i = indexOfNode; i >= 0; i--) {
const member = this.members[i]
if (member.isPresent !== false) {
const inst = member.instance as HTMLElement | undefined
if (member.isPresent !== false && (!inst || inst.isConnected !== false)) {
prevLead = member
break
}
Expand Down Expand Up @@ -76,20 +87,25 @@ export class NodeStack {
prevDep === nextDep

if (!dependencyMatches) {
node.resumeFrom = prevLead
const prevInstance = prevLead.instance as HTMLElement | undefined
const isStale = prevInstance && prevInstance.isConnected === false && !prevLead.snapshot

if (preserveFollowOpacity) {
node.resumeFrom.preserveOpacity = true
}
if (!isStale) {
node.resumeFrom = prevLead

if (prevLead.snapshot) {
node.snapshot = prevLead.snapshot
node.snapshot.latestValues =
prevLead.animationValues || prevLead.latestValues
}
if (preserveFollowOpacity) {
node.resumeFrom.preserveOpacity = true
}

if (prevLead.snapshot) {
node.snapshot = prevLead.snapshot
node.snapshot.latestValues =
prevLead.animationValues || prevLead.latestValues
}

if (node.root && node.root.isUpdating) {
node.isLayoutDirty = true
if (node.root && node.root.isUpdating) {
node.isLayoutDirty = true
}
}
}

Expand Down
86 changes: 43 additions & 43 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2135,65 +2135,65 @@ __metadata:
languageName: node
linkType: hard

"@next/env@npm:15.4.10":
version: 15.4.10
resolution: "@next/env@npm:15.4.10"
checksum: b89dd1279a53c69d86630b63cb550cbacaed28b8e423f73c492581a17cb4c1bda4429ab3c19b3ec24c2a65f06e55d954b553c6553ee94eb2867c628a3d291e04
"@next/env@npm:15.5.10":
version: 15.5.10
resolution: "@next/env@npm:15.5.10"
checksum: cc481d2bd5a7cf97a0d3c093bb9a9b5359ad324c9eb26c89621c90f8d8d11a284fd05cdcc5358db973074fb65c0d9aa01ecbc464b181c9fe3b2bca0776a0fc54
languageName: node
linkType: hard

"@next/swc-darwin-arm64@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-darwin-arm64@npm:15.4.8"
"@next/swc-darwin-arm64@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-darwin-arm64@npm:15.5.7"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard

"@next/swc-darwin-x64@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-darwin-x64@npm:15.4.8"
"@next/swc-darwin-x64@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-darwin-x64@npm:15.5.7"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard

"@next/swc-linux-arm64-gnu@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-linux-arm64-gnu@npm:15.4.8"
"@next/swc-linux-arm64-gnu@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-linux-arm64-gnu@npm:15.5.7"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard

"@next/swc-linux-arm64-musl@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-linux-arm64-musl@npm:15.4.8"
"@next/swc-linux-arm64-musl@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-linux-arm64-musl@npm:15.5.7"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard

"@next/swc-linux-x64-gnu@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-linux-x64-gnu@npm:15.4.8"
"@next/swc-linux-x64-gnu@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-linux-x64-gnu@npm:15.5.7"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard

"@next/swc-linux-x64-musl@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-linux-x64-musl@npm:15.4.8"
"@next/swc-linux-x64-musl@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-linux-x64-musl@npm:15.5.7"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard

"@next/swc-win32-arm64-msvc@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-win32-arm64-msvc@npm:15.4.8"
"@next/swc-win32-arm64-msvc@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-win32-arm64-msvc@npm:15.5.7"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard

"@next/swc-win32-x64-msvc@npm:15.4.8":
version: 15.4.8
resolution: "@next/swc-win32-x64-msvc@npm:15.4.8"
"@next/swc-win32-x64-msvc@npm:15.5.7":
version: 15.5.7
resolution: "@next/swc-win32-x64-msvc@npm:15.5.7"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
Expand Down Expand Up @@ -11154,25 +11154,25 @@ __metadata:
resolution: "next-env@workspace:dev/next"
dependencies:
motion: ^12.33.1
next: 15.4.10
next: 15.5.10
react: 19.0.0
react-dom: 19.0.0
languageName: unknown
linkType: soft

"next@npm:15.4.10":
version: 15.4.10
resolution: "next@npm:15.4.10"
dependencies:
"@next/env": 15.4.10
"@next/swc-darwin-arm64": 15.4.8
"@next/swc-darwin-x64": 15.4.8
"@next/swc-linux-arm64-gnu": 15.4.8
"@next/swc-linux-arm64-musl": 15.4.8
"@next/swc-linux-x64-gnu": 15.4.8
"@next/swc-linux-x64-musl": 15.4.8
"@next/swc-win32-arm64-msvc": 15.4.8
"@next/swc-win32-x64-msvc": 15.4.8
"next@npm:15.5.10":
version: 15.5.10
resolution: "next@npm:15.5.10"
dependencies:
"@next/env": 15.5.10
"@next/swc-darwin-arm64": 15.5.7
"@next/swc-darwin-x64": 15.5.7
"@next/swc-linux-arm64-gnu": 15.5.7
"@next/swc-linux-arm64-musl": 15.5.7
"@next/swc-linux-x64-gnu": 15.5.7
"@next/swc-linux-x64-musl": 15.5.7
"@next/swc-win32-arm64-msvc": 15.5.7
"@next/swc-win32-x64-msvc": 15.5.7
"@swc/helpers": 0.5.15
caniuse-lite: ^1.0.30001579
postcss: 8.4.31
Expand Down Expand Up @@ -11215,7 +11215,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
checksum: 11f76f5de9f0e734dd3925a48821e29291d82191a099d2e35695d51acbcbd4ee965d2842748e4a95cfdf55f38e3edb2180fd3be4adbe36b01936a7130ad60a11
checksum: c60fc5eb6d2fc7868a110a713fc772e69dfb0b13a64dc6ddb3b79a37bfe88c870433959337693b33bc88c1086a0c42e25b6eafbb6e198c935e7e7bff213c40e9
languageName: node
linkType: hard

Expand Down
Loading