Newer
Older
<template>
<div class="virtual-keyboard">
<!-- Allow focus on the keyboard to automatically restore focus on the input -->
<section tabindex="0">
<div class="header">
<a v-if="manager" v-on:click.prevent="optionsModal = true" href="#">
Manage Keyboards
</a>
</div>
<div class="content" v-if="keyboard">
<KeyboardDisplay
:keyboard-index="selectedKeyboard"
v-on:input="addChar"
/>
<div v-else>No keyboard is selected, please choose one.</div>
<KeyboardManager />
</Modal>
</section>
</div>
</template>
<script>
import { mapState } from "vuex";
import KeyboardDisplay from "./KeyboardDisplay.vue";
import KeyboardManager from "./KeyboardManager.vue";
import Modal from "./Modal.vue";
export default {
components: {
KeyboardDisplay,
KeyboardManager,
Modal,
},
props: {
inputField: {
type: [HTMLInputElement, HTMLTextAreaElement],
required: true,
},
manager: {
type: Boolean,
default: false,
},
},
data: () => ({
optionsModal: false,
// Select the keyboard at index 0 in the keyboard list by default
selectedKeyboard: 0,
}),
created() {
this.inputField.addEventListener("blur", this.looseFocus);
this.inputField.addEventListener("keydown", this.keyInput);
},
beforeDestroy() {
this.inputField.removeEventListener("blur", this.looseFocus);
this.inputField.removeEventListener("keydown", this.keyInput);
},
computed: {
...mapState(["keyboards"]),
keyboard() {
return this.keyboards[this.selectedKeyboard];
},
},
methods: {
addChar(key) {
if (!key.character) return;
// Add a character to the input depending on the cursor selection
const start = this.inputField.selectionStart;
const value = this.inputField.value;
this.inputField.value =
value.slice(0, start) +
key.character +
value.slice(this.inputField.selectionEnd);
// Reset the input caret after modifying the input value
this.inputField.selectionStart = this.inputField.selectionEnd =
start + key.character.length;
if (!this.keyboard) return;
const key = this.keyboard.characters.find(
(c) => c.keyboard_code == e.key.charCodeAt()
);
if (!key) return;
this.addChar({ character: key.character });
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
e.preventDefault();
},
looseFocus() {
if (this.optionsModal) return;
// We need a short delay before checking where the user moved the focus
setTimeout(() => {
// In case the user typed on the keyboard, restore focus on the input
if (this.$el.contains(document.activeElement))
return this.inputField.focus();
// Otherwise emit a custom event on the input for the keyboard to be removed
this.inputField.dispatchEvent(
new CustomEvent("vk_rmkeyboard", { detail: this })
);
}, 1);
},
},
watch: {
optionsModal(value) {
// Focus the input when closing the modal
if (!value) this.inputField.focus();
},
},
};
</script>
<style scoped>
.virtual-keyboard {
/* Avoid properties inheritance */
all: initial;
/* Override required attributes */
position: absolute;
display: flex;
/* Arbitrary z-index */
z-index: 99999;
min-width: 20rem;
}
.virtual-keyboard > section {
/* reset style in order to use Pure CSS framework only */
all: unset;
width: 100%;
background-color: white;
border: solid #bfbfbf 1px;
border-radius: 0.25rem;
}
.virtual-keyboard .header {
border-bottom: solid #bfbfbf 1px;
height: 2rem;
}
.virtual-keyboard .content {
padding: 0.25rem;
}
</style>