A11Y: close sidebar "more..." menu on esc and focusOut
This commit is contained in:
parent
252dcfbfa6
commit
bffb9b25e3
|
@ -4,6 +4,7 @@ import { on } from "@ember/modifier";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||||
|
import { scheduleOnce } from "@ember/runloop";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
|
@ -11,13 +12,14 @@ import i18n from "discourse-common/helpers/i18n";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import MoreSectionLink from "./more-section-link";
|
import MoreSectionLink from "./more-section-link";
|
||||||
import SectionLinkButton from "./section-link-button";
|
import SectionLinkButton from "./section-link-button";
|
||||||
|
|
||||||
export default class SidebarMoreSectionLinks extends Component {
|
export default class SidebarMoreSectionLinks extends Component {
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
@tracked activeSectionLink;
|
@tracked activeSectionLink;
|
||||||
@tracked open = false;
|
@tracked open = false;
|
||||||
|
|
||||||
|
#lastFocusEvent;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.#setActiveSectionLink();
|
this.#setActiveSectionLink();
|
||||||
|
@ -26,7 +28,7 @@ export default class SidebarMoreSectionLinks extends Component {
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
this.#removeClickEventListener();
|
this.#removeEventListeners();
|
||||||
this.router.off("routeDidChange", this, this.#setActiveSectionLink);
|
this.router.off("routeDidChange", this, this.#setActiveSectionLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,28 +71,75 @@ export default class SidebarMoreSectionLinks extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@bind
|
||||||
registerClickListener() {
|
closeOnEscape(event) {
|
||||||
this.#addClickEventListener();
|
if (event.key === "Escape" && this.open) {
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@bind
|
||||||
unregisterClickListener() {
|
closeOnFocusOut(event) {
|
||||||
this.#removeClickEventListener();
|
this.#lastFocusEvent = event;
|
||||||
|
scheduleOnce("afterRender", this, this.#handleFocusOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleFocusOut() {
|
||||||
|
const container = document.querySelector(
|
||||||
|
".sidebar-more-section-links-details-content"
|
||||||
|
);
|
||||||
|
const toggleButton = document.querySelector(
|
||||||
|
".sidebar-more-section-links-details-summary"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
container &&
|
||||||
|
!container.contains(this.#lastFocusEvent.relatedTarget) &&
|
||||||
|
toggleButton !== this.#lastFocusEvent.relatedTarget // focusing out to the toggle shouldn't close the menu
|
||||||
|
) {
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
toggleSectionLinks(event) {
|
toggleSectionLinks(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.open = !this.open;
|
this.open = !this.open;
|
||||||
|
|
||||||
|
if (this.open) {
|
||||||
|
scheduleOnce("afterRender", this, this.#focusFirstLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#removeClickEventListener() {
|
#focusFirstLink() {
|
||||||
document.removeEventListener("click", this.closeDetails);
|
const firstLink = document.querySelector(
|
||||||
|
".sidebar-more-section-links-details-content-wrapper a"
|
||||||
|
);
|
||||||
|
if (firstLink) {
|
||||||
|
firstLink.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#addClickEventListener() {
|
#addEventListeners() {
|
||||||
document.addEventListener("click", this.closeDetails);
|
document.addEventListener("click", this.closeDetails);
|
||||||
|
document.addEventListener("keydown", this.closeOnEscape);
|
||||||
|
document.addEventListener("focusout", this.closeOnFocusOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
#removeEventListeners() {
|
||||||
|
document.removeEventListener("click", this.closeDetails);
|
||||||
|
document.removeEventListener("keydown", this.closeOnEscape);
|
||||||
|
document.removeEventListener("focusout", this.closeOnFocusOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
registerEventListeners() {
|
||||||
|
this.#addEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
unregisterEventListeners() {
|
||||||
|
this.#removeEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
#isOutsideDetailsClick(event) {
|
#isOutsideDetailsClick(event) {
|
||||||
|
@ -140,8 +189,8 @@ export default class SidebarMoreSectionLinks extends Component {
|
||||||
{{#if this.open}}
|
{{#if this.open}}
|
||||||
<div class="sidebar-more-section-links-details">
|
<div class="sidebar-more-section-links-details">
|
||||||
<div
|
<div
|
||||||
{{didInsert this.registerClickListener}}
|
{{didInsert this.registerEventListeners}}
|
||||||
{{willDestroy this.unregisterClickListener}}
|
{{willDestroy this.unregisterEventListeners}}
|
||||||
class="sidebar-more-section-links-details-content-wrapper"
|
class="sidebar-more-section-links-details-content-wrapper"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
|
@ -90,13 +90,6 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
||||||
),
|
),
|
||||||
"additional section links are hidden when clicking outside"
|
"additional section links are hidden when clicking outside"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
exists(
|
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-summary[aria-expanded='false']"
|
|
||||||
),
|
|
||||||
"aria-expanded toggles to false when additional links are hidden"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("clicking on everything link", async function (assert) {
|
test("clicking on everything link", async function (assert) {
|
||||||
|
@ -1134,6 +1127,47 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
||||||
|
|
||||||
assert.ok(teardownCalled, "section link teardown callback was called");
|
assert.ok(teardownCalled, "section link teardown callback was called");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("pressing esc closes the more menu", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
await click(
|
||||||
|
".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-summary"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(exists(".sidebar-more-section-links-details-content"));
|
||||||
|
|
||||||
|
const event = new KeyboardEvent("keydown", { key: "Escape" });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
assert.notOk(exists(".sidebar-more-section-links-details-content"));
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(
|
||||||
|
".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-summary"
|
||||||
|
).getAttribute("aria-expanded"),
|
||||||
|
"false",
|
||||||
|
"aria-expanded is set to false after closing the menu"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("first link is focused when the more menu is opened", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
await click(
|
||||||
|
".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-summary"
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstLink = query(
|
||||||
|
".sidebar-more-section-links-details-content-wrapper a"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
document.activeElement,
|
||||||
|
firstLink,
|
||||||
|
"first link is focused"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
acceptance(
|
acceptance(
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3849f0fd3d6f7916de26d43e3a3c9f8e6615bc8f
|
Loading…
Reference in New Issue