From 1dc43ac36612b9b5d696d482be25c3bd61e256a7 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Thu, 18 Nov 2021 15:21:07 -0500 Subject: [PATCH] Fix focus and keyboard nav Only enable keyboard nav and focus if button is triggered by keyboard event --- assets/js/app.js | 59 +++++++++++++++---------- lib/live_beats_web/views/layout_view.ex | 10 +++-- mix.lock | 2 +- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index ef5915f..fbbd488 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -19,48 +19,59 @@ Hooks.Menu = { return val }, reset(){ + this.enabled = true this.activeClass = this.getAttr("data-active-class") this.deactivate(this.menuItems()) this.activeItem = null + window.removeEventListener("keydown", this.handleKeyDown) }, + destroyed(){ this.reset() }, mounted(){ this.menuItemsContainer = document.querySelector(`[aria-labelledby="${this.el.id}"]`) this.reset() this.el.addEventListener("click", e => { - if(e.currentTarget.isSameNode(this.el)){ - this.el.focus() - this.activate(0) - } - }) - this.el.addEventListener("keydown", e => { - if(e.key === "Escape"){ - document.body.click() + // disable if button clicked and click was not a keyboard event + if(e.currentTarget.isSameNode(this.el) && e.detail !== 0){ this.reset() - } else if(e.key === "Enter" && !this.activeItem){ - this.activate(0) - } else if(e.key === "Enter"){ - this.activeItem.click() - } - if(e.key === "ArrowDown"){ - e.preventDefault() - let menuItems = this.menuItems() - this.deactivate(menuItems) - this.activate(menuItems.indexOf(this.activeItem) + 1, menuItems.length - 1) - } else if(e.key === "ArrowUp"){ - e.preventDefault() - let menuItems = this.menuItems() - this.deactivate(menuItems) - this.activate(menuItems.indexOf(this.activeItem) - 1, 0) + this.enabled = false } }) + this.handleKeyDown = (e) => this.onKeyDown(e) + this.menuItemsContainer.addEventListener("phx:show", () => { + if(this.enabled){ this.activate(0) } + window.addEventListener("keydown", this.handleKeyDown) + }) + this.menuItemsContainer.addEventListener("phx:hide", () => this.reset()) }, activate(index, fallbackIndex){ let menuItems = this.menuItems() this.activeItem = menuItems[index] || menuItems[fallbackIndex] this.activeItem.classList.add(this.activeClass) + this.activeItem.focus() }, deactivate(items){ items.forEach(item => item.classList.remove(this.activeClass)) }, - menuItems(){ return Array.from(this.menuItemsContainer.querySelectorAll("[role=menuitem]")) } + menuItems(){ return Array.from(this.menuItemsContainer.querySelectorAll("[role=menuitem]")) }, + onKeyDown(e){ + if(e.key === "Escape"){ + document.body.click() + this.reset() + } else if(e.key === "Enter" && !this.activeItem){ + this.activate(0) + } else if(e.key === "Enter"){ + this.activeItem.click() + } + if(e.key === "ArrowDown"){ + e.preventDefault() + let menuItems = this.menuItems() + this.deactivate(menuItems) + this.activate(menuItems.indexOf(this.activeItem) + 1, menuItems.length - 1) + } else if(e.key === "ArrowUp"){ + e.preventDefault() + let menuItems = this.menuItems() + this.deactivate(menuItems) + this.activate(menuItems.indexOf(this.activeItem) - 1, 0) + } + } } Hooks.Flash = { diff --git a/lib/live_beats_web/views/layout_view.ex b/lib/live_beats_web/views/layout_view.ex index e392f94..d8f7f83 100644 --- a/lib/live_beats_web/views/layout_view.ex +++ b/lib/live_beats_web/views/layout_view.ex @@ -102,23 +102,25 @@ defmodule LiveBeatsWeb.LayoutView do
<.link role="menuitem" + tabindex="-1" redirect_to={profile_path(@current_user)} - class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" + class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500" >View Profile <.link role="menuitem" + tabindex="-1" redirect_to={Routes.settings_path(LiveBeatsWeb.Endpoint, :edit)} - class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" + class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500" >Settings
<.link role="menuitem" + tabindex="-1" href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)} method={:delete} - role="menuitem" - class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" + class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500" >Sign out
diff --git a/mix.lock b/mix.lock index 2f7af5a..88bc484 100644 --- a/mix.lock +++ b/mix.lock @@ -25,7 +25,7 @@ "phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, - "phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "1480b49e52e973f551c88bede2f802e5f5d8c298", []}, + "phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "0127dc0ed174ed6c7c7fc9578400b462785acc9b", []}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"}, "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},