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

Refactor EuiPopover to use new positioning service #948

Merged
merged 9 commits into from
Jun 29, 2018
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
- Add `inspect` type option to icon typedef file. ([#952](https://github.com/elastic/eui/pull/952))
- Simplified form control styles. ([#954](https://github.com/elastic/eui/pull/954))

**Bug fixes**

- `EuiPopover` now positions popover content over all other elements, instead of sometimes clipping ([#948](https://github.com/elastic/eui/pull/948))
- `EuiOnClickOutside` works with child components rendered via React portals ([#948](https://github.com/elastic/eui/pull/948))

**Deprecations**

- Replaced the following SASS variables have been replaced `$euiFormControlHeight--compressed`, `$euiFormControlPadding--compressed`, `euiFormBorderColor--disabled`. ([#954](https://github.com/elastic/eui/pull/954))
Expand Down
8 changes: 4 additions & 4 deletions src-docs/src/views/popover/popover_anchor_position.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export default class extends Component {
anchorPosition="leftUp"
>
<EuiText>
<p>
<p style={{ width: 150 }}>
Be careful with content within left or right aligned popovers. There needs to be
enough content to make make enough height for the arrow positioning.
</p>
Expand Down Expand Up @@ -337,7 +337,7 @@ export default class extends Component {
anchorPosition="leftDown"
>
<EuiText>
<p>
<p style={{ width: 150 }}>
Be careful with content within left or right aligned popovers. There needs to be
enough content to make make enough height for the arrow positioning.
</p>
Expand All @@ -363,7 +363,7 @@ export default class extends Component {
anchorPosition="rightUp"
>
<EuiText>
<p>
<p style={{ width: 150 }}>
Be careful with content within left or right aligned popovers. There needs to be
enough content to make make enough height for the arrow positioning.
</p>
Expand Down Expand Up @@ -402,7 +402,7 @@ export default class extends Component {
anchorPosition="rightDown"
>
<EuiText>
<p>
<p style={{ width: 150 }}>
Be careful with content within left or right aligned popovers. There needs to be
enough content to make make enough height for the arrow positioning.
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,37 @@ exports[`EuiHeaderLinks is rendered 1`] = `
id="headerListMenu"
>
<div
class="euiHeaderSectionItem euiHeaderSectionItem--borderLeft"
class="euiPopover__anchor"
>
<button
aria-label="Open navigation menu"
class="euiHeaderSectionItem__button"
type="button"
<div
class="euiHeaderSectionItem euiHeaderSectionItem--borderLeft"
>
<svg
class="euiIcon euiIcon--medium"
focusable="false"
height="16"
viewBox="0 0 16 16"
width="16"
xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
<button
aria-label="Open navigation menu"
class="euiHeaderSectionItem__button"
type="button"
>
<defs>
<path
d="M2 4V2h2v2H2zm5 0V2h2v2H7zm5 0V2h2v2h-2zM2 9V7h2v2H2zm5 0V7h2v2H7zm5 0V7h2v2h-2zM2 14v-2h2v2H2zm5 0v-2h2v2H7zm5 0v-2h2v2h-2z"
id="apps-a"
<svg
class="euiIcon euiIcon--medium"
focusable="false"
height="16"
viewBox="0 0 16 16"
width="16"
xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<path
d="M2 4V2h2v2H2zm5 0V2h2v2H7zm5 0V2h2v2h-2zM2 9V7h2v2H2zm5 0V7h2v2H7zm5 0V7h2v2h-2zM2 14v-2h2v2H2zm5 0v-2h2v2H7zm5 0v-2h2v2h-2z"
id="apps-a"
/>
</defs>
<use
href="#apps-a"
/>
</defs>
<use
href="#apps-a"
/>
</svg>
</button>
</svg>
</button>
</div>
</div>
</div>
</nav>
Expand Down
45 changes: 30 additions & 15 deletions src/components/outside_click_detector/outside_click_detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Component,
} from 'react';
import PropTypes from 'prop-types';
import { htmlIdGenerator } from '../../services/accessibility';

export class EuiOutsideClickDetector extends Component {
static propTypes = {
Expand All @@ -12,6 +13,28 @@ export class EuiOutsideClickDetector extends Component {
isDisabled: PropTypes.bool,
}

constructor(...args) {
super(...args);

// the id is used to identify which EuiOutsideClickDetector
// is the source of a click event; as the click event bubbles
// up and reaches the click detector's child component the
// id value is stamped on the event. This id is inspected
// in the document's click handler, and if the id doesn't
// exist or doesn't match this detector's id, then trigger
// the outsideClick callback.
//
// Taking this approach instead of checking if the event's
// target element exists in this component's DOM sub-tree is
// necessary for handling clicks originating from children
// rendered through React's portals (EuiPortal). The id tracking
// works because React guarantees the event bubbles through the
// 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();
}

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

if (!this.wrapperRef) {
return;
}

if (this.wrapperRef === event.target) {
return;
}

if (this.wrapperRef.contains(event.target)) {
if (event.euiGeneratedBy === this.id) {
return;
}

Expand All @@ -45,14 +60,14 @@ export class EuiOutsideClickDetector extends Component {
document.removeEventListener('click', this.onClickOutside);
}

onChildClick = event => {
event.nativeEvent.euiGeneratedBy = this.id;
if (this.props.onClick) this.props.onClick(event);
}

render() {
const props = ({ ...this.props.children.props, ...{
ref: node => {
this.wrapperRef = node;
if (this.props.children.ref) {
this.props.children.ref(node);
}
},
onClick: this.onChildClick,
} });

const child = Children.only(this.props.children);
Expand Down
Loading