| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880 | /* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/interactive-supports-focus *//* eslint-disable max-len *//* eslint-disable jsx-a11y/click-events-have-key-events *//* eslint-disable jsx-a11y/no-static-element-interactions */import React from 'react';import classnames from 'classnames';import PropTypes from 'prop-types';import { noop, stubFalse, isDate, get, isFunction, isEqual, pick } from 'lodash';import ConfigContext, { ContextValue } from '../configProvider/context';import DatePickerFoundation, {    DatePickerAdapter,    DatePickerFoundationProps,    DatePickerFoundationState,    DayStatusType,    PresetType,    Type,    RangeType} from '@douyinfe/semi-foundation/datePicker/foundation';import { cssClasses, strings, numbers } from '@douyinfe/semi-foundation/datePicker/constants';import { strings as popoverStrings, numbers as popoverNumbers } from '@douyinfe/semi-foundation/popover/constants';import BaseComponent from '../_base/baseComponent';import Popover, { PopoverProps } from '../popover/index';import DateInput, { DateInputProps } from './dateInput';import MonthsGrid, { MonthsGridProps } from './monthsGrid';import QuickControl from './quickControl';import Footer from './footer';import Trigger from '../trigger';import YearAndMonth, { YearAndMonthProps } from './yearAndMonth';import '@douyinfe/semi-foundation/datePicker/datePicker.scss';import { Locale } from '../locale/interface';import { TimePickerProps } from '../timePicker/TimePicker';import { ScrollItemProps } from '../scrollList/scrollItem';import { InsetInputValue, InsetInputChangeProps } from '@douyinfe/semi-foundation/datePicker/inputFoundation';export interface DatePickerProps extends DatePickerFoundationProps {    'aria-describedby'?: React.AriaAttributes['aria-describedby'];    'aria-errormessage'?: React.AriaAttributes['aria-errormessage'];    'aria-invalid'?: React.AriaAttributes['aria-invalid'];    'aria-labelledby'?: React.AriaAttributes['aria-labelledby'];    'aria-required'?: React.AriaAttributes['aria-required'];    clearIcon?: React.ReactNode;    timePickerOpts?: TimePickerProps;    bottomSlot?: React.ReactNode;    insetLabel?: React.ReactNode;    insetLabelId?: string;    prefix?: React.ReactNode;    topSlot?: React.ReactNode;    renderDate?: (dayNumber?: number, fullDate?: string) => React.ReactNode;    renderFullDate?: (dayNumber?: number, fullDate?: string, dayStatus?: DayStatusType) => React.ReactNode;    triggerRender?: (props: DatePickerProps) => React.ReactNode;    /**     * There are multiple input boxes when selecting a range, and the input boxes will be out of focus multiple times.     *     * Use `onOpenChange` or `onClickOutSide` instead     */    onBlur?: React.MouseEventHandler<HTMLInputElement>;    onClear?: React.MouseEventHandler<HTMLDivElement>;    /**     * There are multiple input boxes when selecting a range, and the input boxes will be focused multiple times.     *     * Use `onOpenChange` or `triggerRender` instead     */    onFocus?: (e: React.MouseEvent, rangeType: RangeType) => void;    onPresetClick?: (item: PresetType, e: React.MouseEvent<HTMLDivElement>) => void;    onClickOutSide?: () => void;    locale?: Locale['DatePicker'];    dateFnsLocale?: Locale['dateFnsLocale'];    yearAndMonthOpts?: ScrollItemProps<any>;    dropdownMargin?: PopoverProps['margin']}export type DatePickerState = DatePickerFoundationState;export default class DatePicker extends BaseComponent<DatePickerProps, DatePickerState> {    static contextType = ConfigContext;    static propTypes = {        'aria-describedby': PropTypes.string,        'aria-errormessage': PropTypes.string,        'aria-invalid': PropTypes.bool,        'aria-labelledby': PropTypes.string,        'aria-required': PropTypes.bool,        borderless: PropTypes.bool,        type: PropTypes.oneOf(strings.TYPE_SET),        size: PropTypes.oneOf(strings.SIZE_SET),        clearIcon: PropTypes.node,        density: PropTypes.oneOf(strings.DENSITY_SET),        defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object, PropTypes.array]),        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object, PropTypes.array]),        defaultPickerValue: PropTypes.oneOfType([            PropTypes.string,            PropTypes.number,            PropTypes.object,            PropTypes.array,        ]),        disabledTime: PropTypes.func,        disabledTimePicker: PropTypes.bool,        hideDisabledOptions: PropTypes.bool,        format: PropTypes.string,        disabled: PropTypes.bool,        multiple: PropTypes.bool,        max: PropTypes.number, // only work when multiple is true        placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),        presets: PropTypes.array,        presetPosition: PropTypes.oneOf(strings.PRESET_POSITION_SET),        onChange: PropTypes.func,        onChangeWithDateFirst: PropTypes.bool,        weekStartsOn: PropTypes.number,        disabledDate: PropTypes.func,        timePickerOpts: PropTypes.object, // When dateTime, dateTimeRange, pass through the props to timePicker        showClear: PropTypes.bool, // Whether to show the clear button        onOpenChange: PropTypes.func,        open: PropTypes.bool,        defaultOpen: PropTypes.bool,        motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),        className: PropTypes.string,        prefixCls: PropTypes.string,        prefix: PropTypes.node,        insetLabel: PropTypes.node,        insetLabelId: PropTypes.string,        zIndex: PropTypes.number,        position: PropTypes.oneOf(popoverStrings.POSITION_SET),        getPopupContainer: PropTypes.func,        onCancel: PropTypes.func,        onConfirm: PropTypes.func,        needConfirm: PropTypes.bool,        inputStyle: PropTypes.object,        timeZone: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),        triggerRender: PropTypes.func,        stopPropagation: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),        autoAdjustOverflow: PropTypes.bool,        onBlur: PropTypes.func,        onFocus: PropTypes.func,        onClear: PropTypes.func,        style: PropTypes.object,        autoFocus: PropTypes.bool,        inputReadOnly: PropTypes.bool, // Text box can be entered        validateStatus: PropTypes.oneOf(strings.STATUS),        renderDate: PropTypes.func,        renderFullDate: PropTypes.func,        spacing: PropTypes.number,        startDateOffset: PropTypes.func,        endDateOffset: PropTypes.func,        autoSwitchDate: PropTypes.bool,        dropdownClassName: PropTypes.string,        dropdownStyle: PropTypes.object,        dropdownMargin: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),        topSlot: PropTypes.node,        bottomSlot: PropTypes.node,        dateFnsLocale: PropTypes.object, // isRequired, but no need to add isRequired key. ForwardStatics function pass static properties to index.jsx, so there is no need for user to pass the prop.        // Support synchronous switching of months        syncSwitchMonth: PropTypes.bool,        // Callback function for panel date switching        onPanelChange: PropTypes.func,        rangeSeparator: PropTypes.string,        preventScroll: PropTypes.bool,        yearAndMonthOpts: PropTypes.object,        onClickOutSide: PropTypes.func,    };    static defaultProps = {        onChangeWithDateFirst: true,        borderless: false,        autoAdjustOverflow: true,        stopPropagation: true,        motion: true,        prefixCls: cssClasses.PREFIX,        presetPosition: 'bottom',        // position: 'bottomLeft',        zIndex: popoverNumbers.DEFAULT_Z_INDEX,        type: 'date',        size: 'default',        density: 'default',        multiple: false,        defaultOpen: false,        disabledHours: noop,        disabledMinutes: noop,        disabledSeconds: noop,        hideDisabledOptions: false,        onBlur: noop,        onFocus: noop,        onClear: noop,        onCancel: noop,        onConfirm: noop,        onChange: noop,        onOpenChange: noop,        onPanelChange: noop,        onPresetClick: noop,        weekStartsOn: numbers.WEEK_START_ON,        disabledDate: stubFalse,        disabledTime: stubFalse,        inputReadOnly: false,        spacing: numbers.SPACING,        autoSwitchDate: true,        syncSwitchMonth: false,        rangeSeparator: strings.DEFAULT_SEPARATOR_RANGE,        insetInput: false,        onClickOutSide: noop,    };    triggerElRef: React.MutableRefObject<HTMLElement>;    panelRef: React.RefObject<HTMLDivElement>;    monthGrid: React.RefObject<MonthsGrid>;    inputRef: DateInputProps['inputRef'];    rangeInputStartRef: DateInputProps['rangeInputStartRef'];    rangeInputEndRef: DateInputProps['rangeInputEndRef'];    focusRecordsRef: React.RefObject<{ rangeStart: boolean; rangeEnd: boolean }>;    clickOutSideHandler: (e: MouseEvent) => void;    _mounted: boolean;    foundation: DatePickerFoundation;    context: ContextValue;    constructor(props: DatePickerProps) {        super(props);        this.state = {            panelShow: props.open || props.defaultOpen,            isRange: false,            inputValue: null, // Staging input values            value: [], // The currently selected date, each date is a Date object            cachedSelectedValue: null, // Save last selected date, maybe include null            prevTimeZone: null,            rangeInputFocus: undefined, // Optional'rangeStart ',' rangeEnd ', false            autofocus: props.autoFocus || (this.isRangeType(props.type, props.triggerRender) && (props.open || props.defaultOpen)),            insetInputValue: null,            triggerDisabled: undefined,        };        this.adapter.setCache('cachedSelectedValue', null);        this.triggerElRef = React.createRef();        this.panelRef = React.createRef();        this.monthGrid = React.createRef();        this.inputRef = React.createRef();        this.rangeInputStartRef = React.createRef();        this.rangeInputEndRef = React.createRef();        this.focusRecordsRef = React.createRef();        // @ts-ignore ignore readonly        this.focusRecordsRef.current = {            rangeStart: false,            rangeEnd: false        };        this.foundation = new DatePickerFoundation(this.adapter);    }    get adapter(): DatePickerAdapter {        return {            ...super.adapter,            togglePanel: (panelShow, cb) => {                this.setState({ panelShow }, cb);                if (!panelShow) {                    this.focusRecordsRef.current.rangeEnd = false;                    this.focusRecordsRef.current.rangeStart = false;                }            },            registerClickOutSide: () => {                if (this.clickOutSideHandler) {                    this.adapter.unregisterClickOutSide();                    this.clickOutSideHandler = null;                }                this.clickOutSideHandler = e => {                    const triggerEl = this.triggerElRef && this.triggerElRef.current;                    const panelEl = this.panelRef && this.panelRef.current;                    const isInTrigger = triggerEl && triggerEl.contains(e.target as Node);                    const isInPanel = panelEl && panelEl.contains(e.target as Node);                    const clickOutSide = !isInTrigger && !isInPanel && this._mounted;                    if (this.adapter.needConfirm()) {                        clickOutSide && this.props.onClickOutSide();                        return;                    } else {                        if (clickOutSide) {                            this.props.onClickOutSide();                            this.foundation.closePanel(e);                        }                    }                };                document.addEventListener('mousedown', this.clickOutSideHandler);            },            unregisterClickOutSide: () => {                document.removeEventListener('mousedown', this.clickOutSideHandler);            },            notifyBlur: (...args) => this.props.onBlur(...args),            notifyFocus: (...args) => this.props.onFocus(...args),            notifyClear: (...args) => this.props.onClear(...args),            notifyChange: (...args) => this.props.onChange(...args),            notifyCancel: (...args) => this.props.onCancel(...args),            notifyConfirm: (...args) => this.props.onConfirm(...args),            notifyOpenChange: (...args) => this.props.onOpenChange(...args),            notifyPresetsClick: (...args) => this.props.onPresetClick(...args),            updateValue: value => this.setState({ value }),            updatePrevTimezone: prevTimeZone => this.setState({ prevTimeZone }),            updateCachedSelectedValue: cachedSelectedValue => {                let _cachedSelectedValue = cachedSelectedValue;                if (cachedSelectedValue && !Array.isArray(cachedSelectedValue)) {                    _cachedSelectedValue = [...cachedSelectedValue as any];                }                this.setState({ cachedSelectedValue: _cachedSelectedValue });            },            updateInputValue: inputValue => {                this.setState({ inputValue });            },            updateInsetInputValue: (insetInputValue: InsetInputValue) => {                const { insetInput } = this.props;                if (insetInput && !isEqual(insetInputValue, this.state.insetInputValue)) {                    this.setState({ insetInputValue });                }            },            needConfirm: () =>                ['dateTime', 'dateTimeRange'].includes(this.props.type) && this.props.needConfirm === true,            typeIsYearOrMonth: () => ['month', 'year', 'monthRange'].includes(this.props.type),            setRangeInputFocus: rangeInputFocus => {                const { preventScroll } = this.props;                if (rangeInputFocus !== this.state.rangeInputFocus) {                    this.setState({ rangeInputFocus });                }                switch (rangeInputFocus) {                    case 'rangeStart':                        const inputStartNode = get(this, 'rangeInputStartRef.current');                        inputStartNode && inputStartNode.focus({ preventScroll });                        /**                         * 解决选择完startDate,切换到endDate后panel被立马关闭的问题。                         * 用户打开panel,选了startDate后,会执行setRangeInputFocus('rangeEnd'),focus到endDateInput,                         * 同时会走到datePicker/foundation.js中的handleSelectedChange方法,在这个方法里会根据focusRecordsRef来判断是否可以关闭panel。                         * 如果在setRangeInputFocus里同步修改了focusRecordsRef的状态为true,那在handleSelectedChange里会误判startDate和endDate都已经完成选择,                         * 导致endDate还没选就关闭了panel                         *                         * Fix the problem that the panel is closed immediately after switching to endDate after starting Date is selected.                         * The user opens the panel and after starting Date is selected, setRangeInputFocus ('rangeEnd') will be executed, focus to endDateInput,                         * At the same time, it will go to the handleSelectedChange method in datePicker/foundation.js, where it will be determined whether the panel can be closed according to focusRecordsRef.                         * If the status of focusRecordsRef is modified synchronously in setRangeInputFocus to true, then in handleSelectedChange it will be misjudged that both begDate and endDate have completed the selection,                         * resulting in the panel being closed before endDate is selected                         */                        setTimeout(() => {                            this.focusRecordsRef.current.rangeStart = true;                        }, 0);                        break;                    case 'rangeEnd':                        const inputEndNode = get(this, 'rangeInputEndRef.current');                        inputEndNode && inputEndNode.focus({ preventScroll });                        /**                         * 解决选择完startDate,切换到endDate后panel被立马关闭的问题。                         * 用户打开panel,选了startDate后,会执行setRangeInputFocus('rangeEnd'),focus到endDateInput,                         * 同时会走到datePicker/foundation.js中的handleSelectedChange方法,在这个方法里会根据focusRecordsRef来判断是否可以关闭panel。                         * 如果在setRangeInputFocus里同步修改了focusRecordsRef的状态为true,那在handleSelectedChange里会误判startDate和endDate都已经完成选择,                         * 导致endDate还没选就关闭了panel                         *                         * Fix the problem that the panel is closed immediately after switching to endDate after starting Date is selected.                         * The user opens the panel and after starting Date is selected, setRangeInputFocus ('rangeEnd') will be executed, focus to endDateInput,                         * At the same time, it will go to the handleSelectedChange method in datePicker/foundation.js, where it will be determined whether the panel can be closed according to focusRecordsRef.                         * If the status of focusRecordsRef is modified synchronously in setRangeInputFocus to true, then in handleSelectedChange it will be misjudged that both begDate and endDate have completed the selection,                         * resulting in the panel being closed before endDate is selected                         */                        setTimeout(() => {                            this.focusRecordsRef.current.rangeEnd = true;                        }, 0);                        break;                    default:                        return;                }            },            couldPanelClosed: () => this.focusRecordsRef.current.rangeStart && this.focusRecordsRef.current.rangeEnd,            isEventTarget: e => e && e.target === e.currentTarget,            setInsetInputFocus: () => {                const { preventScroll } = this.props;                const { rangeInputFocus } = this.state;                switch (rangeInputFocus) {                    case 'rangeEnd':                        if (document.activeElement !== this.rangeInputEndRef.current) {                            const inputEndNode = get(this, 'rangeInputEndRef.current');                            inputEndNode && inputEndNode.focus({ preventScroll });                        }                        break;                    case 'rangeStart':                    default:                        if (document.activeElement !== this.rangeInputStartRef.current) {                            const inputStartNode = get(this, 'rangeInputStartRef.current');                            inputStartNode && inputStartNode.focus({ preventScroll });                        }                        break;                }            },            setInputFocus: () => {                const { preventScroll } = this.props;                const inputNode = get(this, 'inputRef.current');                inputNode && inputNode.focus({ preventScroll });            },            setInputBlur: () => {                const inputNode = get(this, 'inputRef.current');                inputNode && inputNode.blur();            },            setRangeInputBlur: () => {                const { rangeInputFocus } = this.state;                if (rangeInputFocus === 'rangeStart') {                    const inputStartNode = get(this, 'rangeInputStartRef.current');                    inputStartNode && inputStartNode.blur();                } else if (rangeInputFocus === 'rangeEnd') {                    const inputEndNode = get(this, 'rangeInputEndRef.current');                    inputEndNode && inputEndNode.blur();                }                this.adapter.setRangeInputFocus(false);            },            setTriggerDisabled: (disabled: boolean) => {                this.setState({ triggerDisabled: disabled });            }        };    }    isRangeType(type: Type, triggerRender: DatePickerProps['triggerRender']) {        return /range/i.test(type) && !isFunction(triggerRender);    }    componentDidUpdate(prevProps: DatePickerProps) {        if (prevProps.value !== this.props.value) {            this.foundation.initFromProps({                ...this.props,            });        } else if (this.props.timeZone !== prevProps.timeZone) {            this.foundation.initFromProps({                value: this.state.value,                timeZone: this.props.timeZone,                prevTimeZone: prevProps.timeZone,            });        }        if (prevProps.open !== this.props.open) {            this.foundation.initPanelOpenStatus();            if (!this.props.open) {                this.foundation.clearRangeInputFocus();            }        }    }    componentDidMount() {        this._mounted = true;        super.componentDidMount();    }    componentWillUnmount() {        this._mounted = false;        super.componentWillUnmount();    }    open() {        this.foundation.open();    }    close() {        this.foundation.close();    }    /**     *     * When selecting a range, the default focus is on the start input box, passing in `rangeEnd` can focus on the end input box     *     * When `insetInput` is `true`, due to trigger disabled, the cursor will focus on the input box of the popup layer panel     *     * 范围选择时,默认聚焦在开始输入框,传入 `rangeEnd` 可以聚焦在结束输入框     *     * `insetInput` 打开时,由于 trigger 禁用,会把焦点放在弹出面板的输入框上     */    focus(focusType?: Exclude<RangeType, false>) {        this.foundation.focus(focusType);    }    blur() {        this.foundation.blur();    }    setTriggerRef = (node: HTMLDivElement) => (this.triggerElRef.current = node);    // Called when changes are selected by clicking on the selected date    handleSelectedChange: MonthsGridProps['onChange'] = (v, options) => this.foundation.handleSelectedChange(v, options);    // Called when the year and month change    handleYMSelectedChange: YearAndMonthProps['onSelect'] = item => this.foundation.handleYMSelectedChange(item);    disabledDisposeDate: MonthsGridProps['disabledDate'] = (date, ...rest) => this.foundation.disabledDisposeDate(date, ...rest);    disabledDisposeTime: MonthsGridProps['disabledTime'] = (date, ...rest) => this.foundation.disabledDisposeTime(date, ...rest);    renderMonthGrid(locale: Locale['DatePicker'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) {        const {            type,            multiple,            max,            weekStartsOn,            timePickerOpts,            defaultPickerValue,            format,            hideDisabledOptions,            disabledTimePicker,            renderDate,            renderFullDate,            startDateOffset,            endDateOffset,            autoSwitchDate,            density,            syncSwitchMonth,            onPanelChange,            timeZone,            triggerRender,            insetInput,            presetPosition,            yearAndMonthOpts        } = this.props;        const { cachedSelectedValue, rangeInputFocus } = this.state;        const defaultValue = cachedSelectedValue;        return (            <MonthsGrid                ref={this.monthGrid}                locale={locale}                localeCode={localeCode}                dateFnsLocale={dateFnsLocale}                weekStartsOn={weekStartsOn}                type={type}                multiple={multiple}                max={max}                format={format}                disabledDate={this.disabledDisposeDate}                hideDisabledOptions={hideDisabledOptions}                disabledTimePicker={disabledTimePicker}                disabledTime={this.disabledDisposeTime}                defaultValue={defaultValue}                defaultPickerValue={defaultPickerValue}                timePickerOpts={timePickerOpts}                isControlledComponent={!this.adapter.needConfirm() && this.isControlled('value')}                onChange={this.handleSelectedChange}                renderDate={renderDate}                renderFullDate={renderFullDate}                startDateOffset={startDateOffset}                endDateOffset={endDateOffset}                autoSwitchDate={autoSwitchDate}                density={density}                rangeInputFocus={rangeInputFocus}                setRangeInputFocus={this.handleSetRangeFocus}                isAnotherPanelHasOpened={this.isAnotherPanelHasOpened}                syncSwitchMonth={syncSwitchMonth}                onPanelChange={onPanelChange}                timeZone={timeZone}                focusRecordsRef={this.focusRecordsRef}                triggerRender={triggerRender}                insetInput={insetInput}                presetPosition={presetPosition}                renderQuickControls={this.renderQuickControls()}                renderDateInput={this.renderDateInput()}                yearAndMonthOpts={yearAndMonthOpts}            />        );    }    renderQuickControls() {        const { presets, type, presetPosition, insetInput } = this.props;        return (            <QuickControl                type={type}                presets={presets}                insetInput={insetInput}                presetPosition={presetPosition}                onPresetClick={(item, e) => this.foundation.handlePresetClick(item, e)}            />        );    }    renderDateInput() {        const { insetInput, dateFnsLocale, density, type, format, rangeSeparator, defaultPickerValue } = this.props;        const { insetInputValue, value } = this.state;        const props = {            dateFnsLocale,            format,            insetInputValue,            rangeSeparator,            type,            value: value as Date[],            handleInsetDateFocus: this.handleInsetDateFocus,            handleInsetTimeFocus: this.handleInsetTimeFocus,            onInsetInputChange: this.handleInsetInputChange,            rangeInputStartRef: this.rangeInputStartRef,            rangeInputEndRef: this.rangeInputEndRef,            density,            defaultPickerValue        };        return insetInput ? <DateInput {...props} insetInput={insetInput}/> : null;    }    handleOpenPanel = () => this.foundation.openPanel();    handleInputChange: DatePickerFoundation['handleInputChange'] = (...args) => this.foundation.handleInputChange(...args);    handleInsetInputChange = (options: InsetInputChangeProps) => this.foundation.handleInsetInputChange(options);    handleInputComplete: DatePickerFoundation['handleInputComplete'] = v => this.foundation.handleInputComplete(v);    handleInputBlur: DateInputProps['onBlur'] = e => this.foundation.handleInputBlur(get(e, 'nativeEvent.target.value'), e);    handleInputFocus: DatePickerFoundation['handleInputFocus'] = (...args) => this.foundation.handleInputFocus(...args);    handleInputClear: DatePickerFoundation['handleInputClear'] = e => this.foundation.handleInputClear(e);    handleTriggerWrapperClick: DatePickerFoundation['handleTriggerWrapperClick'] = e => this.foundation.handleTriggerWrapperClick(e);    handleSetRangeFocus: DatePickerFoundation['handleSetRangeFocus'] = rangeInputFocus => this.foundation.handleSetRangeFocus(rangeInputFocus);    handleRangeInputBlur = (value: any, e: any) => this.foundation.handleRangeInputBlur(value, e);    handleRangeInputClear: DatePickerFoundation['handleRangeInputClear'] = e => this.foundation.handleRangeInputClear(e);    handleRangeEndTabPress: DatePickerFoundation['handleRangeEndTabPress'] = e => this.foundation.handleRangeEndTabPress(e);    isAnotherPanelHasOpened = (currentRangeInput: RangeType) => {        if (currentRangeInput === 'rangeStart') {            return this.focusRecordsRef.current.rangeEnd;        } else {            return this.focusRecordsRef.current.rangeStart;        }    };    handleInsetDateFocus = (e: React.FocusEvent, rangeType: 'rangeStart' | 'rangeEnd') => {        const monthGridFoundation = get(this, 'monthGrid.current.foundation');        if (monthGridFoundation) {            monthGridFoundation.showDatePanel(strings.PANEL_TYPE_LEFT);            monthGridFoundation.showDatePanel(strings.PANEL_TYPE_RIGHT);        }        this.handleInputFocus(e, rangeType);    }    handleInsetTimeFocus = () => {        const monthGridFoundation = get(this, 'monthGrid.current.foundation');        if (monthGridFoundation) {            monthGridFoundation.showTimePicker(strings.PANEL_TYPE_LEFT);            monthGridFoundation.showTimePicker(strings.PANEL_TYPE_RIGHT);        }    }    handlePanelVisibleChange = (visible: boolean) => {        this.foundation.handlePanelVisibleChange(visible);    }    renderInner(extraProps?: Partial<DatePickerProps>) {        const {            clearIcon,            type,            format,            multiple,            disabled,            showClear,            insetLabel,            insetLabelId,            placeholder,            validateStatus,            inputStyle,            prefix,            locale,            dateFnsLocale,            triggerRender,            size,            inputReadOnly,            rangeSeparator,            insetInput,            defaultPickerValue,            borderless        } = this.props;        const { value, inputValue, rangeInputFocus, triggerDisabled } = this.state;        // This class is not needed when triggerRender is function        const isRangeType = this.isRangeType(type, triggerRender);        const inputDisabled = disabled || insetInput && triggerDisabled;        const inputCls = classnames(`${cssClasses.PREFIX}-input`, {            [`${cssClasses.PREFIX}-range-input`]: isRangeType,            [`${cssClasses.PREFIX}-range-input-${size}`]: isRangeType && size,            [`${cssClasses.PREFIX}-range-input-active`]: isRangeType && rangeInputFocus && !inputDisabled,            [`${cssClasses.PREFIX}-range-input-disabled`]: isRangeType && inputDisabled,            [`${cssClasses.PREFIX}-range-input-${validateStatus}`]: isRangeType && validateStatus,            [`${cssClasses.PREFIX}-borderless`]: borderless        });        const phText = placeholder || locale.placeholder[type]; // i18n        // These values should be passed to triggerRender, do not delete any key if it is not necessary        const props = {            ...extraProps,            placeholder: phText,            clearIcon,            disabled: inputDisabled,            inputValue,            value: value as Date[],            defaultPickerValue,            onChange: this.handleInputChange,            onEnterPress: this.handleInputComplete,            // TODO: remove in next major version            block: true,            inputStyle,            showClear,            insetLabel,            insetLabelId,            type,            format,            multiple,            validateStatus,            inputReadOnly: inputReadOnly || Boolean(insetInput),            // onClick: this.handleOpenPanel,            onBlur: this.handleInputBlur,            onFocus: this.handleInputFocus,            onClear: this.handleInputClear,            prefix,            size,            autofocus: this.state.autofocus,            dateFnsLocale,            rangeInputFocus,            rangeSeparator,            onRangeBlur: this.handleRangeInputBlur,            onRangeClear: this.handleRangeInputClear,            onRangeEndTabPress: this.handleRangeEndTabPress,            rangeInputStartRef: insetInput ? null : this.rangeInputStartRef,            rangeInputEndRef: insetInput ? null : this.rangeInputEndRef,            inputRef: this.inputRef,        };        return (            <div                // tooltip will mount a11y props to children                // eslint-disable-next-line jsx-a11y/role-has-required-aria-props                role="combobox"                aria-label={Array.isArray(value) && value.length ? "Change date" : "Choose date"}                aria-disabled={disabled}                onClick={this.handleTriggerWrapperClick}                className={inputCls}>                {typeof triggerRender === 'function' ? (                    <Trigger                        {...props}                        triggerRender={triggerRender}                        componentName="DatePicker"                        componentProps={{ ...this.props }}                    />                ) : (                    <DateInput {...props} />                )}            </div>        );    }    handleConfirm = (e: React.MouseEvent) => this.foundation.handleConfirm();    handleCancel = (e: React.MouseEvent) => this.foundation.handleCancel();    renderFooter = (locale: Locale['DatePicker'], localeCode: string) => {        if (this.adapter.needConfirm()) {            return (                <Footer                    {...this.props}                    locale={locale}                    localeCode={localeCode}                    onConfirmClick={this.handleConfirm}                    onCancelClick={this.handleCancel}                />            );        }        return null;    };    renderPanel = (locale: Locale['DatePicker'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) => {        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, presetPosition, type } = this.props;        const wrapCls = classnames(            cssClasses.PREFIX,            {                [cssClasses.PANEL_YAM]: this.adapter.typeIsYearOrMonth(),                [`${cssClasses.PREFIX}-compact`]: density === 'compact',            },            dropdownClassName        );        return (            <div ref={this.panelRef} className={wrapCls} style={dropdownStyle} x-type={type}>                {topSlot && (                    <div className={`${cssClasses.PREFIX}-topSlot`} x-semi-prop="topSlot">                        {topSlot}                    </div>                )}                {/* todo: monthRange does not support presetPosition temporarily */}                {presetPosition === "top" && type !== 'monthRange' && this.renderQuickControls()}                {this.adapter.typeIsYearOrMonth()                    ? this.renderYearMonthPanel(locale, localeCode)                    : this.renderMonthGrid(locale, localeCode, dateFnsLocale)}                {presetPosition === "bottom" && type !== 'monthRange' && this.renderQuickControls()}                {bottomSlot && (                    <div className={`${cssClasses.PREFIX}-bottomSlot`} x-semi-prop="bottomSlot">                        {bottomSlot}                    </div>                )}                {this.renderFooter(locale, localeCode)}            </div>        );    };    renderYearMonthPanel = (locale: Locale['DatePicker'], localeCode: string) => {        const { density, presetPosition, yearAndMonthOpts, type } = this.props;        const date = this.state.value[0];        const year = { left: 0, right: 0 };        const month = { left: 0, right: 0 };        if (isDate(date)) {            year.left = date.getFullYear();            month.left = date.getMonth() + 1;        }        if (type === 'monthRange') {            const dateRight = this.state.value[1];            if (isDate(dateRight)) {                year.right = dateRight.getFullYear();                month.right = dateRight.getMonth() + 1;            }        }        return (            <YearAndMonth                locale={locale}                localeCode={localeCode}                disabledDate={this.disabledDisposeDate}                noBackBtn                monthCycled                onSelect={this.handleYMSelectedChange}                currentYear={year}                currentMonth={month}                density={density}                presetPosition={presetPosition}                renderQuickControls={this.renderQuickControls()}                renderDateInput={this.renderDateInput()}                type={type}                yearAndMonthOpts={yearAndMonthOpts}            />        );    };    wrapPopover = (children: React.ReactNode) => {        const { panelShow } = this.state;        // rtl changes the default position        const { direction } = this.context;        const defaultPosition = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';        const {            motion,            zIndex,            position = defaultPosition,            getPopupContainer,            locale,            localeCode,            dateFnsLocale,            stopPropagation,            autoAdjustOverflow,            spacing,            dropdownMargin        } = this.props;        return (            <Popover                getPopupContainer={getPopupContainer}                // wrapWhenSpecial={false}                autoAdjustOverflow={autoAdjustOverflow}                zIndex={zIndex}                motion={motion}                margin={dropdownMargin}                content={this.renderPanel(locale, localeCode, dateFnsLocale)}                trigger="custom"                position={position}                visible={panelShow}                stopPropagation={stopPropagation}                spacing={spacing}                onVisibleChange={this.handlePanelVisibleChange}            >                {children}            </Popover>        );    };    render() {        const { style, className, prefixCls, type } = this.props;        const outerProps = {            style,            className: classnames(className, { [prefixCls]: true }),            ref: this.setTriggerRef,            'aria-invalid': this.props['aria-invalid'],            'aria-errormessage': this.props['aria-errormessage'],            'aria-labelledby': this.props['aria-labelledby'],            'aria-describedby': this.props['aria-describedby'],            'aria-required': this.props['aria-required'],        };        const innerPropKeys: string[] = [];        if (!type.toLowerCase().includes("range")) {            innerPropKeys.push("borderless");        }        const inner = this.renderInner(pick(this.props, innerPropKeys));        const wrappedInner = this.wrapPopover(inner);        return <div {...outerProps}>{wrappedInner}</div>;    }}
 |