diff --git a/src/core/config/forms.js b/src/core/config/forms.js index a4061cb7d..7116218e3 100644 --- a/src/core/config/forms.js +++ b/src/core/config/forms.js @@ -1,3 +1,41 @@ -export const forms = { - mode: "on" +import { cancelEvent } from "../../util" + +const submitter = { + "aria-disabled": { + beforeSubmit: submitter => { + submitter.setAttribute("aria-disabled", "true") + submitter.addEventListener("click", cancelEvent) + }, + + afterSubmit: submitter => { + submitter.removeAttribute("aria-disabled") + submitter.removeEventListener("click", cancelEvent) + } + }, + + "disabled": { + beforeSubmit: submitter => submitter.disabled = true, + afterSubmit: submitter => submitter.disabled = false + } } + +class Config { + #submitter = null + + constructor(config) { + Object.assign(this, config) + } + + get submitter() { + return this.#submitter + } + + set submitter(value) { + this.#submitter = submitter[value] || value + } +} + +export const forms = new Config({ + mode: "on", + submitter: "disabled" +}) diff --git a/src/core/drive/form_submission.js b/src/core/drive/form_submission.js index ca04914a6..c31d8ce60 100644 --- a/src/core/drive/form_submission.js +++ b/src/core/drive/form_submission.js @@ -121,7 +121,7 @@ export class FormSubmission { requestStarted(_request) { this.state = FormSubmissionState.waiting - this.submitter?.setAttribute("disabled", "") + if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter) this.setSubmitsWith() markAsBusy(this.formElement) dispatch("turbo:submit-start", { @@ -167,7 +167,7 @@ export class FormSubmission { requestFinished(_request) { this.state = FormSubmissionState.stopped - this.submitter?.removeAttribute("disabled") + if (this.submitter) config.forms.submitter.afterSubmit(this.submitter) this.resetSubmitterText() clearBusyState(this.formElement) dispatch("turbo:submit-end", { diff --git a/src/tests/functional/form_submission_tests.js b/src/tests/functional/form_submission_tests.js index 99be694e7..8e48789ad 100644 --- a/src/tests/functional/form_submission_tests.js +++ b/src/tests/functional/form_submission_tests.js @@ -240,6 +240,22 @@ test("standard POST form submission toggles submitter [disabled] attribute", asy ) }) +test("standard POST form submission toggles submitter [aria-disabled=true] attribute", async ({ page }) => { + await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled") + await page.click("#standard-post-form-submit") + + assert.equal( + await nextAttributeMutationNamed(page, "standard-post-form-submit", "aria-disabled"), + "true", + "sets [aria-disabled=true] on the submitter" + ) + assert.equal( + await nextAttributeMutationNamed(page, "standard-post-form-submit", "aria-disabled"), + null, + "removes [aria-disabled] from the submitter" + ) +}) + test("replaces input value with data-turbo-submits-with on form submission", async ({ page }) => { page.click("#submits-with-form-input") @@ -410,6 +426,22 @@ test("standard GET form submission toggles submitter [disabled] attribute", asyn ) }) +test("standard GET form submission toggles submitter [aria-disabled] attribute", async ({ page }) => { + await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled") + await page.click("#standard-get-form-submit") + + assert.equal( + await nextAttributeMutationNamed(page, "standard-get-form-submit", "aria-disabled"), + "true", + "sets [aria-disabled] on the submitter" + ) + assert.equal( + await nextAttributeMutationNamed(page, "standard-get-form-submit", "aria-disabled"), + null, + "removes [aria-disabled] from the submitter" + ) +}) + test("standard GET form submission appending keys", async ({ page }) => { await page.goto("/src/tests/fixtures/form.html?query=1") await page.click("#standard form.conflicting-values input[type=submit]") @@ -692,6 +724,22 @@ test("frame POST form targeting frame toggles submitter's [disabled] attribute", ) }) +test("frame POST form targeting frame toggles submitter's [aria-disabled] attribute", async ({ page }) => { + await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled") + await page.click("#targets-frame-post-form-submit") + + assert.equal( + await nextAttributeMutationNamed(page, "targets-frame-post-form-submit", "aria-disabled"), + "true", + "sets [aria-disabled] on the submitter" + ) + assert.equal( + await nextAttributeMutationNamed(page, "targets-frame-post-form-submit", "aria-disabled"), + null, + "removes [aria-disabled] from the submitter" + ) +}) + test("frame GET form targeting frame submission", async ({ page }) => { await page.click("#targets-frame-get-form-submit") @@ -731,6 +779,22 @@ test("frame GET form targeting frame toggles submitter's [disabled] attribute", ) }) +test("frame GET form targeting frame toggles submitter's [aria-disabled] attribute", async ({ page }) => { + await page.evaluate(() => window.Turbo.config.forms.submitter = "aria-disabled") + await page.click("#targets-frame-get-form-submit") + + assert.equal( + await nextAttributeMutationNamed(page, "targets-frame-get-form-submit", "aria-disabled"), + "true", + "sets [aria-disabled] on the submitter" + ) + assert.equal( + await nextAttributeMutationNamed(page, "targets-frame-get-form-submit", "aria-disabled"), + null, + "removes [aria-disabled] from the submitter" + ) +}) + test("frame form GET submission from submitter referencing another frame", async ({ page }) => { await page.click("#frame form[method=get] [type=submit][data-turbo-frame=hello]") await nextBeat() diff --git a/src/util.js b/src/util.js index dcb15c31b..4960b53e9 100644 --- a/src/util.js +++ b/src/util.js @@ -45,6 +45,11 @@ export function dispatch(eventName, { target, cancelable, detail } = {}) { return event } +export function cancelEvent(event) { + event.preventDefault() + event.stopImmediatePropagation() +} + export function nextRepaint() { if (document.visibilityState === "hidden") { return nextEventLoopTick()