@@ -288,7 +288,7 @@ import {
288288 getFirstDay ,
289289} from ' @nextcloud/l10n'
290290import VueDatePicker from ' @vuepic/vue-datepicker'
291- import { computed , useTemplateRef } from ' vue'
291+ import { computed , ref , useTemplateRef } from ' vue'
292292import NcIconSvgWrapper from ' ../NcIconSvgWrapper/NcIconSvgWrapper.vue'
293293import NcTimezonePicker from ' ../NcTimezonePicker/NcTimezonePicker.vue'
294294import { 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
454462const targetElement = useTemplateRef (' target' )
455463const 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