<template> <div tabindex="0" @focus="focusDigit('second0')" class="relative"> <div class="rounded text-gray-200 border w-full px-3 py-2" :class="focusedDigit ? 'bg-primary bg-opacity-50 border-gray-300' : 'bg-primary border-gray-600'" @click="clickInput" v-click-outside="clickOutsideObj"> <div class="flex items-center"> <template v-for="(digit, index) in digitDisplay"> <div v-if="digit == ':'" :key="index" class="px-px" @click.stop="clickMedian(index)">:</div> <div v-else :key="index" class="px-px" :class="{ 'digit-focused': focusedDigit == digit }" @click.stop="focusDigit(digit)">{{ digits[digit] }}</div> </template> </div> </div> </div> </template> <script> export default { props: { value: [String, Number], showThreeDigitHour: Boolean }, data() { return { clickOutsideObj: { handler: this.clickOutside, events: ['mousedown'], isActive: true }, digitDisplay: ['hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0'], focusedDigit: null, digits: { hour2: 0, hour1: 0, hour0: 0, minute1: 0, minute0: 0, second1: 0, second0: 0 }, isOver99Hours: false } }, watch: { value: { immediate: true, handler() { this.initDigits() } } }, computed: {}, methods: { initDigits() { var totalSeconds = !this.value || isNaN(this.value) ? 0 : Number(this.value) totalSeconds = Math.round(totalSeconds) var minutes = Math.floor(totalSeconds / 60) var seconds = totalSeconds - minutes * 60 var hours = Math.floor(minutes / 60) minutes -= hours * 60 this.digits.second1 = seconds <= 9 ? 0 : Number(String(seconds)[0]) this.digits.second0 = seconds <= 9 ? seconds : Number(String(seconds)[1]) this.digits.minute1 = minutes <= 9 ? 0 : Number(String(minutes)[0]) this.digits.minute0 = minutes <= 9 ? minutes : Number(String(minutes)[1]) if (hours > 99) { this.digits.hour2 = Number(String(hours)[0]) this.digits.hour1 = Number(String(hours)[1]) this.digits.hour0 = Number(String(hours)[2]) this.isOver99Hours = true } else { this.digits.hour1 = hours <= 9 ? 0 : Number(String(hours)[0]) this.digits.hour0 = hours <= 9 ? hours : Number(String(hours)[1]) this.isOver99Hours = this.showThreeDigitHour } if (this.isOver99Hours) { this.digitDisplay = ['hour2', 'hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0'] } else { this.digitDisplay = ['hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0'] } }, updateSeconds() { var seconds = this.digits.second0 + this.digits.second1 * 10 seconds += this.digits.minute0 * 60 + this.digits.minute1 * 600 seconds += this.digits.hour0 * 3600 + this.digits.hour1 * 36000 if (this.isOver99Hours) seconds += this.digits.hour2 * 360000 if (Number(this.value) !== seconds) { this.$emit('input', seconds) this.$emit('change', seconds) } }, clickMedian(index) { // Click colon select digit to right if (index >= 5) { this.focusedDigit = 'second1' } else { this.focusedDigit = 'minute1' } }, clickOutside() { this.removeFocus() }, removeFocus() { this.focusedDigit = null this.removeListeners() }, focusDigit(digit) { if (this.focusedDigit == null || isNaN(this.focusedDigit)) this.initListeners() this.focusedDigit = digit }, clickInput() { if (this.focusedDigit) return this.focusDigit('second0') }, shiftFocusLeft() { if (!this.focusedDigit) return if (this.focusedDigit.endsWith('2')) return const isDigit1 = this.focusedDigit.endsWith('1') if (!isDigit1) { const digit1Key = this.focusedDigit.replace('0', '1') this.focusedDigit = digit1Key } else if (this.focusedDigit.startsWith('second')) { this.focusedDigit = 'minute0' } else if (this.focusedDigit.startsWith('minute')) { this.focusedDigit = 'hour0' } else if (this.isOver99Hours && this.focusedDigit.startsWith('hour')) { this.focusedDigit = 'hour2' } }, shiftFocusRight() { if (!this.focusedDigit) return if (this.focusedDigit.endsWith('2')) { // Must be hour2 this.focusedDigit = 'hour1' return } const isDigit1 = this.focusedDigit.endsWith('1') if (isDigit1) { const digit0Key = this.focusedDigit.replace('1', '0') this.focusedDigit = digit0Key } else if (this.focusedDigit.startsWith('hour')) { this.focusedDigit = 'minute1' } else if (this.focusedDigit.startsWith('minute')) { this.focusedDigit = 'second1' } }, increaseFocused() { if (!this.focusedDigit) return const isDigit1 = this.focusedDigit.endsWith('1') const digit = Number(this.digits[this.focusedDigit]) if (isDigit1 && !this.focusedDigit.startsWith('hour')) this.digits[this.focusedDigit] = (digit + 1) % 6 else this.digits[this.focusedDigit] = (digit + 1) % 10 this.updateSeconds() }, decreaseFocused() { if (!this.focusedDigit) return const isDigit1 = this.focusedDigit.endsWith('1') const digit = Number(this.digits[this.focusedDigit]) if (isDigit1 && !this.focusedDigit.startsWith('hour')) this.digits[this.focusedDigit] = digit - 1 < 0 ? 5 : digit - 1 else this.digits[this.focusedDigit] = digit - 1 < 0 ? 9 : digit - 1 this.updateSeconds() }, keydown(evt) { if (!this.focusedDigit || !evt.key) return if (evt.key === 'ArrowLeft') { return this.shiftFocusLeft() } else if (evt.key === 'ArrowRight') { return this.shiftFocusRight() } else if (evt.key === 'ArrowUp') { return this.increaseFocused() } else if (evt.key === 'ArrowDown') { return this.decreaseFocused() } else if (evt.key === 'Enter' || evt.key === 'Escape' || evt.key === 'Tab') { return this.removeFocus() } if (isNaN(evt.key)) return var digit = Number(evt.key) const isDigit1 = this.focusedDigit.endsWith('1') if (isDigit1 && !this.focusedDigit.startsWith('hour') && digit >= 6) { digit = 5 } this.digits[this.focusedDigit] = digit this.updateSeconds() this.shiftFocusRight() }, initListeners() { window.addEventListener('keydown', this.keydown) }, removeListeners() { window.removeEventListener('keydown', this.keydown) } }, mounted() {}, beforeDestroy() { this.removeListeners() } } </script> <style scoped> .digit-focused { background-color: #555; } </style>