handsfree
Version:
A library for creating head-controlled, handsfree user interfaces via computer vision just...like...✨...that!
175 lines (153 loc) • 5.25 kB
JavaScript
/**
* Adds a simple keyboard
* - Use `handsfree.dispatch('SimpleKeyboard:injectKeyboard')` to re-render keyboard
* - Keyboards are injected into `.handsfree-simple-keyboard`
* - They are then given `.handsfree-simple-keyboard-rendered` to signal to CSS that it has a keyboard
* - Adds `body.handsfree-simple-keyboard-is-visible` `on('SimpleKeyboard:show')`
*
* @listens SimpleKeyboard:injectKeyboard Injects the keyboard into all .handsfree-simple-keyboard
* @emits handsfree:SimpleKeyboard:change(value)
* @see https://franciscohodge.com/projects/simple-keyboard/documentation/
*/
const Keyboard = require('simple-keyboard').default
require('simple-keyboard/build/css/index.css')
module.exports = {
name: 'SimpleKeyboard',
// Collection of keyboards
// {$input, $keyboard, keyboard}
keyboards: [],
// The target input element receiving content
$target: null,
// Cache different callbacks
callbacks: {
focusin: []
},
/**
* Setup events
*
* @listens SimpleKeyboard:injectKeyboard
* @listens SimpleKeyboard:show
* @listens SimpleKeyboard:hide
* @listens SimpleKeyboard:set
*
* @emits SimpleKeyboard:injectKeyboard
*/
onUse () {
this.$handsfree.on('SimpleKeyboard:injectKeyboard', () => this.injectKeyboard())
this.$handsfree.on('SimpleKeyboard:show', value => this.show(value))
this.$handsfree.on('SimpleKeyboard:hide', this.hide)
this.$handsfree.on('SimpleKeyboard:set', value => this.set(value))
this.$handsfree.dispatch('SimpleKeyboard:injectKeyboard')
this.listenToFocusEvents()
},
/**
* Shows the keyboard
* - Adds `body.handsfree-simple-keyboard-is-visible`
*/
show () {
document.body.classList.add('handsfree-simple-keyboard-is-visible')
this.set(this.$target.value)
},
/**
* Hides the keyboard
* - Removes `body.handsfree-simple-keyboard-is-visible`
*/
hide () {
document.body.classList.remove('handsfree-simple-keyboard-is-visible')
},
/**
* Sets the value of the keyboard
*
* @param {String} value The new value to use
* @emits handsfree:SimpleKeyboard:change(value)
*/
set (value = '') {
this.keyboards.forEach(board => {
board.keyboard.setInput(value)
this.$target.value = board.$keyboard.value = board.$input.value = value
// Required on the target for reactive frameworks like React/Vue
this.$target.dispatchEvent(new Event('input'))
this.$handsfree.dispatch('SimpleKeyboard:change', value)
})
},
/**
* Injects the keyboard
* - Adds .handsfree-simple-keyboard-rendered to prevent duplicates
*/
injectKeyboard () {
document.querySelectorAll('.handsfree-simple-keyboard:not(.handsfree-simple-keyboard-rendered)').forEach($el => {
const $input = document.createElement('input')
const $keyboard = document.createElement('div')
$keyboard.classList.add('simple-keyboard')
$input.classList.add('simple-keyboard-input')
$el.appendChild($input)
$el.appendChild($keyboard)
$el.classList.add('handsfree-simple-keyboard-rendered')
this.keyboards.push({
$input,
$keyboard,
keyboard: new Keyboard({
/**
* Set the value
*/
onChange: value => this.set(value),
/**
* Handle the `{enter}` key
* @see https://franciscohodge.com/projects/simple-keyboard/documentation/
*/
onKeyPress: button => {
if (button === '{enter}') {
this.$target.dispatchEvent(new KeyboardEvent('keyup', {
bubbles: true,
cancelable: true,
keyCode: 13
}))
this.$handsfree.dispatch('SimpleKeyboard:hide')
}
}
})
})
})
},
/**
* Adds event listeners to input focus events, to know when to trigger show/hide events
*/
listenToFocusEvents () {
const callback = ev => {
const name = ev.target.nodeName
const type = ev.target.type
if (!ev.target.classList.contains('simple-keyboard-input')) {
if ((name === 'INPUT' && (type === 'text' || type === 'password'))
|| (name === 'TEXTAREA')) {
this.$target = ev.target
this.show()
}
}
this.$target && this.setInputType()
}
this.callbacks.focusin.push(callback)
document.addEventListener('click', callback)
document.addEventListener('focusin', callback)
},
/**
* Changes the simple-keyboarde input type to text/password
*/
setInputType () {
let type = 'text'
switch (this.$target.type) {
case 'password': type = 'password'; break
}
this.keyboards.forEach(board => {
board.$input.type = type
})
},
/**
* Stop listening to document.addEventListener
*/
unlistenToFocusEvents () {
this.callbacks.focusin.forEach(callback => {
document.removeEventListener('focusin', callback)
document.removeEventListener('click', callback)
})
}
}