fix: enhance TimePicker to support HH:MM:SS input and truncate seconds
This commit is contained in:
parent
223cbf4020
commit
4b4b188827
@ -71,14 +71,14 @@ import { Popover, TextInput } from 'frappe-ui'
|
|||||||
import { ref, computed, watch, nextTick } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: '' }, // Expect 24h format HH:MM, but will parse flexible input
|
modelValue: { type: String, default: '' }, // Expect 24h format HH:MM (seconds in HH:MM:SS accepted & truncated), parses flexible input
|
||||||
interval: { type: Number, default: 15 },
|
interval: { type: Number, default: 15 },
|
||||||
options: {
|
options: {
|
||||||
// Optional complete override of generated options (array of { value: 'HH:MM', label?: string })
|
// Optional complete override of generated options (array of { value: 'HH:MM', label?: string })
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
placement: {type: String, default: 'bottom-start'},
|
placement: { type: String, default: 'bottom-start' },
|
||||||
placeholder: { type: String, default: 'Select time' },
|
placeholder: { type: String, default: 'Select time' },
|
||||||
variant: { type: String, default: 'outline' },
|
variant: { type: String, default: 'outline' },
|
||||||
allowCustom: { type: Boolean, default: true },
|
allowCustom: { type: Boolean, default: true },
|
||||||
@ -119,10 +119,10 @@ function optionId(idx) {
|
|||||||
|
|
||||||
function minutesFromHHMM(str) {
|
function minutesFromHHMM(str) {
|
||||||
if (!str) return null
|
if (!str) return null
|
||||||
if (!/^\d{2}:\d{2}$/.test(str)) return null
|
if (!/^\d{2}:\d{2}(:\d{2})?$/.test(str)) return null
|
||||||
const [h, m] = str.split(':').map(Number)
|
const [h, m] = str.split(':').map((n) => parseInt(n))
|
||||||
if (h > 23 || m > 59) return null
|
if (h > 23 || m > 59) return null
|
||||||
return h * 60 + m
|
return h * 60 + m // ignore seconds if provided
|
||||||
}
|
}
|
||||||
const minMinutes = computed(() => minutesFromHHMM(props.minTime))
|
const minMinutes = computed(() => minutesFromHHMM(props.minTime))
|
||||||
const maxMinutes = computed(() => minutesFromHHMM(props.maxTime))
|
const maxMinutes = computed(() => minutesFromHHMM(props.maxTime))
|
||||||
@ -168,6 +168,8 @@ function normalize24(raw) {
|
|||||||
if (!raw) return ''
|
if (!raw) return ''
|
||||||
// already HH:MM 24h
|
// already HH:MM 24h
|
||||||
if (/^\d{2}:\d{2}$/.test(raw)) return raw
|
if (/^\d{2}:\d{2}$/.test(raw)) return raw
|
||||||
|
// HH:MM:SS -> truncate seconds
|
||||||
|
if (/^\d{2}:\d{2}:\d{2}$/.test(raw)) return raw.slice(0, 5)
|
||||||
const parsed = parseFlexibleTime(raw)
|
const parsed = parseFlexibleTime(raw)
|
||||||
return parsed.valid ? parsed.hh24 + ':' + parsed.mm : ''
|
return parsed.valid ? parsed.hh24 + ':' + parsed.mm : ''
|
||||||
}
|
}
|
||||||
@ -188,15 +190,23 @@ function parseFlexibleTime(input) {
|
|||||||
s = s.replace(/\./g, '') // remove periods like a.m.
|
s = s.replace(/\./g, '') // remove periods like a.m.
|
||||||
// Insert space before am/pm if missing
|
// Insert space before am/pm if missing
|
||||||
s = s.replace(/(\d)(am|pm)$/, '$1 $2')
|
s = s.replace(/(\d)(am|pm)$/, '$1 $2')
|
||||||
const re = /^(\d{1,2})(:?)(\d{0,2})\s*([ap]m)?$/
|
// supports: H, HH, HMM, HHMM, H:MM, HH:MM, HH:MM:SS, H:MM:SS + optional am/pm
|
||||||
|
// Simplify by using explicit colon format: H{1,2}(:MM)?(:SS)? with constraint that if SS present, MM must be present
|
||||||
|
const re = /^(\d{1,2})(?::(\d{1,2}))?(?::(\d{1,2}))?\s*([ap]m)?$/
|
||||||
const m = s.match(re)
|
const m = s.match(re)
|
||||||
if (!m) return { valid: false }
|
if (!m) return { valid: false }
|
||||||
let [, hhStr, colon, mmStr, ap] = m
|
let [, hhStr, mmStr, ssStr, ap] = m
|
||||||
let hh = parseInt(hhStr)
|
let hh = parseInt(hhStr)
|
||||||
if (isNaN(hh) || hh < 0 || hh > 23) return { valid: false }
|
if (isNaN(hh) || hh < 0 || hh > 23) return { valid: false }
|
||||||
let mm = mmStr ? parseInt(mmStr) : 0
|
// If seconds provided but minutes missing -> invalid
|
||||||
if (colon && !mmStr) mm = 0
|
if (ssStr && !mmStr) return { valid: false }
|
||||||
|
let mm = mmStr != null && mmStr !== '' ? parseInt(mmStr) : 0
|
||||||
if (isNaN(mm) || mm < 0 || mm > 59) return { valid: false }
|
if (isNaN(mm) || mm < 0 || mm > 59) return { valid: false }
|
||||||
|
if (ssStr) {
|
||||||
|
const ss = parseInt(ssStr)
|
||||||
|
if (isNaN(ss) || ss < 0 || ss > 59) return { valid: false }
|
||||||
|
// seconds currently ignored for internal value & total
|
||||||
|
}
|
||||||
if (ap) {
|
if (ap) {
|
||||||
if (hh === 12 && ap === 'am') hh = 0
|
if (hh === 12 && ap === 'am') hh = 0
|
||||||
else if (hh < 12 && ap === 'pm') hh += 12
|
else if (hh < 12 && ap === 'pm') hh += 12
|
||||||
@ -357,7 +367,6 @@ function scheduleScroll() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
watch(showOptions, (open) => {
|
watch(showOptions, (open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
emit('open')
|
emit('open')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user