1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import classNames from "classnames";
|
18 | import * as React from "react";
|
19 | import DayPicker, { CaptionElementProps, DayModifiers, DayPickerProps, NavbarElementProps } from "react-day-picker";
|
20 |
|
21 | import { AbstractPureComponent2, Boundary, DISPLAYNAME_PREFIX, Divider, Props } from "@blueprintjs/core";
|
22 |
|
23 | import * as DateClasses from "./common/classes";
|
24 | import { DateRange } from "./common/dateRange";
|
25 | import * as DateUtils from "./common/dateUtils";
|
26 | import * as Errors from "./common/errors";
|
27 | import { MonthAndYear } from "./common/monthAndYear";
|
28 | import { DatePickerCaption } from "./datePickerCaption";
|
29 | import {
|
30 | combineModifiers,
|
31 | DatePickerModifiers,
|
32 | getDefaultMaxDate,
|
33 | getDefaultMinDate,
|
34 | HOVERED_RANGE_MODIFIER,
|
35 | IDatePickerBaseProps,
|
36 | SELECTED_RANGE_MODIFIER,
|
37 | } from "./datePickerCore";
|
38 | import { DatePickerNavbar } from "./datePickerNavbar";
|
39 | import { DateRangeSelectionStrategy } from "./dateRangeSelectionStrategy";
|
40 | import { DateRangeShortcut, Shortcuts } from "./shortcuts";
|
41 | import { TimePicker } from "./timePicker";
|
42 |
|
43 |
|
44 | export type DateRangePickerProps = IDateRangePickerProps;
|
45 |
|
46 | export interface IDateRangePickerProps extends IDatePickerBaseProps, Props {
|
47 | |
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | allowSingleDayRange?: boolean;
|
55 |
|
56 | |
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | boundaryToModify?: Boundary;
|
63 |
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | contiguousCalendarMonths?: boolean;
|
71 |
|
72 | |
73 |
|
74 |
|
75 |
|
76 | defaultValue?: DateRange;
|
77 |
|
78 | |
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | onChange?: (selectedDates: DateRange) => void;
|
85 |
|
86 | |
87 |
|
88 |
|
89 |
|
90 |
|
91 | onHoverChange?: (hoveredDates: DateRange, hoveredDay: Date, hoveredBoundary: Boundary) => void;
|
92 |
|
93 | |
94 |
|
95 |
|
96 | onShortcutChange?: (shortcut: DateRangeShortcut, index: number) => void;
|
97 |
|
98 | |
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | shortcuts?: boolean | DateRangeShortcut[];
|
107 |
|
108 | |
109 |
|
110 |
|
111 |
|
112 | selectedShortcutIndex?: number;
|
113 |
|
114 | |
115 |
|
116 |
|
117 |
|
118 |
|
119 | singleMonthOnly?: boolean;
|
120 |
|
121 | |
122 |
|
123 |
|
124 |
|
125 | value?: DateRange;
|
126 | }
|
127 |
|
128 |
|
129 | export interface IDateRangePickerState {
|
130 | hoverValue?: DateRange;
|
131 | leftView?: MonthAndYear;
|
132 | rightView?: MonthAndYear;
|
133 | value?: DateRange;
|
134 | time?: DateRange;
|
135 | selectedShortcutIndex?: number;
|
136 | }
|
137 |
|
138 | export class DateRangePicker extends AbstractPureComponent2<DateRangePickerProps, IDateRangePickerState> {
|
139 | public static defaultProps: DateRangePickerProps = {
|
140 | allowSingleDayRange: false,
|
141 | contiguousCalendarMonths: true,
|
142 | dayPickerProps: {},
|
143 | maxDate: getDefaultMaxDate(),
|
144 | minDate: getDefaultMinDate(),
|
145 | reverseMonthAndYearMenus: false,
|
146 | shortcuts: true,
|
147 | singleMonthOnly: false,
|
148 | timePickerProps: {},
|
149 | };
|
150 |
|
151 | public static displayName = `${DISPLAYNAME_PREFIX}.DateRangePicker`;
|
152 |
|
153 |
|
154 | private modifiers: DatePickerModifiers = {
|
155 | [SELECTED_RANGE_MODIFIER]: day => {
|
156 | const { value } = this.state;
|
157 | return value[0] != null && value[1] != null && DateUtils.isDayInRange(day, value, true);
|
158 | },
|
159 | [`${SELECTED_RANGE_MODIFIER}-start`]: day => DateUtils.areSameDay(this.state.value[0], day),
|
160 | [`${SELECTED_RANGE_MODIFIER}-end`]: day => DateUtils.areSameDay(this.state.value[1], day),
|
161 |
|
162 | [HOVERED_RANGE_MODIFIER]: day => {
|
163 | const {
|
164 | hoverValue,
|
165 | value: [selectedStart, selectedEnd],
|
166 | } = this.state;
|
167 | if (selectedStart == null && selectedEnd == null) {
|
168 | return false;
|
169 | }
|
170 | if (hoverValue == null || hoverValue[0] == null || hoverValue[1] == null) {
|
171 | return false;
|
172 | }
|
173 | return DateUtils.isDayInRange(day, hoverValue, true);
|
174 | },
|
175 | [`${HOVERED_RANGE_MODIFIER}-start`]: day => {
|
176 | const { hoverValue } = this.state;
|
177 | if (hoverValue == null || hoverValue[0] == null) {
|
178 | return false;
|
179 | }
|
180 | return DateUtils.areSameDay(hoverValue[0], day);
|
181 | },
|
182 | [`${HOVERED_RANGE_MODIFIER}-end`]: day => {
|
183 | const { hoverValue } = this.state;
|
184 | if (hoverValue == null || hoverValue[1] == null) {
|
185 | return false;
|
186 | }
|
187 | return DateUtils.areSameDay(hoverValue[1], day);
|
188 | },
|
189 | };
|
190 |
|
191 | public constructor(props: DateRangePickerProps, context?: any) {
|
192 | super(props, context);
|
193 | const value = getInitialValue(props);
|
194 | const time: DateRange = value;
|
195 | const initialMonth = getInitialMonth(props, value);
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | const initialMonthEqualsMinMonth = DateUtils.areSameMonth(initialMonth, props.minDate);
|
202 | const initalMonthEqualsMaxMonth = DateUtils.areSameMonth(initialMonth, props.maxDate);
|
203 | if (!props.singleMonthOnly && !initialMonthEqualsMinMonth && initalMonthEqualsMaxMonth) {
|
204 | initialMonth.setMonth(initialMonth.getMonth() - 1);
|
205 | }
|
206 |
|
207 |
|
208 |
|
209 |
|
210 | const leftView = MonthAndYear.fromDate(initialMonth);
|
211 | const rightDate = value[1];
|
212 | const rightView =
|
213 | !props.contiguousCalendarMonths && rightDate != null && !DateUtils.areSameMonth(initialMonth, rightDate)
|
214 | ? MonthAndYear.fromDate(rightDate)
|
215 | : leftView.getNextMonth();
|
216 | this.state = {
|
217 | hoverValue: [null, null],
|
218 | leftView,
|
219 | rightView,
|
220 | selectedShortcutIndex:
|
221 | this.props.selectedShortcutIndex !== undefined ? this.props.selectedShortcutIndex : -1,
|
222 | time,
|
223 | value,
|
224 | };
|
225 | }
|
226 |
|
227 | public render() {
|
228 | const { className, contiguousCalendarMonths, singleMonthOnly } = this.props;
|
229 | const isShowingOneMonth = singleMonthOnly || DateUtils.areSameMonth(this.props.minDate, this.props.maxDate);
|
230 |
|
231 | const classes = classNames(DateClasses.DATEPICKER, DateClasses.DATERANGEPICKER, className, {
|
232 | [DateClasses.DATERANGEPICKER_CONTIGUOUS]: contiguousCalendarMonths,
|
233 | [DateClasses.DATERANGEPICKER_SINGLE_MONTH]: isShowingOneMonth,
|
234 | });
|
235 |
|
236 |
|
237 | return (
|
238 | <div className={classes}>
|
239 | {this.maybeRenderShortcuts()}
|
240 | <div>
|
241 | {this.renderCalendars(isShowingOneMonth)}
|
242 | {this.maybeRenderTimePickers()}
|
243 | </div>
|
244 | </div>
|
245 | );
|
246 | }
|
247 |
|
248 | public componentDidUpdate(prevProps: DateRangePickerProps, prevState: IDateRangePickerState) {
|
249 | super.componentDidUpdate(prevProps, prevState);
|
250 |
|
251 | if (
|
252 | !DateUtils.areRangesEqual(prevProps.value, this.props.value) ||
|
253 | prevProps.contiguousCalendarMonths !== this.props.contiguousCalendarMonths
|
254 | ) {
|
255 | const nextState = getStateChange(
|
256 | prevProps.value,
|
257 | this.props.value,
|
258 | this.state,
|
259 | prevProps.contiguousCalendarMonths,
|
260 | );
|
261 | this.setState(nextState);
|
262 | }
|
263 |
|
264 | if (this.props.selectedShortcutIndex !== prevProps.selectedShortcutIndex) {
|
265 | this.setState({ selectedShortcutIndex: this.props.selectedShortcutIndex });
|
266 | }
|
267 | }
|
268 |
|
269 | protected validateProps(props: DateRangePickerProps) {
|
270 | const { defaultValue, initialMonth, maxDate, minDate, boundaryToModify, value } = props;
|
271 | const dateRange: DateRange = [minDate, maxDate];
|
272 |
|
273 | if (defaultValue != null && !DateUtils.isDayRangeInRange(defaultValue, dateRange)) {
|
274 | console.error(Errors.DATERANGEPICKER_DEFAULT_VALUE_INVALID);
|
275 | }
|
276 |
|
277 | if (initialMonth != null && !DateUtils.isMonthInRange(initialMonth, dateRange)) {
|
278 | console.error(Errors.DATERANGEPICKER_INITIAL_MONTH_INVALID);
|
279 | }
|
280 |
|
281 | if (maxDate != null && minDate != null && maxDate < minDate && !DateUtils.areSameDay(maxDate, minDate)) {
|
282 | console.error(Errors.DATERANGEPICKER_MAX_DATE_INVALID);
|
283 | }
|
284 |
|
285 | if (value != null && !DateUtils.isDayRangeInRange(value, dateRange)) {
|
286 | console.error(Errors.DATERANGEPICKER_VALUE_INVALID);
|
287 | }
|
288 |
|
289 | if (boundaryToModify != null && boundaryToModify !== Boundary.START && boundaryToModify !== Boundary.END) {
|
290 | console.error(Errors.DATERANGEPICKER_PREFERRED_BOUNDARY_TO_MODIFY_INVALID);
|
291 | }
|
292 | }
|
293 |
|
294 | private shouldHighlightCurrentDay = (date: Date) => {
|
295 | const { highlightCurrentDay } = this.props;
|
296 |
|
297 | return highlightCurrentDay && DateUtils.isToday(date);
|
298 | };
|
299 |
|
300 | private getDateRangePickerModifiers = () => {
|
301 | const { modifiers } = this.props;
|
302 |
|
303 | return combineModifiers(this.modifiers, {
|
304 | isToday: this.shouldHighlightCurrentDay,
|
305 | ...modifiers,
|
306 | });
|
307 | };
|
308 |
|
309 | private renderDay = (day: Date) => {
|
310 | const date = day.getDate();
|
311 |
|
312 | return <div className={DateClasses.DATEPICKER_DAY_WRAPPER}>{date}</div>;
|
313 | };
|
314 |
|
315 | private disabledDays = (day: Date) => !DateUtils.isDayInRange(day, [this.props.minDate, this.props.maxDate]);
|
316 |
|
317 | private getDisabledDaysModifier = () => {
|
318 | const {
|
319 | dayPickerProps: { disabledDays },
|
320 | } = this.props;
|
321 |
|
322 | return disabledDays instanceof Array ? [this.disabledDays, ...disabledDays] : [this.disabledDays, disabledDays];
|
323 | };
|
324 |
|
325 | private maybeRenderShortcuts() {
|
326 | const { shortcuts } = this.props;
|
327 | if (shortcuts == null || shortcuts === false) {
|
328 | return null;
|
329 | }
|
330 |
|
331 | const { selectedShortcutIndex } = this.state;
|
332 | const { allowSingleDayRange, maxDate, minDate, timePrecision } = this.props;
|
333 | return [
|
334 | <Shortcuts
|
335 | key="shortcuts"
|
336 | {...{
|
337 | allowSingleDayRange,
|
338 | maxDate,
|
339 | minDate,
|
340 | selectedShortcutIndex,
|
341 | shortcuts,
|
342 | timePrecision,
|
343 | }}
|
344 | onShortcutClick={this.handleShortcutClick}
|
345 | />,
|
346 | <Divider key="div" />,
|
347 | ];
|
348 | }
|
349 |
|
350 | private maybeRenderTimePickers() {
|
351 | const { timePrecision, timePickerProps } = this.props;
|
352 | if (timePrecision == null && timePickerProps === DateRangePicker.defaultProps.timePickerProps) {
|
353 | return null;
|
354 | }
|
355 | return (
|
356 | <div className={DateClasses.DATERANGEPICKER_TIMEPICKERS}>
|
357 | <TimePicker
|
358 | precision={timePrecision}
|
359 | {...timePickerProps}
|
360 | onChange={this.handleTimeChangeLeftCalendar}
|
361 | value={this.state.time[0]}
|
362 | />
|
363 | <TimePicker
|
364 | precision={timePrecision}
|
365 | {...timePickerProps}
|
366 | onChange={this.handleTimeChangeRightCalendar}
|
367 | value={this.state.time[1]}
|
368 | />
|
369 | </div>
|
370 | );
|
371 | }
|
372 |
|
373 | private handleTimeChange = (newTime: Date, dateIndex: number) => {
|
374 | this.props.timePickerProps?.onChange?.(newTime);
|
375 |
|
376 | const { value, time } = this.state;
|
377 | const newValue = DateUtils.getDateTime(
|
378 | value[dateIndex] != null ? DateUtils.clone(value[dateIndex]) : new Date(),
|
379 | newTime,
|
380 | );
|
381 | const newDateRange: DateRange = [value[0], value[1]];
|
382 | newDateRange[dateIndex] = newValue;
|
383 | const newTimeRange: DateRange = [time[0], time[1]];
|
384 | newTimeRange[dateIndex] = newTime;
|
385 | this.props.onChange?.(newDateRange);
|
386 | this.setState({ value: newDateRange, time: newTimeRange });
|
387 | };
|
388 |
|
389 | private handleTimeChangeLeftCalendar = (time: Date) => {
|
390 | this.handleTimeChange(time, 0);
|
391 | };
|
392 |
|
393 | private handleTimeChangeRightCalendar = (time: Date) => {
|
394 | this.handleTimeChange(time, 1);
|
395 | };
|
396 |
|
397 | private renderCalendars(isShowingOneMonth: boolean) {
|
398 | const { dayPickerProps, locale, localeUtils, maxDate, minDate } = this.props;
|
399 | const dayPickerBaseProps: DayPickerProps = {
|
400 | locale,
|
401 | localeUtils,
|
402 | modifiers: this.getDateRangePickerModifiers(),
|
403 | showOutsideDays: true,
|
404 | ...dayPickerProps,
|
405 | disabledDays: this.getDisabledDaysModifier(),
|
406 | onDayClick: this.handleDayClick,
|
407 | onDayMouseEnter: this.handleDayMouseEnter,
|
408 | onDayMouseLeave: this.handleDayMouseLeave,
|
409 | selectedDays: this.state.value,
|
410 | };
|
411 |
|
412 | if (isShowingOneMonth) {
|
413 | return (
|
414 | <DayPicker
|
415 | {...dayPickerBaseProps}
|
416 | captionElement={this.renderSingleCaption}
|
417 | navbarElement={this.renderSingleNavbar}
|
418 | fromMonth={minDate}
|
419 | month={this.state.leftView.getFullDate()}
|
420 | numberOfMonths={1}
|
421 | onMonthChange={this.handleLeftMonthChange}
|
422 | toMonth={maxDate}
|
423 | renderDay={dayPickerProps?.renderDay ?? this.renderDay}
|
424 | />
|
425 | );
|
426 | } else {
|
427 | return [
|
428 | <DayPicker
|
429 | key="left"
|
430 | {...dayPickerBaseProps}
|
431 | canChangeMonth={true}
|
432 | captionElement={this.renderLeftCaption}
|
433 | navbarElement={this.renderLeftNavbar}
|
434 | fromMonth={minDate}
|
435 | month={this.state.leftView.getFullDate()}
|
436 | numberOfMonths={1}
|
437 | onMonthChange={this.handleLeftMonthChange}
|
438 | toMonth={DateUtils.getDatePreviousMonth(maxDate)}
|
439 | renderDay={dayPickerProps?.renderDay ?? this.renderDay}
|
440 | />,
|
441 | <DayPicker
|
442 | key="right"
|
443 | {...dayPickerBaseProps}
|
444 | canChangeMonth={true}
|
445 | captionElement={this.renderRightCaption}
|
446 | navbarElement={this.renderRightNavbar}
|
447 | fromMonth={DateUtils.getDateNextMonth(minDate)}
|
448 | month={this.state.rightView.getFullDate()}
|
449 | numberOfMonths={1}
|
450 | onMonthChange={this.handleRightMonthChange}
|
451 | toMonth={maxDate}
|
452 | renderDay={dayPickerProps?.renderDay ?? this.renderDay}
|
453 | />,
|
454 | ];
|
455 | }
|
456 | }
|
457 |
|
458 | private renderSingleNavbar = (navbarProps: NavbarElementProps) => (
|
459 | <DatePickerNavbar {...navbarProps} maxDate={this.props.maxDate} minDate={this.props.minDate} />
|
460 | );
|
461 |
|
462 | private renderLeftNavbar = (navbarProps: NavbarElementProps) => (
|
463 | <DatePickerNavbar
|
464 | {...navbarProps}
|
465 | hideRightNavButton={this.props.contiguousCalendarMonths}
|
466 | maxDate={this.props.maxDate}
|
467 | minDate={this.props.minDate}
|
468 | />
|
469 | );
|
470 |
|
471 | private renderRightNavbar = (navbarProps: NavbarElementProps) => (
|
472 | <DatePickerNavbar
|
473 | {...navbarProps}
|
474 | hideLeftNavButton={this.props.contiguousCalendarMonths}
|
475 | maxDate={this.props.maxDate}
|
476 | minDate={this.props.minDate}
|
477 | />
|
478 | );
|
479 |
|
480 | private renderSingleCaption = (captionProps: CaptionElementProps) => (
|
481 | <DatePickerCaption
|
482 | {...captionProps}
|
483 | maxDate={this.props.maxDate}
|
484 | minDate={this.props.minDate}
|
485 | onMonthChange={this.handleLeftMonthSelectChange}
|
486 | onYearChange={this.handleLeftYearSelectChange}
|
487 | reverseMonthAndYearMenus={this.props.reverseMonthAndYearMenus}
|
488 | />
|
489 | );
|
490 |
|
491 | private renderLeftCaption = (captionProps: CaptionElementProps) => (
|
492 | <DatePickerCaption
|
493 | {...captionProps}
|
494 | maxDate={DateUtils.getDatePreviousMonth(this.props.maxDate)}
|
495 | minDate={this.props.minDate}
|
496 | onMonthChange={this.handleLeftMonthSelectChange}
|
497 | onYearChange={this.handleLeftYearSelectChange}
|
498 | reverseMonthAndYearMenus={this.props.reverseMonthAndYearMenus}
|
499 | />
|
500 | );
|
501 |
|
502 | private renderRightCaption = (captionProps: CaptionElementProps) => (
|
503 | <DatePickerCaption
|
504 | {...captionProps}
|
505 | maxDate={this.props.maxDate}
|
506 | minDate={DateUtils.getDateNextMonth(this.props.minDate)}
|
507 | onMonthChange={this.handleRightMonthSelectChange}
|
508 | onYearChange={this.handleRightYearSelectChange}
|
509 | reverseMonthAndYearMenus={this.props.reverseMonthAndYearMenus}
|
510 | />
|
511 | );
|
512 |
|
513 | private handleDayMouseEnter = (day: Date, modifiers: DayModifiers, e: React.MouseEvent<HTMLDivElement>) => {
|
514 | this.props.dayPickerProps.onDayMouseEnter?.(day, modifiers, e);
|
515 |
|
516 | if (modifiers.disabled) {
|
517 | return;
|
518 | }
|
519 | const { dateRange, boundary } = DateRangeSelectionStrategy.getNextState(
|
520 | this.state.value,
|
521 | day,
|
522 | this.props.allowSingleDayRange,
|
523 | this.props.boundaryToModify,
|
524 | );
|
525 | this.setState({ hoverValue: dateRange });
|
526 | this.props.onHoverChange?.(dateRange, day, boundary);
|
527 | };
|
528 |
|
529 | private handleDayMouseLeave = (day: Date, modifiers: DayModifiers, e: React.MouseEvent<HTMLDivElement>) => {
|
530 | this.props.dayPickerProps.onDayMouseLeave?.(day, modifiers, e);
|
531 | if (modifiers.disabled) {
|
532 | return;
|
533 | }
|
534 | this.setState({ hoverValue: undefined });
|
535 | this.props.onHoverChange?.(undefined, day, undefined);
|
536 | };
|
537 |
|
538 | private handleDayClick = (day: Date, modifiers: DayModifiers, e: React.MouseEvent<HTMLDivElement>) => {
|
539 | this.props.dayPickerProps.onDayClick?.(day, modifiers, e);
|
540 |
|
541 | if (modifiers.disabled) {
|
542 |
|
543 | this.forceUpdate();
|
544 | return;
|
545 | }
|
546 |
|
547 | const nextValue = DateRangeSelectionStrategy.getNextState(
|
548 | this.state.value,
|
549 | day,
|
550 | this.props.allowSingleDayRange,
|
551 | this.props.boundaryToModify,
|
552 | ).dateRange;
|
553 |
|
554 |
|
555 |
|
556 | this.handleDayMouseEnter(day, modifiers, e);
|
557 |
|
558 | this.handleNextState(nextValue);
|
559 | };
|
560 |
|
561 | private handleShortcutClick = (shortcut: DateRangeShortcut, selectedShortcutIndex: number) => {
|
562 | const { onChange, contiguousCalendarMonths, onShortcutChange } = this.props;
|
563 | const { dateRange, includeTime } = shortcut;
|
564 | if (includeTime) {
|
565 | const newDateRange: DateRange = [dateRange[0], dateRange[1]];
|
566 | const newTimeRange: DateRange = [dateRange[0], dateRange[1]];
|
567 | const nextState = getStateChange(this.state.value, dateRange, this.state, contiguousCalendarMonths);
|
568 | this.setState({ ...nextState, time: newTimeRange });
|
569 | onChange?.(newDateRange);
|
570 | } else {
|
571 | this.handleNextState(dateRange);
|
572 | }
|
573 |
|
574 | if (this.props.selectedShortcutIndex === undefined) {
|
575 | this.setState({ selectedShortcutIndex });
|
576 | }
|
577 |
|
578 | onShortcutChange?.(shortcut, selectedShortcutIndex);
|
579 | };
|
580 |
|
581 | private handleNextState = (nextValue: DateRange) => {
|
582 | const { value } = this.state;
|
583 | nextValue[0] = DateUtils.getDateTime(nextValue[0], this.state.time[0]);
|
584 | nextValue[1] = DateUtils.getDateTime(nextValue[1], this.state.time[1]);
|
585 |
|
586 | const nextState = getStateChange(value, nextValue, this.state, this.props.contiguousCalendarMonths);
|
587 |
|
588 | if (this.props.value == null) {
|
589 | this.setState(nextState);
|
590 | }
|
591 | this.props.onChange?.(nextValue);
|
592 | };
|
593 |
|
594 | private handleLeftMonthChange = (newDate: Date) => {
|
595 | const leftView = MonthAndYear.fromDate(newDate);
|
596 | this.props.dayPickerProps.onMonthChange?.(leftView.getFullDate());
|
597 | this.updateLeftView(leftView);
|
598 | };
|
599 |
|
600 | private handleRightMonthChange = (newDate: Date) => {
|
601 | const rightView = MonthAndYear.fromDate(newDate);
|
602 | this.props.dayPickerProps.onMonthChange?.(rightView.getFullDate());
|
603 | this.updateRightView(rightView);
|
604 | };
|
605 |
|
606 | private handleLeftMonthSelectChange = (leftMonth: number) => {
|
607 | const leftView = new MonthAndYear(leftMonth, this.state.leftView.getYear());
|
608 | this.props.dayPickerProps.onMonthChange?.(leftView.getFullDate());
|
609 | this.updateLeftView(leftView);
|
610 | };
|
611 |
|
612 | private handleRightMonthSelectChange = (rightMonth: number) => {
|
613 | const rightView = new MonthAndYear(rightMonth, this.state.rightView.getYear());
|
614 | this.props.dayPickerProps.onMonthChange?.(rightView.getFullDate());
|
615 | this.updateRightView(rightView);
|
616 | };
|
617 |
|
618 | private updateLeftView(leftView: MonthAndYear) {
|
619 | let rightView = this.state.rightView.clone();
|
620 | if (!leftView.isBefore(rightView) || this.props.contiguousCalendarMonths) {
|
621 | rightView = leftView.getNextMonth();
|
622 | }
|
623 | this.setViews(leftView, rightView);
|
624 | }
|
625 |
|
626 | private updateRightView(rightView: MonthAndYear) {
|
627 | let leftView = this.state.leftView.clone();
|
628 | if (!rightView.isAfter(leftView) || this.props.contiguousCalendarMonths) {
|
629 | leftView = rightView.getPreviousMonth();
|
630 | }
|
631 | this.setViews(leftView, rightView);
|
632 | }
|
633 |
|
634 | |
635 |
|
636 |
|
637 |
|
638 |
|
639 |
|
640 |
|
641 | private handleLeftYearSelectChange = (leftDisplayYear: number) => {
|
642 | let leftView = new MonthAndYear(this.state.leftView.getMonth(), leftDisplayYear);
|
643 | this.props.dayPickerProps.onMonthChange?.(leftView.getFullDate());
|
644 | const { minDate, maxDate } = this.props;
|
645 | const adjustedMaxDate = DateUtils.getDatePreviousMonth(maxDate);
|
646 |
|
647 | const minMonthAndYear = new MonthAndYear(minDate.getMonth(), minDate.getFullYear());
|
648 | const maxMonthAndYear = new MonthAndYear(adjustedMaxDate.getMonth(), adjustedMaxDate.getFullYear());
|
649 |
|
650 | if (leftView.isBefore(minMonthAndYear)) {
|
651 | leftView = minMonthAndYear;
|
652 | } else if (leftView.isAfter(maxMonthAndYear)) {
|
653 | leftView = maxMonthAndYear;
|
654 | }
|
655 |
|
656 | let rightView = this.state.rightView.clone();
|
657 | if (!leftView.isBefore(rightView) || this.props.contiguousCalendarMonths) {
|
658 | rightView = leftView.getNextMonth();
|
659 | }
|
660 |
|
661 | this.setViews(leftView, rightView);
|
662 | };
|
663 |
|
664 | private handleRightYearSelectChange = (rightDisplayYear: number) => {
|
665 | let rightView = new MonthAndYear(this.state.rightView.getMonth(), rightDisplayYear);
|
666 | this.props.dayPickerProps.onMonthChange?.(rightView.getFullDate());
|
667 | const { minDate, maxDate } = this.props;
|
668 | const adjustedMinDate = DateUtils.getDateNextMonth(minDate);
|
669 |
|
670 | const minMonthAndYear = MonthAndYear.fromDate(adjustedMinDate);
|
671 | const maxMonthAndYear = MonthAndYear.fromDate(maxDate);
|
672 |
|
673 | if (rightView.isBefore(minMonthAndYear)) {
|
674 | rightView = minMonthAndYear;
|
675 | } else if (rightView.isAfter(maxMonthAndYear)) {
|
676 | rightView = maxMonthAndYear;
|
677 | }
|
678 |
|
679 | let leftView = this.state.leftView.clone();
|
680 | if (!rightView.isAfter(leftView) || this.props.contiguousCalendarMonths) {
|
681 | leftView = rightView.getPreviousMonth();
|
682 | }
|
683 |
|
684 | this.setViews(leftView, rightView);
|
685 | };
|
686 |
|
687 | private setViews(leftView: MonthAndYear, rightView: MonthAndYear) {
|
688 | this.setState({ leftView, rightView });
|
689 | }
|
690 | }
|
691 |
|
692 | function getStateChange(
|
693 | value: DateRange,
|
694 | nextValue: DateRange,
|
695 | state: IDateRangePickerState,
|
696 | contiguousCalendarMonths: boolean,
|
697 | ): IDateRangePickerState {
|
698 | if (value != null && nextValue == null) {
|
699 | return { value: [null, null] };
|
700 | } else if (nextValue != null) {
|
701 | let leftView = state.leftView.clone();
|
702 | let rightView = state.rightView.clone();
|
703 |
|
704 | const nextValueStartView = MonthAndYear.fromDate(nextValue[0]);
|
705 | const nextValueEndView = MonthAndYear.fromDate(nextValue[1]);
|
706 |
|
707 |
|
708 |
|
709 |
|
710 |
|
711 | if (nextValueStartView == null && nextValueEndView != null) {
|
712 | if (!nextValueEndView.isSame(leftView) && !nextValueEndView.isSame(rightView)) {
|
713 | rightView = nextValueEndView;
|
714 | if (!leftView.isBefore(rightView)) {
|
715 | leftView = rightView.getPreviousMonth();
|
716 | }
|
717 | }
|
718 | } else if (nextValueStartView != null && nextValueEndView == null) {
|
719 |
|
720 |
|
721 |
|
722 |
|
723 | if (!nextValueStartView.isSame(leftView) && !nextValueStartView.isSame(rightView)) {
|
724 | leftView = nextValueStartView;
|
725 | if (!rightView.isAfter(leftView)) {
|
726 | rightView = leftView.getNextMonth();
|
727 | }
|
728 | }
|
729 | } else if (nextValueStartView != null && nextValueEndView != null) {
|
730 |
|
731 |
|
732 |
|
733 |
|
734 | if (nextValueStartView.isSame(nextValueEndView)) {
|
735 | if (leftView.isSame(nextValueStartView) || rightView.isSame(nextValueStartView)) {
|
736 |
|
737 | } else {
|
738 | leftView = nextValueStartView;
|
739 | rightView = nextValueStartView.getNextMonth();
|
740 | }
|
741 | } else {
|
742 |
|
743 | if (!leftView.isSame(nextValueStartView)) {
|
744 | leftView = nextValueStartView;
|
745 | rightView = nextValueStartView.getNextMonth();
|
746 | }
|
747 | if (contiguousCalendarMonths === false && !rightView.isSame(nextValueEndView)) {
|
748 | rightView = nextValueEndView;
|
749 | }
|
750 | }
|
751 | }
|
752 |
|
753 | return {
|
754 | leftView,
|
755 | rightView,
|
756 | value: nextValue,
|
757 | };
|
758 | } else if (contiguousCalendarMonths === true) {
|
759 |
|
760 |
|
761 | if (!state.leftView.getNextMonth().isSameMonth(state.rightView)) {
|
762 | const nextRightView = state.leftView.getNextMonth();
|
763 | return { rightView: nextRightView };
|
764 | }
|
765 | }
|
766 |
|
767 | return {};
|
768 | }
|
769 |
|
770 | function getInitialValue(props: DateRangePickerProps): DateRange | null {
|
771 | if (props.value != null) {
|
772 | return props.value;
|
773 | }
|
774 | if (props.defaultValue != null) {
|
775 | return props.defaultValue;
|
776 | }
|
777 | return [null, null];
|
778 | }
|
779 |
|
780 | function getInitialMonth(props: DateRangePickerProps, value: DateRange): Date {
|
781 | const today = new Date();
|
782 |
|
783 | if (props.initialMonth != null) {
|
784 | return props.initialMonth;
|
785 | } else if (value[0] != null) {
|
786 | return DateUtils.clone(value[0]);
|
787 | } else if (value[1] != null) {
|
788 | const month = DateUtils.clone(value[1]);
|
789 | if (!DateUtils.areSameMonth(month, props.minDate)) {
|
790 | month.setMonth(month.getMonth() - 1);
|
791 | }
|
792 | return month;
|
793 | } else if (DateUtils.isDayInRange(today, [props.minDate, props.maxDate])) {
|
794 | return today;
|
795 | } else {
|
796 | return DateUtils.getDateBetween([props.minDate, props.maxDate]);
|
797 | }
|
798 | }
|