Skip to content

Commit 0f5e4fc

Browse files
fix(NcDateTimePicker): manual date input
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent 7f48617 commit 0f5e4fc

1 file changed

Lines changed: 89 additions & 7 deletions

File tree

src/components/NcDateTimePicker/NcDateTimePicker.vue

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ import {
288288
getFirstDay,
289289
} from '@nextcloud/l10n'
290290
import VueDatePicker from '@vuepic/vue-datepicker'
291-
import { computed, useTemplateRef } from 'vue'
291+
import { computed, ref, useTemplateRef } from 'vue'
292292
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
293293
import NcTimezonePicker from '../NcTimezonePicker/NcTimezonePicker.vue'
294294
import { t } from '../../l10n.ts'
@@ -421,6 +421,13 @@ const props = withDefaults(defineProps<{
421421
* @default false
422422
*/
423423
inline?: boolean
424+
425+
/**
426+
* Enable or disable manual text input for the date picker.
427+
*
428+
* @default true
429+
*/
430+
manualInput?: boolean
424431
}>(), {
425432
ariaLabel: t('Datepicker input'),
426433
ariaLabelMenu: t('Datepicker menu'),
@@ -433,6 +440,7 @@ const props = withDefaults(defineProps<{
433440
modelValue: null,
434441
// set by fallbackPlaceholder
435442
placeholder: undefined,
443+
manualInput: true,
436444
type: 'date',
437445
inline: false,
438446
})
@@ -453,6 +461,7 @@ const emit = defineEmits<{
453461
454462
const targetElement = useTemplateRef('target')
455463
const pickerInstance = useTemplateRef('picker')
464+
const manualInputValue = ref<Date | null>(null)
456465
457466
/**
458467
* Mapping of the model-value prop to the format expected by the library.
@@ -559,12 +568,17 @@ const realFormat = computed<LibraryFormatOptions>(() => {
559568
}
560569
561570
if (formatter) {
562-
return (input: Date | [Date, Date]) => Array.isArray(input)
563-
? formatter.formatRange(input[0], input[1])
564-
: formatter.format(input)
571+
return (input: Date | [Date, Date]) => {
572+
if (Array.isArray(input)) {
573+
return isFiniteDateRange(input)
574+
? formatter.formatRange(input[0], input[1])
575+
: ''
576+
}
577+
578+
return isFiniteDate(input) ? formatter.format(input) : ''
579+
}
565580
}
566581
567-
// fallback to default formatting
568582
return undefined
569583
})
570584
@@ -627,6 +641,50 @@ function onUpdateModelValue(value: LibraryModelValue): void {
627641
}
628642
}
629643
644+
/**
645+
* Propagate valid manually typed input through the same model normalization path as picker selections.
646+
*
647+
* @param event The raw input event
648+
* @param parsedDate The library-parsed date or null if parsing failed
649+
*/
650+
function onTextInput(event: Event, parsedDate: Date | null): void {
651+
if (parsedDate) {
652+
manualInputValue.value = parsedDate
653+
return
654+
}
655+
656+
const input = event.target instanceof HTMLInputElement ? event.target.value : ''
657+
const tempDate = new Date(input)
658+
manualInputValue.value = Number.isNaN(tempDate.getTime()) ? null : tempDate
659+
660+
if (manualInputValue.value !== null && pickerInstance.value) {
661+
pickerInstance.value.updateInternalModelValue(manualInputValue.value)
662+
}
663+
}
664+
665+
function onTextSubmit(): void {
666+
commitManualInput()
667+
}
668+
669+
function onManualInputKeySubmit(): void {
670+
commitManualInput()
671+
}
672+
673+
function onBlur(): void {
674+
commitManualInput()
675+
emit('blur')
676+
}
677+
678+
function commitManualInput(): void {
679+
if (manualInputValue.value === null) {
680+
return
681+
}
682+
683+
onUpdateModelValue(manualInputValue.value)
684+
manualInputValue.value = null
685+
pickerInstance.value?.closeMenu()
686+
}
687+
630688
/**
631689
* Format a vuepick time object to native JS Date object.
632690
*
@@ -762,6 +820,26 @@ function sameDay(a: Date, b: Date): boolean {
762820
&& a.getDate() === b.getDate()
763821
)
764822
}
823+
824+
/**
825+
*
826+
* @param value
827+
*/
828+
function isFiniteDate(value: unknown): value is Date {
829+
return value instanceof Date && Number.isFinite(value.getTime())
830+
}
831+
832+
/**
833+
*
834+
* @param value
835+
*/
836+
function isFiniteDateRange(value: unknown): value is [Date, Date] {
837+
return Array.isArray(value)
838+
&& value.length === 2
839+
&& isFiniteDate(value[0])
840+
&& isFiniteDate(value[1])
841+
}
842+
765843
</script>
766844

767845
<template>
@@ -789,13 +867,17 @@ function sameDay(a: Date, b: Date): boolean {
789867
sixWeeks="fair"
790868
:inline
791869
:teleport="appendToBody ? (targetElement || undefined) : false"
792-
textInput
870+
:textInput="manualInput"
793871
:weekNumName
794872
:weekNumbers="showWeekNumber ? { type: 'iso' } : undefined"
795873
:weekStart
796874
v-bind="pickerType"
797875
@update:modelValue="onUpdateModelValue"
798-
@blur="emit('blur')">
876+
@blur="onBlur"
877+
@keydown.enter.capture="onManualInputKeySubmit"
878+
@keydown.tab.capture="onManualInputKeySubmit"
879+
@textInput="onTextInput"
880+
@textSubmit="onTextSubmit">
799881
<template #action-buttons>
800882
<NcButton size="small" variant="tertiary" @click="cancelSelection">
801883
{{ t('Cancel') }}

0 commit comments

Comments
 (0)