keyHandler(name, callback) – A helper for keypress events

This topic is tangentially related to prosemirror.

I really like the way the prosemirror-keymap library handles capturing keypress events, and I wanted to be able to do something similar for registering event listeners outside of prosemirror. As a result, I created a helper function called keyHandler that wraps a callback and makes sure it’s only called for the specificed key-combination.

My goal was to be able to write handlers like this:

document.addEventListener("keypress", keyHandler("Ctrl-/", () => {
  console.log("Pressed Ctrl + /")
})

What I wound up making seems to work, but I don’t fully understand the keymap package. The code looks like:

import * as keyName from "w3c-keyname"

export function keyHandler(binding: string, callback: Function) {
  let normalizedBinding = normalizeKeyName(binding)
  return function(event: KeyboardEvent): void {
    let name = keyName(event)
    let isChar = name.length == 1 && name != " "

    let notCharName = modifiers(name, event, !isChar)
    let shiftName = modifiers(name, event, true)
    let modPressed = event.shiftKey || event.altKey || event.metaKey
    let baseName = keyName.base[event.keyCode]
    if (notCharName === normalizedBinding) {
      callback(event)
    } else if (isChar && modPressed && baseName != name) {
      if (shiftName === normalizedBinding) {
        callback(event)
      }
    }
  }
}

/**
 * Everything below here is taken from the `prosemirror-keymap` package.
 */
const mac = typeof navigator != "undefined" ? /Mac/.test(navigator.platform) : false

function normalizeKeyName(name: string): string {
  let parts = name.split(/-(?!$)/),
    result = parts[parts.length - 1]
  if (result == "Space") result = " "
  let alt, ctrl, shift, meta
  for (let i = 0; i < parts.length - 1; i++) {
    let mod = parts[i]
    if (/^(cmd|meta|m)$/i.test(mod)) meta = true
    else if (/^a(lt)?$/i.test(mod)) alt = true
    else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true
    else if (/^s(hift)?$/i.test(mod)) shift = true
    else if (/^mod$/i.test(mod)) {
      if (mac) meta = true
      else ctrl = true
    } else throw new Error("Unrecognized modifier name: " + mod)
  }
  if (alt) result = "Alt-" + result
  if (ctrl) result = "Ctrl-" + result
  if (meta) result = "Meta-" + result
  if (shift) result = "Shift-" + result
  return result
}

function modifiers(name: string, event: KeyboardEvent, shift: boolean) {
  if (event.altKey) name = "Alt-" + name
  if (event.ctrlKey) name = "Ctrl-" + name
  if (event.metaKey) name = "Meta-" + name
  if (shift !== false && event.shiftKey) name = "Shift-" + name
  return name
}

If @marijn or any others have some feedback on this it would be greatly appreciated. I would also be up for open-sourcing the code.