Skip to content

Commit 6c727d4

Browse files
committed
refactor(DatePicker, DateRangePicker): improve support for calendar cell hover, date preview features, and manual input
1 parent e01bc44 commit 6c727d4

File tree

5 files changed

+104
-43
lines changed

5 files changed

+104
-43
lines changed

js/src/calendar.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,8 +716,8 @@ class Calendar extends BaseComponent {
716716
_initializeDates() {
717717
// Convert dates to date objects based on the selection type
718718
this._calendarDate = convertToDateObject(
719-
this._config.calendarDate || this._config.startDate || this._config.endDate || new Date(), this._config.selectionType
720-
)
719+
this._config.calendarDate || this._config.startDate || this._config.endDate, this._config.selectionType
720+
) || new Date()
721721
this._startDate = convertToDateObject(this._config.startDate, this._config.selectionType)
722722
this._endDate = convertToDateObject(this._config.endDate, this._config.selectionType)
723723
this._minDate = convertToDateObject(this._config.minDate, this._config.selectionType)

js/src/date-range-picker.js

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ const CLASS_NAME_DROPDOWN = 'date-picker-dropdown'
5656
const CLASS_NAME_INDICATOR = 'date-picker-indicator'
5757
const CLASS_NAME_INPUT = 'date-picker-input'
5858
const CLASS_NAME_INPUT_GROUP = 'date-picker-input-group'
59+
const CLASS_NAME_INPUT_PREVIEW = 'date-picker-input-preview'
60+
const CLASS_NAME_INPUT_WRAPPER = 'date-picker-input-wrapper'
5961
const CLASS_NAME_IS_INVALID = 'is-invalid'
6062
const CLASS_NAME_IS_VALID = 'is-valid'
6163
const CLASS_NAME_FOOTER = 'date-picker-footer'
@@ -199,15 +201,17 @@ class DateRangePicker extends BaseComponent {
199201
this._calendar = null
200202
this._calendars = null
201203
this._endInput = null
204+
this._endInputTimeout = null
205+
this._endPreviewInput = null
202206
this._indicatorElement = null
203207
this._menu = null
204208
this._startInput = null
209+
this._startInputTimeout = null
210+
this._startPreviewInput = null
205211
this._timepickers = null
206212
this._timePickerEnd = null
207213
this._timePickerStart = null
208214
this._togglerElement = null
209-
this._startInputTimeout = null
210-
this._endInputTimeout = null
211215

212216
this._createDateRangePicker()
213217
this._createDateRangePickerCalendars()
@@ -368,24 +372,18 @@ class DateRangePicker extends BaseComponent {
368372
this._startInputTimeout = setTimeout(() => {
369373
const date = this._parseDate(event.target.value)
370374

371-
if (date === 'invalid') {
372-
this._startDate = null
373-
this._calendar.update(this._getCalendarConfig())
374-
}
375-
376-
// valid date or empty date
377-
if ((date instanceof Date && !Number.isNaN(date)) || (date === null)) {
378-
// Check if the date is disabled
379-
if (date && isDateDisabled(date, this._config.minDate, this._config.maxDate, this._config.disabledDates)) {
375+
if (date instanceof Date && date.getTime()) {
376+
if (isDateDisabled(date, this._config.minDate, this._config.maxDate, this._config.disabledDates)) {
380377
return // Don't update if date is disabled
381378
}
382379

383-
this._startInput.value = this._setInputValue(date)
384-
this._startDate = date
385380
this._calendarDate = date
386-
this._calendar.update(this._getCalendarConfig())
381+
this._startInput.value = this._setInputValue(date)
387382
}
388383

384+
this._startDate = date
385+
this._calendar.update(this._getCalendarConfig())
386+
389387
EventHandler.trigger(this._element, EVENT_START_DATE_CHANGE, {
390388
date
391389
})
@@ -427,24 +425,18 @@ class DateRangePicker extends BaseComponent {
427425
this._endInputTimeout = setTimeout(() => {
428426
const date = this._parseDate(event.target.value)
429427

430-
if (date === 'invalid') {
431-
this._endDate = null
432-
this._calendar.update(this._getCalendarConfig())
433-
}
434-
435-
// valid date or empty date
436-
if ((date instanceof Date && !Number.isNaN(date)) || (date === null)) {
437-
// Check if the date is disabled
428+
if (date instanceof Date && date.getTime()) {
438429
if (date && isDateDisabled(date, this._config.minDate, this._config.maxDate, this._config.disabledDates)) {
439430
return // Don't update if date is disabled
440431
}
441432

442-
this._endInput.value = this._setInputValue(date)
443-
this._endDate = date
444433
this._calendarDate = date
445-
this._calendar.update(this._getCalendarConfig())
434+
this._endInput.value = this._setInputValue(date)
446435
}
447436

437+
this._endDate = date
438+
this._calendar.update(this._getCalendarConfig())
439+
448440
EventHandler.trigger(this._element, EVENT_END_DATE_CHANGE, {
449441
date
450442
})
@@ -474,14 +466,17 @@ class DateRangePicker extends BaseComponent {
474466
}
475467
})
476468

477-
if (this._config.previewDateOnHover) {
469+
if (this._config.previewDateOnHover && !this._config.disabled) {
478470
EventHandler.on(calendar, 'cellHover.coreui.calendar', event => {
479471
if (this._selectEndDate) {
480-
this._endInput.value = event.date ? this._setInputValue(event.date) : this._setInputValue(this._endDate)
472+
const previewValue = event.date ? this._setInputValue(event.date) : this._setInputValue(this._endDate)
473+
this._updatePreviewInputVisibility(this._endPreviewInput, event.date ? previewValue : '')
474+
481475
return
482476
}
483477

484-
this._startInput.value = event.date ? this._setInputValue(event.date) : this._setInputValue(this._startDate)
478+
const previewValue = event.date ? this._setInputValue(event.date) : this._setInputValue(this._startDate)
479+
this._updatePreviewInputVisibility(this._startPreviewInput, event.date ? previewValue : '')
485480
})
486481
}
487482

@@ -590,6 +585,48 @@ class DateRangePicker extends BaseComponent {
590585
this._menu = dropdownEl
591586
}
592587

588+
_updatePreviewInputVisibility(previewInput, value) {
589+
if (!previewInput) {
590+
return
591+
}
592+
593+
if (value && value.trim() !== '') {
594+
previewInput.style.display = 'block'
595+
previewInput.value = value
596+
} else {
597+
previewInput.style.display = 'none'
598+
previewInput.value = ''
599+
}
600+
}
601+
602+
_createInputWrapper(inputEl, isStart = true) {
603+
if (!this._config.previewDateOnHover || this._config.disabled) {
604+
return inputEl
605+
}
606+
607+
const wrapperEl = document.createElement('div')
608+
wrapperEl.classList.add(CLASS_NAME_INPUT_WRAPPER)
609+
610+
wrapperEl.append(inputEl)
611+
612+
const previewInputEl = document.createElement('input')
613+
previewInputEl.classList.add(CLASS_NAME_INPUT, CLASS_NAME_INPUT_PREVIEW)
614+
previewInputEl.type = 'text'
615+
previewInputEl.readOnly = true
616+
previewInputEl.tabIndex = -1
617+
previewInputEl.style.display = 'none'
618+
619+
if (isStart) {
620+
this._startPreviewInput = previewInputEl
621+
} else {
622+
this._endPreviewInput = previewInputEl
623+
}
624+
625+
wrapperEl.append(previewInputEl)
626+
627+
return wrapperEl
628+
}
629+
593630
_createDateRangePickerInputGroup() {
594631
const inputGroupEl = document.createElement('div')
595632
inputGroupEl.classList.add(CLASS_NAME_INPUT_GROUP)
@@ -616,14 +653,16 @@ class DateRangePicker extends BaseComponent {
616653
this._startInput = startInputEl
617654
this._endInput = endInputEl
618655

619-
inputGroupEl.append(startInputEl)
656+
const startInputWrapper = this._createInputWrapper(startInputEl, true)
657+
inputGroupEl.append(startInputWrapper)
620658

621659
if (this._config.separator) {
622660
inputGroupEl.append(inputGroupTextSeparatorEl)
623661
}
624662

625663
if (this._config.range) {
626-
inputGroupEl.append(endInputEl)
664+
const endInputWrapper = this._createInputWrapper(endInputEl, false)
665+
inputGroupEl.append(endInputWrapper)
627666
}
628667

629668
if (this._config.indicator) {
@@ -712,13 +751,8 @@ class DateRangePicker extends BaseComponent {
712751
})
713752

714753
EventHandler.on(calendarEl, 'calendarMouseleave.coreui.calendar', () => {
715-
if (this._startDate) {
716-
this._startInput.value = this._setInputValue(this._startDate)
717-
}
718-
719-
if (this._endDate) {
720-
this._endInput.value = this._setInputValue(this._endDate)
721-
}
754+
this._updatePreviewInputVisibility(this._startPreviewInput, '')
755+
this._updatePreviewInputVisibility(this._endPreviewInput, '')
722756
})
723757

724758
if (this._config.timepicker) {

js/src/util/calendar.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export const convertToDateObject = (date, selectionType) => {
2424
}
2525

2626
if (date instanceof Date) {
27+
if (Number.isNaN(date.getTime())) {
28+
return null
29+
}
30+
2731
return date
2832
}
2933

js/src/util/date-range-picker.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const createDateWithTime = groups => {
124124
const parsedSecond = Number.parseInt(second, 10) || 0
125125

126126
if (!validateTimeComponents(parsedHour, parsedMinute, parsedSecond)) {
127-
return 'invalid'
127+
return new Date('invalid')
128128
}
129129

130130
return new Date(parsedYear, parsedMonth, parsedDay, parsedHour, parsedMinute, parsedSecond)
@@ -166,13 +166,13 @@ export const getLocalDateFromString = (dateString, locale = 'en-US', includeTime
166166
const groups = tryParseWithPatterns(dateString, patterns, includeTime)
167167

168168
if (!groups) {
169-
return 'invalid'
169+
return new Date('invalid')
170170
}
171171

172172
// Validate date components
173173
const { month, day } = groups
174174
if (!validateDateComponents(month, day)) {
175-
return 'invalid'
175+
return new Date('invalid')
176176
}
177177

178178
// Create and return appropriate date object

scss/_date-picker.scss

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,16 @@
130130
}
131131
}
132132

133-
.date-picker-input {
133+
.date-picker-input-wrapper {
134134
position: relative;
135135
flex: 1 1 auto;
136136
width: 1%;
137137
min-width: 0;
138+
}
139+
140+
.date-picker-input {
141+
position: relative;
142+
z-index: 1;
138143
padding: 0;
139144
font-family: var(--#{$prefix}date-picker-font-family);
140145
@include font-size(var(--#{$prefix}date-picker-font-size));
@@ -168,6 +173,24 @@
168173
}
169174
}
170175

176+
:not(.date-picker-input-wrapper) > .date-picker-input {
177+
flex: 1 1 auto;
178+
width: 1%;
179+
min-width: 0;
180+
}
181+
182+
.date-picker-input-preview {
183+
position: absolute;
184+
top: 0;
185+
right: 0;
186+
bottom: 0;
187+
left: 0;
188+
z-index: 5;
189+
width: 100%;
190+
height: 100%;
191+
background: var(--#{$prefix}date-picker-bg);
192+
}
193+
171194
.date-picker-cleaner,
172195
.date-picker-separator,
173196
.date-picker-indicator {

0 commit comments

Comments
 (0)