@@ -89,11 +89,13 @@ Meaning an array with two dates is used, the first date is the range start and t
8989 <div>
9090 <fieldset class="type-select">
9191 <legend>Picker mode</legend>
92- <NcCheckboxRadioSwitch v-model="type" type="radio" value="range">Date</NcCheckboxRadioSwitch>
93- <NcCheckboxRadioSwitch v-model="type" type="radio" value="range-datetime">Date and time</NcCheckboxRadioSwitch>
92+ <NcCheckboxRadioSwitch v-model="type" type="radio" value="date-range">Date</NcCheckboxRadioSwitch>
93+ <NcCheckboxRadioSwitch v-model="type" type="radio" value="time-range">Time</NcCheckboxRadioSwitch>
94+ <NcCheckboxRadioSwitch v-model="type" type="radio" value="datetime-range">Date and time</NcCheckboxRadioSwitch>
9495 </fieldset>
9596
9697 <NcDateTimePicker
98+ :key="type"
9799 v-model="time"
98100 :type />
99101 <div>
@@ -106,17 +108,20 @@ Meaning an array with two dates is used, the first date is the range start and t
106108export default {
107109 data() {
108110 return {
109- time: [new Date(2025, 3, 18), new Date(2025, 3, 21)],
110- type: 'range',
111+ time: [new Date(2025, 3, 18, 12, 30 ), new Date(2025, 3, 21, 13, 30 )],
112+ type: 'date- range',
111113 }
112114 },
113115 methods: {
114116 formatDate(date) {
115- const text = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
116- if (this.type === 'range') {
117- return text
117+ const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
118+ const timeString = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
119+ if (this.type === 'date-range') {
120+ return dateString
121+ } else if (this.type === 'time-range') {
122+ return timeString
118123 }
119- return `${text } ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0') }`
124+ return `${dateString } ${timeString }`
120125 },
121126 },
122127}
@@ -161,6 +166,13 @@ export default {
161166</docs >
162167
163168<script setup lang="ts">
169+ import type {
170+ // The emitted object for time picker
171+ TimeObj as LibraryTimeObject ,
172+ // The accepted model value
173+ ModelValue as LibraryModelValue ,
174+ } from ' @vuepic/vue-datepicker'
175+
164176import {
165177 mdiCalendarBlank ,
166178 mdiChevronDown ,
@@ -271,11 +283,14 @@ const props = withDefaults(defineProps<{
271283
272284 /**
273285 * Type of the picker.
274- * The 'range' type will enable a range picker for dates,
275- * while 'range-datetime' will allow picking a date range with times.
286+ * There is some special handling for ranges as those types require a `[Date, Date]` model value.
287+ * - The 'date-range' type will enable a range picker for dates
288+ * - The 'time-range' allows picking a time range.
289+ * - The 'datetime-range' allows picking dates with times assigned.
290+ *
276291 * @default ' date'
277292 */
278- type? : ' date' | ' datetime' | ' time' | ' week' | ' month' | ' year' | ' range' | ' range- datetime'
293+ type? : ' date' | ' datetime' | ' time' | ' week' | ' month' | ' year' | ' date- range' | ' time-range ' | ' datetime-range '
279294}>(), {
280295 ariaLabel: t (' Datepicker input' ),
281296 ariaLabelMenu: t (' Datepicker menu' ),
@@ -303,8 +318,9 @@ const emit = defineEmits<{
303318 /**
304319 * If range picker is enabled then an array containing start and end date are emitted.
305320 * Otherwise the selected date is emitted.
321+ * `null` is emitted if `clearable` is set to `true` and the value was cleared.
306322 */
307- ' update:modelValue' : [Date | [Date , Date ]]
323+ ' update:modelValue' : [Date | [Date , Date ] | null ]
308324 ' update:timezoneId' : [string ]
309325}>()
310326
@@ -323,20 +339,32 @@ const value = computed(() => {
323339 const end = new Date (date )
324340 end .setUTCDate (date .getUTCDate () + 6 )
325341 return [date , end ]
326- } else if (props .type .startsWith (' range' )) {
342+ } else if (props .type === ' year' ) {
343+ const date = props .modelValue instanceof Date ? props .modelValue : new Date ()
344+ return date .getUTCFullYear ()
345+ } else if (props .type === ' month' ) {
346+ const date = props .modelValue instanceof Date ? props .modelValue : new Date ()
347+ return { year: date .getUTCFullYear (), month: date .getUTCMonth () }
348+ } else if (props .type === ' time' || props .type === ' time-range' ) {
349+ const time = [props .modelValue ?? (props .type === ' time-range' ? [new Date (), new Date ()] : new Date ())].flat ()
350+ // default time range is 1 hour
351+ if (props .modelValue === undefined && props .type === ' time-range' ) {
352+ time [1 ].setHours (time [1 ].getHours () + 1 )
353+ }
354+ const timeValue = time .map ((date ) => ({
355+ hours: date .getHours (),
356+ minutes: date .getMinutes (),
357+ seconds: date .getSeconds (),
358+ } as LibraryTimeObject ))
359+ return props .type === ' time' ? timeValue [0 ] : timeValue
360+ } else if (props .type .endsWith (' -range' )) {
327361 if (props .modelValue === undefined ) {
328362 const start = new Date ()
329363 const end = new Date (start )
330364 end .setUTCDate (start .getUTCDate () + 7 )
331365 return [start , end ]
332366 }
333367 return props .modelValue
334- } else if (props .type === ' year' ) {
335- const date = props .modelValue instanceof Date ? props .modelValue : new Date ()
336- return date .getUTCFullYear ()
337- } else if (props .type === ' month' ) {
338- const date = props .modelValue instanceof Date ? props .modelValue : new Date ()
339- return { year: date .getUTCFullYear (), month: date .getUTCMonth () }
340368 }
341369
342370 // no special handling for other types needed
@@ -356,7 +384,7 @@ const placeholderFallback = computed(() => {
356384 return t (' Select month' )
357385 } else if (props .type === ' year' ) {
358386 return t (' Select year' )
359- } else if (props .type .startsWith ( ' range' )) {
387+ } else if (props .type .endsWith ( ' - range' )) {
360388 return t (' Select time range' )
361389 }
362390 // should not be reached
@@ -377,10 +405,12 @@ const realFormat = computed(() => {
377405 }
378406
379407 let formatter: Intl .DateTimeFormat | undefined
380- if (props .type === ' datetime' || props .type === ' range-datetime' ) {
381- formatter = new Intl .DateTimeFormat (getCanonicalLocale (), { dateStyle: ' medium' , timeStyle: ' short' })
382- } else if (props .type === ' date' || props .type === ' range' ) {
408+ if (props .type === ' date' || props .type === ' date-range' ) {
383409 formatter = new Intl .DateTimeFormat (getCanonicalLocale (), { dateStyle: ' medium' })
410+ } else if (props .type === ' time' || props .type === ' time-range' ) {
411+ formatter = new Intl .DateTimeFormat (getCanonicalLocale (), { timeStyle: ' short' })
412+ } else if (props .type === ' datetime' || props .type === ' datetime-range' ) {
413+ formatter = new Intl .DateTimeFormat (getCanonicalLocale (), { dateStyle: ' medium' , timeStyle: ' short' })
384414 } else if (props .type === ' month' ) {
385415 formatter = new Intl .DateTimeFormat (getCanonicalLocale (), { year: ' numeric' , month: ' 2-digit' })
386416 } else if (props .type === ' year' ) {
@@ -398,17 +428,17 @@ const realFormat = computed(() => {
398428})
399429
400430const pickerType = computed (() => ({
401- timePicker: props .type === ' time' ,
431+ timePicker: props .type === ' time' || props . type === ' time-range ' ,
402432 yearPicker: props .type === ' year' ,
403433 monthPicker: props .type === ' month' ,
404434 weekPicker: props .type === ' week' ,
405- range: props .type .startsWith ( ' range' ) && {
435+ range: props .type .endsWith ( ' - range' ) && {
406436 // do not use partial ranges (meaning after selecting the start [Date, null] will be emitted)
407437 // if this is needed someday we can enable it,
408438 // but its not covered by our component interface (props / events) documentation so just disabled for now.
409439 partialRange: false ,
410440 },
411- enableTimePicker: ! (props .type === ' date' || props .type === ' range' ),
441+ enableTimePicker: ! (props .type === ' date' || props .type === ' date- range' ),
412442 flow: props .type === ' datetime'
413443 ? [' calendar' , ' time' ] as [' calendar' , ' time' ]
414444 : undefined ,
@@ -418,17 +448,50 @@ const pickerType = computed(() => ({
418448 * Called on model value update of the library.
419449 * @param value The value emitted from the underlying library
420450 */
421- function onUpdateModelValue(value : Date | [Date , Date ] | number | { month: number , year: number }): void {
422- let date = value as Date | [Date , Date ]
423- if (props .type === ' month' ) {
451+ function onUpdateModelValue(value : LibraryModelValue ): void {
452+ if (value === null ) {
453+ return emit (' update:modelValue' , null )
454+ }
455+
456+ if (props .type === ' time' ) {
457+ // time is provided as an object
458+ emit (' update:modelValue' , formatLibraryTime (value as LibraryTimeObject ))
459+ } else if (props .type === ' time-range' ) {
460+ // same as time but as an array with two elements
461+ const start = formatLibraryTime (value [0 ])
462+ const end = formatLibraryTime (value [1 ])
463+ // ensure end is beyond the start
464+ if (end .getTime () < start .getTime ()) {
465+ end .setDate (end .getDate () + 1 )
466+ }
467+ emit (' update:modelValue' , [start , end ])
468+ } else if (props .type === ' month' ) {
469+ // month is emitted as an object with month and year attribute
424470 const data = value as { month: number , year: number }
425- date = new Date (data .year , data .month , 1 )
471+ emit ( ' update:modelValue ' , new Date (data .year , data .month , 1 ) )
426472 } else if (props .type === ' year' ) {
427- date = new Date (value as number , 0 )
473+ // Years are emitted as the numeric year e.g. 2022
474+ emit (' update:modelValue' , new Date (value as number , 0 ))
428475 } else if (props .type === ' week' ) {
429- date = value [0 ]
476+ // weeks are emitted as [Date, Date]
477+ emit (' update:modelValue' , value [0 ])
478+ } else {
479+ // otherwise it already emits the correct format
480+ emit (' update:modelValue' , value as Date | [Date , Date ])
430481 }
431- emit (' update:modelValue' , date )
482+ }
483+
484+ /**
485+ * Format a vuepick time object to native JS Date object.
486+ *
487+ * @param time - The library time value object
488+ */
489+ function formatLibraryTime(time : LibraryTimeObject ): Date {
490+ const date = new Date ()
491+ date .setHours (time .hours )
492+ date .setMinutes (time .minutes )
493+ date .setSeconds (time .seconds )
494+ return date
432495}
433496
434497// Localization
0 commit comments