Skip to content

Commit

Permalink
[ClickAwayListener] Add mouseEvent and touchEvent property (#10694)
Browse files Browse the repository at this point in the history
* Add functionality to ClickAwayListener

Allow passing of `props.mouseEvent` and `props.touchEvent`, so the
ClickAwayListener is not limited to `ontouchend` and `onmouseup`

* fix default props

* build docs

* small changes
  • Loading branch information
tgrowden authored and oliviertassinari committed Mar 17, 2018
1 parent be00525 commit 955eb1b
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 31 deletions.
11 changes: 8 additions & 3 deletions docs/src/modules/utils/generateMarkdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,22 +250,27 @@ function generateInheritance(reactAPI) {

const component = inheritedComponent[1];
let pathname;
let prefix = '';
let suffix = '';

switch (component) {
case 'Transition':
prefix = 'react-transition-group ';
suffix = ', from react-transition-group,';
pathname = 'https://reactcommunity.org/react-transition-group/#Transition';
break;

case 'EventListener':
suffix = ', from react-event-listener,';
pathname = 'https://github.com/oliviertassinari/react-event-listener';
break;

default:
pathname = `/api/${kebabCase(component)}`;
break;
}

return `## Inheritance
The properties of the ${prefix}[${component}](${pathname}) component are also available.
The properties of the [${component}](${pathname}) component${suffix} are also available.
`;
}
Expand Down
6 changes: 6 additions & 0 deletions pages/api/click-away-listener.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ Listen for click events that are triggered outside of the component children.
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name required">children *</span> | <span class="prop-type">node | | |
| <span class="prop-name">mouseEvent</span> | <span class="prop-type">enum:&nbsp;'onClick'&nbsp;&#124;<br>&nbsp;'onMouseDown'&nbsp;&#124;<br>&nbsp;'onMouseUp'&nbsp;&#124;<br>&nbsp;false<br> | <span class="prop-default">'onMouseUp'</span> | |
| <span class="prop-name required">onClickAway *</span> | <span class="prop-type">func | | |
| <span class="prop-name">touchEvent</span> | <span class="prop-type">enum:&nbsp;'onTouchStart'&nbsp;&#124;<br>&nbsp;'onTouchEnd'&nbsp;&#124;<br>&nbsp;false<br> | <span class="prop-default">'onTouchEnd'</span> | |

Any other properties supplied will be [spread to the root element](/guides/api#spread).

## Inheritance

The properties of the [EventListener](https://github.com/oliviertassinari/react-event-listener) component, from react-event-listener, are also available.

2 changes: 1 addition & 1 deletion pages/api/collapse.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ you need to use the following style sheet name: `MuiCollapse`.

## Inheritance

The properties of the react-transition-group [Transition](https://reactcommunity.org/react-transition-group/#Transition) component are also available.
The properties of the [Transition](https://reactcommunity.org/react-transition-group/#Transition) component, from react-transition-group, are also available.

## Demos

Expand Down
2 changes: 1 addition & 1 deletion pages/api/fade.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Any other properties supplied will be [spread to the root element](/guides/api#s

## Inheritance

The properties of the react-transition-group [Transition](https://reactcommunity.org/react-transition-group/#Transition) component are also available.
The properties of the [Transition](https://reactcommunity.org/react-transition-group/#Transition) component, from react-transition-group, are also available.

## Demos

Expand Down
2 changes: 1 addition & 1 deletion pages/api/grow.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Any other properties supplied will be [spread to the root element](/guides/api#s

## Inheritance

The properties of the react-transition-group [Transition](https://reactcommunity.org/react-transition-group/#Transition) component are also available.
The properties of the [Transition](https://reactcommunity.org/react-transition-group/#Transition) component, from react-transition-group, are also available.

## Demos

Expand Down
2 changes: 1 addition & 1 deletion pages/api/slide.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Any other properties supplied will be [spread to the root element](/guides/api#s

## Inheritance

The properties of the react-transition-group [Transition](https://reactcommunity.org/react-transition-group/#Transition) component are also available.
The properties of the [Transition](https://reactcommunity.org/react-transition-group/#Transition) component, from react-transition-group, are also available.

## Demos

Expand Down
2 changes: 1 addition & 1 deletion pages/api/zoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Any other properties supplied will be [spread to the root element](/guides/api#s

## Inheritance

The properties of the react-transition-group [Transition](https://reactcommunity.org/react-transition-group/#Transition) component are also available.
The properties of the [Transition](https://reactcommunity.org/react-transition-group/#Transition) component, from react-transition-group, are also available.

## Demos

Expand Down
2 changes: 2 additions & 0 deletions src/utils/ClickAwayListener.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as React from 'react';

export interface ClickAwayListenerProps {
children: React.ReactNode;
mouseEvent?: 'onClick' | 'onMouseDown' | 'onMouseUp' | false;
onClickAway: (event: React.ChangeEvent<{}>) => void;
touchEvent?: 'onTouchStart' | 'onTouchEnd' | false;
}

declare const ClickAwayListener: React.ComponentType<ClickAwayListenerProps>;
Expand Down
48 changes: 32 additions & 16 deletions src/utils/ClickAwayListener.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @inheritedComponent EventListener

import React from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
Expand Down Expand Up @@ -32,36 +34,50 @@ class ClickAwayListener extends React.Component {
}

// IE11 support, which trigger the handleClickAway even after the unbind
if (this.mounted) {
const el = findDOMNode(this);
const doc = ownerDocument(el);
if (!this.mounted) {
return;
}

if (
doc.documentElement &&
doc.documentElement.contains(event.target) &&
!isDescendant(el, event.target)
) {
this.props.onClickAway(event);
}
const el = findDOMNode(this);
const doc = ownerDocument(el);

if (
doc.documentElement &&
doc.documentElement.contains(event.target) &&
!isDescendant(el, event.target)
) {
this.props.onClickAway(event);
}
};

render() {
const { children, mouseEvent, touchEvent, onClickAway, ...other } = this.props;
const listenerProps = {};
if (mouseEvent !== false) {
listenerProps[mouseEvent] = this.handleClickAway;
}
if (touchEvent !== false) {
listenerProps[touchEvent] = this.handleClickAway;
}

return (
<EventListener
target="document"
onMouseup={this.handleClickAway}
onTouchend={this.handleClickAway}
>
{this.props.children}
<EventListener target="document" {...listenerProps} {...other}>
{children}
</EventListener>
);
}
}

ClickAwayListener.propTypes = {
children: PropTypes.node.isRequired,
mouseEvent: PropTypes.oneOf(['onClick', 'onMouseDown', 'onMouseUp', false]),
onClickAway: PropTypes.func.isRequired,
touchEvent: PropTypes.oneOf(['onTouchStart', 'onTouchEnd', false]),
};

ClickAwayListener.defaultProps = {
mouseEvent: 'onMouseUp',
touchEvent: 'onTouchEnd',
};

export default ClickAwayListener;
112 changes: 105 additions & 7 deletions src/utils/ClickAwayListener.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,30 @@ import ClickAwayListener from './ClickAwayListener';

describe('<ClickAwayListener />', () => {
let mount;
let wrapper;

before(() => {
mount = createMount();
});

afterEach(() => {
wrapper.unmount();
});

after(() => {
mount.cleanUp();
});

it('should render the children', () => {
const children = <span>Hello</span>;
const wrapper = mount(<ClickAwayListener onClickAway={() => {}}>{children}</ClickAwayListener>);
wrapper = mount(<ClickAwayListener onClickAway={() => {}}>{children}</ClickAwayListener>);
assert.strictEqual(wrapper.contains(children), true);
});

describe('prop: onClickAway', () => {
it('should be call when clicking away', () => {
const handleClickAway = spy();
const wrapper = mount(
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway}>
<span>Hello</span>
</ClickAwayListener>,
Expand All @@ -39,12 +44,11 @@ describe('<ClickAwayListener />', () => {

assert.strictEqual(handleClickAway.callCount, 1);
assert.deepEqual(handleClickAway.args[0], [event]);
wrapper.unmount();
});

it('should not be call when clicking inside', () => {
const handleClickAway = spy();
const wrapper = mount(
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway}>
<span>Hello</span>
</ClickAwayListener>,
Expand All @@ -57,12 +61,11 @@ describe('<ClickAwayListener />', () => {
}

assert.strictEqual(handleClickAway.callCount, 0);
wrapper.unmount();
});

it('should not be call when defaultPrevented', () => {
const handleClickAway = spy();
const wrapper = mount(
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway}>
<ClickAwayListener onClickAway={event => event.preventDefault()}>
<span>Hello</span>
Expand All @@ -73,7 +76,102 @@ describe('<ClickAwayListener />', () => {
const event = new window.Event('mouseup', { view: window, bubbles: true, cancelable: true });
window.document.body.dispatchEvent(event);
assert.strictEqual(handleClickAway.callCount, 0);
wrapper.unmount();
});
});

describe('prop: mouseEvent', () => {
it('should not call `props.onClickAway` when `props.mouseEvent` is `false`', () => {
const handleClickAway = spy();
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway} mouseEvent={false}>
<span>Hello</span>
</ClickAwayListener>,
);

const event = document.createEvent('MouseEvents');
event.initEvent('mouseup', true, true);
window.document.body.dispatchEvent(event);

assert.strictEqual(handleClickAway.callCount, 0);
});

it('should call `props.onClickAway` when the appropriate mouse event is triggered', () => {
const handleClickAway = spy();
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway} mouseEvent="onMouseDown">
<span>Hello</span>
</ClickAwayListener>,
);

const mouseUpEvent = document.createEvent('MouseEvents');
mouseUpEvent.initEvent('mouseup', true, true);
window.document.body.dispatchEvent(mouseUpEvent);

assert.strictEqual(handleClickAway.callCount, 0);

const mouseDownEvent = document.createEvent('MouseEvents');
mouseDownEvent.initEvent('mousedown', true, true);
window.document.body.dispatchEvent(mouseDownEvent);

assert.strictEqual(handleClickAway.callCount, 1);
assert.deepEqual(handleClickAway.args[0], [mouseDownEvent]);
});
});

describe('prop: touchEvent', () => {
it('should not call `props.onClickAway` when `props.touchEvent` is `false`', () => {
const handleClickAway = spy();
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway} touchEvent={false}>
<span>Hello</span>
</ClickAwayListener>,
);

const event = document.createEvent('Events');
event.initEvent('touchend', true, true);
window.document.body.dispatchEvent(event);

assert.strictEqual(handleClickAway.callCount, 0);
});

it('should call `props.onClickAway` when the appropriate touch event is triggered', () => {
const handleClickAway = spy();
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway} touchEvent="onTouchStart">
<span>Hello</span>
</ClickAwayListener>,
);

const touchEndEvent = document.createEvent('Events');
touchEndEvent.initEvent('touchend', true, true);
window.document.body.dispatchEvent(touchEndEvent);

assert.strictEqual(handleClickAway.callCount, 0);

const touchStartEvent = document.createEvent('Events');
touchStartEvent.initEvent('touchstart', true, true);
window.document.body.dispatchEvent(touchStartEvent);

assert.strictEqual(handleClickAway.callCount, 1);
assert.deepEqual(handleClickAway.args[0], [touchStartEvent]);
});
});

describe('IE11 issue', () => {
it('should not call the hook if the event is triggered after being unmounted', () => {
const handleClickAway = spy();
wrapper = mount(
<ClickAwayListener onClickAway={handleClickAway}>
<span>Hello</span>
</ClickAwayListener>,
);
wrapper.instance().mounted = false;

const event = document.createEvent('MouseEvents');
event.initEvent('mouseup', true, true);
window.document.body.dispatchEvent(event);

assert.strictEqual(handleClickAway.callCount, 0);
});
});
});

0 comments on commit 955eb1b

Please sign in to comment.