DEV: select-kit third major update with focus on accessibility (#13303)

DEV: select-kit third major update with focus on accessibility (#13303)

Major changes included:

  • better support for screen readers
  • trapping focus in modals
  • better tabbing order in composer
  • alerts on no content found/number of items found
  • better autofocus in modals
  • mini-tag-chooser is now a multi-select component
  • each multi-select-component will now display selection on one row
diff --git a/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs b/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs
index 9e17bda..bac1fac 100644
--- a/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/report-filters/group.hbs
@@ -3,7 +3,9 @@
   valueProperty="value"
   content=groupOptions
   value=groupId
-  allowAny=filter.allow_any
   none="admin.dashboard.reports.groups"
   onChange=(action "onChange")
+  options=(hash
+    allowAny=filter.allow_any
+  )
 }}
diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs
index 4d37e15..c896d5e 100644
--- a/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/compact-list.hbs
@@ -1,10 +1,12 @@
 {{list-setting
   value=settingValue
   settingName=setting.setting
-  allowAny=allowAny
   choices=settingChoices
   onChange=(action "onChangeListSetting")
   onChangeChoices=(action "onChangeChoices")
+  options=(hash
+    allowAny=allowAny
+  )
 }}
 
 {{setting-validation-message message=validationMessage}}
diff --git a/app/assets/javascripts/admin/addon/templates/components/value-list.hbs b/app/assets/javascripts/admin/addon/templates/components/value-list.hbs
index 853daac..94b1d12 100644
--- a/app/assets/javascripts/admin/addon/templates/components/value-list.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/value-list.hbs
@@ -21,7 +21,9 @@
 {{/if}}
 
 {{combo-box
-  allowAny=true
+  options=(hash
+    allowAny=true
+  )
   none=noneKey
   valueProperty=null
   nameProperty=null
diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index 7ffdb99..c319408 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -682,6 +682,7 @@ export default Component.extend(ComposerUpload, {
 
     extraButtons(toolbar) {
       toolbar.addButton({
+        tabindex: "0",
         id: "quote",
         group: "fontStyles",
         icon: "far-comment",
diff --git a/app/assets/javascripts/discourse/app/components/composer-save-button.js b/app/assets/javascripts/discourse/app/components/composer-save-button.js
index 4c7500d..efa5d2b 100644
--- a/app/assets/javascripts/discourse/app/components/composer-save-button.js
+++ b/app/assets/javascripts/discourse/app/components/composer-save-button.js
@@ -1,7 +1,6 @@
 import Button from "discourse/components/d-button";
 
 export default Button.extend({
-  tabindex: 5,
   classNameBindings: [":btn-primary", ":create", "disableSubmit:disabled"],
   title: "composer.title",
 });
diff --git a/app/assets/javascripts/discourse/app/components/composer-user-selector.js b/app/assets/javascripts/discourse/app/components/composer-user-selector.js
index fd7babb..8bf2d05 100644
--- a/app/assets/javascripts/discourse/app/components/composer-user-selector.js
+++ b/app/assets/javascripts/discourse/app/components/composer-user-selector.js
@@ -1,6 +1,5 @@
 import Component from "@ember/component";
 import discourseComputed from "discourse-common/utils/decorators";
-import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
 
 export default Component.extend({
   init() {
@@ -12,7 +11,7 @@ export default Component.extend({
     this._super(...arguments);
 
     if (this.focusTarget === "usernames") {
-      putCursorAtEnd(this.element.querySelector("input"));
+      this.element.querySelector(".select-kit .select-kit-header").focus();
     }
   },
 
diff --git a/app/assets/javascripts/discourse/app/components/d-button.js b/app/assets/javascripts/discourse/app/components/d-button.js
index 36987be..73d6350 100644
--- a/app/assets/javascripts/discourse/app/components/d-button.js
+++ b/app/assets/javascripts/discourse/app/components/d-button.js
@@ -21,6 +21,7 @@ export default Component.extend({
   translatedAriaLabel: null,
   forwardEvent: false,
   preventFocus: false,
+  onKeyDown: null,
 
   isLoading: computed({
     set(key, value) {
@@ -105,6 +106,13 @@ export default Component.extend({
     }
   },
 
+  keyDown(e) {
+    if (this.onKeyDown) {
+      e.stopPropagation();
+      this.onKeyDown(e);
+    }
+  },
+
   click(event) {
     let { action } = this;
 
@@ -132,6 +140,7 @@ export default Component.extend({
       DiscourseURL.routeTo(this.href);
     }
 
+    event.preventDefault();
     event.stopPropagation();
 
     return false;
diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js
index e2cc964..c6ebb29 100644
--- a/app/assets/javascripts/discourse/app/components/d-editor.js
+++ b/app/assets/javascripts/discourse/app/components/d-editor.js
@@ -37,6 +37,7 @@ import { siteDir } from "discourse/lib/text-direction";
 import toMarkdown from "discourse/lib/to-markdown";
 import { translations } from "pretty-text/emoji/data";
 import { wantsNewWindow } from "discourse/lib/intercept-click";
+import { action } from "@ember/object";
 
 // Our head can be a static string or a function that returns a string
 // based on input (like for numbered lists).
@@ -182,6 +183,7 @@ class Toolbar {
 
     const createdButton = {
       id: button.id,
+      tabindex: button.tabindex || "-1",
       className: button.className || button.id,
       label: button.label,
       icon: button.label ? null : button.icon || button.id,
@@ -442,13 +444,19 @@ export default Component.extend({
         if (this._state !== "inDOM" || !this.element) {
           return;
         }
-        const $preview = $(this.element.querySelector(".d-editor-preview"));
-        if ($preview.length === 0) {
+
+        const preview = this.element.querySelector(".d-editor-preview");
+        if (!preview) {
           return;
         }
 
+        // prevents any tab focus in preview
+        preview.querySelectorAll("a").forEach((anchor) => {
+          anchor.setAttribute("tabindex", "-1");
+        });
+
         if (this.previewUpdated) {
-          this.previewUpdated($preview);
+          this.previewUpdated($(preview));
         }
       });
     });
@@ -1027,6 +1035,45 @@ export default Component.extend({
     });
   },
 
+  @action
+  rovingButtonBar(event) {
+    let target = event.target;
+    let siblingFinder;
+    if (event.code === "ArrowRight") {
+      siblingFinder = "nextElementSibling";
+    } else if (event.code === "ArrowLeft") {
+      siblingFinder = "previousElementSibling";
+    } else {
+      return true;
+    }
+
+    while (
+      target.parentNode &&
+      !target.parentNode.classList.contains("d-editor-button-bar")
+    ) {
+      target = target.parentNode;
+    }
+
+    let focusable = target[siblingFinder];
+    if (focusable) {
+      while (
+        (focusable.tagName !== "BUTTON" &&
+          !focusable.classList.contains("select-kit")) ||
+        focusable.classList.contains("hidden")
+      ) {
+        focusable = focusable[siblingFinder];
+      }
+
+      if (focusable?.tagName === "DETAILS") {
+        focusable = focusable.querySelector("summary");
+      }
+
+      focusable?.focus();
+    }
+
+    return true;
+  },
+
   actions: {
     emoji() {
       if (this.disabled) {
diff --git a/app/assets/javascripts/discourse/app/components/d-modal-body.js b/app/assets/javascripts/discourse/app/components/d-modal-body.js
index 4f2f382..2ea7d11 100644
--- a/app/assets/javascripts/discourse/app/components/d-modal-body.js
+++ b/app/assets/javascripts/discourse/app/components/d-modal-body.js
@@ -5,7 +5,6 @@ export default Component.extend({
   fixed: false,
   submitOnEnter: true,
   dismissable: true,

[... diff too long, it was truncated ...]

GitHub sha: cb59681d865fbf738aab5c64b7b3a3f23f4c4db9

This commit appears in #13303 which was approved by ZogStriP. It was merged by jjaffeux.

This commit has been mentioned on Discourse Meta. There might be relevant details there: