A11Y: select kit close on focus out (#21274)

When navigating with the keyboard, the select-kit would not close when
focus was moved to an element outside of the body. For example, when
navigating via Tab or Shift+Tab, once the end (or beginning) of the list
was reached, focus would move out of the SK element, but the SK itself
would stay visible.

Switching from a click event to a focusout event solves the issue and
covers both mouse and keyboard navigation.
This commit is contained in:
Penar Musaraj 2023-05-02 14:22:31 -04:00 committed by GitHub
parent bfd3bd5516
commit 1b2a1c94d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 46 additions and 53 deletions

View File

@ -113,10 +113,12 @@ acceptance("Category Edit", function (needs) {
const allowedTagChooser = selectKit("#category-allowed-tags");
await allowedTagChooser.expand();
await allowedTagChooser.selectRowByValue("monkey");
await allowedTagChooser.collapse();
const allowedTagGroupChooser = selectKit("#category-allowed-tag-groups");
await allowedTagGroupChooser.expand();
await allowedTagGroupChooser.selectRowByValue("TagGroup1");
await allowedTagGroupChooser.collapse();
await click("#save-category");
@ -129,6 +131,7 @@ acceptance("Category Edit", function (needs) {
await allowedTagChooser.expand();
await allowedTagChooser.deselectItemByValue("monkey");
await allowedTagChooser.collapse();
await allowedTagGroupChooser.expand();
await allowedTagGroupChooser.deselectItemByValue("TagGroup1");

View File

@ -39,10 +39,16 @@ acceptance("User Preferences - Categories", function (needs) {
".tracking-controls__regular-categories .category-selector"
);
await trackedCategoriesSelector.collapse();
await regularCategoriesSelector.expand();
await regularCategoriesSelector.deselectItemByValue("4");
await regularCategoriesSelector.collapse();
await trackedCategoriesSelector.expand();
await trackedCategoriesSelector.selectRowByValue("4");
await trackedCategoriesSelector.collapse();
await click(".save-changes");
assert.deepEqual(putRequestData, {
@ -63,6 +69,7 @@ acceptance("User Preferences - Categories", function (needs) {
await categorySelector.expand();
// User has `regular_category_ids` set to [4] in fixtures
await categorySelector.selectRowByValue(4);
await categorySelector.collapse();
await click(".save-changes");
assert.deepEqual(putRequestData, {

View File

@ -173,17 +173,25 @@ acceptance("User Preferences - Tracking", function (needs) {
assert.notOk(
trackedCategoriesSelector.rowByValue("4").exists(),
"category that is set to regular is not available for selection"
"category that is set to regular is not available for selection under tracked"
);
const regularCategoriesSelector = selectKit(
".tracking-controls__regular-categories .category-selector"
);
await trackedCategoriesSelector.collapse();
await regularCategoriesSelector.expand();
await regularCategoriesSelector.deselectItemByValue("4");
assert.ok(
regularCategoriesSelector.rowByValue("4").exists(),
"category is no longer selected under regular"
);
await regularCategoriesSelector.collapse();
await trackedCategoriesSelector.expand();
await trackedCategoriesSelector.selectRowByValue("4");
await trackedCategoriesSelector.collapse();
await click(".save-changes");
assert.deepEqual(putRequestData, {

View File

@ -58,9 +58,8 @@ module(
await this.subject.fillInFilter("baz");
await this.subject.selectRowByValue("monkey");
const error = query(".select-kit-error").innerText;
assert.strictEqual(
error,
query(".select-kit-error").innerText,
I18n.t("select_kit.max_content_reached", {
count: this.siteSettings.max_tags_per_topic,
})

View File

@ -75,6 +75,7 @@ export default SelectKitComponent.extend({
},
select(value, item) {
this.selectKit.set("multiSelectInFocus", true);
if (this.selectKit.hasSelection && this.selectKit.options.maximum === 1) {
this.selectKit.deselectByValue(
this.getValue(this.selectedContent.firstObject)

View File

@ -94,6 +94,7 @@ export default Component.extend(
noneItem: null,
newItem: null,
filter: null,
multiSelectInFocus: null,
modifyContent: bind(this, this._modifyContentWrapper),
modifySelection: bind(this, this._modifySelectionWrapper),
@ -441,6 +442,7 @@ export default Component.extend(
items = makeArray(items);
if (this.multiSelect) {
this.selectKit.set("multiSelectInFocus", true);
items = items.filter(
(i) =>
i !== this.newItem &&

View File

@ -1,5 +1,5 @@
import Component from "@ember/component";
import { bind } from "@ember/runloop";
import { bind } from "discourse-common/utils/decorators";
import { computed } from "@ember/object";
export default Component.extend({
@ -10,55 +10,41 @@ 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
);
this.element.addEventListener("focusout", this._handleFocusOut, true);
},
willDestroyElement() {
this._super(...arguments);
document.removeEventListener(
this.rootEventType,
this.handleRootMouseDownHandler,
true
);
this.element.removeEventListener("focusout", this._handleFocusOut, true);
},
handleRootMouseDown(event) {
@bind
_handleFocusOut(event) {
if (!this.selectKit.isExpanded) {
return;
}
const headerElement = document.querySelector(
`#${this.selectKit.uniqueID}-header`
);
if (headerElement && headerElement.contains(event.target)) {
if (!this.selectKit.mainElement()) {
return;
}
if (this.element.contains(event.target)) {
if (this.selectKit.mainElement().contains(event.relatedTarget)) {
return;
}
if (this.selectKit.mainElement()) {
this.selectKit.close(event);
// We have to use a custom flag for multi-selects to keep UI visible.
// We can't rely on event.relatedTarget for these cases because,
// when adding/removing items in a multi-select, the DOM element
// has already been removed by this point, and therefore
// event.relatedTarget is going to be null.
if (this.selectKit.multiSelectInFocus) {
this.selectKit.set("multiSelectInFocus", false);
return;
}
this.selectKit.close(event);
},
});

View File

@ -122,7 +122,10 @@ export default Component.extend(UtilsMixin, {
event.stopPropagation();
event.preventDefault(); // prevents the space to trigger a scroll page-next
this.selectKit.open(event);
} else if (event.key === "Escape") {
} else if (
event.key === "Escape" ||
(event.shiftKey && event.key === "Tab")
) {
event.stopPropagation();
if (this.selectKit.isExpanded) {
this.selectKit.close(event);

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();