From 83646b6ab91d60c1c995e8a84dfdd33c1c53a864 Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Tue, 3 Oct 2017 01:32:13 +0530 Subject: [PATCH] Enhance internal state control by providing onOuterClick prop This PR is not ready to be merged yet. When merged, this will fix https://github.com/paypal/downshift/issues/206 This PR does the following (Will appreciate early feedback and comments):- 1. Add onOuterClick description to README. I can remove the link to a specific issue from there 2. Add onOuterClick to propTypes with type PropTypes.func 3. Call this.props.onOuterClick as a callback to reset function when it is called under onMouseUp event 4. Add a test case for newly added functionality. This is still incomplete. I started with the test case expecting it to fail in its current state where I open the menu and simulate a outside click with click on document.body. With the current state of the onOuterClick callback this should have resulted in isOpen to be false there by failing the test but it is not the case. Exploring. Also, to simulate the click on document.body. I borrowed the function `mouseDownAndUp` from `downshift.lifecycle.js`. I can either move this test over there or expose that function in a better way than duplicating. Let me know your feedback on the approach? Also, please point out if you believe that I understood the problem incorrectly. --- .all-contributorsrc | 5 ++++- README.md | 21 ++++++++++++++++++++- other/ssr/__tests__/index.js | 2 +- src/__tests__/downshift.props.js | 21 +++++++++++++++++++++ src/downshift.js | 6 +++++- tsconfig.json | 5 +---- 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2fed6ebc7..c2ca369eb 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -109,7 +109,10 @@ "avatar_url": "https://avatars3.githubusercontent.com/u/746482?v=4", "profile": "https://divyendusingh.com", "contributions": [ - "example" + "example", + "code", + "doc", + "test" ] }, { diff --git a/README.md b/README.md index 7cbe8cf2f..d7ba1fc64 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,25 @@ server rendering items (which each have an `id` prop generated based on the You should not normally need to set this prop. It's only useful if you're rendering into a different `window` context from where your JavaScript is running, for example an iframe. + +### onOuterClick + +> `function` | optional + +A helper callback to help control internal state of downshift like `isOpen` as mentioned in +[this issue](https://github.com/paypal/downshift/issues/206). The same behavior can be achieved +using `onStateChange`, but this prop is provided as a helper because it's a fairly common +use-case if you're controlling the `isOpen` state: + +```jsx + this.setState({menuIsOpen: false})} +> + {/* your callback */} + +``` + ## Control Props downshift manages its own state internally and calls your `onChange` and @@ -600,7 +619,7 @@ Thanks goes to these people ([emoji key][emojis]): | [
Kent C. Dodds](https://kentcdodds.com)
[💻](https://github.com/paypal/downshift/commits?author=kentcdodds "Code") [📖](https://github.com/paypal/downshift/commits?author=kentcdodds "Documentation") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/paypal/downshift/commits?author=kentcdodds "Tests") | [
Ryan Florence](http://twitter.com/ryanflorence)
[🤔](#ideas-ryanflorence "Ideas, Planning, & Feedback") | [
Jared Forsyth](http://jaredforsyth.com)
[🤔](#ideas-jaredly "Ideas, Planning, & Feedback") [📖](https://github.com/paypal/downshift/commits?author=jaredly "Documentation") | [
Jack Moore](https://github.com/jtmthf)
[💡](#example-jtmthf "Examples") | [
Travis Arnold](http://travisrayarnold.com)
[💻](https://github.com/paypal/downshift/commits?author=souporserious "Code") [📖](https://github.com/paypal/downshift/commits?author=souporserious "Documentation") | [
Marcy Sutton](http://marcysutton.com)
[🐛](https://github.com/paypal/downshift/issues?q=author%3Amarcysutton "Bug reports") [🤔](#ideas-marcysutton "Ideas, Planning, & Feedback") | [
Jeremy Gayed](http://www.jeremygayed.com)
[💡](#example-tizmagik "Examples") | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| [
Haroen Viaene](https://haroen.me)
[💡](#example-Haroenv "Examples") | [
monssef](https://github.com/rezof)
[💡](#example-rezof "Examples") | [
Federico Zivolo](https://fezvrasta.github.io)
[📖](https://github.com/paypal/downshift/commits?author=FezVrasta "Documentation") | [
Divyendu Singh](https://divyendusingh.com)
[💡](#example-divyenduz "Examples") | [
Muhammad Salman](https://github.com/salmanmanekia)
[💻](https://github.com/paypal/downshift/commits?author=salmanmanekia "Code") | [
João Alberto](https://twitter.com/psicotropidev)
[💻](https://github.com/paypal/downshift/commits?author=psicotropicos "Code") | [
Bernard Lin](https://github.com/bernard-lin)
[💻](https://github.com/paypal/downshift/commits?author=bernard-lin "Code") [📖](https://github.com/paypal/downshift/commits?author=bernard-lin "Documentation") | +| [
Haroen Viaene](https://haroen.me)
[💡](#example-Haroenv "Examples") | [
monssef](https://github.com/rezof)
[💡](#example-rezof "Examples") | [
Federico Zivolo](https://fezvrasta.github.io)
[📖](https://github.com/paypal/downshift/commits?author=FezVrasta "Documentation") | [
Divyendu Singh](https://divyendusingh.com)
[💡](#example-divyenduz "Examples") [💻](https://github.com/paypal/downshift/commits?author=divyenduz "Code") [📖](https://github.com/paypal/downshift/commits?author=divyenduz "Documentation") [⚠️](https://github.com/paypal/downshift/commits?author=divyenduz "Tests") | [
Muhammad Salman](https://github.com/salmanmanekia)
[💻](https://github.com/paypal/downshift/commits?author=salmanmanekia "Code") | [
João Alberto](https://twitter.com/psicotropidev)
[💻](https://github.com/paypal/downshift/commits?author=psicotropicos "Code") | [
Bernard Lin](https://github.com/bernard-lin)
[💻](https://github.com/paypal/downshift/commits?author=bernard-lin "Code") [📖](https://github.com/paypal/downshift/commits?author=bernard-lin "Documentation") | | [
Geoff Davis](https://geoffdavis.info)
[💡](#example-geoffdavis92 "Examples") | [
Anup](https://github.com/reznord)
[📖](https://github.com/paypal/downshift/commits?author=reznord "Documentation") | [
Ferdinand Salis](http://ferdinandsalis.com)
[🐛](https://github.com/paypal/downshift/issues?q=author%3Aferdinandsalis "Bug reports") [💻](https://github.com/paypal/downshift/commits?author=ferdinandsalis "Code") | [
Kye Hohenberger](https://github.com/tkh44)
[🐛](https://github.com/paypal/downshift/issues?q=author%3Atkh44 "Bug reports") | [
Julien Goux](https://github.com/jgoux)
[🐛](https://github.com/paypal/downshift/issues?q=author%3Ajgoux "Bug reports") [💻](https://github.com/paypal/downshift/commits?author=jgoux "Code") [⚠️](https://github.com/paypal/downshift/commits?author=jgoux "Tests") | [
Joachim Seminck](https://github.com/jseminck)
[💻](https://github.com/paypal/downshift/commits?author=jseminck "Code") | [
Jesse Harlin](http://jesseharlin.net/)
[🐛](https://github.com/paypal/downshift/issues?q=author%3Athe-simian "Bug reports") [💡](#example-the-simian "Examples") | | [
Matt Parrish](https://github.com/pbomb)
[🔧](#tool-pbomb "Tools") | [
thom](http://thom.kr)
[💻](https://github.com/paypal/downshift/commits?author=thomhos "Code") | [
Vu Tran](http://twitter.com/tranvu)
[💻](https://github.com/paypal/downshift/commits?author=vutran "Code") | [
Codie Mullins](https://github.com/codiemullins)
[💻](https://github.com/paypal/downshift/commits?author=codiemullins "Code") [💡](#example-codiemullins "Examples") | [
Mohammad Rajabifard](https://morajabi.me)
[📖](https://github.com/paypal/downshift/commits?author=morajabi "Documentation") [🤔](#ideas-morajabi "Ideas, Planning, & Feedback") | [
Frank Tan](https://github.com/tansongyang)
[💻](https://github.com/paypal/downshift/commits?author=tansongyang "Code") | [
Kier Borromeo](https://kierb.com)
[💡](#example-srph "Examples") | | [
Paul Veevers](https://github.com/paul-veevers)
[💻](https://github.com/paypal/downshift/commits?author=paul-veevers "Code") | diff --git a/other/ssr/__tests__/index.js b/other/ssr/__tests__/index.js index b345f6026..63ee2ac1b 100644 --- a/other/ssr/__tests__/index.js +++ b/other/ssr/__tests__/index.js @@ -12,7 +12,7 @@ test('does not throw an error when server rendering', () => { )} - + , ) }).not.toThrow() }) diff --git a/src/__tests__/downshift.props.js b/src/__tests__/downshift.props.js index 62798a11d..e240a868f 100644 --- a/src/__tests__/downshift.props.js +++ b/src/__tests__/downshift.props.js @@ -74,6 +74,27 @@ test('uses given environment', () => { expect(environment.document.getElementById).toHaveBeenCalledTimes(1) }) +test('can override onOuterClick callback to maintain isOpen state', () => { + const children = () =>
+ const onOuterClick = jest.fn() + const {openMenu} = setup({children, onOuterClick}) + openMenu() + mouseDownAndUp(document.body) + expect(onOuterClick).toHaveBeenCalledTimes(1) + expect(onOuterClick).toHaveBeenCalledWith( + expect.objectContaining({ + // just verify that it's the controller object + isOpen: false, + getItemProps: expect.any(Function), + }), + ) +}) + +function mouseDownAndUp(node) { + node.dispatchEvent(new window.MouseEvent('mousedown', {bubbles: true})) + node.dispatchEvent(new window.MouseEvent('mouseup', {bubbles: true})) +} + function setup({children = () =>
, ...props} = {}) { let renderArg const childSpy = jest.fn(controllerArg => { diff --git a/src/downshift.js b/src/downshift.js index f54ac97a2..1fb3162a4 100644 --- a/src/downshift.js +++ b/src/downshift.js @@ -33,6 +33,7 @@ class Downshift extends Component { onStateChange: PropTypes.func, onUserAction: PropTypes.func, onClick: PropTypes.func, + onOuterClick: PropTypes.func, itemCount: PropTypes.number, id: PropTypes.string, environment: PropTypes.shape({ @@ -63,6 +64,7 @@ class Downshift extends Component { onStateChange: () => {}, onUserAction: () => {}, onChange: () => {}, + onOuterClick: () => {}, environment: typeof window === 'undefined' /* istanbul ignore next (ssr) */ ? {} @@ -671,7 +673,9 @@ class Downshift extends Component { !this._rootNode.contains(event.target)) && this.getState().isOpen ) { - this.reset({type: Downshift.stateChangeTypes.mouseUp}) + this.reset({type: Downshift.stateChangeTypes.mouseUp}, () => + this.props.onOuterClick(this.getStateAndHelpers()), + ) } } this.props.environment.addEventListener('mousedown', onMouseDown) diff --git a/tsconfig.json b/tsconfig.json index a354aad33..98cf65653 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,5 @@ "jsx": "react", "noUnusedLocals": true }, - "include": [ - "test/**/*.tsx" - ] + "include": ["test/**/*.tsx"] } -