@@ -56,6 +56,69 @@ function normalizeMinuteStep(step?: number): number {
5656 return Math . min ( 30 , Math . max ( 1 , Math . floor ( step ?? 1 ) ) )
5757}
5858
59+ function getDateValueTime ( value ?: DateValue ) : { hour : number ; minute : number } | null {
60+ if ( ! value || typeof value !== 'object' ) {
61+ return null
62+ }
63+
64+ if ( ! ( 'hour' in value ) || ! ( 'minute' in value ) ) {
65+ return null
66+ }
67+
68+ if ( typeof value . hour !== 'number' || typeof value . minute !== 'number' ) {
69+ return null
70+ }
71+
72+ return {
73+ hour : value . hour ,
74+ minute : value . minute ,
75+ }
76+ }
77+
78+ function getDateKey ( value ?: DateValue ) : string | null {
79+ if ( ! value || typeof value !== 'object' ) {
80+ return null
81+ }
82+
83+ if ( ! ( 'year' in value ) || ! ( 'month' in value ) || ! ( 'day' in value ) ) {
84+ return null
85+ }
86+
87+ if (
88+ typeof value . year !== 'number' ||
89+ typeof value . month !== 'number' ||
90+ typeof value . day !== 'number'
91+ ) {
92+ return null
93+ }
94+
95+ return `${ value . year } -${ value . month } -${ value . day } `
96+ }
97+
98+ function getDateParts ( value ?: DateValue ) : { year : number ; month : number ; day : number } | null {
99+ if ( ! value || typeof value !== 'object' ) {
100+ return null
101+ }
102+
103+ if ( ! ( 'year' in value ) || ! ( 'month' in value ) || ! ( 'day' in value ) ) {
104+ return null
105+ }
106+
107+ if (
108+ typeof value . year !== 'number' ||
109+ typeof value . month !== 'number' ||
110+ typeof value . day !== 'number'
111+ ) {
112+ return null
113+ }
114+
115+ return {
116+ year : value . year ,
117+ month : value . month ,
118+ day : value . day ,
119+ }
120+ }
121+
59122function DayViewTable ( props : {
60123 weeks : DateValue [ ] [ ]
61124 weekDays : { short : string } [ ]
@@ -151,6 +214,8 @@ function TimePanel(props: {
151214 minuteStep : number
152215 activeRangeIndex : number
153216 onActiveRangeIndexChange : ( index : number ) => void
217+ resolveTimeForValue : ( value ?: DateValue ) => { hour : number ; minute : number } | null
218+ setTimeForValue : ( value : DateValue , time : { hour : number ; minute : number } ) => void
154219} ) {
155220 const minuteOptions = createMemo ( ( ) => {
156221 const values : number [ ] = [ ]
@@ -173,31 +238,45 @@ function TimePanel(props: {
173238 { api => {
174239 const selectedIndex = ( ) => ( api ( ) . selectionMode === 'range' ? props . activeRangeIndex : 0 )
175240 const fallbackDate = ( ) => new Date ( )
176- const selectedDate = ( ) => api ( ) . valueAsDate [ selectedIndex ( ) ]
177- const selectedHour = ( ) => selectedDate ( ) ?. getHours ( ) ?? fallbackDate ( ) . getHours ( )
178- const selectedMinute = ( ) => selectedDate ( ) ?. getMinutes ( ) ?? fallbackDate ( ) . getMinutes ( )
241+ const selectedValue = ( ) => api ( ) . value [ selectedIndex ( ) ]
242+ const selectedTime = ( ) => props . resolveTimeForValue ( selectedValue ( ) )
243+ const selectedHour = ( ) => selectedTime ( ) ?. hour ?? fallbackDate ( ) . getHours ( )
244+ const selectedMinute = ( ) => selectedTime ( ) ?. minute ?? fallbackDate ( ) . getMinutes ( )
245+ const nowAsCalendarDateTime = ( ) => {
246+ const now = new Date ( )
247+
248+ return new CalendarDateTime (
249+ now . getFullYear ( ) ,
250+ now . getMonth ( ) + 1 ,
251+ now . getDate ( ) ,
252+ now . getHours ( ) ,
253+ now . getMinutes ( ) ,
254+ )
255+ }
179256
180257 const setTime = ( hour : number , minute : number ) => {
181258 const index = selectedIndex ( )
182259 const current = api ( ) . value [ index ]
183- const fallback = api ( ) . focusedValue
260+ const focusedValue = api ( ) . focusedValue
261+ const fallback = focusedValue ?? nowAsCalendarDateTime ( )
262+ const base = current ?? focusedValue ?? fallback
184263
185- if ( ! current && ! fallback ) {
186- return
187- }
188-
189- const base = current ?? fallback
190264 const nextValue =
191265 base instanceof CalendarDateTime
192266 ? base . set ( { hour, minute } )
193267 : new CalendarDateTime ( base . year , base . month , base . day , hour , minute )
194268
195269 const nextValues = [ ...api ( ) . value ]
196- if ( api ( ) . selectionMode === 'range' && index === 1 && ! nextValues [ 0 ] && fallback ) {
197- nextValues [ 0 ] = fallback
270+ if ( api ( ) . selectionMode === 'range' && index === 1 && ! nextValues [ 0 ] && focusedValue ) {
271+ nextValues [ 0 ] = focusedValue
198272 }
199273 nextValues [ index ] = nextValue
200- api ( ) . setValue ( nextValues )
274+ props . setTimeForValue ( nextValue , { hour, minute } )
275+
276+ api ( ) . setValue ( [ ] )
277+ queueMicrotask ( ( ) => {
278+ api ( ) . setValue ( nextValues )
279+ } )
201280 }
202281
203282 const selectHour = ( hourOption : number ) => {
@@ -238,7 +317,7 @@ function TimePanel(props: {
238317 }
239318
240319 const isHourSelected = ( hour : number ) => {
241- if ( ! selectedDate ( ) ) {
320+ if ( ! selectedValue ( ) ) {
242321 return false
243322 }
244323
@@ -250,7 +329,10 @@ function TimePanel(props: {
250329 }
251330
252331 return (
253- < div data-slot = "date-time-picker-time-panel" class = "w-full min-w-[13.5rem] space-y-2 sm:h-[320px] overflow-hidden" >
332+ < div
333+ data-slot = "date-time-picker-time-panel"
334+ class = "w-full min-w-[13.5rem] space-y-2 sm:h-[320px] overflow-hidden"
335+ >
254336 < Show when = { api ( ) . selectionMode === 'range' } >
255337 < div class = "grid grid-cols-2 gap-1" >
256338 < button
@@ -277,7 +359,10 @@ function TimePanel(props: {
277359 </ Show >
278360
279361 < div
280- class = { cx ( 'grid gap-2 h-full' , props . hourCycle === 12 ? 'grid-cols-3' : 'grid-cols-2' ) }
362+ class = { cx (
363+ 'grid gap-2 h-full' ,
364+ props . hourCycle === 12 ? 'grid-cols-3' : 'grid-cols-2' ,
365+ ) }
281366 >
282367 < ScrollArea class = "rounded-component border" >
283368 < div class = "grid gap-1 p-1" >
@@ -307,7 +392,7 @@ function TimePanel(props: {
307392 type = "button"
308393 class = { buttonVariants ( {
309394 variant :
310- selectedDate ( ) && selectedMinute ( ) === minute ? 'default' : 'ghost' ,
395+ selectedValue ( ) && selectedMinute ( ) === minute ? 'default' : 'ghost' ,
311396 size : 'icon' ,
312397 class : 'h-8 w-full' ,
313398 } ) }
@@ -329,7 +414,7 @@ function TimePanel(props: {
329414 type = "button"
330415 class = { buttonVariants ( {
331416 variant :
332- selectedDate ( ) &&
417+ selectedValue ( ) &&
333418 ( ( period === 'AM' && selectedHour ( ) < 12 ) ||
334419 ( period === 'PM' && selectedHour ( ) >= 12 ) )
335420 ? 'default'
@@ -382,7 +467,19 @@ export const DateTimePicker = (props: DateTimePickerProps) => {
382467 )
383468
384469 const defaultFormat = ( date : DateValue ) => {
385- const parsedDate = new Date ( Date . parse ( date . toString ( ) ) )
470+ const dateParts = getDateParts ( date )
471+ if ( ! dateParts ) {
472+ return date . toString ( )
473+ }
474+
475+ const time = resolveTimeForValue ( date ) ?? { hour : 0 , minute : 0 }
476+ const parsedDate = new Date (
477+ dateParts . year ,
478+ dateParts . month - 1 ,
479+ dateParts . day ,
480+ time . hour ,
481+ time . minute ,
482+ )
386483
387484 return new Intl . DateTimeFormat ( local . locale ?? globalLocale . locale ?? 'en-US' , {
388485 year : 'numeric' ,
@@ -395,6 +492,35 @@ export const DateTimePicker = (props: DateTimePickerProps) => {
395492 }
396493
397494 const [ activeRangeIndex , setActiveRangeIndex ] = createSignal ( 0 )
495+ const [ timeByDateKey , setTimeByDateKey ] = createSignal <
496+ Record < string , { hour : number ; minute : number } >
497+ > ( { } )
498+
499+ const resolveTimeForValue = ( value ?: DateValue ) => {
500+ const valueTime = getDateValueTime ( value )
501+ if ( valueTime ) {
502+ return valueTime
503+ }
504+
505+ const key = getDateKey ( value )
506+ if ( ! key ) {
507+ return null
508+ }
509+
510+ return timeByDateKey ( ) [ key ] ?? null
511+ }
512+
513+ const setTimeForValue = ( value : DateValue , time : { hour : number ; minute : number } ) => {
514+ const key = getDateKey ( value )
515+ if ( ! key ) {
516+ return
517+ }
518+
519+ setTimeByDateKey ( prev => ( {
520+ ...prev ,
521+ [ key ] : time ,
522+ } ) )
523+ }
398524
399525 return (
400526 < DatePicker
@@ -458,6 +584,8 @@ export const DateTimePicker = (props: DateTimePickerProps) => {
458584 onActiveRangeIndexChange = { index => {
459585 setActiveRangeIndex ( index )
460586 } }
587+ resolveTimeForValue = { resolveTimeForValue }
588+ setTimeForValue = { setTimeForValue }
461589 />
462590 </ div >
463591 </ DatePickerContent >
@@ -475,6 +603,8 @@ export const DateTimePicker = (props: DateTimePickerProps) => {
475603 onActiveRangeIndexChange = { index => {
476604 setActiveRangeIndex ( index )
477605 } }
606+ resolveTimeForValue = { resolveTimeForValue }
607+ setTimeForValue = { setTimeForValue }
478608 />
479609 </ div >
480610 </ DatePickerContent >
0 commit comments