@@ -89,11 +89,13 @@ Meaning an array with two dates is used, the first date is the range start and t
89
89
<div>
90
90
<fieldset class="type-select">
91
91
<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>
94
95
</fieldset>
95
96
96
97
<NcDateTimePicker
98
+ :key="type"
97
99
v-model="time"
98
100
:type />
99
101
<div>
@@ -106,17 +108,20 @@ Meaning an array with two dates is used, the first date is the range start and t
106
108
export default {
107
109
data() {
108
110
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',
111
113
}
112
114
},
113
115
methods: {
114
116
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
118
123
}
119
- return `${text } ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0') }`
124
+ return `${dateString } ${timeString }`
120
125
},
121
126
},
122
127
}
@@ -161,6 +166,13 @@ export default {
161
166
</docs>
162
167
163
168
<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
+
164
176
import {
165
177
mdiCalendarBlank,
166
178
mdiChevronDown,
@@ -271,11 +283,14 @@ const props = withDefaults(defineProps<{
271
283
272
284
/**
273
285
* 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
+ *
276
291
* @default 'date'
277
292
*/
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 '
279
294
}>(), {
280
295
ariaLabel: t('Datepicker input'),
281
296
ariaLabelMenu: t('Datepicker menu'),
@@ -303,8 +318,9 @@ const emit = defineEmits<{
303
318
/**
304
319
* If range picker is enabled then an array containing start and end date are emitted.
305
320
* Otherwise the selected date is emitted.
321
+ * `null` is emitted if `clearable` is set to `true` and the value was cleared.
306
322
*/
307
- 'update:modelValue': [Date | [Date, Date]]
323
+ 'update:modelValue': [Date | [Date, Date] | null ]
308
324
'update:timezoneId': [string]
309
325
}>()
310
326
@@ -323,20 +339,32 @@ const value = computed(() => {
323
339
const end = new Date(date)
324
340
end.setUTCDate(date.getUTCDate() + 6)
325
341
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')) {
327
361
if (props.modelValue === undefined) {
328
362
const start = new Date()
329
363
const end = new Date(start)
330
364
end.setUTCDate(start.getUTCDate() + 7)
331
365
return [start, end]
332
366
}
333
367
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() }
340
368
}
341
369
342
370
// no special handling for other types needed
@@ -356,7 +384,7 @@ const placeholderFallback = computed(() => {
356
384
return t('Select month')
357
385
} else if (props.type === 'year') {
358
386
return t('Select year')
359
- } else if (props.type.startsWith(' range')) {
387
+ } else if (props.type.endsWith('- range')) {
360
388
return t('Select time range')
361
389
}
362
390
// should not be reached
@@ -377,10 +405,12 @@ const realFormat = computed(() => {
377
405
}
378
406
379
407
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') {
383
409
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' })
384
414
} else if (props.type === 'month') {
385
415
formatter = new Intl.DateTimeFormat(getCanonicalLocale(), { year: 'numeric', month: '2-digit' })
386
416
} else if (props.type === 'year') {
@@ -398,17 +428,17 @@ const realFormat = computed(() => {
398
428
})
399
429
400
430
const pickerType = computed(() => ({
401
- timePicker: props.type === 'time',
431
+ timePicker: props.type === 'time' || props.type === 'time-range' ,
402
432
yearPicker: props.type === 'year',
403
433
monthPicker: props.type === 'month',
404
434
weekPicker: props.type === 'week',
405
- range: props.type.startsWith(' range') && {
435
+ range: props.type.endsWith('- range') && {
406
436
// do not use partial ranges (meaning after selecting the start [Date, null] will be emitted)
407
437
// if this is needed someday we can enable it,
408
438
// but its not covered by our component interface (props / events) documentation so just disabled for now.
409
439
partialRange: false,
410
440
},
411
- enableTimePicker: !(props.type === 'date' || props.type === 'range'),
441
+ enableTimePicker: !(props.type === 'date' || props.type === 'date- range'),
412
442
flow: props.type === 'datetime'
413
443
? ['calendar', 'time'] as ['calendar', 'time']
414
444
: undefined,
@@ -418,17 +448,50 @@ const pickerType = computed(() => ({
418
448
* Called on model value update of the library.
419
449
* @param value The value emitted from the underlying library
420
450
*/
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
424
470
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) )
426
472
} 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))
428
475
} 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])
430
481
}
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
432
495
}
433
496
434
497
// Localization
0 commit comments