diff --git a/webextension/css/popup.css b/webextension/css/popup.css index dd460d3..607563c 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -130,11 +130,13 @@ table { color: white; } -.button.primary:hover { +.button.primary:hover, +.button.primary:focus { background-color: #0675d3; } -.button.secondary:hover { +.button.secondary:hover, +.button.secondary:focus { background-color: rgba(0, 0, 0, 0.05); } @@ -198,7 +200,8 @@ table { justify-content: center; } -.panel-back-arrow:hover { +.panel-back-arrow:hover, +.panel-back-arrow:focus { background: #dedede; } @@ -249,7 +252,8 @@ table { transition: background-color 75ms; } -.onboarding-button:hover { +.onboarding-button:hover, +.onboarding-button:active { background-color: #0675d3; } @@ -264,12 +268,14 @@ manage things like container crud */ } .pop-button:hover, +.pop-button:focus, +.panel-footer-secondary:focus, .panel-footer-secondary:hover { background-color: rgba(0, 0, 0, 0.05); } -.pop-button:active, -.panel-footer-secondary:active { +.pop-button:focus, +.panel-footer-secondary:focus { background-color: rgba(0, 0, 0, 0.08); } @@ -350,7 +356,8 @@ span ~ .panel-header-text { transition: background-color 75ms; } -.clickable.userContext-wrapper:hover { +.container-panel-row:hover .clickable.userContext-wrapper, +.container-panel-row:focus .clickable.userContext-wrapper { background: #f2f2f2; } @@ -360,7 +367,6 @@ span ~ .panel-header-text { } /* .userContext-icon is used natively, Bug 1333811 was raised to fix */ -.userContext-icon, .usercontext-icon { background-image: var(--identity-icon); background-position: center center; @@ -372,8 +378,8 @@ span ~ .panel-header-text { flex: 0 0 48px; } -.clickable:hover .userContext-icon, -.clickable:hover .usercontext-icon { +.container-panel-row:hover .clickable .usercontext-icon, +.container-panel-row:focus .clickable .usercontext-icon { background-image: url('/img/container-newtab.svg'); fill: 'gray'; filter: url('/img/filters.svg#fill'); @@ -482,7 +488,8 @@ span ~ .panel-header-text { cursor: pointer; } -.clickable:hover { +.clickable:hover, +.clickable:focus { background-color: #ebebeb; } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index b1289ae..c1d14a3 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -107,6 +107,15 @@ const Logic = { browser.storage.local.set({browserActionBadgesClicked: storage.browserActionBadgesClicked}); }, + addEnterHandler(element, handler) { + element.addEventListener("click", handler); + element.addEventListener("keydown", (e) => { + if (e.keyCode === 13) { + handler(e); + } + }); + }, + refreshIdentities() { return browser.runtime.sendMessage({ method: "queryIdentities" @@ -214,7 +223,7 @@ Logic.registerPanel(P_ONBOARDING_1, { // This method is called when the object is registered. initialize() { // Let's move to the next panel. - document.querySelector("#onboarding-start-button").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#onboarding-start-button"), () => { localStorage.setItem("onboarded1", true); Logic.showPanel(P_ONBOARDING_2); }); @@ -235,7 +244,7 @@ Logic.registerPanel(P_ONBOARDING_2, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - document.querySelector("#onboarding-next-button").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#onboarding-next-button"), () => { localStorage.setItem("onboarded2", true); Logic.showPanel(P_ONBOARDING_3); }); @@ -256,7 +265,7 @@ Logic.registerPanel(P_ONBOARDING_3, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - document.querySelector("#onboarding-almost-done-button").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#onboarding-almost-done-button"), () => { localStorage.setItem("onboarded3", true); Logic.showPanel(P_ONBOARDING_4); }); @@ -297,18 +306,18 @@ Logic.registerPanel(P_CONTAINERS_LIST, { // This method is called when the object is registered. initialize() { - document.querySelector("#container-add-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#container-add-link"), () => { Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() }); }); - document.querySelector("#edit-containers-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#edit-containers-link"), () => { Logic.sendTelemetryPayload({ event: "edit-containers" }); Logic.showPanel(P_CONTAINERS_EDIT); }); - document.querySelector("#sort-containers-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#sort-containers-link"), () => { browser.runtime.sendMessage({ method: "sortTabs" }).then(() => { @@ -317,6 +326,30 @@ Logic.registerPanel(P_CONTAINERS_LIST, { window.close(); }); }); + + document.addEventListener("keydown", (e) => { + const element = document.activeElement; + function next() { + const nextElement = element.nextElementSibling; + if (nextElement) { + nextElement.focus(); + } + } + function previous() { + const previousElement = element.previousElementSibling; + if (previousElement) { + previousElement.focus(); + } + } + switch (e.keyCode) { + case 40: + next(); + break; + case 38: + previous(); + break; + } + }); }, // This method is called when the panel is shown. @@ -330,11 +363,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const manage = document.createElement("td"); tr.classList.add("container-panel-row"); + + tr.setAttribute("tabindex", "0"); + context.classList.add("userContext-wrapper", "open-newtab", "clickable"); manage.classList.add("show-tabs", "pop-button"); context.innerHTML = escaped`
-
@@ -351,8 +387,10 @@ Logic.registerPanel(P_CONTAINERS_LIST, { tr.appendChild(manage); } - tr.addEventListener("click", e => { - if (e.target.matches(".open-newtab") || e.target.parentNode.matches(".open-newtab")) { + Logic.addEnterHandler(tr, e => { + if (e.target.matches(".open-newtab") + || e.target.parentNode.matches(".open-newtab") + || e.type === "keydown") { browser.runtime.sendMessage({ method: "openTab", userContextId: identity.userContextId, @@ -372,6 +410,12 @@ Logic.registerPanel(P_CONTAINERS_LIST, { list.innerHTML = ""; list.appendChild(fragment); + /* Not sure why extensions require a focus for the doorhanger, + however it allows us to have a tabindex before the first selected item + */ + document.addEventListener("focus", () => { + list.querySelector("tr").focus(); + }); return Promise.resolve(); }, @@ -385,11 +429,11 @@ Logic.registerPanel(P_CONTAINER_INFO, { // This method is called when the object is registered. initialize() { - document.querySelector("#close-container-info-panel").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#close-container-info-panel"), () => { Logic.showPreviousPanel(); }); - document.querySelector("#container-info-hideorshow").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#container-info-hideorshow"), () => { const identity = Logic.currentIdentity(); browser.runtime.sendMessage({ method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", @@ -420,7 +464,7 @@ Logic.registerPanel(P_CONTAINER_INFO, { moveTabsEl.parentNode.insertBefore(fragment, moveTabsEl.nextSibling); } else { - moveTabsEl.addEventListener("click", () => { + Logic.addEnterHandler(moveTabsEl, () => { browser.runtime.sendMessage({ method: "moveTabsToWindow", userContextId: Logic.currentIdentity().userContextId, @@ -483,7 +527,7 @@ Logic.registerPanel(P_CONTAINER_INFO, { // On click, we activate this tab. But only if this tab is active. if (tab.active) { tr.classList.add("clickable"); - tr.addEventListener("click", () => { + Logic.addEnterHandler(tr, () => { browser.runtime.sendMessage({ method: "showTab", tabId: tab.id, @@ -508,7 +552,7 @@ Logic.registerPanel(P_CONTAINERS_EDIT, { // This method is called when the object is registered. initialize() { - document.querySelector("#exit-edit-mode-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#exit-edit-mode-link"), () => { Logic.showPanel(P_CONTAINERS_LIST); }); }, @@ -523,7 +567,7 @@ Logic.registerPanel(P_CONTAINERS_EDIT, { tr.innerHTML = escaped`
-
@@ -546,7 +590,7 @@ Logic.registerPanel(P_CONTAINERS_EDIT, { tr.querySelector(".remove-container .pop-button-image").setAttribute("title", `Edit ${identity.name} container`); - tr.addEventListener("click", e => { + Logic.addEnterHandler(tr, e => { if (e.target.matches(".edit-container-icon") || e.target.parentNode.matches(".edit-container-icon")) { Logic.showPanel(P_CONTAINER_EDIT, identity); } else if (e.target.matches(".delete-container-icon") || e.target.parentNode.matches(".delete-container-icon")) { @@ -574,17 +618,17 @@ Logic.registerPanel(P_CONTAINER_EDIT, { initialize() { this.initializeRadioButtons(); - document.querySelector("#edit-container-panel-back-arrow").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#edit-container-panel-back-arrow"), () => { Logic.showPreviousPanel(); }); - document.querySelector("#edit-container-cancel-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#edit-container-cancel-link"), () => { Logic.showPreviousPanel(); }); this._editForm = document.getElementById("edit-container-panel-form"); const editLink = document.querySelector("#edit-container-ok-link"); - editLink.addEventListener("click", this._submitForm.bind(this)); + Logic.addEnterHandler(editLink, this._submitForm.bind(this)); editLink.addEventListener("submit", this._submitForm.bind(this)); this._editForm.addEventListener("submit", this._submitForm.bind(this)); }, @@ -663,11 +707,11 @@ Logic.registerPanel(P_CONTAINER_DELETE, { // This method is called when the object is registered. initialize() { - document.querySelector("#delete-container-cancel-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#delete-container-cancel-link"), () => { Logic.showPreviousPanel(); }); - document.querySelector("#delete-container-ok-link").addEventListener("click", () => { + Logic.addEnterHandler(document.querySelector("#delete-container-ok-link"), () => { /* This promise wont resolve if the last tab was removed from the window. as the message async callback stops listening, this isn't an issue for us however it might be in future if you want to do anything post delete do it in the background script. diff --git a/webextension/manifest.json b/webextension/manifest.json index a9582a7..910aa8e 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -33,6 +33,15 @@ "webRequest" ], + "commands": { + "_execute_browser_action": { + "suggested_key": { + "default": "Ctrl+Y" + }, + "description": "Open containers panel" + } + }, + "browser_action": { "browser_style": true, "default_icon": { diff --git a/webextension/popup.html b/webextension/popup.html index 26c00ce..794c9b0 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -40,25 +40,25 @@

Containers

- Sort Containers + Sort Containers
- -
+
Panel Back Arrow @@ -92,7 +92,7 @@
@@ -116,7 +116,7 @@
@@ -133,8 +133,8 @@

If you remove this container now, container tabs will be closed. Are you sure you want to remove this Container?