Tumortisch-Dist/resources/app/lib/3rdparty/keyboard/keyboard.js

364 lines
11 KiB
JavaScript
Raw Normal View History

2021-06-15 16:00:08 +02:00
if( !exports ) var exports = {};
(function(exports, global){
"use strict";
function splice(str, index, count, add) {
return str.slice(0, index) + (add || "") + str.slice(index + count);
}
function checkSelectionSupport(type){
if (["text", "search", "URL", "tel", "password"].indexOf(type) == -1 ) return false;
return true;
}
function dispatchKeyboardOpenEvent(){
document.dispatchEvent(new CustomEvent('keyboard-open'));
}
function dispatchKeyboardCloseEvent(){
document.dispatchEvent(new CustomEvent('keyboard-close'));
}
function Element(Keyboard, el){
this.Keyboard = Keyboard;
el.layout = global.Keyboard.layout[el.type] ? el.type : '_default';
function dispatchEvent(event, keyInfo){
var event = new KeyboardEvent(event,{
key: keyInfo.symbol,
code: 'Key' + keyInfo.symbol,
keyCode : keyInfo.symbol.charCodeAt(0),
which : keyInfo.symbol.charCodeAt(0),
altKey : false,
ctrlKey : false,
shiftKey : false,
metaKey : false
});
event.virtual = true;
el.dispatchEvent(event);
}
this.onEvent = function(keyInfo){
dispatchEvent('keydown', keyInfo);
dispatchEvent('keypress', keyInfo);
dispatchEvent('keyup', keyInfo);
dispatchEvent('input', keyInfo);
dispatchEvent('change', keyInfo);
};
this.keydownfunc = function(e){
// If keydown is authentic, skip our internal update and let the default play out
if (!e.virtual) return;
e.preventDefault();
var selectionSupported = checkSelectionSupport(this.type);
if (e.key == "backspace") {
if ( !selectionSupported ) {
this.value = this.value.slice(0, -1);
} else {
var pos = el.selectionStart;
var offset = el.selectionEnd-el.selectionStart? 0:1;
this.value = this.value.substring(0, el.selectionStart-offset) + this.value.slice(el.selectionEnd);
this.setSelectionRange(pos-offset, pos-offset);
}
return;
}
// Toggle will change the layout to a different one that is pre-determined
// An empty toggle key will toggle the keyboard back to default
if (e.key.substring(0, 6) == "toggle") {
var newType = e.key.substring(6);
if(newType.length >= 0 && global.Keyboard.layout[newType]) {
return this.Keyboard.Keyboard.show(newType);
}
return;
}
if( this.value.length >= this.maxLength && this.maxLength != -1 ){
return;
}
var update = e.key || String.fromCharCode(e.keyCode);
// Support an input case that will capitalize letters as needed
if(( e.target.dataset.case === 'sentence' && (this.value.length === 0 || this.value.slice(-1) === "." || this.value.slice(-2) === ". " ))
|| ( e.target.dataset.case === 'capitalize' && this.value.slice(-1) === " " ))
{
update = update.toUpperCase();
}
// TODO Mimic selection for input elements that don't support selection api as well
// IDEA: selection-polyfill? get caret's pixel location instead?
if ( !selectionSupported ) {
this.value += update;
} else {
var pos = el.selectionStart + update.length;
this.value = splice(
this.value,
el.selectionStart,
el.selectionEnd-el.selectionStart,
update
);
this.setSelectionRange(pos, pos); // reset the position after the splice
}
// TODO Calculate scroll amount based on caret position
this.scrollLeft = this.scrollWidth;
};
el.addEventListener('focus', this.focus.bind(this));
el.addEventListener('blur', this.blur.bind(this));
el.addEventListener('keydown', this.keydownfunc);
}
Element.prototype.focus = function(e){
this.Keyboard.show(e.target.layout);
this.Keyboard.on('key', this.onEvent);
};
Element.prototype.blur = function(e){
this.Keyboard.hide(e.target.layout);
this.Keyboard.off('key', this.onEvent);
};
function Keyboard(inputs, holder){
var self = this;
Array.prototype.slice.call(inputs, 0).forEach(function(input){
input.Keyboard = new Element(self, input);
});
this.active = false;
this.listeners = {key:[]};
this.keyboardEl = null;
this.layout = null;
this.keyboardEl = document.createElement("div");
this.keyboardEl.classList.add("keyboard-container");
this.keyboardEl.addEventListener("mousedown", handleKeydownEvents.bind(this));
this.keyboardEl.addEventListener("mouseup", handleKeyupEvents.bind(this));
// TODO - find a way to enable this touchstart event again.
// It prevents the :active state from being triggered on keys
this.keyboardEl.addEventListener("touchstart", handleKeydownEvents.bind(this));
this.keyboardEl.addEventListener("touchend", handleKeyupEvents.bind(this));
// Generate keyboard HTML, bind events, insert them to given element
this.show = function (layout) {
// Clear any timers relating to keyhold
this.clearKeyHoldTimers();
if (!global.Keyboard.layout[layout]) throw new Error("keyboard initiation: Missing layout: " + layout);
if( self.layout && layout === self.layout && this.active){
return;
}
this.active = true;
self.layout = layout;
this.keyboardEl.innerHTML = "";
this.keyboardEl.classList.remove('keyboard-container-hidden');
var closeButton = document.createElement("span");
closeButton.classList.add('keyboard-close-button');
closeButton.innerHTML = '✖';
this.keyboardEl.appendChild(closeButton);
closeButton.onclick = function() {
document.activeElement.blur()
}.bind(this);
closeButton.ontouchstart = function() {
document.activeElement.blur()
}.bind(this);
function foreachLayout(row, rowIndex, layout) {
var rowEl = document.createElement("div");
rowEl.classList.add("keyboard-row");
rowEl.classList.add("keyboard-row--" + rowIndex);
function foreachRow(key, keyIndex, row) {
var keyEl = document.createElement("div");
keyEl.classList.add("keyboard-key");
keyEl.classList.add("keyboard-key--" + keyIndex);
// Parse the layout configuration
for (var dataName in key) {
switch (dataName) {
case "symbol":
if (!key.label) keyEl.innerHTML = key[dataName];
keyEl.dataset.symbol = key[dataName];
break;
case "label":
keyEl.innerHTML = key[dataName];
break;
default:
keyEl.dataset[dataName] = key[dataName];
}
}
rowEl.appendChild(keyEl);
}
row.forEach(foreachRow);
self.keyboardEl.appendChild(rowEl);
}
global.Keyboard.layout[self.layout].forEach(foreachLayout);
// Append keys to el
// Send an event signifying open of keyboard
dispatchKeyboardOpenEvent()
holder.appendChild(self.keyboardEl);
};
this.hide = function(){
self.active = false;
// Clear any timers relating to keyhold to cleanup
this.clearKeyHoldTimers();
// Send an event signifying close of keyboard
dispatchKeyboardCloseEvent()
setTimeout(function(){
if( self.active ) return;
self.keyboardEl.classList.add("keyboard-container-hidden");
self.keyboardEl.innerHTML = "";
},25);
}
this.on = function(evt, action){
if( !this.listeners[evt] ){
this.listeners[evt] = [];
}
this.listeners[evt].push(action);
};
this.off = function(evt, action){
if( !this.listeners[evt] ) return;
this.listeners[evt] = this.listeners[evt].filter(function(listener){
return action.toString() !== listener.toString();
});
};
/**
* Add input(s) after the fact
* @param inputs
*/
this.add = function(inputs){
if(!Array.isArray(inputs))
inputs = [inputs];
Array.prototype.slice.call(inputs, 0).forEach(function(input){
input.Keyboard = new Element(self, input);
});
};
/**
* Clear timer events bound to the keyboard
*/
this.clearKeyHoldTimers = function () {
// Clear timeout to make sure multiple keypress does not start
if(this.keyHoldTimeout) {
window.clearTimeout(this.keyHoldTimeout);
}
// Clear interval to make sure multiple keypress does not continue
if(this.keyHoldInterval) {
window.clearInterval(this.keyHoldInterval);
}
}
};
// We would like to pipe all keyboard events through one handler
var handleKeydownEvents = function (e) {
var self = this;
e.preventDefault();
// Check to make sure it's a key that's pressed
if (!e.target.classList.contains("keyboard-key")) return;
// the :active pseudo class is not working with touch events
// use active class to get around this issue
e.target.classList.add('active');
var keyInfo = e.target.dataset;
self.listeners['key'].forEach(function(action){
action(keyInfo);
});
// Allow for a key to be input multiple times by holding it down
// Ensure that any current timers are cleared before continuing
self.clearKeyHoldTimers();
// Timeout will provide a delay to prevent accidental holding
// After that, the interval will provide repeated input
self.keyHoldTimeout = window.setTimeout(function() {
self.keyHoldInterval = window.setInterval(function() {
self.listeners['key'].forEach(function(action){
action(keyInfo);
});
}, 100)
}, 400)
};
var handleKeyupEvents = function (e) {
e.target.classList.remove('active');
// Clear timeout to make sure multiple keypress does not start
this.clearKeyHoldTimers();
}
global.Keyboard = Keyboard;
global.Keyboard.layout = {
_default: [
[
{"symbol": "Q"},
{"symbol": "W"},
{"symbol": "E"},
{"symbol": "R"},
{"symbol": "T"},
{"symbol": "Y"},
{"symbol": "U"},
{"symbol": "I"},
{"symbol": "O"},
{"symbol": "P"},
{"label": ".com", "symbol": ".COM"},
{"symbol": "7"},
{"symbol": "8"},
{"symbol": "9"},
{"label": "\u21E6", "symbol":"backspace"}
],
[
// {"label": "tab", "func": "tab"},
{"symbol": "A"},
{"symbol": "S"},
{"symbol": "D"},
{"symbol": "F"},
{"symbol": "G"},
{"symbol": "H"},
{"symbol": "J"},
{"symbol": "K"},
{"symbol": "L"},
{"symbol": "@"},
{"label": ".net", "symbol": ".NET"},
{"symbol": "4"},
{"symbol": "5"},
{"symbol": "6"}
],
[
{"symbol": "Z"},
{"symbol": "X"},
{"symbol": "C"},
{"symbol": "V"},
{"symbol": "B"},
{"symbol": "N"},
{"symbol": "M"},
{"symbol": "."},
{"symbol": "_"},
{"symbol": "-"},
{"symbol": " ", "label": "space"},
{"symbol": "0"},
{"symbol": "1"},
{"symbol": "2"},
{"symbol": "3"}
]
]
};
})(exports || {}, window);