Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Focus [autofocus] on navigation and frame load #169

Merged
merged 4 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/core/drive/page_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {

finishRendering() {
super.finishRendering()
if (this.isPreview) {
this.focusFirstAutofocusableElement()
}
this.focusFirstAutofocusableElement()
javan marked this conversation as resolved.
Show resolved Hide resolved
}

get currentHeadSnapshot() {
Expand Down
6 changes: 5 additions & 1 deletion src/core/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,16 @@ export abstract class Renderer<E extends Element, S extends Snapshot<E> = Snapsh
}

focusFirstAutofocusableElement() {
const element = this.newSnapshot.firstAutofocusableElement
const element = this.connectedSnapshot.firstAutofocusableElement
if (elementIsFocusable(element)) {
element.focus()
}
}

get connectedSnapshot() {
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot
}

get currentElement() {
return this.currentSnapshot.element
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export class Snapshot<E extends Element = Element> {
}
}

get isConnected() {
return this.element.isConnected
}

get firstAutofocusableElement() {
return this.element.querySelector("[autofocus]")
}
Expand Down
25 changes: 25 additions & 0 deletions src/tests/fixtures/autofocus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Autofocus</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
<script src="/src/tests/fixtures/test.js"></script>
</head>
<body>
<h1>Autofocus</h1>

<button autofocus id="first-autofocus-element" type="button">First [autofocus]</button>
<button autofocus id="second-autofocus-element" type="button">Second [autofocus]</button>

<a id="frame-outer-link" href="/src/tests/fixtures/frames/form.html" data-turbo-frame="frame">Outer #frame link to frames/form.html</a>

<turbo-frame id="frame">
<a id="frame-inner-link" href="/src/tests/fixtures/frames/form.html">Inner #frame link to frames/form.html</a>
</turbo-frame>

<turbo-frame id="drives-frame" target="frame">
<a id="drives-frame-target-link" href="/src/tests/fixtures/frames/form.html">#drives-frame link to frames/form.html</a>
</turbo-frame>
</body>
</html>
2 changes: 1 addition & 1 deletion src/tests/fixtures/frames.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<body>
<h1>Frames</h1>

<turbo-frame id="frame">
<turbo-frame id="frame" data-loaded-from="/src/tests/fixtures/frames.html">
<h2>Frames: #frame</h2>
</turbo-frame>

Expand Down
4 changes: 3 additions & 1 deletion src/tests/fixtures/frames/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
<turbo-frame id="frame">
<form action="/__turbo/messages" method="post" class="stream">
<input type="hidden" name="content" value="Hello!">
<input type="submit">
<input id="frames-form-first-autofocus-element" autofocus type="submit">
</form>
<div id="messages">
<div class="message">Frame redirected</div>
</div>

<button type="button" id="frames-form-second-autofocus-element" autofocus>Second autofocus</button>
</turbo-frame>
</body>
</html>
2 changes: 1 addition & 1 deletion src/tests/fixtures/frames/frame.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
</head>
<body>
<turbo-frame id="frame">
<turbo-frame id="frame" data-loaded-from="/src/tests/fixtures/frames/frame.html">
<h2>Frame: Loaded</h2>
</turbo-frame>

Expand Down
1 change: 1 addition & 0 deletions src/tests/fixtures/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ <h1>Navigation</h1>
<svg width="600" height="100" viewbox="-300 -50 600 100"><text><a id="same-origin-link-inside-svg-element" href="/src/tests/fixtures/one.html">Same-origin link inside SVG element</a></text></svg>
<svg width="600" height="100" viewbox="-300 -50 600 100"><text><a id="cross-origin-link-inside-svg-element" href="about:blank">Cross-origin link inside SVG element</a></text></svg>
<p><a id="link-to-disabled-frame" href="/src/tests/fixtures/frames/hello.html" data-turbo-frame="hello">Disabled turbo-frame</a></p>
<p><a id="autofocus-link" href="/src/tests/fixtures/autofocus.html">autofocus.html link</a></p>
</section>

<turbo-frame id="hello" disabled></turbo-frame>
Expand Down
47 changes: 47 additions & 0 deletions src/tests/functional/autofocus_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { TurboDriveTestCase } from "../helpers/turbo_drive_test_case"

export class AutofocusTests extends TurboDriveTestCase {
async setup() {
await this.goToLocation("/src/tests/fixtures/autofocus.html")
}

async "test autofocus first autofocus element on load"() {
this.assert.ok(await this.hasSelector("#first-autofocus-element:focus"), "focuses the first [autofocus] element on the page")
this.assert.notOk(await this.hasSelector("#second-autofocus-element:focus"), "focuses the first [autofocus] element on the page")
}

async "test autofocus first [autofocus] element on visit"() {
await this.goToLocation("/src/tests/fixtures/navigation.html")
await this.clickSelector("#autofocus-link")
await this.nextBody

this.assert.ok(await this.hasSelector("#first-autofocus-element:focus"), "focuses the first [autofocus] element on the page")
this.assert.notOk(await this.hasSelector("#second-autofocus-element:focus"), "focuses the first [autofocus] element on the page")
}

async "test navigating a frame with a descendant link autofocuses [autofocus]:first-of-type"() {
await this.clickSelector("#frame-inner-link")
await this.nextBeat

this.assert.ok(await this.hasSelector("#frames-form-first-autofocus-element:focus"), "focuses the first [autofocus] element in frame")
this.assert.notOk(await this.hasSelector("#frames-form-second-autofocus-element:focus"), "focuses the first [autofocus] element in frame")
}

async "test navigating a frame with a link targeting the frame autofocuses [autofocus]:first-of-type"() {
await this.clickSelector("#frame-outer-link")
await this.nextBeat

this.assert.ok(await this.hasSelector("#frames-form-first-autofocus-element:focus"), "focuses the first [autofocus] element in frame")
this.assert.notOk(await this.hasSelector("#frames-form-second-autofocus-element:focus"), "focuses the first [autofocus] element in frame")
}

async "test navigating a frame with a turbo-frame targeting the frame autofocuses [autofocus]:first-of-type"() {
await this.clickSelector("#drives-frame-target-link")
await this.nextBeat

this.assert.ok(await this.hasSelector("#frames-form-first-autofocus-element:focus"), "focuses the first [autofocus] element in frame")
this.assert.notOk(await this.hasSelector("#frames-form-second-autofocus-element:focus"), "focuses the first [autofocus] element in frame")
}
}

AutofocusTests.registerSuite()
10 changes: 10 additions & 0 deletions src/tests/functional/frame_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ export class FrameTests extends FunctionalTestCase {
await this.goToLocation("/src/tests/fixtures/frames.html")
}

async "test following a link preserves the current <turbo-frame> element's attributes"() {
const currentPath = await this.pathname

await this.clickSelector("#hello a")
await this.nextBeat

const frame = await this.querySelector("turbo-frame#frame")
this.assert.equal(await frame.getAttribute("data-loaded-from"), currentPath)
}

async "test following a link to a page without a matching frame results in an empty frame"() {
await this.clickSelector("#missing a")
await this.nextBeat
Expand Down
1 change: 1 addition & 0 deletions src/tests/functional/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./async_script_tests"
export * from "./autofocus_tests"
export * from "./form_submission_tests"
export * from "./frame_tests"
export * from "./loading_tests"
Expand Down