Skip to content

Commit

Permalink
Fix tooltip re-position after content resize & respect scroll bars (#936
Browse files Browse the repository at this point in the history
)

* Fix tooltip re-position after a content resize, respect cntainer boundary+scroll bars

* changelog

* Change initial EuiTooltip content to avoid creating scrollbars

* Make position service container element optional

* updated changelog
  • Loading branch information
chandlerprall authored Jun 22, 2018
1 parent 594a1f2 commit d9a2a1b
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 13 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## [`master`](https://github.com/elastic/eui/tree/master)

No public interface changes since `0.0.55`.
**Bug fixes**

- `EuiTooltip` re-positions content correctly after the window is resized ([#936](https://github.com/elastic/eui/pull/936))

## [`0.0.55`](https://github.com/elastic/eui/tree/v0.0.55)

Expand Down
24 changes: 22 additions & 2 deletions src/components/tool_tip/tool_tip.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ const positionsToClassNameMap = {

export const POSITIONS = Object.keys(positionsToClassNameMap);

const DEFAULT_TOOLTIP_STYLES = {
// position the tooltip content near the top-left
// corner of the window so it can't create scrollbars
// 50,50 because who knows what negative margins, padding, etc
top: 50,
left: 50,
// just in case, avoid any potential flicker by hiding
// the tooltip before it is positioned
opacity: 0
};

export class EuiToolTip extends Component {
constructor(props) {
super(props);
Expand All @@ -29,14 +40,23 @@ export class EuiToolTip extends Component {
visible: false,
hasFocus: false,
calculatedPosition: this.props.position,
toolTipStyles: {},
toolTipStyles: DEFAULT_TOOLTIP_STYLES,
arrowStyles: {},
id: this.props.id || makeId(),
};
}

setPopoverRef = ref => {
this.popover = ref;

// if the popover has been unmounted, clear
// any previous knowledge about its size
if (ref == null) {
this.setState({
toolTipStyles: DEFAULT_TOOLTIP_STYLES,
arrowStyles: {}
});
}
}

showToolTip = () => {
Expand All @@ -52,7 +72,7 @@ export class EuiToolTip extends Component {
position: requestedPosition,
offset: 16, // offset popover 16px from the anchor
arrowConfig: {
arrowWidth: 14,
arrowWidth: 12,
arrowBuffer: 4
}
});
Expand Down
30 changes: 22 additions & 8 deletions src/services/popover/popover_positioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,36 @@ const positionSubstitutes = {
* @param position {string} Position the user wants. One of ["top", "right", "bottom", "left"]
* @param [buffer=16] {number} Minimum distance between the popover and the bounding container
* @param [offset=0] {number} Distance between the popover and the anchor
* @param [container=document.body] {HTMLElement|React.Component} Element the popover must be constrained to fit within
* * @param [arrowConfig] {{arrowWidth: number, arrowBuffer: number}} If present, describes the size & constraints for an arrow element, and the function return value will include an `arrow` param with position details
* @param [container] {HTMLElement|React.Component} Element the popover must be constrained to fit within
* @param [arrowConfig] {{arrowWidth: number, arrowBuffer: number}} If present, describes the size & constraints for an arrow element, and the function return value will include an `arrow` param with position details
*
* @returns {{top: number, left: number, position: string, fit: number, arrow?: {left: number, top: number}}|null} absolute page coordinates for the popover,
* and the placements's relation to the anchor; if there's no room this returns null
*/
export function findPopoverPosition({ anchor, popover, position, buffer = 16, offset = 0, container = document.body, arrowConfig }) {
export function findPopoverPosition({ anchor, popover, position, buffer = 16, offset = 0, container, arrowConfig }) {
container = findDOMNode(container); // resolve any React abstractions

// find the screen-relative bounding boxes of the anchor, popover, and container
const anchorBoundingBox = getElementBoundingBox(anchor);
const popoverBoundingBox = getElementBoundingBox(popover);
const containerBoundingBox = getElementBoundingBox(container);

// calculate the window's bounds
// window.(innerWidth|innerHeight) do not account for scrollbars
// so prefer the clientWidth/clientHeight of the DOM if available
const documentWidth = document.documentElement.clientWidth || window.innerWidth;
const documentHeight = document.documentElement.clientHeight || window.innerHeight;
const windowBoundingBox = {
top: 0,
right: window.innerWidth,
bottom: window.innerHeight,
right: documentWidth,
bottom: documentHeight,
left: 0,
height: window.innerHeight,
width: window.innerWidth
height: documentHeight,
width: documentWidth
};

// if no container element is given fall back to using the window viewport
const containerBoundingBox = container ? getElementBoundingBox(container) : windowBoundingBox;

/**
* `position` was specified by the function caller and is a strong hint
* as to the preferred location of the popover relative to the anchor.
Expand Down Expand Up @@ -236,6 +242,14 @@ export function getPopoverScreenCoordinates({
// calculate the fit of the popover in this location
// fit is in range 0.0 -> 1.0 and is the percentage of the popover which is visible in this location
const combinedBoundingBox = intersectBoundingBoxes(windowBoundingBox, containerBoundingBox);

// shrink the visible bounding box by `buffer`
// to compute a fit value
combinedBoundingBox.top += buffer;
combinedBoundingBox.right -= buffer;
combinedBoundingBox.bottom -= buffer;
combinedBoundingBox.left += buffer;

const fit = getVisibleFit(
{
top: popoverPlacement.top,
Expand Down
4 changes: 2 additions & 2 deletions src/services/popover/popover_positioning.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ describe('popover_positioning', () => {

// give the container limited space on both left and top, forcing to bottom-right
const container = document.createElement('div');
container.getBoundingClientRect = () => makeBB(100, 300, 768, 30);
container.getBoundingClientRect = () => makeBB(50, 300, 768, 30);

expect(findPopoverPosition({
position: 'left',
Expand All @@ -382,7 +382,7 @@ describe('popover_positioning', () => {
})).toEqual({
fit: 1,
position: 'right',
top: 100,
top: 85,
left: 155
});
});
Expand Down

0 comments on commit d9a2a1b

Please sign in to comment.