packages/core/src/models/Keyboard.ts (110 lines of code) (raw):
import { observable, define, action } from '@formily/reactive'
import { KeyCode } from '@designable/shared'
import { Engine } from './Engine'
import { Shortcut } from './Shortcut'
import { AbstractKeyboardEvent } from '../events/keyboard/AbstractKeyboardEvent'
import { IEngineContext } from '../types'
const Modifiers: [string, KeyCode][] = [
['metaKey', KeyCode.Meta],
['shiftKey', KeyCode.Shift],
['ctrlKey', KeyCode.Control],
['altKey', KeyCode.Alt],
]
export interface IKeyboard {
engine: Engine
}
export class Keyboard {
engine: Engine
shortcuts: Shortcut[] = []
sequence: KeyCode[] = []
keyDown: KeyCode = null
modifiers = {}
requestTimer = null
constructor(engine?: Engine) {
this.engine = engine
this.shortcuts = engine.props?.shortcuts || []
this.makeObservable()
}
matchCodes(context: IEngineContext) {
for (let i = 0; i < this.shortcuts.length; i++) {
const shortcut = this.shortcuts[i]
if (shortcut.match(this.sequence, context)) {
return true
}
}
return false
}
preventCodes() {
return this.shortcuts.some((shortcut) => {
return shortcut.preventCodes(this.sequence)
})
}
includes(key: KeyCode) {
return this.sequence.some((code) => Shortcut.matchCode(code, key))
}
excludes(key: KeyCode) {
this.sequence = this.sequence.filter(
(code) => !Shortcut.matchCode(key, code)
)
}
addKeyCode(key: KeyCode) {
if (!this.includes(key)) {
this.sequence.push(key)
}
}
removeKeyCode(key: KeyCode) {
if (this.includes(key)) {
this.excludes(key)
}
}
isModifier(code: KeyCode) {
return Modifiers.some((modifier) => Shortcut.matchCode(modifier[1], code))
}
handleModifiers(event: AbstractKeyboardEvent) {
Modifiers.forEach(([key, code]) => {
if (event[key]) {
if (!this.includes(code)) {
this.sequence = [code].concat(this.sequence)
}
}
})
}
handleKeyboard(event: AbstractKeyboardEvent, context: IEngineContext) {
if (event.eventType === 'keydown') {
this.keyDown = event.data
this.addKeyCode(this.keyDown)
this.handleModifiers(event)
if (this.matchCodes(context)) {
this.sequence = []
}
this.requestClean(4000)
if (this.preventCodes()) {
event.preventDefault()
event.stopPropagation()
}
} else {
if (this.isModifier(event.data)) {
this.sequence = []
}
this.keyDown = null
}
}
isKeyDown(code: KeyCode) {
return this.keyDown === code
}
requestClean(duration = 320) {
clearTimeout(this.requestTimer)
this.requestTimer = setTimeout(() => {
this.keyDown = null
this.sequence = []
clearTimeout(this.requestTimer)
}, duration)
}
makeObservable() {
define(this, {
sequence: observable.shallow,
keyDown: observable.ref,
handleKeyboard: action,
})
}
}