diff --git a/CHANGELOG.md b/CHANGELOG.md index 65c96d112d3..f630124c7f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Fixed disabled states of icon buttons ([#963](https://github.com/elastic/eui/pull/963)) - Added word-break fallback for FF & IE in table cell ([#962](https://github.com/elastic/eui/pull/962)) +- Fixed `EuiPopover` to show content over modals, flyouts, etc ([#967](https://github.com/elastic/eui/pull/967)) ## [`1.0.1`](https://github.com/elastic/eui/tree/v1.0.1) diff --git a/src-docs/src/views/flyout/flyout_complicated.js b/src-docs/src/views/flyout/flyout_complicated.js index 562ea077318..81b4ecdcf01 100644 --- a/src-docs/src/views/flyout/flyout_complicated.js +++ b/src-docs/src/views/flyout/flyout_complicated.js @@ -12,6 +12,7 @@ import { EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, + EuiPopover, EuiSpacer, EuiTab, EuiTabs, @@ -27,6 +28,7 @@ export class FlyoutComplicated extends Component { isFlyoutVisible: false, isSwitchChecked: true, selectedTabId: '1', + isPopoverOpen: false, }; this.tabs = [{ @@ -55,6 +57,14 @@ export class FlyoutComplicated extends Component { this.setState({ isFlyoutVisible: true }); } + closePopover = () => { + this.setState({ isPopoverOpen: false }); + } + + togglePopover = () => { + this.setState(({ isPopoverOpen }) => ({ isPopoverOpen: !isPopoverOpen })); + } + onSelectedTabChanged = id => { this.setState({ selectedTabId: id, @@ -162,6 +172,13 @@ export class FlyoutComplicated extends Component { + Even popovers can be included} + isOpen={this.state.isPopoverOpen} + > +

This is the popover content, notice how it can overflow the flyout!

+
{flyoutContent} {htmlCode} diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index f7a8189c8c1..b48c7f14b6d 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -16,7 +16,7 @@ import { EuiPanel, SIZES } from '../panel'; import { EuiPortal } from '../portal'; -import { findPopoverPosition } from '../../services/popover/popover_positioning'; +import { findPopoverPosition, getElementZIndex } from '../../services/popover/popover_positioning'; const anchorPositionToPopoverPositionMap = { 'up': 'top', @@ -189,9 +189,15 @@ export class EuiPopover extends Component { } }); + // the popver's z-index must inherit from the button + // this keeps a button's popver under a flyover that would cover the button + // but a popover triggered inside a flyover will appear over that flyover + const zIndex = getElementZIndex(this.button, this.panel); + const popoverStyles = { top, left, + zIndex, }; const arrowStyles = arrow; diff --git a/src/services/popover/popover_positioning.js b/src/services/popover/popover_positioning.js index b6a1b1527fa..d41f55d6a35 100644 --- a/src/services/popover/popover_positioning.js +++ b/src/services/popover/popover_positioning.js @@ -508,3 +508,67 @@ export function intersectBoundingBoxes(firstBox, secondBox) { return intersection; } + + +/** + * Returns the top-most defined z-index in the element's ancestor hierarchy + * relative to the `target` element; if no z-index is defined, returns "0" + * @param element {HTMLElement|React.Component} + * @param cousin {HTMLElement|React.Component} + * @returns {string} + */ +export function getElementZIndex(element, cousin) { + element = findDOMNode(element); + cousin = findDOMNode(cousin); + + /** + * finding the z-index of `element` is not the full story + * its the CSS stacking context that is important + * take this DOM for example: + * body + * section[z-index: 1000] + * p[z-index: 500] + * button + * div + * + * what z-index does the `div` need to display next to `button`? + * the `div` and `section` are where the stacking context splits + * so `div` needs to copy `section`'s z-index in order to + * appear next to / over `button` + * + * calculate this by starting at `button` and finding its offsetParents + * then walk the parents from top -> down until the stacking context + * split is found, or if there is no split then a specific z-index is unimportant + */ + + // build the array of the element + its offset parents + const nodesToInspect = []; + while (true) { + nodesToInspect.push(element); + + element = element.offsetParent; + + // stop if there is no parent + if (element == null) break; + + // stop if the parent contains the related element + // as this is the z-index ancestor + if (element.contains(cousin)) break; + } + + // reverse the nodes to walk from top -> element + nodesToInspect.reverse(); + + return nodesToInspect.reduce( + (foundZIndex, node) => { + if (foundZIndex != null) return foundZIndex; + + // get this node's z-index css value + const zIndex = window.document.defaultView.getComputedStyle(node).getPropertyValue('z-index'); + + // if the z-index is not a number (e.g. "auto") return null, else the value + return isNaN(zIndex) ? null : zIndex; + }, + null + ) || '0'; +}