123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 |
- /* eslint-disable max-len */
- import BaseFoundation, { DefaultAdapter } from '../base/foundation';
- import { strings } from './constants';
- import {
- format,
- set,
- addMonths,
- subMonths,
- subYears,
- addYears,
- differenceInCalendarMonths,
- differenceInCalendarYears,
- isSameDay,
- parseISO
- } from 'date-fns';
- import { isBefore, isValidDate, getDefaultFormatToken, getFullDateOffset } from './_utils/index';
- import { formatFullDate, WeekStartNumber } from './_utils/getMonthTable';
- import { compatibleParse } from './_utils/parser';
- import { includes, isSet, isEqual, isFunction } from 'lodash';
- import { zonedTimeToUtc } from '../utils/date-fns-extra';
- import { getDefaultFormatTokenByType } from './_utils/getDefaultFormatToken';
- import isNullOrUndefined from '../utils/isNullOrUndefined';
- import { BaseValueType, ValueType } from './foundation';
- import { MonthDayInfo } from './monthFoundation';
- import { ArrayElement } from '../utils/type';
- const dateDiffFns = {
- month: differenceInCalendarMonths,
- year: differenceInCalendarYears,
- };
- const dateCalcFns = {
- prevMonth: subMonths,
- nextMonth: addMonths,
- prevYear: subYears,
- nextYear: addYears,
- };
- type Type = ArrayElement<typeof strings.TYPE_SET>;
- interface MonthsGridElementProps {
- navPrev?: React.ReactNode;
- navNext?: React.ReactNode;
- renderDate?: () => React.ReactNode;
- renderFullDate?: () => React.ReactNode;
- }
- export type PanelType = 'left' | 'right';
- export type YearMonthChangeType = 'prevMonth' | 'nextMonth' | 'prevYear' | 'nextYear';
- export interface MonthsGridFoundationProps extends MonthsGridElementProps {
- type?: Type;
- defaultValue?: ValueType;
- defaultPickerValue?: ValueType;
- multiple?: boolean;
- max?: number;
- splitPanels?: boolean;
- weekStartsOn?: WeekStartNumber;
- disabledDate?: (date: Date, options?: { rangeStart: string; rangeEnd: string }) => boolean;
- disabledTime?: (date: Date | Date[], panelType: PanelType) => void;
- disabledTimePicker?: boolean;
- hideDisabledOptions?: boolean;
- onMaxSelect?: (v?: any) => void;
- timePickerOpts?: any;
- isControlledComponent?: boolean;
- rangeStart?: string;
- rangeInputFocus?: boolean | string;
- locale?: any;
- localeCode?: string;
- format?: string;
- startDateOffset?: () => void;
- endDateOffset?: () => void;
- autoSwitchDate?: boolean;
- motionEnd?: boolean;
- density?: string;
- dateFnsLocale?: any;
- timeZone?: string | number;
- syncSwitchMonth?: boolean;
- onChange?: (
- value: [Date] | [Date, Date],
- options?: { closePanel?: boolean; needCheckFocusRecord?: boolean }
- ) => void;
- onPanelChange?: (date: Date | Date[], dateString: string | string[]) => void;
- setRangeInputFocus?: (rangeInputFocus: 'rangeStart' | 'rangeEnd') => void;
- isAnotherPanelHasOpened?: (currentRangeInput: 'rangeStart' | 'rangeEnd') => boolean;
- focusRecordsRef?: any;
- triggerRender?: (props: Record<string, any>) => any;
- insetInput: boolean;
- }
- export interface MonthInfo {
- pickerDate: Date;
- showDate: Date;
- isTimePickerOpen: boolean;
- isYearPickerOpen: boolean;
- }
- export interface MonthsGridFoundationState {
- selected: Set<string>;
- monthLeft: MonthInfo;
- monthRight: MonthInfo;
- maxWeekNum: number; // Maximum number of weeks left and right for manual height adjustment
- hoverDay: string; // Real-time hover date
- rangeStart: string; // Start date for range selection
- rangeEnd: string; // End date of range selection
- currentPanelHeight: number; // current month panel height,
- offsetRangeStart: string;
- offsetRangeEnd: string;
- weeksRowNum?: number;
- }
- export interface MonthsGridDateAdapter {
- updateDaySelected: (selected: Set<string>) => void;
- }
- export interface MonthsGridRangeAdapter {
- setRangeStart: (rangeStart: string) => void;
- setRangeEnd: (rangeEnd: string) => void;
- setHoverDay: (hoverDay: string) => void;
- setWeeksHeight: (maxWeekNum: number) => void;
- setOffsetRangeStart: (offsetRangeStart: string) => void;
- setOffsetRangeEnd: (offsetRangeEnd: string) => void;
- }
- export interface MonthsGridAdapter extends DefaultAdapter<MonthsGridFoundationProps, MonthsGridFoundationState>, MonthsGridRangeAdapter, MonthsGridDateAdapter {
- updateMonthOnLeft: (v: MonthInfo) => void;
- updateMonthOnRight: (v: MonthInfo) => void;
- notifySelectedChange: MonthsGridFoundationProps['onChange'];
- notifyMaxLimit: MonthsGridFoundationProps['onMaxSelect'];
- notifyPanelChange: MonthsGridFoundationProps['onPanelChange'];
- setRangeInputFocus: MonthsGridFoundationProps['setRangeInputFocus'];
- isAnotherPanelHasOpened: MonthsGridFoundationProps['isAnotherPanelHasOpened'];
- }
- export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapter> {
- newBiMonthPanelDate: [Date, Date];
- constructor(adapter: MonthsGridAdapter) {
- super({ ...adapter });
- // Date change data when double panels
- this.newBiMonthPanelDate = [this.getState('monthLeft').pickerDate, this.getState('monthRight').pickerDate];
- }
- init() {
- const defaultValue = this.getProp('defaultValue');
- this.initDefaultPickerValue();
- this.updateSelectedFromProps(defaultValue);
- }
- initDefaultPickerValue() {
- const defaultPickerValue = compatibleParse(this.getProp('defaultPickerValue'));
- if (defaultPickerValue && isValidDate(defaultPickerValue)) {
- this._updatePanelDetail(strings.PANEL_TYPE_LEFT, {
- pickerDate: defaultPickerValue,
- });
- this._updatePanelDetail(strings.PANEL_TYPE_RIGHT, {
- pickerDate: addMonths(defaultPickerValue, 1),
- });
- }
- }
- updateSelectedFromProps(values: ValueType, refreshPicker = true) {
- const type: Type = this.getProp('type');
- const { selected, rangeStart, rangeEnd } = this.getStates();
- if (values && (values as BaseValueType[]).length) {
- switch (type) {
- case 'date':
- this._initDatePickerFromValue(values as BaseValueType[], refreshPicker);
- break;
- case 'dateRange':
- this._initDateRangePickerFromValue(values as BaseValueType[]);
- break;
- case 'dateTime':
- this._initDateTimePickerFromValue(values as BaseValueType[]);
- break;
- case 'dateTimeRange':
- this._initDateTimeRangePickerFormValue(values as BaseValueType[]);
- break;
- default:
- break;
- }
- } else if (Array.isArray(values) && !values.length || !values) {
- // Empty panel when value is empty Select date
- if (isSet(selected) && selected.size) {
- this._adapter.updateDaySelected(new Set());
- }
- if (rangeStart) {
- this._adapter.setRangeStart('');
- }
- if (rangeEnd) {
- this._adapter.setRangeEnd('');
- }
- }
- }
- calcDisabledTime(panelType: PanelType) {
- const { disabledTime, type } = this.getProps();
- if (typeof disabledTime === 'function' && panelType && ['dateTime', 'dateTimeRange'].includes(type)) {
- const { rangeStart, rangeEnd, monthLeft } = this.getStates();
- const selected = [];
- if (type === 'dateTimeRange') {
- if (rangeStart) {
- selected.push(rangeStart);
- }
- if (rangeStart && rangeEnd) {
- selected.push(rangeEnd);
- }
- } else if (monthLeft && monthLeft.showDate) {
- selected.push(monthLeft.showDate);
- }
- const selectedDates = selected.map(str => (str instanceof Date ? str : parseISO(str)));
- const cbDates = type === 'dateTimeRange' ? selectedDates : selectedDates[0];
- return disabledTime(cbDates, panelType);
- }
- }
- _initDatePickerFromValue(values: BaseValueType[], refreshPicker = true) {
- const monthLeft = this.getState('monthLeft');
- const newMonthLeft = { ...monthLeft };
- // REMOVE:
- this._adapter.updateMonthOnLeft(newMonthLeft);
- const newSelected = new Set<string>();
- if (!this._isMultiple()) {
- values[0] && newSelected.add(format(values[0] as Date, strings.FORMAT_FULL_DATE));
- } else {
- values.forEach(date => {
- date && newSelected.add(format(date as Date, strings.FORMAT_FULL_DATE));
- });
- }
- if (refreshPicker) {
- this.handleShowDateAndTime(strings.PANEL_TYPE_LEFT, values[0] || newMonthLeft.pickerDate);
- } else {
- // FIXME:
- this.handleShowDateAndTime(strings.PANEL_TYPE_LEFT, newMonthLeft.pickerDate);
- }
- this._adapter.updateDaySelected(newSelected);
- }
- _initDateRangePickerFromValue(values: BaseValueType[], withTime = false) {
- // init month panel
- const monthLeft = this.getState('monthLeft');
- const monthRight = this.getState('monthRight');
- const adjustResult = this._autoAdjustMonth(
- { ...monthLeft, pickerDate: values[0] || monthLeft.pickerDate },
- { ...monthRight, pickerDate: values[1] || monthRight.pickerDate }
- );
- this.handleShowDateAndTime(strings.PANEL_TYPE_LEFT, adjustResult.monthLeft.pickerDate);
- this.handleShowDateAndTime(strings.PANEL_TYPE_RIGHT, adjustResult.monthRight.pickerDate);
- // init range
- const formatToken = withTime ? strings.FORMAT_DATE_TIME : strings.FORMAT_FULL_DATE;
- let rangeStart = values[0] && format(values[0] as Date, formatToken);
- let rangeEnd = values[1] && format(values[1] as Date, formatToken);
- if (this._isNeedSwap(rangeStart, rangeEnd)) {
- [rangeStart, rangeEnd] = [rangeEnd, rangeStart];
- }
- this._adapter.setRangeStart(rangeStart);
- this._adapter.setRangeEnd(rangeEnd);
- this._adapter.setHoverDay(rangeEnd);
- }
- _initDateTimePickerFromValue(values: BaseValueType[]) {
- this._initDatePickerFromValue(values);
- }
- _initDateTimeRangePickerFormValue(values: BaseValueType[]) {
- this._initDateRangePickerFromValue(values, true);
- }
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- destroy() { }
- /**
- * sync change another panel month when change months from the else yam panel
- * call it when
- * - current change panel targe date month is same with another panel date
- *
- * @example
- * - panelType=right, target=new Date('2022-09-01') and left panel is in '2022-09' => call it, left panel minus one month to '2022-08'
- * - panelType=left, target=new Date('2021-12-01') and right panel is in '2021-12' => call it, right panel add one month to '2021-01'
- */
- handleSyncChangeMonths(options: { panelType: PanelType, target: Date }) {
- const { panelType, target } = options;
- const { type } = this._adapter.getProps();
- const { monthLeft, monthRight } = this._adapter.getStates();
- if (this.isRangeType(type)) {
- if (panelType === 'right' && differenceInCalendarMonths(target, monthLeft.pickerDate) === 0) {
- this.handleYearOrMonthChange('prevMonth', 'left', 1, true);
- } else if (panelType === 'left' && differenceInCalendarMonths(monthRight.pickerDate, target) === 0) {
- this.handleYearOrMonthChange('nextMonth', 'right', 1, true);
- }
- }
- }
- /**
- * Get the target date based on the panel type and switch type
- */
- getTargetChangeDate(options: { panelType: PanelType, switchType: YearMonthChangeType }) {
- const { panelType, switchType } = options;
- const { monthRight, monthLeft } = this._adapter.getStates();
- const currentDate = panelType === 'left' ? monthLeft.pickerDate : monthRight.pickerDate;
- let target: Date;
-
- switch (switchType) {
- case 'prevMonth':
- target = addMonths(currentDate, -1);
- break;
- case 'nextMonth':
- target = addMonths(currentDate, 1);
- break;
- case 'prevYear':
- target = addYears(currentDate, -1);
- break;
- case 'nextYear':
- target = addYears(currentDate, 1);
- break;
- }
- return target;
- }
- /**
- * Change month by yam panel
- */
- toMonth(panelType: PanelType, target: Date) {
- const { type } = this._adapter.getProps();
- const diff = this._getDiff('month', target, panelType);
- this.handleYearOrMonthChange(diff < 0 ? 'prevMonth' : 'nextMonth', panelType, Math.abs(diff), false);
-
- if (this.isRangeType(type)) {
- this.handleSyncChangeMonths({ panelType, target });
- }
- }
- toYear(panelType: PanelType, target: Date) {
- const diff = this._getDiff('year', target, panelType);
- this.handleYearOrMonthChange(diff < 0 ? 'prevYear' : 'nextYear', panelType, Math.abs(diff), false);
- }
- toYearMonth(panelType: PanelType, target: Date) {
- this.toYear(panelType, target);
- this.toMonth(panelType, target);
- }
- isRangeType(type?: Type) {
- const { type: typeFromProp } = this.getProps();
- const realType = type ? type : typeFromProp;
- return typeof realType === 'string' && /range/i.test(realType);
- }
- handleSwitchMonthOrYear(switchType: YearMonthChangeType, panelType: PanelType) {
- const { type, syncSwitchMonth } = this.getProps();
- const rangeType = this.isRangeType(type);
- // range type and syncSwitchMonth, we should change panels at same time
- if (rangeType && syncSwitchMonth) {
- this.handleYearOrMonthChange(switchType, 'left', 1, true);
- this.handleYearOrMonthChange(switchType, 'right', 1, true);
- } else {
- this.handleYearOrMonthChange(switchType, panelType);
- /**
- * default behavior (v2.2.0)
- * In order to prevent the two panels from being the same month, this will confuse the user when selecting the range
- * https://github.com/DouyinFE/semi-design/issues/260
- */
- if (rangeType) {
- const target = this.getTargetChangeDate({ panelType, switchType });
- this.handleSyncChangeMonths({ panelType, target });
- }
- }
- }
- prevMonth(panelType: PanelType) {
- this.handleSwitchMonthOrYear('prevMonth', panelType);
- }
- nextMonth(panelType: PanelType) {
- this.handleSwitchMonthOrYear('nextMonth', panelType);
- }
- prevYear(panelType: PanelType) {
- this.handleSwitchMonthOrYear('prevYear', panelType);
- }
- nextYear(panelType: PanelType) {
- this.handleSwitchMonthOrYear('nextYear', panelType);
- }
- /**
- * Calculate the year and month difference
- */
- _getDiff(type: 'month' | 'year', target: Date, panelType: PanelType) {
- const panelDetail = this._getPanelDetail(panelType);
- const diff = dateDiffFns[type] && dateDiffFns[type](target, panelDetail.pickerDate);
- return diff;
- }
- _getPanelDetail(panelType: PanelType) {
- return panelType === strings.PANEL_TYPE_RIGHT ? this.getState('monthRight') : this.getState('monthLeft');
- }
- /**
- * Format locale date
- * locale get from LocaleProvider
- * @param {Date} date
- * @param {String} token
- * @returns
- */
- localeFormat(date: Date, token: string) {
- const dateFnsLocale = this._adapter.getProp('dateFnsLocale');
- return format(date, token, { locale: dateFnsLocale });
- }
- isValidTimeZone(timeZone?: string | number) {
- const propTimeZone = this.getProp('timeZone');
- const _timeZone = isNullOrUndefined(timeZone) ? propTimeZone : timeZone;
- return ['string', 'number'].includes(typeof _timeZone) && _timeZone !== '';
- }
- /**
- * 根据 type 处理 onChange 返回的参数
- *
- * - 返回的日期需要把用户时间转换为设置的时区时间
- * - 用户时间:用户计算机系统时间
- * - 时区时间:通过 ConfigProvider 设置的 timeZone
- * - 例子:用户设置时区为+9,计算机所在时区为+8区,然后用户选择了22:00
- * - DatePicker 内部保存日期 state 为 +8 的 22:00 => a = new Date("2021-05-25 22:00:00")
- * - 传出去时,需要把 +8 的 22:00 => +9 的 22:00 => b = zonedTimeToUtc(a, "+09:00");
- *
- * The parameters returned by onChange are processed according to type
- *
- * -The returned date needs to convert the user time to the set time zone time
- * -User time: user computer system time
- * -Time zone: timeZone set by ConfigProvider
- * -Example: The user sets the time zone to + 9, and the time zone where the computer is located is + 8, and then the user selects 22:00
- * -DatePicker internal save date state is + 8 22:00 = > a = new Date ("2021-05-25 22:00:00")
- * -When passing out, you need to put + 8's 22:00 = > + 9's 22:00 = > b = zonedTimeToUtc (a, "+ 09:00");
- *
- * e.g.
- * let a = new Date ("2021-05-25 22:00:00");
- * = > Tue May 25 2021 22:00:00 GMT + 0800 (China Standard Time)
- * let b = zonedTimeToUtc (a, "+ 09:00");
- * = > Tue May 25 2021 21:00:00 GMT + 0800 (China Standard Time)
- *
- * @param {Date|Date[]} value
- */
- disposeCallbackArgs(value: Date | Date[]) {
- let _value = Array.isArray(value) ? value : (value && [value]) || [];
- if (this.isValidTimeZone()) {
- const timeZone = this.getProp('timeZone');
- _value = _value.map(date => zonedTimeToUtc(date, timeZone));
- }
- const type = this.getProp('type');
- const formatToken = this.getProp('format') || getDefaultFormatTokenByType(type);
- let notifyValue,
- notifyDate;
- switch (type) {
- case 'date':
- case 'dateTime':
- case 'month':
- if (!this._isMultiple()) {
- notifyValue = _value[0] && this.localeFormat(_value[0], formatToken);
- [notifyDate] = _value;
- } else {
- notifyValue = _value.map(v => v && this.localeFormat(v, formatToken));
- notifyDate = [..._value];
- }
- break;
- case 'dateRange':
- case 'dateTimeRange':
- notifyValue = _value.map(v => v && this.localeFormat(v, formatToken));
- notifyDate = [..._value];
- break;
- default:
- break;
- }
- return {
- notifyValue,
- notifyDate,
- };
- }
- handleYearOrMonthChange(
- type: YearMonthChangeType,
- panelType: PanelType = strings.PANEL_TYPE_LEFT,
- step = 1,
- notSeparateInRange = false
- ) {
- const { autoSwitchDate, type: datePanelType } = this.getProps();
- const { monthLeft, monthRight } = this.getStates();
- const isRangeType = this.isRangeType(datePanelType);
- const isLeftPanelInRange = isRangeType && panelType === strings.PANEL_TYPE_LEFT;
- const panelDetail = this._getPanelDetail(panelType);
- const { pickerDate } = panelDetail;
- const fn = dateCalcFns[type];
- const targetMonth = fn(pickerDate, step);
- // Determine if the date has changed
- const panelDateHasUpdate = (panelType === strings.PANEL_TYPE_LEFT && !isEqual(targetMonth, monthLeft.pickerDate)) ||
- (panelType === strings.PANEL_TYPE_RIGHT && !isEqual(targetMonth, monthRight.pickerDate));
- this._updatePanelDetail(panelType, { pickerDate: targetMonth });
- if (panelDateHasUpdate) { // When the date changes
- if (!isRangeType) { // Single Panel Type
- const { notifyValue, notifyDate } = this.disposeCallbackArgs(targetMonth);
- this._adapter.notifyPanelChange(notifyDate, notifyValue);
- } else { // Double Panel Type
- if (isLeftPanelInRange) { // Left panel
- this.newBiMonthPanelDate[0] = targetMonth;
- } else { // Right panel
- this.newBiMonthPanelDate[1] = targetMonth;
- }
- if (!(isLeftPanelInRange && notSeparateInRange)) { // Not synchronously switching the left panel in the scene
- const { notifyValue, notifyDate } = this.disposeCallbackArgs(this.newBiMonthPanelDate);
- this._adapter.notifyPanelChange(notifyDate, notifyValue);
- }
- }
- }
- if (autoSwitchDate) {
- this.updateDateAfterChangeYM(type, targetMonth);
- }
- }
- /**
- * You have chosen to switch the year and month in the future to directly update the Date without closing the date panel
- * @param {*} type
- * @param {*} targetDate
- */
- updateDateAfterChangeYM(
- type: YearMonthChangeType,
- targetDate: Date
- ) {
- const { multiple, disabledDate } = this.getProps();
- const { selected: selectedSet, rangeStart, rangeEnd } = this.getStates();
- // FIXME:
- const includeRange = ['dateRange', 'dateTimeRange'].includes(type);
- const options = { closePanel: false };
- if (!multiple && !includeRange && selectedSet.size) {
- const selectedStr = Array.from(selectedSet)[0] as string;
- const selectedDate = new Date(selectedStr);
- const year = targetDate.getFullYear();
- const month = targetDate.getMonth();
- const fullDate = set(selectedDate, { year, month });
- if (disabledDate(fullDate, { rangeStart, rangeEnd })) {
- return;
- }
- this._adapter.notifySelectedChange([fullDate], options);
- }
- }
- _isMultiple() {
- return Boolean(this.getProp('multiple')) && this.getProp('type') === 'date';
- }
- _isRange() {
- // return this._adapter.getProp('type') === dateRangeTypeKey;
- }
- handleDayClick(day: MonthDayInfo, panelType: PanelType) {
- const type = this.getProp('type');
- switch (true) {
- case type === 'date' || type === 'dateTime':
- this.handleDateSelected(day, panelType);
- break;
- case type === 'dateRange' || type === 'dateTimeRange':
- this.handleRangeSelected(day);
- break;
- default:
- break;
- }
- }
- handleDateSelected(day: { fullDate: string; fullValidDate?: Date }, panelType: PanelType) {
- const { max, type, isControlledComponent, dateFnsLocale } = this.getProps();
- const multiple = this._isMultiple();
- const { selected } = this.getStates();
- const monthDetail = this._getPanelDetail(panelType);
- const newSelected = new Set(multiple ? [...selected] : []);
- const { fullDate } = day;
- const time = monthDetail.pickerDate;
- const dateStr = type === 'dateTime' ? this._mergeDateAndTime(fullDate, time) : fullDate;
- if (!multiple) {
- newSelected.add(dateStr);
- } else {
- if (newSelected.has(dateStr)) {
- newSelected.delete(dateStr);
- } else if (max && newSelected.size === max) {
- this._adapter.notifyMaxLimit();
- } else {
- newSelected.add(dateStr);
- }
- }
- const dateFormat = this.getValidDateFormat();
- // When passed to the upper layer, it is converted into a Date object to ensure that the input parameter format of initFormDefaultValue is consistent
- const newSelectedDates = [...newSelected].map(_dateStr => compatibleParse(_dateStr, dateFormat, undefined, dateFnsLocale));
- this.handleShowDateAndTime(panelType, time);
- if (!isControlledComponent) {
- // Uncontrolled components, update internal values when operating, and notify external
- // MonthGrid internally uses string to represent fullDate for easy rendering
- this._adapter.updateDaySelected(newSelected);
- }
- this._adapter.notifySelectedChange(newSelectedDates as [Date]);
- }
- handleShowDateAndTime(panelType: PanelType, pickerDate: number | Date, showDate?: Date) {
- const _showDate = showDate || pickerDate;
- this._updatePanelDetail(panelType, { showDate: _showDate, pickerDate });
- }
- /**
- * link date and time
- *
- * @param {Date|string} date
- * @param {Date|string} time
- * @returns {Date}
- */
- _mergeDateAndTime(date: Date | string, time: Date | string) {
- const dateFnsLocale = this._adapter.getProp('dateFnsLocale');
- const dateStr = format(
- isValidDate(date) ? date as Date : compatibleParse(date as string, strings.FORMAT_FULL_DATE, undefined, dateFnsLocale),
- strings.FORMAT_FULL_DATE
- );
- const timeStr = format(
- isValidDate(time) ? time as Date : compatibleParse(time as string, strings.FORMAT_TIME_PICKER, undefined, dateFnsLocale),
- strings.FORMAT_TIME_PICKER
- );
- const timeFormat = this.getValidTimeFormat();
- return compatibleParse(`${dateStr} ${timeStr}`, timeFormat, undefined, dateFnsLocale);
- }
- handleRangeSelected(day: MonthDayInfo) {
- let { rangeStart, rangeEnd } = this.getStates();
- const { startDateOffset, endDateOffset, type, dateFnsLocale, rangeInputFocus, triggerRender } = this._adapter.getProps();
- const { fullDate } = day;
- let rangeStartReset = false;
- let rangeEndReset = false;
- const isDateRangeAndHasOffset = (startDateOffset || endDateOffset) && type === 'dateRange';
- if (isDateRangeAndHasOffset) {
- rangeStart = getFullDateOffset(startDateOffset, fullDate);
- rangeEnd = getFullDateOffset(endDateOffset, fullDate);
- } else {
- if (rangeInputFocus === 'rangeEnd') {
- rangeEnd = fullDate;
- // rangStart Parten in dateTime: 'yyyy-MM-dd HH:MM:SS', rangeEnd parten: 'yyyy-MM-dd'
- if ((rangeStart && rangeEnd) && isBefore(rangeEnd, rangeStart.trim().split(/\s+/)[0])) {
- rangeStart = null;
- rangeStartReset = true;
- }
- // Compatible to select date after opening the panel without click input
- } else if (rangeInputFocus === 'rangeStart' || !rangeInputFocus) {
- rangeStart = fullDate;
- // rangEnd Parten in dateTime: 'yyyy-MM-dd HH:MM:SS', rangeStart parten: 'yyyy-MM-dd'
- if ((rangeStart && rangeEnd) && isBefore(rangeEnd.trim().split(/\s+/)[0], rangeStart)) {
- rangeEnd = null;
- rangeEndReset = true;
- }
- }
- }
- // next focus logic
- const isRangeType = /range/i.test(type);
- if (isRangeType) {
- if (isDateRangeAndHasOffset) {
- this._adapter.setRangeStart(rangeStart);
- this._adapter.setRangeEnd(rangeEnd);
- } else {
- if (rangeInputFocus === 'rangeEnd') {
- this._adapter.setRangeEnd(rangeEnd);
- if (rangeStartReset) {
- this._adapter.setRangeStart(rangeStart);
- }
- if (!this._adapter.isAnotherPanelHasOpened('rangeEnd') || !rangeStart) {
- this._adapter.setRangeInputFocus('rangeStart');
- }
- } else if (rangeInputFocus === 'rangeStart' || !rangeInputFocus) {
- this._adapter.setRangeStart(rangeStart);
- if (rangeEndReset) {
- this._adapter.setRangeEnd(rangeEnd);
- }
- if (!this._adapter.isAnotherPanelHasOpened('rangeStart') || !rangeEnd) {
- this._adapter.setRangeInputFocus('rangeEnd');
- }
- }
- }
- }
- const dateFormat = this.getValidDateFormat();
- // only notify when choose completed
- if (rangeStart || rangeEnd) {
- const [startDate, endDate] = [
- compatibleParse(rangeStart, dateFormat, undefined, dateFnsLocale),
- compatibleParse(rangeEnd, dateFormat, undefined, dateFnsLocale),
- ];
- let date: [Date, Date] = [startDate, endDate];
- // If the type is dateRangeTime, add the value of time
- if (type === 'dateTimeRange') {
- const startTime = this.getState('monthLeft').pickerDate;
- const endTime = this.getState('monthRight').pickerDate;
- const start = rangeStart ? this._mergeDateAndTime(rangeStart, startTime) : null;
- const end = rangeEnd ? this._mergeDateAndTime(rangeEnd, endTime) : null;
- if (isSameDay(startDate, endDate) && isBefore(end, start)) {
- date = [start, start];
- } else {
- date = [start, end];
- }
- }
- /**
- * no need to check focus then
- * - dateRange and isDateRangeAndHasOffset
- * - dateRange and triggerRender
- */
- const needCheckFocusRecord = !(type === 'dateRange' && (isDateRangeAndHasOffset || isFunction(triggerRender)));
- this._adapter.notifySelectedChange(date, { needCheckFocusRecord });
- }
- }
- _isNeedSwap(rangeStart: Date | string, rangeEnd: Date | string) {
- // Check whether the start and end are reasonable and whether they need to be reversed
- return rangeStart && rangeEnd && isBefore(rangeEnd, rangeStart);
- }
- /**
- * Day may be empty, this is unhover state
- * @param {*} day
- */
- handleDayHover(day = { fullDate: '' }, panelType?: PanelType) {
- const { fullDate } = day;
- const { startDateOffset, endDateOffset, type } = this.getProps();
- this._adapter.setHoverDay(fullDate);
- if ((startDateOffset || endDateOffset) && type === 'dateRange') {
- const offsetRangeStart = getFullDateOffset(startDateOffset, fullDate);
- const offsetRangeEnd = getFullDateOffset(endDateOffset, fullDate);
- this._adapter.setOffsetRangeStart(offsetRangeStart);
- this._adapter.setOffsetRangeEnd(offsetRangeEnd);
- }
- }
- // Guarantee that monthLeft, monthRight will not appear in the same month or monthLeft is greater than MonthRight
- _autoAdjustMonth(monthLeft: MonthInfo, monthRight: MonthInfo) {
- let newMonthLeft = monthLeft;
- let newMonthRight = monthRight;
- const difference = differenceInCalendarMonths(monthLeft.pickerDate, monthRight.pickerDate);
- if (difference > 0) {
- // The month on the left is larger than the month on the right, swap
- newMonthLeft = { ...monthRight };
- newMonthRight = { ...monthLeft };
- } else if (difference === 0) {
- // Around the same month, the number of months on the right + 1
- newMonthLeft = monthLeft;
- newMonthRight = { ...monthRight, pickerDate: addMonths(monthRight.pickerDate, 1) };
- }
- return { monthLeft: newMonthLeft, monthRight: newMonthRight };
- }
- getValidTimeFormat() {
- const formatProp = this.getProp('format') || strings.FORMAT_TIME_PICKER;
- const timeFormatTokens = [];
- if (includes(formatProp, 'h') || includes(formatProp, 'H')) {
- timeFormatTokens.push('HH');
- }
- if (includes(formatProp, 'm')) {
- timeFormatTokens.push('mm');
- }
- if (includes(formatProp, 's')) {
- timeFormatTokens.push('ss');
- }
- return timeFormatTokens.join(':');
- }
- getValidDateFormat() {
- return this.getProp('format') || getDefaultFormatToken(this.getProp('type'));
- }
- handleTimeChange(newTime: { timeStampValue: number }, panelType: PanelType) {
- const { rangeEnd, rangeStart } = this.getStates();
- const dateFnsLocale = this.getProp('dateFnsLocale');
- const ts = newTime.timeStampValue;
- const type = this.getProp('type');
- const panelDetail = this._getPanelDetail(panelType);
- const { showDate } = panelDetail;
- const timeDate = new Date(ts);
- const dateFormat = this.getValidDateFormat();
- const destRange = panelType === strings.PANEL_TYPE_RIGHT ? rangeEnd : rangeStart;
- let year,
- monthNo,
- date;
- // if (pickerDate && isValidDate(pickerDate)) {
- // year = pickerDate.getFullYear();
- // monthNo = pickerDate.getMonth();
- // date = pickerDate.getDate();
- // } else
- if (type === 'dateTimeRange' && destRange) {
- const rangeDate = compatibleParse(destRange, dateFormat, undefined, dateFnsLocale);
- year = rangeDate.getFullYear();
- monthNo = rangeDate.getMonth();
- date = rangeDate.getDate();
- } else {
- year = showDate.getFullYear();
- monthNo = showDate.getMonth();
- date = showDate.getDate();
- }
- const hours = timeDate.getHours();
- const minutes = timeDate.getMinutes();
- const seconds = timeDate.getSeconds();
- const milSeconds = timeDate.getMilliseconds();
- const dateArgs = [year, monthNo, date, hours, minutes, seconds, milSeconds] as const;
- const fullValidDate = new Date(...dateArgs);
- if (type === 'dateTimeRange') {
- this.handleShowDateAndTime(panelType, fullValidDate, showDate);
- this._updateTimeInDateRange(panelType, fullValidDate);
- } else {
- const fullDate = formatFullDate(year, monthNo + 1, date);
- this.handleDateSelected(
- {
- fullDate,
- fullValidDate,
- },
- panelType
- );
- this.handleShowDateAndTime(panelType, fullValidDate);
- this._adapter.notifySelectedChange([fullValidDate]);
- }
- }
- /**
- * Update the time part in the range
- * @param {string} panelType
- * @param {Date} timeDate
- */
- _updateTimeInDateRange(panelType: PanelType, timeDate: Date) {
- const { isControlledComponent, dateFnsLocale } = this.getProps();
- let rangeStart = this.getState('rangeStart');
- let rangeEnd = this.getState('rangeEnd');
- const dateFormat = this.getValidDateFormat();
- // TODO: Modify a time individually
- if (rangeStart && rangeEnd) {
- let startDate = compatibleParse(rangeStart, dateFormat, undefined, dateFnsLocale);
- let endDate = compatibleParse(rangeEnd, dateFormat, undefined, dateFnsLocale);
- // console.log('_updateTimeInDateRange()', rangeStart, rangeEnd, startDate, endDate);
- if (panelType === strings.PANEL_TYPE_RIGHT) {
- endDate = this._mergeDateAndTime(timeDate, timeDate);
- rangeEnd = format(endDate, strings.FORMAT_DATE_TIME);
- if (this._isNeedSwap(rangeStart, rangeEnd)) {
- [rangeStart, rangeEnd] = [rangeEnd, rangeStart];
- [startDate, endDate] = [endDate, startDate];
- }
- if (!isControlledComponent) {
- this._adapter.setRangeEnd(rangeEnd);
- }
- } else {
- startDate = this._mergeDateAndTime(timeDate, timeDate);
- rangeStart = format(startDate, strings.FORMAT_DATE_TIME);
- if (this._isNeedSwap(rangeStart, rangeEnd)) {
- [rangeStart, rangeEnd] = [rangeEnd, rangeStart];
- [startDate, endDate] = [endDate, startDate];
- }
- if (!isControlledComponent) {
- this._adapter.setRangeStart(rangeStart);
- }
- }
- // console.log('_updateTimeInDateRange()', rangeStart, rangeEnd, startDate, endDate);
- this._adapter.notifySelectedChange([startDate, endDate]);
- }
- }
- _updatePanelDetail(
- panelType: PanelType,
- kvs: {
- showDate?: number | Date;
- pickerDate?: number | Date;
- isTimePickerOpen?: boolean;
- isYearPickerOpen?: boolean;
- }
- ) {
- const { monthLeft, monthRight } = this.getStates();
- if (panelType === strings.PANEL_TYPE_RIGHT) {
- this._adapter.updateMonthOnRight({ ...monthRight, ...kvs });
- } else {
- this._adapter.updateMonthOnLeft({ ...monthLeft, ...kvs });
- }
- }
- showYearPicker(panelType: PanelType) {
- this._updatePanelDetail(panelType, { isTimePickerOpen: false, isYearPickerOpen: true });
- }
- showTimePicker(panelType: PanelType, opt?: boolean) {
- if (this.getProp('disabledTimePicker')) {
- return;
- }
- this._updatePanelDetail(panelType, { isTimePickerOpen: true, isYearPickerOpen: false });
- }
- showDatePanel(panelType: PanelType) {
- this._updatePanelDetail(panelType, { isTimePickerOpen: false, isYearPickerOpen: false });
- }
- /**
- * Get year and month panel open type
- *
- * It is useful info to set minHeight of weeks.
- * - When yam open type is 'left' or 'right', weeks minHeight should be set
- * If the minHeight is not set, the change of the number of weeks will cause the scrollList to be unstable
- */
- getYAMOpenType() {
- const { monthLeft, monthRight } = this._adapter.getStates();
- const leftYearPickerOpen = monthLeft.isYearPickerOpen;
- const rightYearPickerOpen = monthRight.isYearPickerOpen;
- if (leftYearPickerOpen && rightYearPickerOpen) {
- return 'both';
- } else if (leftYearPickerOpen) {
- return 'left';
- } else if (rightYearPickerOpen) {
- return 'right';
- } else {
- return 'none';
- }
- }
- }
|