Skip to content

Commit

Permalink
Merge pull request #54 from metalabdesign/container-detection
Browse files Browse the repository at this point in the history
 Improve parent container detection
  • Loading branch information
10xjs authored Mar 15, 2018
2 parents e83eba5 + 5cb7a51 commit 7893d1a
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 43 deletions.
115 changes: 75 additions & 40 deletions packages/flowtip-react-dom/src/FlowTip.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import flowtip, {
import type {RectLike, Region, Align, Dimensions, Result} from 'flowtip-core';

import getContainingBlock from './util/getContainingBlock';
import getClippingBlock from './util/getClippingBlock';
import getContentRect from './util/getContentRect';
import findDOMNode from './util/findDOMNode';

// Static `flowtip` layout calculation result mock for use during initial client
Expand All @@ -30,8 +32,8 @@ const STATIC_RESULT: Result = {
};

export type State = {
containingBlock: RectLike,
bounds: RectLike | null,
containingBlock: Rect,
bounds: Rect | null,
content: Dimensions | null,
tail: Dimensions | null,
result: Result | null,
Expand Down Expand Up @@ -140,11 +142,12 @@ class FlowTip extends React.Component<Props, State> {

_nextContent: Dimensions | null = null;
_nextTail: Dimensions | null = null;
_nextContainingBlock: RectLike = Rect.zero;
_nextBounds: RectLike | null = Rect.zero;
_nextContainingBlock: Rect = Rect.zero;
_nextBounds: Rect | null = null;
_lastRegion: Region | void;
_isMounted: boolean = false;
_containingBlockNode: HTMLElement | null = null;
_clippingBlockNode: HTMLElement | null = null;
_node: HTMLElement | null = null;
state = this._getState(this.props);

Expand All @@ -153,7 +156,7 @@ class FlowTip extends React.Component<Props, State> {
_handleScroll = this._handleScroll.bind(this);

// ===========================================================================
// Lifecycle Methods.
// Lifecycle Methods
// ===========================================================================
componentDidMount(): void {
this._isMounted = true;
Expand All @@ -169,6 +172,7 @@ class FlowTip extends React.Component<Props, State> {
}

componentWillReceiveProps(nextProps: Props): void {
this._nextContainingBlock = this._getContainingBlockRect();
this._nextBounds = this._getBoundsRect(nextProps);

this._updateState(nextProps);
Expand All @@ -182,14 +186,15 @@ class FlowTip extends React.Component<Props, State> {
this._isMounted = false;

this._containingBlockNode = null;
this._clippingBlockNode = null;
this._node = null;

window.removeEventListener('scroll', this._handleScroll);
window.removeEventListener('resize', this._handleScroll);
}

// ===========================================================================
// State Management.
// State Management
// ===========================================================================

_getLastRegion(nextProps: Props): Region | void {
Expand Down Expand Up @@ -402,59 +407,89 @@ class FlowTip extends React.Component<Props, State> {
}

// ===========================================================================
// DOM Measurement Methods.
// DOM Measurement Methods
// ===========================================================================

_getBoundsRect(nextProps: Props): RectLike | null {
const viewport = new Rect(
0,
0,
window.document.documentElement.clientWidth,
window.document.documentElement.clientHeight,
);
_getBoundsRect(nextProps: Props): Rect | null {
const viewportRect = new Rect(0, 0, window.innerWidth, window.innerHeight);

const bounds = Rect.grow(
nextProps.bounds ? Rect.intersect(viewport, nextProps.bounds) : viewport,
-nextProps.edgeOffset,
);
const processBounds = (boundsRect: RectLike) => {
const visibleBounds = Rect.grow(
Rect.intersect(viewportRect, boundsRect),
-nextProps.edgeOffset,
);

// A rect with negative dimensions doesn't make sense here.
// Returning null will disable rendering content.
if (visibleBounds.width < 0 || visibleBounds.height < 0) {
return Rect.zero;
}

return visibleBounds;
};

if (nextProps.bounds) {
return processBounds(nextProps.bounds);
}

if (document.body && this._clippingBlockNode === document.documentElement) {
return processBounds(
new Rect(
-document.body.scrollLeft,
-document.body.scrollTop,
document.body.scrollWidth,
document.body.scrollHeight,
),
);
}

// A rect with neagitve dimensions doesn't make sense here.
// Returning null disable rendering of any content.
if (bounds.width >= 0 && bounds.height >= 0) {
return bounds;
if (this._clippingBlockNode) {
return processBounds(getContentRect(this._clippingBlockNode));
}

return null;
}

_getContainingBlockRect(): RectLike {
if (!this._containingBlockNode) return Rect.zero;
return Rect.from(this._containingBlockNode.getBoundingClientRect());
_getContainingBlockRect(): Rect {
if (!this._containingBlockNode) {
return Rect.zero;
}

if (
document.body &&
this._containingBlockNode === document.documentElement
) {
return new Rect(
-document.body.scrollLeft,
-document.body.scrollTop,
document.body.scrollWidth,
document.body.scrollHeight,
);
}

return getContentRect(this._containingBlockNode);
}

// ===========================================================================
// DOM Element Accessors.
// DOM Element Accessors
// ===========================================================================

_updateDOMNodes(): void {
const node = findDOMNode(this);
this._node = node instanceof HTMLElement ? node : null;

const block = this._node && getContainingBlock(this._node.parentNode);
if (block) {
this._containingBlockNode = block;
} else {
// Refine nullable `document.body`.
// see: https://stackoverflow.com/questions/42377663
if (document.body === null) {
throw new Error('document.body is null');
}
this._containingBlockNode = document.body;

if (node instanceof HTMLElement) {
this._node = node;

this._containingBlockNode =
getContainingBlock(node.parentNode) || document.documentElement;

this._clippingBlockNode =
getClippingBlock(node.parentNode) || document.documentElement;
}
}

// ===========================================================================
// Event Handlers.
// Event Handlers
// ===========================================================================

/**
Expand Down Expand Up @@ -501,7 +536,7 @@ class FlowTip extends React.Component<Props, State> {
}

// ===========================================================================
// Render Methods.
// Render Methods
// ===========================================================================

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/flowtip-react-dom/src/util/findAncestor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow

const findAncestor = (
callback: (node: Node) => boolean,
callback: (node: HTMLElement) => boolean,
node: ?Node,
): HTMLElement | null => {
let current = node;
Expand Down
17 changes: 17 additions & 0 deletions packages/flowtip-react-dom/src/util/getClippingBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @flow

import findAncestor from './findAncestor';

const getClippingBlock = (node: ?Node): HTMLElement | null => {
const result = findAncestor((node) => {
if (node === document.documentElement) return true;

const style = getComputedStyle(node);

return style.overflow && style.overflow !== 'visible';
}, node);

return result;
};

export default getClippingBlock;
5 changes: 3 additions & 2 deletions packages/flowtip-react-dom/src/util/getContainingBlock.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// @flow

import findAncestor from './findAncestor';

const getContainingBlock = (node: ?Node): HTMLElement | null => {
const result = findAncestor((node) => {
if (node.tagName === 'BODY') return true;
if (node === document.documentElement) return true;

const style = window.getComputedStyle(node);
const style = getComputedStyle(node);

return style.position && style.position !== 'static';
}, node);
Expand Down
22 changes: 22 additions & 0 deletions packages/flowtip-react-dom/src/util/getContentRect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// @flow

import {Rect} from 'flowtip-core';

const getContentRect = (node: HTMLElement): Rect => {
const rect = node.getBoundingClientRect();
const style = getComputedStyle(node);

const topBorder = parseInt(style.borderTopWidth, 10);
const rightBorder = parseInt(style.borderRightWidth, 10);
const bottomBorder = parseInt(style.borderBottomWidth, 10);
const leftBorder = parseInt(style.borderLeftWidth, 10);

return new Rect(
rect.left + leftBorder || 0,
rect.top + topBorder || 0,
Math.min(rect.width - leftBorder - rightBorder, node.clientWidth) || 0,
Math.min(rect.height - topBorder - bottomBorder, node.clientHeight) || 0,
);
};

export default getContentRect;

0 comments on commit 7893d1a

Please sign in to comment.