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...