Skip to content

Instantly share code, notes, and snippets.

@nexpr
Created April 12, 2018 16:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nexpr/7c4d46e4a7030374ce735b23e658fe11 to your computer and use it in GitHub Desktop.
Save nexpr/7c4d46e4a7030374ce735b23e658fe11 to your computer and use it in GitHub Desktop.
custom modifier key
<!doctype html>
<script type="module">
import cmkey from "./cmkey.js"
const log = text => {
document.querySelector("#div").prepend(text, document.createElement("br"))
}
cmkey.setOptions({
modifier: "Space",
submodifier: "Home",
action: {
KeyA() { log("m-a pressed") },
KeyB() { log("m-b pressed") },
KeyC: "State1"
},
subaction: {
KeyX() { log("s-x pressed") },
},
stateactions: {
State1: {
"M/KeyA"() { log("m-c m-a pressed") },
KeyB() { log("m-c b pressed") },
"S/KeyC"() { log("m-c s-c pressed") },
KeyD: "State2",
},
State2: {
KeyA() { log("m-c d a pressed") },
},
},
except_selector: "input",
})
cmkey.addStateChangedListener(state => {
const state_elem = document.querySelector("#state")
state_elem.innerHTML = state || ""
state_elem.style.backgroundColor = {
State1: "yellow",
State2: "greenyellow",
}[state]
})
</script>
<style>
#state {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
}
</style>
<input>
<div>Log</div>
<div id="div"></div>
<div id="state"></div>
export default {
setOptions(opt) {
Object.assign(options, opt)
},
getOptions() {
return options
},
addStateChangedListener(fn) {
if (typeof fn === "function") {
state_changed_listeners.push(fn)
}
},
__detectKey() {
window.addEventListener("keydown", eve => {
console.log(eve.code)
})
},
}
const options = {
selector: "",
except_selector: "",
modifier: "",
submodifier: "",
action: {},
subaction: {},
stateactions: {},
}
const state_changed_listeners = []
const status = {
_state: null,
get state() {
return this._state
},
set state(value) {
state_changed_listeners.forEach(f => f(value))
this._state = value
},
modifier: false,
submodifier: false,
}
window.addEventListener(
"keydown",
eve => {
const handled = (() => {
const code = eve.code
if (!options.modifier) {
return false
}
if ((code === options.modifier && status.submodifier) || (code === options.submodifier && status.modifier)) {
return false
}
if (code === options.modifier) {
status.modifier = true
return true
}
if (code === options.submodifier) {
status.submodifier = true
return true
}
const matched =
(options.selector ? eve.target.matches(options.selector) : true) &&
(options.except_selector ? !eve.target.matches(options.except_selector) : true)
if (!matched) {
return false
}
if (status.state) {
const action = options.stateactions[status.state] || {}
const prefix = status.modifier ? "M/" : status.submodifier ? "S/" : ""
return doAction(action, prefix + code)
} else {
const action = status.modifier ? options.action : status.submodifier ? options.subaction : {}
return doAction(action, code)
}
function doAction(action, key) {
if (typeof action[key] === "function") {
action[key](eve)
status.state = null
return true
} else if (typeof action[key] === "string") {
status.state = action[key]
return true
}
return false
}
return false
})()
if (handled) {
eve.preventDefault()
} else {
status.state = null
}
},
true
)
window.addEventListener(
"keyup",
eve => {
const code = eve.code
if (code === options.modifier) {
status.modifier = false
}
if (code === options.submodifier) {
status.submodifier = false
}
},
true
)
<!doctype html>
<script type="module">
import Cmkey from "./Cmkey_class.js"
const log = text => {
document.querySelector("#div").prepend(text, document.createElement("br"))
}
new Cmkey({
modifier: "Space",
submodifier: "Home",
action: {
KeyA() { log("m-a pressed") },
KeyB() { log("m-b pressed") },
KeyC: "State1"
},
subaction: {
KeyX() { log("s-x pressed") },
},
stateactions: {
State1: {
"M/KeyA"() { log("m-c m-a pressed") },
KeyB() { log("m-c b pressed") },
"S/KeyC"() { log("m-c s-c pressed") },
KeyD: "State2",
},
State2: {
KeyA() { log("m-c d a pressed") },
},
},
except_selector: "input",
onstatechange: state => {
const state_elem = document.querySelector("#state")
state_elem.innerHTML = state || ""
state_elem.style.backgroundColor = {
State1: "yellow",
State2: "greenyellow",
}[state]
}
}).observe(window)
</script>
<style>
#state {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
}
</style>
<input>
<div>Log</div>
<div id="div"></div>
<div id="state"></div>
export default class Cmkey {
constructor(options) {
this.options = Object.assign(
{
selector: "",
except_selector: "",
modifier: "",
submodifier: "",
action: {},
subaction: {},
stateactions: {},
onstatechange: null,
},
options
)
this._state = null
this.modifier = false
this.submodifier = false
this.listeners = {
keydown: keydownListener.bind(this),
keyup: keyupListener.bind(this),
}
this.use_capture = null
}
get state() {
return this._state
}
set state(value) {
this.options.onstatechange && this.options.onstatechange(value)
this._state = value
}
observe(elem, use_capture) {
this.use_capture = use_capture
elem.addEventListener("keydown", this.listeners.keydown, use_capture)
elem.addEventListener("keyup", this.listeners.keyup, use_capture)
}
unobserve() {
elem.removeEventListener("keydown", this.listeners.keydown, this.use_capture)
elem.removeEventListener("keyup", this.listeners.keyup, this.use_capture)
}
}
function keydownListener(eve) {
const {options} = this
const handled = (() => {
const code = eve.code
if (!options.modifier) {
return false
}
if ((code === options.modifier && this.submodifier) || (code === options.submodifier && this.modifier)) {
return false
}
if (code === options.modifier) {
this.modifier = true
return true
}
if (code === options.submodifier) {
this.submodifier = true
return true
}
const matched =
(options.selector ? eve.target.matches(options.selector) : true) &&
(options.except_selector ? !eve.target.matches(options.except_selector) : true)
if (!matched) {
return false
}
const doAction = (action, key) => {
if (typeof action[key] === "function") {
action[key](eve)
this.state = null
return true
} else if (typeof action[key] === "string") {
this.state = action[key]
return true
}
return false
}
if (this.state) {
const action = options.stateactions[this.state] || {}
const prefix = this.modifier ? "M/" : this.submodifier ? "S/" : ""
return doAction(action, prefix + code)
} else {
const action = this.modifier ? options.action : this.submodifier ? options.subaction : {}
return doAction(action, code)
}
return false
})()
if (handled) {
eve.preventDefault()
} else {
this.state = null
}
}
function keyupListener(eve) {
const code = eve.code
if (code === this.options.modifier) {
this.modifier = false
}
if (code === this.options.submodifier) {
this.submodifier = false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment