Browse Source

fix: improve UX of hotkey input

Sv443 1 year ago
parent
commit
086a200454
4 changed files with 61 additions and 43 deletions
  1. 1 1
      src/menu/hotkeyInput.css
  2. 57 38
      src/menu/hotkeyInput.ts
  3. 0 1
      src/menu/menu_old.ts
  4. 3 3
      src/utils/BytmDialog.css

+ 1 - 1
src/menu/hotkeyInput.css

@@ -7,7 +7,7 @@
 
 .bytm-hotkey-reset {
   font-size: 0.9em;
-  margin-left: 5px;
+  margin-right: 10px;
 }
 
 .bytm-hotkey-info {

+ 57 - 38
src/menu/hotkeyInput.ts

@@ -6,12 +6,15 @@ import "./hotkeyInput.css";
 
 interface HotkeyInputProps {
   initialValue?: HotkeyObj;
-  resetValue?: HotkeyObj;
   onChange: (hotkey: HotkeyObj) => void;
 }
 
+let initialHotkey: HotkeyObj | undefined;
+
 /** Creates a hotkey input element */
-export function createHotkeyInput({ initialValue, resetValue, onChange }: HotkeyInputProps): HTMLElement {
+export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps): HTMLElement {
+  initialHotkey = initialValue;
+
   const wrapperElem = document.createElement("div");
   wrapperElem.classList.add("bytm-hotkey-wrapper");
 
@@ -26,26 +29,44 @@ export function createHotkeyInput({ initialValue, resetValue, onChange }: Hotkey
   inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
 
   const resetElem = document.createElement("span");
-  resetElem.classList.add("bytm-hotkey-reset", "bytm-link");
+  resetElem.classList.add("bytm-hotkey-reset", "bytm-link", "bytm-hidden");
   resetElem.role = "button";
   resetElem.tabIndex = 0;
   resetElem.textContent = `(${t("reset")})`;
+  resetElem.ariaLabel = resetElem.title = t("reset");
+
+  const deactivate = () => {
+    siteEvents.emit("hotkeyInputActive", false);
+    const curVal = getFeatures().switchSitesHotkey ?? initialValue;
+    inputElem.value = curVal?.code ?? t("hotkey_input_click_to_change");
+    inputElem.dataset.state = "inactive";
+    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
+    infoElem.innerHTML = curVal ? getHotkeyInfoHtml(curVal) : "";
+  };
+
+  const activate = () => {
+    siteEvents.emit("hotkeyInputActive", true);
+    inputElem.value = "< ... >";
+    inputElem.dataset.state = "active";
+    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
+  };
 
   const resetClicked = (e: MouseEvent | KeyboardEvent) => {
     e.preventDefault();
     e.stopImmediatePropagation();
 
-    onChange(resetValue!);
-    inputElem.value = resetValue!.code;
-    inputElem.dataset.state = "inactive";
-    infoElem.textContent = getHotkeyInfo(resetValue!);
+    onChange(initialValue!);
+    deactivate();
+    inputElem.value = initialValue!.code;
+    infoElem.innerHTML = getHotkeyInfoHtml(initialValue!);
+    resetElem.classList.add("bytm-hidden");
   };
 
   resetElem.addEventListener("click", resetClicked);
-  resetElem.addEventListener("keydown", (e) => e.key === "Enter" && resetClicked(e));
+  resetElem.addEventListener("keydown", (e) => ["Enter", " ", "Space"].includes(e.key) && resetClicked(e));
 
   if(initialValue)
-    infoElem.textContent = getHotkeyInfo(initialValue);
+    infoElem.innerHTML = getHotkeyInfoHtml(initialValue);
 
   let lastKeyDown: HotkeyObj | undefined;
 
@@ -65,7 +86,8 @@ export function createHotkeyInput({ initialValue, resetValue, onChange }: Hotkey
     } as HotkeyObj;
     inputElem.value = hotkey.code;
     inputElem.dataset.state = "inactive";
-    infoElem.textContent = getHotkeyInfo(hotkey);
+    infoElem.innerHTML = getHotkeyInfoHtml(hotkey);
+    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
     onChange(hotkey);
   });
 
@@ -82,54 +104,51 @@ export function createHotkeyInput({ initialValue, resetValue, onChange }: Hotkey
       shift: e.shiftKey,
       ctrl: e.ctrlKey,
       alt: e.altKey,
-    } as HotkeyObj;
+    } satisfies HotkeyObj;
+
+    const keyChanged = initialHotkey?.code !== hotkey.code || initialHotkey?.shift !== hotkey.shift || initialHotkey?.ctrl !== hotkey.ctrl || initialHotkey?.alt !== hotkey.alt;
     lastKeyDown = hotkey;
 
