mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 20:22:45 +08:00
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:
parent
0d4fad67db
commit
bc54b0055c
|
@ -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() {
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user