A11Y: Improve topic entrance modal

Clicking the Replies cell of a topic in a topics list shows a little
modal with 2 buttons that take you to the first and last posts of the
topic. This modal is currently completely inaccessible to
keyboard/screen reader users because it can't be reached using the
keyboard.

This commit improves the modal so that it traps focus when it's shown
and makes it possible to close the modal using the esc key.
This commit is contained in:
OsamaSayegh 2022-03-22 11:08:31 +03:00 committed by Osama Sayegh
parent 0d4fad67db
commit bc54b0055c
4 changed files with 90 additions and 5 deletions

View File

@ -2,7 +2,7 @@ import CleansUp from "discourse/mixins/cleans-up";
import Component from "@ember/component";
import DiscourseURL from "discourse/lib/url";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import { scheduleOnce } from "@ember/runloop";
function entranceDate(dt, showTime) {
@ -31,9 +31,11 @@ function entranceDate(dt, showTime) {
export default Component.extend(CleansUp, {
elementId: "topic-entrance",
classNameBindings: ["visible::hidden"],
_position: null,
topic: null,
visible: null,
_position: null,
_originalActiveElement: null,
_activeButton: null,
@discourseComputed("topic.created_at")
createdDate: (createdAt) => new Date(createdAt),
@ -74,12 +76,64 @@ export default Component.extend(CleansUp, {
$self.css(pos);
},
@bind
_escListener(e) {
if (e.key === "Escape") {
this.cleanUp();
} else if (e.key === "Tab") {
if (this._activeButton === "top") {
this._jumpBottomButton().focus();
this._activeButton = "bottom";
e.preventDefault();
} else if (this._activeButton === "bottom") {
this._jumpTopButton().focus();
this._activeButton = "top";
e.preventDefault();
}
}
},
_jumpTopButton() {
return this.element.querySelector(".jump-top");
},
_jumpBottomButton() {
return this.element.querySelector(".jump-bottom");
},
_setupEscListener() {
document.body.addEventListener("keydown", this._escListener);
},
_removeEscListener() {
document.body.removeEventListener("keydown", this._escListener);
},
_trapFocus() {
this._originalActiveElement = document.activeElement;
this._jumpTopButton().focus();
this._activeButton = "top";
},
_releaseFocus() {
if (this._originalActiveElement) {
this._originalActiveElement.focus();
this._originalActiveElement = null;
}
},
_applyDomChanges() {
this._setCSS();
this._setupEscListener();
this._trapFocus();
},
_show(data) {
this._position = data.position;
this.setProperties({ topic: data.topic, visible: true });
scheduleOnce("afterRender", this, this._setCSS);
scheduleOnce("afterRender", this, this._applyDomChanges);
$("html")
.off("mousedown.topic-entrance")
@ -98,6 +152,8 @@ export default Component.extend(CleansUp, {
cleanUp() {
this.setProperties({ topic: null, visible: false });
$("html").off("mousedown.topic-entrance");
this._removeEscListener();
this._releaseFocus();
},
willDestroyElement() {

View File

@ -1,7 +1,7 @@
{{#d-button action=(action "enterTop") class="btn-default full jump-top"}}
{{#d-button action=(action "enterTop") class="btn-default full jump-top" ariaLabel="topic_entrance.sr_jump_top_button"}}
{{d-icon "step-backward"}} {{html-safe topDate}}
{{/d-button}}
{{#d-button action=(action "enterBottom") class="btn-default full jump-bottom"}}
{{#d-button action=(action "enterBottom") class="btn-default full jump-bottom" ariaLabel="topic_entrance.sr_jump_bottom_button"}}
{{html-safe bottomDate}} {{d-icon "step-forward"}}
{{/d-button}}

View File

@ -0,0 +1,26 @@
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
import { click, triggerKeyEvent, visit } from "@ember/test-helpers";
import { test } from "qunit";
const ESC_KEYCODE = 27;
acceptance("Topic Entrance Modal", function () {
test("can be closed with the esc key", async function (assert) {
await visit("/");
await click(".topic-list-item button.posts-map");
const topicEntrance = query("#topic-entrance");
assert.ok(
!topicEntrance.classList.contains("hidden"),
"topic entrance modal appears"
);
assert.equal(
document.activeElement,
topicEntrance.querySelector(".jump-top"),
"the jump top button has focus when the modal is shown"
);
await triggerKeyEvent(topicEntrance, "keydown", ESC_KEYCODE);
assert.ok(
topicEntrance.classList.contains("hidden"),
"topic entrance modal disappears after pressing esc"
);
});
});

View File

@ -3987,6 +3987,9 @@ en:
no_group_messages_title: "No group messages found"
topic_entrance:
sr_jump_top_button: "Jump to the first post"
sr_jump_bottom_button: "Jump to the last post"
fullscreen_table:
expand_btn: "Expand Table"