A11Y: Improve select-kit accessibility (#21400)

This improves keyboard navigation in and out of select-kit components.

The improvements include:

- `Tab` will now dismiss the dropdown once the active element is outside
the select-kit element
- pressing `Escape` will not bubble, this is most noticeable in the
composer, pressing `Esc` there now when a dropdown is expanded will not
dismiss the composer
- `Shift+Tab` will also dismiss the dropdown once focus is outside it
This commit is contained in:
Penar Musaraj
2023-05-09 09:46:05 -04:00
committed by GitHub
parent b5292c8139
commit e8aea3c558
6 changed files with 153 additions and 49 deletions

View File

@@ -1,5 +1,6 @@
import Component from "@ember/component";
import { bind } from "@ember/runloop";
import { bind } from "discourse-common/utils/decorators";
import { later } from "@ember/runloop";
import { computed } from "@ember/object";
export default Component.extend({
@@ -10,55 +11,53 @@ export default Component.extend({
return false;
}),
rootEventType: "click",
init() {
this._super(...arguments);
this.handleRootMouseDownHandler = bind(this, this.handleRootMouseDown);
},
didInsertElement() {
this._super(...arguments);
this.element.style.position = "relative";
document.addEventListener(
this.rootEventType,
this.handleRootMouseDownHandler,
true
);
document.addEventListener("click", this.handleClick, true);
this.selectKit
.mainElement()
.addEventListener("keydown", this._handleKeydown, true);
},
willDestroyElement() {
this._super(...arguments);
document.removeEventListener(
this.rootEventType,
this.handleRootMouseDownHandler,
true
);
document.removeEventListener("click", this.handleClick, true);
this.selectKit
.mainElement()
.removeEventListener("keydown", this._handleKeydown, true);
},
handleRootMouseDown(event) {
if (!this.selectKit.isExpanded) {
@bind
handleClick(event) {
if (!this.selectKit.isExpanded || !this.selectKit.mainElement()) {
return;
}
const headerElement = document.querySelector(
`#${this.selectKit.uniqueID}-header`
);
if (headerElement && headerElement.contains(event.target)) {
if (this.selectKit.mainElement().contains(event.target)) {
return;
}
if (this.element.contains(event.target)) {
this.selectKit.close(event);
},
@bind
_handleKeydown(event) {
if (!this.selectKit.isExpanded || event.key !== "Tab") {
return;
}
if (this.selectKit.mainElement()) {
later(() => {
if (
this.isDestroying ||
this.isDestroyed ||
this.selectKit.mainElement().contains(document.activeElement)
) {
return;
}
this.selectKit.close(event);
}
}, 50);
},
});

View File

@@ -94,6 +94,8 @@ export default Component.extend(UtilsMixin, {
if (event.key === "Escape") {
this.selectKit.close(event);
this.selectKit.headerElement().focus();
event.preventDefault();
event.stopPropagation();
return false;
}

View File

@@ -41,7 +41,6 @@ export default Component.extend(UtilsMixin, {
if (!this.site.mobileView) {
this.element.addEventListener("mouseenter", this.handleMouseEnter);
this.element.addEventListener("focus", this.handleMouseEnter);
this.element.addEventListener("blur", this.handleBlur);
}
},
@@ -49,9 +48,8 @@ export default Component.extend(UtilsMixin, {
this._super(...arguments);
if (!this.site.mobileView) {
this.element.removeEventListener("mouseenter", this.handleBlur);
this.element.removeEventListener("mouseenter", this.handleMouseEnter);
this.element.removeEventListener("focus", this.handleMouseEnter);
this.element.removeEventListener("blur", this.handleMouseEnter);
}
},
@@ -134,20 +132,6 @@ export default Component.extend(UtilsMixin, {
return false;
},
@action
handleBlur(event) {
if (
(!this.isDestroying || !this.isDestroyed) &&
event.target &&
this.selectKit.mainElement()
) {
if (!this.selectKit.mainElement().contains(event.target)) {
this.selectKit.close(event);
}
}
return false;
},
click(event) {
event.preventDefault();
event.stopPropagation();
@@ -193,6 +177,8 @@ export default Component.extend(UtilsMixin, {
} else if (event.key === "Escape") {
this.selectKit.close(event);
this.selectKit.headerElement().focus();
event.preventDefault();
event.stopPropagation();
} else {
if (this.isValidInput(event.key)) {
this.selectKit.set("filter", event.key);