A11Y: close sidebar "more..." menu on esc and focusOut

This commit is contained in:
awesomerobot 2024-10-02 17:36:58 -04:00
parent 252dcfbfa6
commit bffb9b25e3
3 changed files with 104 additions and 20 deletions

View File

@ -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"
> >

View File

@ -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(

1
pgvector Submodule

@ -0,0 +1 @@
Subproject commit 3849f0fd3d6f7916de26d43e3a3c9f8e6615bc8f