diff --git a/src/core/drive/navigator.js b/src/core/drive/navigator.js
index 8210c7471..4924344c7 100644
--- a/src/core/drive/navigator.js
+++ b/src/core/drive/navigator.js
@@ -99,7 +99,7 @@ export class Navigator {
} else {
await this.view.renderPage(snapshot, false, true, this.currentVisit)
}
- if(!snapshot.shouldPreserveScrollPosition) {
+ if (snapshot.refreshScroll !== "preserve") {
this.view.scrollToTop()
}
this.view.clearSnapshotCache()
diff --git a/src/core/drive/page_snapshot.js b/src/core/drive/page_snapshot.js
index 58a5a75a9..56e0c63f1 100644
--- a/src/core/drive/page_snapshot.js
+++ b/src/core/drive/page_snapshot.js
@@ -74,12 +74,12 @@ export class PageSnapshot extends Snapshot {
return this.headSnapshot.getMetaValue("view-transition") === "same-origin"
}
- get shouldMorphPage() {
- return this.getSetting("refresh-method") === "morph"
+ get refreshMethod() {
+ return this.getSetting("refresh-method")
}
- get shouldPreserveScrollPosition() {
- return this.getSetting("refresh-scroll") === "preserve"
+ get refreshScroll() {
+ return this.getSetting("refresh-scroll")
}
// Private
diff --git a/src/core/drive/page_view.js b/src/core/drive/page_view.js
index 1bcb04134..e746e87b3 100644
--- a/src/core/drive/page_view.js
+++ b/src/core/drive/page_view.js
@@ -16,7 +16,8 @@ export class PageView extends View {
}
renderPage(snapshot, isPreview = false, willRender = true, visit) {
- const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage
+ const refreshMethod = visit?.refresh?.method || this.snapshot.refreshMethod
+ const shouldMorphPage = this.isPageRefresh(visit) && refreshMethod === "morph"
const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer
const renderer = new rendererClass(this.snapshot, snapshot, rendererClass.renderElement, isPreview, willRender)
@@ -60,7 +61,8 @@ export class PageView extends View {
}
shouldPreserveScrollPosition(visit) {
- return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition
+ const refreshScroll = visit?.refresh?.scroll || this.snapshot.refreshScroll
+ return this.isPageRefresh(visit) && refreshScroll === "preserve"
}
get snapshot() {
diff --git a/src/core/drive/visit.js b/src/core/drive/visit.js
index ec7565979..7c3e73b46 100644
--- a/src/core/drive/visit.js
+++ b/src/core/drive/visit.js
@@ -12,7 +12,8 @@ const defaultOptions = {
willRender: true,
updateHistory: true,
shouldCacheSnapshot: true,
- acceptsStreamResponse: false
+ acceptsStreamResponse: false,
+ refresh: {}
}
export const TimingMetric = {
@@ -72,7 +73,8 @@ export class Visit {
updateHistory,
shouldCacheSnapshot,
acceptsStreamResponse,
- direction
+ direction,
+ refresh
} = {
...defaultOptions,
...options
@@ -92,6 +94,7 @@ export class Visit {
this.shouldCacheSnapshot = shouldCacheSnapshot
this.acceptsStreamResponse = acceptsStreamResponse
this.direction = direction || Direction[action]
+ this.refresh = refresh
}
get adapter() {
diff --git a/src/core/session.js b/src/core/session.js
index 1047d4463..3d268c28b 100644
--- a/src/core/session.js
+++ b/src/core/session.js
@@ -107,10 +107,11 @@ export class Session {
}
}
- refresh(url, requestId) {
+ refresh(url, options = {}) {
+ const { method, requestId, scroll } = options
const isRecentRequest = requestId && this.recentRequests.has(requestId)
if (!isRecentRequest && !this.navigator.currentVisit) {
- this.visit(url, { action: "replace", shouldCacheSnapshot: false })
+ this.visit(url, { action: "replace", shouldCacheSnapshot: false, refresh: { method, scroll } })
}
}
diff --git a/src/core/streams/stream_actions.js b/src/core/streams/stream_actions.js
index a038add0d..8fdfc26be 100644
--- a/src/core/streams/stream_actions.js
+++ b/src/core/streams/stream_actions.js
@@ -50,6 +50,10 @@ export const StreamActions = {
},
refresh() {
- session.refresh(this.baseURI, this.requestId)
+ const method = this.getAttribute("method")
+ const requestId = this.requestId
+ const scroll = this.getAttribute("scroll")
+
+ session.refresh(this.baseURI, { method, requestId, scroll })
}
}
diff --git a/src/tests/functional/page_refresh_tests.js b/src/tests/functional/page_refresh_tests.js
index 0b6945063..0ce44b296 100644
--- a/src/tests/functional/page_refresh_tests.js
+++ b/src/tests/functional/page_refresh_tests.js
@@ -177,6 +177,13 @@ test("uses morphing to update remote frames marked with refresh='morph'", async
await expect(page.locator("#refresh-reload")).toHaveText("Loaded reloadable frame")
})
+test("overrides the meta value to render with replace when the Turbo Stream has [method=replace] attribute", async ({ page }) => {
+ await page.goto("/src/tests/fixtures/page_refresh.html")
+
+ await page.evaluate(() => document.body.insertAdjacentHTML("beforeend", ``))
+ await nextEventNamed(page, "turbo:render", { renderMethod: "replace" })
+})
+
test("don't refresh frames contained in [data-turbo-permanent] elements", async ({ page }) => {
await page.goto("/src/tests/fixtures/page_refresh.html")
@@ -242,6 +249,18 @@ test("it preserves the scroll position when the turbo-refresh-scroll meta tag is
await assertPageScroll(page, 10, 10)
})
+test("overrides the meta value to reset the scroll position when the Turbo Stream has [scroll=reset] attribute", async ({ page }) => {
+ await page.goto("/src/tests/fixtures/page_refresh.html")
+
+ await page.evaluate(() => window.scrollTo(10, 10))
+ await assertPageScroll(page, 10, 10)
+
+ await page.evaluate(() => document.body.insertAdjacentHTML("beforeend", ``))
+ await nextEventNamed(page, "turbo:render", { renderMethod: "morph" })
+
+ await assertPageScroll(page, 0, 0)
+})
+
test("it does not preserve the scroll position on regular 'advance' navigations, despite of using a 'preserve' option", async ({ page }) => {
await page.goto("/src/tests/fixtures/page_refresh.html")