From c81763dcd8a9f536aa77281bc32e147a69674c70 Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Thu, 13 Oct 2022 16:16:05 -0400 Subject: [PATCH] A11Y: Make input popup errors keyboard-accessible (#18570) Also sets focus to the nearest input when popups are dismissed. --- .../app/components/popup-input-tip.js | 17 ++++++++++++++-- .../tests/acceptance/composer-test.js | 20 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/popup-input-tip.js b/app/assets/javascripts/discourse/app/components/popup-input-tip.js index 1bd8b266be..e92c143e9a 100644 --- a/app/assets/javascripts/discourse/app/components/popup-input-tip.js +++ b/app/assets/javascripts/discourse/app/components/popup-input-tip.js @@ -5,13 +5,15 @@ import { getOwner } from "discourse-common/lib/get-owner"; import { htmlSafe } from "@ember/template"; export default Component.extend({ + tagName: "a", classNameBindings: [":popup-tip", "good", "bad", "lastShownAt::hide"], - attributeBindings: ["role", "ariaLabel"], + attributeBindings: ["role", "ariaLabel", "tabindex"], rerenderTriggers: ["validation.reason"], tipReason: null, lastShownAt: or("shownAt", "validation.lastShownAt"), bad: reads("validation.failed"), good: not("bad"), + tabindex: "0", @discourseComputed("bad") role(bad) { @@ -25,10 +27,21 @@ export default Component.extend({ return reason?.replace(/(<([^>]+)>)/gi, ""); }, - click() { + dismiss() { this.set("shownAt", null); const composer = getOwner(this).lookup("controller:composer"); composer.clearLastValidatedAt(); + this.element.previousElementSibling?.focus(); + }, + + click() { + this.dismiss(); + }, + + keyDown(event) { + if (event.key === "Enter") { + this.dismiss(); + } }, didReceiveAttrs() { diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 061c086c74..fe74e72aa7 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -4,6 +4,7 @@ import { fillIn, settled, triggerEvent, + triggerKeyEvent, visit, } from "@ember/test-helpers"; import { toggleCheckDraftPopup } from "discourse/controllers/composer"; @@ -176,16 +177,29 @@ acceptance("Composer", function (needs) { await click("#reply-control button.create"); assert.ok( - !exists(".title-input .popup-tip.bad.hide"), + exists(".title-input .popup-tip.bad"), "it shows the empty title error" ); assert.ok( - !exists(".d-editor-wrapper .popup-tip.bad.hide"), + exists(".d-editor-textarea-wrapper .popup-tip.bad"), "it shows the empty body error" ); await fillIn("#reply-title", "this is my new topic title"); - assert.ok(exists(".title-input .popup-tip.good"), "the title is now good"); + assert.ok( + exists(".title-input .popup-tip.good.hide"), + "the title is now good" + ); + + await triggerKeyEvent( + ".d-editor-textarea-wrapper .popup-tip.bad", + "keydown", + "Enter" + ); + assert.ok( + exists(".d-editor-textarea-wrapper .popup-tip.bad.hide"), + "body error is dismissed via keyboard" + ); await fillIn(".d-editor-input", "this is the *content* of a post"); assert.strictEqual(