Skip to content

Commit

Permalink
Merge pull request gpbl#598 from bartpeeters/master
Browse files Browse the repository at this point in the history
Alternate solution for timeout blur
  • Loading branch information
gpbl authored Mar 4, 2018
2 parents ce4160a + cb789be commit 6ae71cc
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 50 deletions.
13 changes: 12 additions & 1 deletion docs/src/pages/api/DayPickerInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export default () => (
<a href="#inputProps">inputProps</a>,{' '}
<a href="#overlayComponent">overlayComponent</a>,{' '}
<a href="#parseDate">parseDate</a>, <a href="#placeholder">placeholder</a>,{' '}
<a href="#showOverlay">showOverlay</a>, <a href="#value">value</a>
<a href="#showOverlay">showOverlay</a>, <a href="#value">value</a>,{' '}
<a href="#keepFocus">keepFocus</a>
</p>
<h4>Event handlers</h4>
<p>
Expand Down Expand Up @@ -246,6 +247,16 @@ function MyDayPickerInput(props) {
<p>
The value of the <code>input</code> field.
</p>
<h3>
<Anchor id="keepFocus" />
keepFocus <code>boolean = true</code>
</h3>
<p>
Keep focus on the input when switching focus to something inside the
dayPickerOverlay. You should disable this if you have custom inputs in
something like an
<a href="#overlayComponent">overlayComponent</a>.
</p>

<hr />
<h2>Event handlers</h2>
Expand Down
81 changes: 44 additions & 37 deletions src/DayPickerInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default class DayPickerInput extends React.Component {
dayPickerProps: PropTypes.object,
hideOnDayClick: PropTypes.bool,
clickUnselectsDay: PropTypes.bool,
keepFocus: PropTypes.bool,
component: PropTypes.any,
overlayComponent: PropTypes.any,

Expand Down Expand Up @@ -93,6 +94,7 @@ export default class DayPickerInput extends React.Component {
showOverlay: false,
hideOnDayClick: true,
clickUnselectsDay: false,
keepFocus: true,
component: 'input',
inputProps: {},
overlayComponent: ({ children, classNames }) => (
Expand All @@ -114,7 +116,6 @@ export default class DayPickerInput extends React.Component {
this.state.showOverlay = props.showOverlay;

this.hideAfterDayClick = this.hideAfterDayClick.bind(this);
this.handleContainerMouseDown = this.handleContainerMouseDown.bind(this);
this.handleInputClick = this.handleInputClick.bind(this);
this.handleInputFocus = this.handleInputFocus.bind(this);
this.handleInputBlur = this.handleInputBlur.bind(this);
Expand All @@ -123,6 +124,8 @@ export default class DayPickerInput extends React.Component {
this.handleInputKeyUp = this.handleInputKeyUp.bind(this);
this.handleDayClick = this.handleDayClick.bind(this);
this.handleMonthChange = this.handleMonthChange.bind(this);
this.handleOverlayFocus = this.handleOverlayFocus.bind(this);
this.handleOverlayBlur = this.handleOverlayBlur.bind(this);
}

componentWillReceiveProps(nextProps) {
Expand Down Expand Up @@ -164,7 +167,6 @@ export default class DayPickerInput extends React.Component {
componentWillUnmount() {
clearTimeout(this.clickTimeout);
clearTimeout(this.hideTimeout);
clearTimeout(this.blurTimeout);
}

getStateFromProps(props) {
Expand Down Expand Up @@ -203,7 +205,7 @@ export default class DayPickerInput extends React.Component {

input = null;
daypicker = null;
clickedInside = false;
overlayNode = null;
clickTimeout = null;
hideTimeout = null;

Expand Down Expand Up @@ -265,15 +267,6 @@ export default class DayPickerInput extends React.Component {
this.hideTimeout = setTimeout(() => this.hideDayPicker(), HIDE_TIMEOUT);
}

handleContainerMouseDown() {
this.clickedInside = true;
// The input's onBlur method is called from a queue right after the onMouseDown event.
// setTimeout adds another callback in the queue, which is called after the onBlur event.
this.clickTimeout = setTimeout(() => {
this.clickedInside = false;
}, 0);
}

handleInputClick(e) {
this.showDayPicker();
if (this.props.inputProps.onClick) {
Expand All @@ -291,19 +284,30 @@ export default class DayPickerInput extends React.Component {
}

handleInputBlur(e) {
if (this.clickedInside) {
this.showDayPicker();
// Force input's focus if blur event was caused by clicking inside the overlay
this.blurTimeout = setTimeout(() => this.input.focus(), 0);
} else {
this.hideDayPicker();
}
this.setState({
showOverlay:
this.overlayNode && this.overlayNode.contains(e.relatedTarget),
});
if (this.props.inputProps.onBlur) {
e.persist();
this.props.inputProps.onBlur(e);
}
}

handleOverlayFocus(e) {
if (this.props.keepFocus === true) {
e.preventDefault();
this.input.focus();
}
}

handleOverlayBlur(e) {
this.setState({
showOverlay:
this.overlayNode && this.overlayNode.contains(e.relatedTarget),
});
}

handleInputChange(e) {
const {
dayPickerProps,
Expand Down Expand Up @@ -438,32 +442,35 @@ export default class DayPickerInput extends React.Component {
}
const Overlay = this.props.overlayComponent;
return (
<Overlay
classNames={classNames}
month={this.state.month}
selectedDay={selectedDay}
input={this.input}
<span
onFocus={this.handleOverlayFocus}
ref={el => (this.overlayNode = el)}
onBlur={this.handleOverlayBlur}
>
<DayPicker
ref={el => (this.daypicker = el)}
onTodayButtonClick={onTodayButtonClick}
{...dayPickerProps}
<Overlay
classNames={classNames}
month={this.state.month}
selectedDays={selectedDay}
onDayClick={this.handleDayClick}
onMonthChange={this.handleMonthChange}
/>
</Overlay>
selectedDay={selectedDay}
input={this.input}
>
<DayPicker
ref={el => (this.daypicker = el)}
onTodayButtonClick={onTodayButtonClick}
{...dayPickerProps}
month={this.state.month}
selectedDays={selectedDay}
onDayClick={this.handleDayClick}
onMonthChange={this.handleMonthChange}
/>
</Overlay>
</span>
);
}

render() {
const Input = this.props.component;
return (
<div
className={this.props.classNames.container}
onMouseDown={this.handleContainerMouseDown}
>
<div className={this.props.classNames.container}>
<Input
ref={el => (this.input = el)}
placeholder={this.props.placeholder}
Expand Down
37 changes: 26 additions & 11 deletions test/daypickerinput/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ describe('DayPickerInput', () => {
});
});

describe('overlayfocus', () => {
afterEach(() => {
document.activeElement.blur();
});
it('should focus the input if keepFocus is true', () => {
const wrapper = mount(<DayPickerInput showOverlay keepFocus />);
wrapper.find('.DayPickerInput-Overlay').simulate('focus');
const instance = wrapper.instance();
expect(document.activeElement).toEqual(instance.input);
});
it('should not focus the input if keepFocus is false', () => {
const wrapper = mount(<DayPickerInput showOverlay keepFocus={false} />);
wrapper.find('.DayPickerInput-Overlay').simulate('focus');
const instance = wrapper.instance();
expect(document.activeElement).not.toEqual(instance.input);
});
});

describe('blur', () => {
it('should hide the overlay when the input is blurred', () => {
const wrapper = mount(<DayPickerInput value="12/15/2017" />);
Expand All @@ -52,17 +70,14 @@ describe('DayPickerInput', () => {
wrapper.find('input').simulate('blur');
expect(onBlur).toHaveBeenCalledTimes(1);
});
it('should focus the input if blur after clicking the overlay', done => {
const wrapper = mount(<DayPickerInput />);
wrapper.find('.DayPickerInput').simulate('mousedown');
const instance = wrapper.instance();
expect(instance.clickedInside).toBe(true);
expect(instance.clickTimeout).not.toBeNull();
wrapper.find('input').simulate('blur');
setTimeout(() => {
expect(document.activeElement).toEqual(instance.input);
done();
}, 1);
});

describe('overlayblur', () => {
it('should hide the overlay', () => {
const wrapper = mount(<DayPickerInput showOverlay keepFocus />);
wrapper.find('.DayPickerInput-Overlay').simulate('focus');
wrapper.find('.DayPickerInput-Overlay').simulate('blur');
expect(wrapper.find('.DayPicker')).toHaveLength(0);
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/daypickerinput/rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe('DayPickerInput', () => {
mount(<DayPickerInput />, { attachTo: container });
const spy = jest.spyOn(window, 'clearTimeout');
ReactDOM.unmountComponentAtNode(container);
expect(spy).toHaveBeenCalledTimes(3);
expect(spy).toHaveBeenCalledTimes(2);
spy.mockRestore();
});
it('should set today when clicking on today button', () => {
Expand Down

0 comments on commit 6ae71cc

Please sign in to comment.