<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> <div v-else>No keyboard to display, please add one.</div> <Modal v-if="optionsModal" v-model="optionsModal"> <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; }, keyInput(e) { 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 }); 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>