Repository: gpbl/react-day-picker (ts)

Issue:
By pressing tab in DayPickerInput, the body element is focused
Hey, thanks for a great component - nice with a lightweight and customizable date picker for once!

Using the `DayPickerInput` breaks the tab flow. If the `DayPicker` is open (after input gains focus), and you hit tab to move to the next input field the focus is lost. `document.activeElement` is changed to `<body>`. I haven't dug into exactly what's going on, but my guess is that the browser tries to set focus to an element inside the `DayPicker`, which is then lost after it closes.

Because the `DayPicker` requires focus to catch keyboard events, the keyboard navigation is not working. I don't think you should move the `focus` to the `DayPicker`, but it needs to be able to catch the keyboard events from the input field, and preventDefault the ones it uses.


Pull request description:
Intercept TAB key to fix body being focused
Close #594

Patch:
diff --git a/src/DayPickerInput.js b/src/DayPickerInput.js
index 6888f8e5f..03345b2dd 100644
--- a/src/DayPickerInput.js
+++ b/src/DayPickerInput.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 
 import DayPicker from './DayPicker';
 import { getModifiersForDay } from './ModifiersUtils';
-import { ESC } from './keys';
+import { ESC, TAB } from './keys';
 
 // When clicking on a day cell, overlay will be hidden after this timeout
 export const HIDE_TIMEOUT = 100;
@@ -119,6 +119,7 @@ export default class DayPickerInput extends React.Component {
     this.handleInputFocus = this.handleInputFocus.bind(this);
     this.handleInputBlur = this.handleInputBlur.bind(this);
     this.handleInputChange = this.handleInputChange.bind(this);
+    this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
     this.handleInputKeyUp = this.handleInputKeyUp.bind(this);
     this.handleDayClick = this.handleDayClick.bind(this);
   }
@@ -334,9 +335,21 @@ export default class DayPickerInput extends React.Component {
     this.updateState(day, value);
   }
 
+  handleInputKeyDown(e) {
+    if (e.keyCode === TAB) {
+      this.setState({ showOverlay: false });
+    }
+    if (this.props.inputProps.onKeyDown) {
+      e.persist();
+      this.props.inputProps.onKeyDown(e);
+    }
+  }
+
   handleInputKeyUp(e) {
     // Hide the overlay if the ESC key is pressed
-    this.setState({ showOverlay: e.keyCode !== ESC });
+    if (e.keyCode === ESC) {
+      this.setState({ showOverlay: false });
+    }
     if (this.props.inputProps.onKeyUp) {
       e.persist();
       this.props.inputProps.onKeyUp(e);
@@ -451,6 +464,7 @@ export default class DayPickerInput extends React.Component {
           onChange={this.handleInputChange}
           onFocus={this.handleInputFocus}
           onBlur={this.handleInputBlur}
+          onKeyDown={this.handleInputKeyDown}
           onKeyUp={this.handleInputKeyUp}
           onClick={this.handleInputClick}
         />
diff --git a/src/keys.js b/src/keys.js
index 232f8e887..c6dafcabb 100644
--- a/src/keys.js
+++ b/src/keys.js
@@ -5,3 +5,4 @@ export const DOWN = 40;
 export const ENTER = 13;
 export const SPACE = 32;
 export const ESC = 27;
+export const TAB = 9;


Test patch:
diff --git a/test/daypickerinput/events.js b/test/daypickerinput/events.js
index 8103a29ea..1fffec210 100644
--- a/test/daypickerinput/events.js
+++ b/test/daypickerinput/events.js
@@ -139,6 +139,21 @@ describe('DayPickerInput', () => {
         });
       });
     });
+    describe('keydown', () => {
+      it('should hide the overlay on TAB', () => {
+        const wrapper = mount(<DayPickerInput />);
+        wrapper.instance().showDayPicker();
+        wrapper.update();
+        wrapper.find('input').simulate('keydown', { keyCode: keys.TAB });
+        expect(wrapper.state('showOverlay')).toBe(false);
+      });
+      it('should call `onKeyDown` event handler', () => {
+        const onKeyDown = jest.fn();
+        const wrapper = mount(<DayPickerInput inputProps={{ onKeyDown }} />);
+        wrapper.find('input').simulate('keydown');
+        expect(onKeyDown).toHaveBeenCalledTimes(1);
+      });
+    });
     describe('keyup', () => {
       it('should hide the overlay on ESC', () => {
         const wrapper = mount(<DayPickerInput />);