Skip to content

Commit

Permalink
feat(DatePicker): improve focus logic
Browse files Browse the repository at this point in the history
  • Loading branch information
YSMJ1994 committed Mar 6, 2024
1 parent 94cb9c3 commit 264293b
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 25 deletions.
94 changes: 86 additions & 8 deletions components/date-picker/__tests__/index-spec.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -1654,14 +1657,89 @@ describe('RangePicker', () => {
});

describe('issues', () => {
it('should render replacing the Focus frame close #3998', () => {
wrapper = mount(<RangePicker visible={true}/>);
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(<RangePicker followTrigger defaultValue={['2024-03-02', '2024-03-03']}/>, {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();
});
});
});
59 changes: 42 additions & 17 deletions components/date-picker/range-picker.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
};

Expand All @@ -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';
}

Expand Down Expand Up @@ -368,7 +361,7 @@ class RangePicker extends Component {
}

case 'endValue':
if (!prevStartValue) {
if (!prevStartValue || this.autoSwitchDateInput) {
newState.activeDateInput = 'startValue';
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)}
/>
);

Expand All @@ -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)}
/>
);

Expand Down Expand Up @@ -1095,13 +1119,14 @@ class RangePicker extends Component {
return (
<div {...obj.pickOthers(RangePicker.propTypes, others)} className={classNames}>
<PopupComponent
autoFocus
align={popupAlign}
{...popupProps}
followTrigger={followTrigger}
disabled={disabled}
visible={state.visible}
onVisibleChange={this.onVisibleChange}
beforeOpen={func.makeChain(this.enableAutoSwitchDateInput, popupProps && popupProps.beforeOpen)}
afterOpen={func.makeChain(this.afterOpen, popupProps && popupProps.afterOpen)}
triggerType={popupTriggerType}
container={popupContainer}
style={popupStyle}
Expand Down

0 comments on commit 264293b

Please sign in to comment.