Skip to content

Instantly share code, notes, and snippets.

@jb-alvarado
Created May 6, 2024 11:53
Show Gist options
  • Save jb-alvarado/af07271a6777d1cd5a00b9e973ea41a9 to your computer and use it in GitHub Desktop.
Save jb-alvarado/af07271a6777d1cd5a00b9e973ea41a9 to your computer and use it in GitHub Desktop.
Vue/Nuxt 3 custom time picker that support 24 hours in HH:mm:ss.ms format.
<template>
<div id="timeField" class="input input-sm input-bordered flex px-3 py-0">
<div class="grow">
<input
ref="timeInput"
:value="secToTime(props.modelValue)"
type="text"
pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]{1,3})?"
class="w-full px-1 py-0"
@click="setCursorPos"
@change="$emit('update:modelValue', timeToSec($event))"
/>
</div>
<div class="w-auto">
<div class="flex flex-col text-xs py-[2px]">
<button
class="bg-base-300 hover:bg-base-300/50 px-1 text-[9px] h-[13px] rounded-t"
tabindex="0"
@click="countUp"
>
<i class="bi-chevron-up" />
</button>
<button class="bg-base-300 hover:bg-base-300/50 px-1 text-[9px] h-[13px] rounded-b" @click="countDown">
<i class="bi-chevron-down" />
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
type: Number,
required: true,
},
})
const timeInput = ref()
const cursorPos = ref(8)
function secToTime(sec: number) {
const hours = Math.floor(sec / 3600)
sec %= 3600
const minutes = Math.floor(sec / 60)
const seconds = Math.floor(sec % 60)
const ms = Math.round((sec - Math.floor(sec)) * 1000) / 1000
const secFmt = (seconds + ms).toFixed(3)
const m = String(minutes).padStart(2, '0')
const h = String(hours).padStart(2, '0')
const s = secFmt.padStart(6, '0')
return `${h}:${m}:${s}`
}
function timeToSec(event: any) {
const time = event.target?.value ?? 0
const [h, m, s] = time.split(':').map((val: string) => Number(val) || 0)
return h * 3600 + m * 60 + s
}
function setCursorPos() {
cursorPos.value = timeInput.value?.selectionStart
}
function countUp() {
let count = 0
if (cursorPos.value && cursorPos.value >= 6) {
count = 1
} else if (cursorPos.value && cursorPos.value >= 3) {
count = 60
} else {
count = 3600
}
emit('update:modelValue', props.modelValue + count)
nextTick(() => {
timeInput.value?.focus()
timeInput.value?.setSelectionRange(cursorPos.value, cursorPos.value)
})
}
function countDown() {
let sec = props.modelValue
let count = 0
if (cursorPos.value && cursorPos.value >= 6) {
count = 1
} else if (cursorPos.value && cursorPos.value >= 3) {
count = 60
} else {
count = 3600
}
sec -= count
if (sec < 0) {
emit('update:modelValue', 0)
} else {
emit('update:modelValue', sec)
}
nextTick(() => {
timeInput.value?.focus()
timeInput.value?.setSelectionRange(cursorPos.value, cursorPos.value)
})
}
</script>
<style scoped>
#timeField:has(> div > input:invalid) {
border: red solid 1px;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment