@@ -6,6 +6,7 @@ import { makeVTextFieldProps, VTextField } from '@/components/VTextField/VTextFi
66
77// Composables
88import { useDate } from '@/composables/date'
9+ import { makeDateFormatProps , useDateFormat } from '@/composables/dateFormat'
910import { makeDisplayProps , useDisplay } from '@/composables/display'
1011import { makeFocusProps , useFocus } from '@/composables/focus'
1112import { forwardRefs } from '@/composables/forwardRefs'
@@ -14,7 +15,7 @@ import { useProxiedModel } from '@/composables/proxiedModel'
1415
1516// Utilities
1617import { computed , ref , shallowRef , watch } from 'vue'
17- import { genericComponent , omit , propsFactory , useRender , wrapInArray } from '@/util'
18+ import { createRange , genericComponent , omit , propsFactory , useRender , wrapInArray } from '@/util'
1819
1920// Types
2021import type { PropType } from 'vue'
@@ -35,7 +36,6 @@ export type VDateInputSlots = Omit<VTextFieldSlots, 'default'> & {
3536
3637export const makeVDateInputProps = propsFactory ( {
3738 displayFormat : [ Function , String ] ,
38- inputFormat : [ Function , String ] ,
3939 location : {
4040 type : String as PropType < StrategyProps [ 'location' ] > ,
4141 default : 'bottom start' ,
@@ -46,6 +46,7 @@ export const makeVDateInputProps = propsFactory({
4646 default : ( ) => [ 'blur' , 'enter' ] ,
4747 } ,
4848
49+ ...makeDateFormatProps ( ) ,
4950 ...makeDisplayProps ( {
5051 mobile : null ,
5152 } ) ,
@@ -54,7 +55,6 @@ export const makeVDateInputProps = propsFactory({
5455 hideActions : true ,
5556 } ) ,
5657 ...makeVTextFieldProps ( {
57- placeholder : 'mm/dd/yyyy' ,
5858 prependIcon : '$calendar' ,
5959 } ) ,
6060 ...omit ( makeVDatePickerProps ( {
@@ -76,8 +76,9 @@ export const VDateInput = genericComponent<VDateInputSlots>()({
7676 } ,
7777
7878 setup ( props , { emit, slots } ) {
79- const { t } = useLocale ( )
79+ const { t, current : currentLocale } = useLocale ( )
8080 const adapter = useDate ( )
81+ const { isValid, parseDate, formatDate, parserFormat } = useDateFormat ( props , currentLocale )
8182 const { mobile } = useDisplay ( props )
8283 const { isFocused, focus, blur } = useFocus ( props )
8384
@@ -100,77 +101,10 @@ export const VDateInput = genericComponent<VDateInputSlots>()({
100101 if ( typeof props . displayFormat === 'function' ) {
101102 return props . displayFormat ( date )
102103 }
103-
104- return adapter . format ( date , props . displayFormat ?? 'keyboardDate' )
105- }
106-
107- function parseDateString ( dateString : string , format : string ) {
108- function countConsecutiveChars ( str : string , startIndex : number ) : number {
109- const char = str [ startIndex ]
110- let count = 0
111- while ( str [ startIndex + count ] === char ) count ++
112- return count
104+ if ( props . displayFormat ) {
105+ return adapter . format ( date , props . displayFormat ?? 'keyboardDate' )
113106 }
114-
115- function parseDateParts ( dateString : string , format : string ) {
116- const dateParts : Record < string , number > = { }
117- let stringIndex = 0
118- const upperFormat = format . toUpperCase ( )
119-
120- for ( let formatIndex = 0 ; formatIndex < upperFormat . length ; ) {
121- const formatChar = upperFormat [ formatIndex ]
122- const charCount = countConsecutiveChars ( upperFormat , formatIndex )
123- const dateValue = dateString . slice ( stringIndex , stringIndex + charCount )
124-
125- if ( [ 'Y' , 'M' , 'D' ] . includes ( formatChar ) ) {
126- const numValue = parseInt ( dateValue )
127- if ( isNaN ( numValue ) ) return null
128- dateParts [ formatChar ] = numValue
129- }
130-
131- formatIndex += charCount
132- stringIndex += charCount
133- }
134-
135- return dateParts
136- }
137-
138- function validateDateParts ( dateParts : Record < string , number > ) {
139- const { Y : year , M : month , D : day } = dateParts
140- if ( ! year || ! month || ! day ) return null
141- if ( month < 1 || month > 12 ) return null
142- if ( day < 1 || day > 31 ) return null
143- return { year, month, day }
144- }
145-
146- const dateParts = parseDateParts ( dateString , format )
147- if ( ! dateParts ) return null
148-
149- const validatedParts = validateDateParts ( dateParts )
150- if ( ! validatedParts ) return null
151-
152- const { year, month, day } = validatedParts
153-
154- return { year, month, day }
155- }
156-
157- function parseUserInput ( value : string ) {
158- if ( typeof props . inputFormat === 'function' ) {
159- return props . inputFormat ( value )
160- }
161-
162- if ( typeof props . inputFormat === 'string' ) {
163- const formattedDate = parseDateString ( value , props . inputFormat )
164-
165- if ( ! formattedDate ) {
166- return model . value
167- }
168-
169- const { year, month, day } = formattedDate
170- return adapter . parseISO ( `${ year } -${ String ( month ) . padStart ( 2 , '0' ) } -${ String ( day ) . padStart ( 2 , '0' ) } ` )
171- }
172-
173- return adapter . isValid ( value ) ? adapter . date ( value ) : model . value
107+ return formatDate ( date )
174108 }
175109
176110 const display = computed ( ( ) => {
@@ -271,13 +205,35 @@ export const VDateInput = genericComponent<VDateInputSlots>()({
271205 }
272206
273207 function onUserInput ( { value } : HTMLInputElement ) {
274- model . value = ! value ? emptyModelValue ( ) : parseUserInput ( value )
208+ if ( ! value . trim ( ) ) {
209+ model . value = emptyModelValue ( )
210+ } else if ( ! props . multiple ) {
211+ if ( isValid ( value ) ) {
212+ model . value = parseDate ( value )
213+ }
214+ } else {
215+ const parts = value . trim ( ) . split ( / \D + - \D + | [ ^ \d \- / . ] + / )
216+ if ( parts . every ( isValid ) ) {
217+ if ( props . multiple === 'range' ) {
218+ model . value = getRange ( parts )
219+ } else {
220+ model . value = parts . map ( parseDate )
221+ }
222+ }
223+ }
224+ }
225+
226+ function getRange ( inputDates : string [ ] ) {
227+ const [ start , stop ] = inputDates . map ( parseDate ) . toSorted ( ( a , b ) => adapter . isAfter ( a , b ) ? 1 : - 1 )
228+ const diff = adapter . getDiff ( stop ?? start , start , 'days' )
229+ return [ start , ...createRange ( diff , 1 )
230+ . map ( i => adapter . addDays ( start , i ) ) ]
275231 }
276232
277233 useRender ( ( ) => {
278234 const confirmEditProps = VConfirmEdit . filterProps ( props )
279235 const datePickerProps = VDatePicker . filterProps ( omit ( props , [ 'active' , 'location' , 'rounded' ] ) )
280- const textFieldProps = VTextField . filterProps ( props )
236+ const textFieldProps = VTextField . filterProps ( omit ( props , [ 'placeholder' ] ) )
281237
282238 return (
283239 < VTextField
@@ -287,6 +243,7 @@ export const VDateInput = genericComponent<VDateInputSlots>()({
287243 style = { props . style }
288244 modelValue = { display . value }
289245 inputmode = { inputmode . value }
246+ placeholder = { props . placeholder ?? parserFormat . value }
290247 readonly = { isReadonly . value }
291248 onKeydown = { isInteractive . value ? onKeydown : undefined }
292249 focused = { menu . value || isFocused . value }
0 commit comments