Skip to content

Commit e0ab2cd

Browse files
committed
Don't autofocus when rendering page refreshes with morphing
When rendering a page refresh with morphing, if the page has an element with [autofocus], Turbo was focusing on it (default behavior inherited from `PageRenderer`). With this change, it will only autofocus when no other element in the page has the focus. This is meant to solve the problem where, you are writing on a form and it loses the focus because a broadcasted page refresh arrives.
1 parent 89be8e4 commit e0ab2cd

File tree

5 files changed

+62
-6
lines changed

5 files changed

+62
-6
lines changed

src/core/drive/morph_renderer.js

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export class MorphRenderer extends PageRenderer {
1111
return "morph"
1212
}
1313

14+
get shouldAutofocus() {
15+
return document.activeElement == null || document.activeElement == document.body
16+
}
17+
1418
// Private
1519

1620
async #morphBody() {

src/core/renderer.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export class Renderer {
1616
return true
1717
}
1818

19+
get shouldAutofocus() {
20+
return true
21+
}
22+
1923
get reloadReason() {
2024
return
2125
}
@@ -40,9 +44,11 @@ export class Renderer {
4044
}
4145

4246
focusFirstAutofocusableElement() {
43-
const element = this.connectedSnapshot.firstAutofocusableElement
44-
if (element) {
45-
element.focus()
47+
if (this.shouldAutofocus) {
48+
const element = this.connectedSnapshot.firstAutofocusableElement
49+
if (element) {
50+
element.focus()
51+
}
4652
}
4753
}
4854

src/tests/fixtures/page_refresh.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,10 @@ <h3>Element with Stimulus controller</h3>
110110
<input>
111111
</div>
112112

113-
<form method="get" data-turbo-action="replace" oninput="this.requestSubmit()">
113+
<form id="with-autofocus" method="get" data-turbo-action="replace" oninput="this.requestSubmit()">
114114
<label>
115115
Search
116-
<input name="query">
116+
<input id="input-with-auto-focus" name="query" autofocus>
117117
</label>
118118
<button>Form with params to refresh the page</button>
119119
</form>

src/tests/functional/autofocus_tests.js

+46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect, test } from "@playwright/test"
2+
import { nextBeat, nextEventNamed } from "../helpers/page"
23

34
test.beforeEach(async ({ page }) => {
45
await page.goto("/src/tests/fixtures/autofocus.html")
@@ -74,3 +75,48 @@ test("receiving a Turbo Stream message with an [autofocus] element when an eleme
7475
})
7576
await expect(page.locator("#first-autofocus-element")).toBeFocused()
7677
})
78+
79+
test("focus on [autofocus] elements on page refreshes with morphing when no other element in the page has the focus", async ({ page }) => {
80+
await page.goto("/src/tests/fixtures/page_refresh.html")
81+
82+
const input = await page.locator("input[autofocus]")
83+
84+
await page.evaluate(() => {
85+
document.activeElement.blur()
86+
})
87+
88+
await nextBeat()
89+
expect(input).not.toBeFocused()
90+
91+
await page.evaluate(() => {
92+
document.querySelector("form#with-autofocus").requestSubmit()
93+
})
94+
95+
await nextEventNamed(page, "turbo:render", { renderMethod: "morph" })
96+
97+
await expect(input).toBeFocused()
98+
})
99+
100+
test("don't focus on [autofocus] elements on page refreshes with morphing when other element in the page has the focus", async ({ page }) => {
101+
await page.goto("/src/tests/fixtures/page_refresh.html")
102+
103+
const input = await page.locator("input[autofocus]")
104+
const link = await page.locator("#refresh-after-navigation-link")
105+
106+
await page.evaluate(() => {
107+
document.getElementById("refresh-after-navigation-link").focus()
108+
})
109+
110+
await nextBeat()
111+
expect(link).toBeFocused()
112+
expect(input).not.toBeFocused()
113+
114+
await page.evaluate(() => {
115+
document.querySelector("form#with-autofocus").requestSubmit()
116+
})
117+
118+
await nextEventNamed(page, "turbo:render", { renderMethod: "morph" })
119+
120+
expect(link).toBeFocused()
121+
expect(input).not.toBeFocused()
122+
})

src/tests/functional/page_refresh_tests.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ test("renders a page refresh with morphing when the paths are the same but searc
109109
await nextEventNamed(page, "turbo:render", { renderMethod: "morph" })
110110
})
111111

112-
test("renders a page refresh with morphing when the GET form paths are the same but search params are diferent", async ({ page }) => {
112+
test("renders a page refresh with morphing when the GET form paths are the same but search params are different", async ({ page }) => {
113113
await page.goto("/src/tests/fixtures/page_refresh.html")
114114

115115
const input = page.locator("form[method=get] input[name=query]")

0 commit comments

Comments
 (0)