JSFiddle - React, Tailwind, and code Playground

by nickcoutsos

HTML

<html>
<body>
  <template id="sequence">
    <ol class="sequence">
    </ol>
  </template>
  <template id="stage">
    <ol class="stage">
    </ol>
  </template>
  <template id="token">
    <li class="token"></li>
  </template>
</body>
</html>

CSS

:root {
  font-family: sans-serif;
}

.stage {
  display: inline-block;
  margin: 0 5px;
  padding: 0;
}
.token {
  display: inline-block;
  color: white;
  background-color: #246;
  margin: 1px;
  padding: 2px 4px;
  border-radius: 4px;
}
.token.modifier {
  background-color: #264;
}

JavaScript

const modifiersMap = {
  ctrl: 'ctrl',
  control: 'ctrl',
  meta: 'meta',
  gui: 'meta',
  cmd: 'meta',
  command: 'meta',
  win: 'meta',
  alt: 'alt',
  option: 'alt',
  opt: 'alt',
  shift: 'shift'
}

const modifiers = ['alt', 'ctrl', 'meta', 'shift']

function setsAreEqual (a, b) {
	return (
    a.size === b.size &&
    [...a].every(v => b.has(v))
  )
}


function Stage (modifiers = [], keys = []) {
	this.modifiers = new Set(modifiers)
  this.keys = new Set(keys)
}

function Shortcut (stages, handler) {
	this.sequence = stages
  this.handler = handler
}

Shortcut.prototype.match = function (buffer) {
	return (
  	buffer.length === this.sequence.length &&
    this.sequence.every((stage, i) => (
    	setsAreEqual(buffer[i].modifiers, stage.modifiers) &&
      setsAreEqual(buffer[i].keys, stage.keys)
    ))
  )
}

function parseShortcut(shortcut) {
  const tokens = []
  const pattern = /^\s*([a-z0-9]+|\+)/
  let match = shortcut.match(pattern)
  while (match) {
    tokens.push(match[1])
    shortcut = shortcut.slice(match[0].length)
    match = shortcut.match(pattern)
  }

  return tokens
}

function sequenceTokens(tokens) {
  return tokens.reduce((seq, token, i) => {
    const previousToken = i > 0 ? tokens[i - 1] : null
    const previousStage = seq[seq.length - 1]
    const continuing = previousToken === '+'
    let stage = new Stage()

    if (token === '+') {
      if (!previousStage) {
        throw new Error('Cannot use "+" as first token')
      } else if (continuing) {
        throw new Error('Unexpected "+ +"')
      }
    } else if (continuing) {
      stage = previousStage
    } else {
    	seq.push(stage)
    }

    if (modifiersMap[token]) {
      stage.modifiers.add(`${modifiersMap[token]}`)
    } else {
      stage.keys.add(token)
    }

    return seq
  }, [])
}

function getKeyEventModifiers(event) {
	return modifiers.reduce((acc, mod) => {
    if (event[`${mod}Key`]) {
      acc.push(mod)
    }
    return acc
  }, [])
}

class ShortcutListener...