Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix EuiOutsideClickDetector to work when its nested inside itself #1039

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
**Bug fixes**

- Fixed `EuiContextMenuPanel` calling `ref` after being unmounted ([#1038](https://github.com/elastic/eui/pull/1038))
- `EuiOutsideClickDetector` supports nested detectors in the DOM tree ([#1039](https://github.com/elastic/eui/pull/1039))

## [`3.1.0`](https://github.com/elastic/eui/tree/v3.1.0)

Expand Down
12 changes: 9 additions & 3 deletions src/components/outside_click_detector/outside_click_detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class EuiOutsideClickDetector extends Component {
// virtual DOM and executes EuiClickDetector's onClick handler,
// stamping the id even though the event originates outside
// this component's reified DOM tree.
this.id = htmlIdGenerator();
this.id = htmlIdGenerator()();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious: What does the double parens do/mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's calling the function that htmlIdGenerator returns; The initial use of the function was incorrect here but worked for the detector's purposes for reasons. Calling htmlIdGenerator returns a function which can then be called with our without a prefix, and that returns a string value.

}

onClickOutside = event => {
Expand All @@ -45,7 +45,7 @@ export class EuiOutsideClickDetector extends Component {
return;
}

if (event.euiGeneratedBy === this.id) {
if (event.euiGeneratedBy && event.euiGeneratedBy.includes(this.id)) {
return;
}

Expand All @@ -61,7 +61,13 @@ export class EuiOutsideClickDetector extends Component {
}

onChildClick = event => {
event.nativeEvent.euiGeneratedBy = this.id;
// to support nested click detectors, build an array
// of detector ids that have been encountered
if (event.nativeEvent.hasOwnProperty('euiGeneratedBy')) {
event.nativeEvent.euiGeneratedBy.push(this.id);
} else {
event.nativeEvent.euiGeneratedBy = [this.id];
}
if (this.props.onClick) this.props.onClick(event);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render } from 'enzyme';
import { render, mount } from 'enzyme';

import { EuiOutsideClickDetector } from './outside_click_detector';

Expand All @@ -14,4 +14,45 @@ describe('EuiOutsideClickDetector', () => {
expect(component)
.toMatchSnapshot();
});

describe('behavior', () => {
test('nested detectors', () => {
const unrelatedDetector = jest.fn();
const parentDetector = jest.fn();
const childDetector = jest.fn();

// enzyme doesn't mount the components into the global jsdom `document`
// but that's where the click detector listener is,
// pass the top-level mounted component's click event on to document
const triggerDocumentClick = e => {
const event = new Event('click');
event.euiGeneratedBy = e.nativeEvent.euiGeneratedBy;
document.dispatchEvent(event);
};

const component = mount(
<div onClick={triggerDocumentClick}>
<div>
<EuiOutsideClickDetector onOutsideClick={parentDetector}>
<div>
<EuiOutsideClickDetector onOutsideClick={childDetector}>
<div data-test-subj="target"/>
</EuiOutsideClickDetector>
</div>
</EuiOutsideClickDetector>
</div>

<EuiOutsideClickDetector onOutsideClick={unrelatedDetector}>
<div/>
</EuiOutsideClickDetector>
</div>
);

component.find('[data-test-subj="target"]').simulate('click');

expect(unrelatedDetector).toHaveBeenCalledTimes(1);
expect(parentDetector).toHaveBeenCalledTimes(0);
expect(childDetector).toHaveBeenCalledTimes(0);
});
});
});