diff --git a/components/date-picker/__tests__/index-spec.js b/components/date-picker/__tests__/index-spec.js index 3ee1801b05..9eceb4dd7e 100644 --- a/components/date-picker/__tests__/index-spec.js +++ b/components/date-picker/__tests__/index-spec.js @@ -1,14 +1,17 @@ import React, { useState } from 'react'; +import ReactTestUtils from 'react-dom/test-utils'; import Enzyme, { mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import assert from 'power-assert'; import moment from 'moment'; import DatePicker from '../index'; import { KEYCODE } from '../../util'; +import '../style'; Enzyme.configure({ adapter: new Adapter() }); const { RangePicker, MonthPicker, YearPicker, WeekPicker } = DatePicker; +const delay = duration => new Promise(r => setTimeout(r, duration)); const startValue = moment('2017-11-20', 'YYYY-MM-DD', true); const endValue = moment('2017-12-15', 'YYYY-MM-DD', true); const defaultTimeValue = moment('09:00:00', 'HH:mm:ss', true); @@ -1654,14 +1657,89 @@ describe('RangePicker', () => { }); describe('issues', () => { - it('should render replacing the Focus frame close #3998', () => { - wrapper = mount(); - wrapper.find('td[title="2024-03-01"] .next-calendar-date').simulate('click'); - wrapper.find('span.next-range-picker-panel-input-start-date').hasClass('next-focus'); - wrapper.find('td[title="2024-03-08"] .next-calendar-date').simulate('click'); - wrapper.find('span.next-range-picker-panel-input-end-date').hasClass('next-focus'); - wrapper.find('td[title="2024-03-08"] .next-calendar-date').simulate('click'); - wrapper.find('span.next-range-picker-panel-input-start-date').hasClass('next-focus'); + it('should render replacing the Focus frame close #3998', async function() { + this.timeout(99999999); + const div = document.createElement('div'); + document.body.appendChild(div); + const wrapper = mount(, {attachTo: div}); + + const clickPanelInput = async (index) => { + const paneInputs = div.querySelectorAll('.next-range-picker-panel-header input'); + assert(paneInputs.length === 2); + paneInputs[index].click(); + await delay(200); + } + const assertPanelInputValue = (index, value) => { + const paneInputs = div.querySelectorAll('.next-range-picker-panel-header input'); + assert(paneInputs.length === 2); + assert(paneInputs[index].value === value); + } + const assertPanelInputFocus = (index) => { + // FIXME 框架限制,focus 状态无法改变,技术升级后再实现 + // const paneInputs = div.querySelectorAll('.next-range-picker-panel-header input'); + // assert(paneInputs.length === 2); + // assert(document.activeElement && document.activeElement === paneInputs[index]); + } + const assertPanelInputHasFocusClass = (index) => { + // FIXME 框架限制,focus 状态无法改变,技术升级后再实现 + // const paneInputs = div.querySelectorAll('.next-range-picker-panel-header .next-input'); + // assert(paneInputs.length === 2); + // assert(paneInputs[index].classList.contains('next-focus')); + } + const clickDate = async (value) => { + // ReactTestUtils.Simulate.click(div.querySelector(`td[title="${value}"] .next-calendar-date`)); + div.querySelector(`td[title="${value}"] .next-calendar-date`).click(); + await delay(100); + } + + const triggerInputs = div.querySelectorAll('.next-range-picker-trigger .next-input'); + assert(triggerInputs.length === 2); + triggerInputs[0].click(); + + await delay(500); + assertPanelInputFocus(0); + assertPanelInputHasFocusClass(0); + await clickDate('2024-03-01'); + assertPanelInputValue(0, '2024-03-01'); + assertPanelInputHasFocusClass(1); + await clickDate('2024-03-08'); + assertPanelInputValue(1, '2024-03-08'); + assertPanelInputHasFocusClass(1); + + await clickPanelInput(0); + assertPanelInputFocus(0); + assertPanelInputHasFocusClass(0); + await clickDate('2024-03-01'); + assertPanelInputValue(0, '2024-03-01'); + assertPanelInputHasFocusClass(1); + clickDate('2024-03-01'); + assertPanelInputHasFocusClass(1); + assertPanelInputValue(1, '2024-03-01'); + + clickPanelInput(1); + assertPanelInputFocus(1); + assertPanelInputHasFocusClass(1); + clickDate('2024-03-05'); + assertPanelInputValue(1, '2024-03-05'); + assertPanelInputHasFocusClass(0); + clickDate('2024-03-05'); + assertPanelInputHasFocusClass(0); + assertPanelInputValue(0, '2024-03-05'); + + document.body.click(); + await delay(300); + triggerInputs[1].click(); + await delay(500); + assertPanelInputFocus(1); + assertPanelInputHasFocusClass(1); + await clickDate('2024-03-08'); + assertPanelInputValue(1, '2024-03-08'); + assertPanelInputHasFocusClass(0); + await clickDate('2024-03-08'); + assertPanelInputValue(0, '2024-03-08'); + assertPanelInputHasFocusClass(0); + + wrapper.unmount(); }); }); }); diff --git a/components/date-picker/range-picker.jsx b/components/date-picker/range-picker.jsx index 10ff1a4ef2..b2ec3198a1 100644 --- a/components/date-picker/range-picker.jsx +++ b/components/date-picker/range-picker.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, createRef } from 'react'; import PropTypes from 'prop-types'; import { polyfill } from 'react-lifecycles-compat'; import classnames from 'classnames'; @@ -243,6 +243,10 @@ class RangePicker extends Component { onVisibleChange: func.noop, }; + startDateInputRef = createRef(); + endDateInputRef = createRef(); + autoSwitchDateInput = false; + constructor(props, context) { super(props, context); const { format, timeFormat, dateTimeFormat } = getDateTimeFormat(props.format, props.showTime, props.type); @@ -289,27 +293,16 @@ class RangePicker extends Component { }; } - getFormValue = (value) => { - return value ? value.format(this.state.dateTimeFormat) : null; - } - onValueChange = (values, handler = 'onChange') => { - const { startValue, endValue, activeDateInput } = this.state let ret; if (!values.length || !this.state.inputAsString) { ret = values; } else { ret = [ - values[0] ? this.getFormValue(values[0]) : null, - values[1] ? this.getFormValue(values[1]) : null, + values[0] ? values[0].format(this.state.dateTimeFormat) : null, + values[1] ? values[1].format(this.state.dateTimeFormat) : null, ]; } - if (this.getFormValue(values[1]) === this.getFormValue(endValue) && activeDateInput === 'endValue') { - this.onFocusDateInput('startValue') - } - if (this.getFormValue(values[0]) === this.getFormValue(startValue) && activeDateInput === 'startValue') { - this.onFocusDateInput('endValue') - } this.props[handler](ret); }; @@ -331,7 +324,7 @@ class RangePicker extends Component { switch (active || prevActiveDateInput) { case 'startValue': { - if (!prevEndValue || value.valueOf() <= prevEndValue.valueOf()) { + if (!prevEndValue || this.autoSwitchDateInput) { newState.activeDateInput = 'endValue'; } @@ -368,7 +361,7 @@ class RangePicker extends Component { } case 'endValue': - if (!prevStartValue) { + if (!prevStartValue || this.autoSwitchDateInput) { newState.activeDateInput = 'startValue'; } @@ -408,6 +401,11 @@ class RangePicker extends Component { const newStartValue = 'startValue' in newState ? newState.startValue : prevStartValue; const newEndValue = 'endValue' in newState ? newState.endValue : prevEndValue; + // 每当 input 发生了自动切换,则关闭自动切换 + if (newState.activeDateInput !== prevActiveDateInput) { + this.autoSwitchDateInput = false; + } + // 受控状态选择不更新值 if ('value' in this.props) { delete newState.startValue; @@ -700,6 +698,28 @@ class RangePicker extends Component { return disabledTime; }; + enableAutoSwitchDateInput = () => { + this.autoSwitchDateInput = true; + } + + afterOpen = () => { + // autoFocus 逻辑手动处理 + switch(this.state.activeDateInput) { + case 'startValue': { + if (this.startDateInputRef.current) { + this.startDateInputRef.current.getInstance().focus(); + } + break; + } + case 'endValue': { + if (this.endDateInputRef.current) { + this.endDateInputRef.current.getInstance().focus(); + } + break; + } + } + } + renderPreview([startValue, endValue], others) { const { prefix, className, renderPreview } = this.props; const { dateTimeFormat } = this.state; @@ -837,6 +857,8 @@ class RangePicker extends Component { value={startDateInputValue} onFocus={() => this.onFocusDateInput('startValue')} className={startDateInputCls} + ref={this.startDateInputRef} + onClick={func.makeChain(this.enableAutoSwitchDateInput, sharedInputProps.onClick)} /> ); @@ -848,6 +870,8 @@ class RangePicker extends Component { value={endDateInputValue} onFocus={() => this.onFocusDateInput('endValue')} className={endDateInputCls} + ref={this.endDateInputRef} + onClick={func.makeChain(this.enableAutoSwitchDateInput, sharedInputProps.onClick)} /> ); @@ -1095,13 +1119,14 @@ class RangePicker extends Component { return (