UX: Trigger primary action in modals using Enter

UX: Trigger primary action in modals using Enter

A modal’s primary action (blue button in the default theme) can now be invoked by hitting Enter on the keyboard. This applies to all modals that aren’t strict forms as long as the focus is not on a textarea element.

diff --git a/app/assets/javascripts/discourse/components/d-modal.js.es6 b/app/assets/javascripts/discourse/components/d-modal.js.es6
index f870780..f3ed9bc 100644
--- a/app/assets/javascripts/discourse/components/d-modal.js.es6
+++ b/app/assets/javascripts/discourse/components/d-modal.js.es6
@@ -33,6 +33,10 @@ export default Ember.Component.extend({
       if (e.which === 27 && this.dismissable) {
         Ember.run.next(() => $(".modal-header a.close").click());
       }
+
+      if (e.which === 13 && this.triggerClickOnEnter(e)) {
+        Ember.run.next(() => $(".modal-footer .btn-primary").click());
+      }
     });
 
     this.appEvents.on("modal:body-shown", this, "_modalBodyShown");
@@ -44,6 +48,18 @@ export default Ember.Component.extend({
     this.appEvents.off("modal:body-shown", this, "_modalBodyShown");
   },
 
+  triggerClickOnEnter(e) {
+    // skip when in a form or a textarea element
+    if (
+      $(e.target).parents("form").length > 0 ||
+      (document.activeElement && document.activeElement.nodeName === "TEXTAREA")
+    ) {
+      return false;
+    }
+
+    return true;
+  },
+
   mouseDown(e) {
     if (!this.dismissable) {
       return;
diff --git a/test/javascripts/acceptance/modal-test.js.es6 b/test/javascripts/acceptance/modal-test.js.es6
index c15d23b..45a7114 100644
--- a/test/javascripts/acceptance/modal-test.js.es6
+++ b/test/javascripts/acceptance/modal-test.js.es6
@@ -52,3 +52,38 @@ QUnit.test("modal", async function(assert) {
     "ESC should not close the modal"
   );
 });
+
+acceptance("Modal Keyboard Events", { loggedIn: true });
+
+QUnit.test("modal-keyboard-events", async function(assert) {
+  await visit("/t/internationalization-localization/280");
+
+  await click(".toggle-admin-menu");
+  await click(".topic-admin-status-update button");
+  await keyEvent(".d-modal", "keydown", 13);
+
+  assert.ok(
+    find("#modal-alert:visible").length === 1,
+    "hitting Enter triggers modal action"
+  );
+  assert.ok(
+    find(".d-modal:visible").length === 1,
+    "hitting Enter does not dismiss modal due to alert error"
+  );
+
+  await keyEvent("#main-outlet", "keydown", 27);
+  assert.ok(
+    find(".d-modal:visible").length === 0,
+    "ESC should close the modal"
+  );
+
+  await click(".topic-body button.reply");
+
+  await click(".d-editor-button-bar .btn.link");
+
+  await keyEvent(".d-modal", "keydown", 13);
+  assert.ok(
+    find(".d-modal:visible").length === 0,
+    "modal should disappear on hitting Enter"
+  );
+});

GitHub sha: a91ad81e

1 Like

This commit is fine, but FYI is very close to the equivalent code that doesn’t use jQuery.

Instead of $(e.target).parents("form") for example you could do e.target.closest('form').

These days I prefer not to use jQuery unless I absolutely have to.

3 Likes

Thanks, I did not know closest could be used this way.

2 Likes