-    inputElem.value = hotkey.code;
-    inputElem.dataset.state = "inactive";
-    infoElem.textContent = getHotkeyInfo(hotkey);
-    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
     onChange(hotkey);
-  });
 
-  const deactivate = () => {
-    siteEvents.emit("hotkeyInputActive", false);
-    const curVal = getFeatures().switchSitesHotkey ?? initialValue;
-    inputElem.value = curVal?.code ?? t("hotkey_input_click_to_change");
-    inputElem.dataset.state = "inactive";
-    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
-    infoElem.textContent = curVal ? getHotkeyInfo(curVal) : "";
-  };
+    if(keyChanged) {
+      deactivate();
+      resetElem.classList.remove("bytm-hidden");
+    }
+    else
+      resetElem.classList.add("bytm-hidden");
 
-  const activate = () => {
-    siteEvents.emit("hotkeyInputActive", true);
-    inputElem.value = "< ... >";
-    inputElem.dataset.state = "active";
-    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
-  };
+    inputElem.value = hotkey.code;
+    inputElem.dataset.state = "inactive";
+    infoElem.innerHTML = getHotkeyInfoHtml(hotkey);
+  });
 
   siteEvents.on("cfgMenuClosed", deactivate);
 
   inputElem.addEventListener("click", () => {
-    if(inputElem.dataset.state === "active")
-      deactivate();
+    if(inputElem.dataset.state === "inactive")
+      activate();
     else
+      deactivate();
+  });
+  inputElem.addEventListener("keydown", (e) => {
+    if(inputElem.dataset.state === "inactive" && ["Enter", " ", "Space"].includes(e.key))
       activate();
   });
 
+  wrapperElem.appendChild(resetElem);
   wrapperElem.appendChild(infoElem);
   wrapperElem.appendChild(inputElem);
-  resetValue && wrapperElem.appendChild(resetElem);
 
   return wrapperElem;
 }
 
-function getHotkeyInfo(hotkey: HotkeyObj) {
+function getHotkeyInfoHtml(hotkey: HotkeyObj) {
   const modifiers = [] as string[];
-  hotkey.ctrl && modifiers.push(t("hotkey_key_ctrl"));
-  hotkey.shift && modifiers.push(t("hotkey_key_shift"));
-  hotkey.alt && modifiers.push(getOS() === "mac" ? t("hotkey_key_mac_option") : t("hotkey_key_alt"));
-  return modifiers.reduce((a, c) => a += `${c} + `, "");
+  hotkey.ctrl && modifiers.push(`<kbd class="bytm-kbd">${t("hotkey_key_ctrl")}</kbd>`);
+  hotkey.shift && modifiers.push(`<kbd class="bytm-kbd">${t("hotkey_key_shift")}</kbd>`);
+  hotkey.alt && modifiers.push(`<kbd class="bytm-kbd">${getOS() === "mac" ? t("hotkey_key_mac_option") : t("hotkey_key_alt")}</kbd>`);
+  return `${modifiers.join(" ")}${modifiers.length > 0 ? " + " : ""}`;
 }
 
 /** Crude OS detection for keyboard layout purposes */

+ 0 - 1
src/menu/menu_old.ts

@@ -457,7 +457,6 @@ async function addCfgMenu() {
           case "hotkey":
             wrapperElem = createHotkeyInput({
               initialValue: initialVal as HotkeyObj,
-              resetValue: featInfo.switchSitesHotkey.default,
               onChange: (hotkey) => {
                 confChanged(featKey as keyof FeatureConfig, initialVal, hotkey);
               },

+ 3 - 3
src/utils/BytmDialog.css

@@ -306,7 +306,7 @@
 
 /* Markdown stuff */
 
-.bytm-markdown-container kbd {
+.bytm-markdown-container kbd, .bytm-kbd {
   --bytm-easing: cubic-bezier(0.31, 0.58, 0.24, 1.15);
   display: inline-block;
   vertical-align: bottom;
@@ -321,12 +321,12 @@
   transition: padding 0.1s var(--bytm-easing), box-shadow 0.1s var(--bytm-easing);
 }
 
-.bytm-markdown-container kbd:active {
+.bytm-markdown-container kbd:active, .bytm-kbd:active {
   padding-bottom: 2px;
   box-shadow: inset 0 0 0 initial;
 }
 
-.bytm-markdown-container kbd::selection {
+.bytm-markdown-container kbd::selection, .bytm-kbd::selection {
   background: rgba(0, 0, 0, 0);
 }