Skip to content

Commit

Permalink
feat: referenceNode support for reference objects (closes #800) (#801)
Browse files Browse the repository at this point in the history
referenceNode can be specified to better facilitate non-fixed
positioning when a popper's reference is a "reference object".

In particular, reference objects can now return coordinates in
"client-space" (i.e. getBoundingClientRect) and popper will
calculate positioning using the specified referenceNode rather
then falling back to the documentElement.

Consequently, enabling Popper.enableEventListeners() will now
work as expected, automatically updating the popper position, when
scrolling and resizing the browser window; assuming a referenceNode
has been provided.
  • Loading branch information
Benjamin-Dobell authored and FezVrasta committed Jul 9, 2019
1 parent 9f1d603 commit 56b26da
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/popper/index.d.ts
Expand Up @@ -133,6 +133,7 @@ declare namespace Popper {
export interface ReferenceObject {
clientHeight: number;
clientWidth: number;
referenceNode?: Node;

getBoundingClientRect(): ClientRect;
}
Expand Down
1 change: 1 addition & 0 deletions packages/popper/index.js.flow
Expand Up @@ -118,6 +118,7 @@ declare module 'popper.js' {
declare type ReferenceObject = {
+clientHeight: number,
+clientWidth: number,
+referenceNode?: Node,

getBoundingClientRect():
| ClientRect
Expand Down
3 changes: 2 additions & 1 deletion packages/popper/src/utils/getBoundaries.js
@@ -1,5 +1,6 @@
import getScrollParent from './getScrollParent';
import getParentNode from './getParentNode';
import getReferenceNode from './getReferenceNode';
import findCommonOffsetParent from './findCommonOffsetParent';
import getOffsetRectRelativeToArbitraryNode from './getOffsetRectRelativeToArbitraryNode';
import getViewportOffsetRectRelativeToArtbitraryNode from './getViewportOffsetRectRelativeToArtbitraryNode';
Expand Down Expand Up @@ -28,7 +29,7 @@ export default function getBoundaries(
// NOTE: 1 DOM access here

let boundaries = { top: 0, left: 0 };
const offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
const offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference));

// Handle viewport case
if (boundariesElement === 'viewport' ) {
Expand Down
10 changes: 10 additions & 0 deletions packages/popper/src/utils/getReferenceNode.js
@@ -0,0 +1,10 @@
/**
* Returns the reference node of the reference object, or the reference object itself.
* @method
* @memberof Popper.Utils
* @param {Element|Object} reference - the reference element (the popper will be relative to this)
* @returns {Element} parent
*/
export default function getReferenceNode(reference) {
return reference && reference.referenceNode ? reference.referenceNode : reference;
}
3 changes: 2 additions & 1 deletion packages/popper/src/utils/getReferenceOffsets.js
@@ -1,6 +1,7 @@
import findCommonOffsetParent from './findCommonOffsetParent';
import getOffsetRectRelativeToArbitraryNode from './getOffsetRectRelativeToArbitraryNode';
import getFixedPositionOffsetParent from './getFixedPositionOffsetParent';
import getReferenceNode from './getReferenceNode';

/**
* Get offsets to the reference element
Expand All @@ -13,6 +14,6 @@ import getFixedPositionOffsetParent from './getFixedPositionOffsetParent';
* @returns {Object} An object containing the offsets which will be applied to the popper
*/
export default function getReferenceOffsets(state, popper, reference, fixedPosition = null) {
const commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
const commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference));
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
}
44 changes: 44 additions & 0 deletions packages/popper/tests/functional/core.js
Expand Up @@ -1428,6 +1428,50 @@ const arrowSize = 5;
});
});

it('uses a reference object with a referenceNode to position its popper', done => {
// As above
if (isIE10) {
pending();
}

jasmineWrapper.innerHTML = `
<div id="reference" style="position: absolute; left: 10px; top: 10px; right: 10px; bottom: 10px;">
<div id="popper" style="background: purple;">popper</div>
</div>
`;

const popper = document.getElementById('popper');

const reference = {
getBoundingClientRect() {
return {
top: 10,
left: 100,
right: 150,
bottom: 90,
width: 50,
height: 80,
};
},
clientWidth: 50,
clientHeight: 80,
referenceNode: document.getElementById('reference'),
};

new Popper(reference, popper, {
placement: 'bottom-start',
onCreate() {
expect(getRect(popper).top).toBe(
reference.getBoundingClientRect().bottom
);
expect(getRect(popper).left).toBe(
reference.getBoundingClientRect().left
);
done();
},
});
});

it('checks cases where the reference element is fixed', done => {
jasmineWrapper.innerHTML = `
<div id="reference" style="position: fixed; top: 100px; left: 100px; background: pink">reference</div>
Expand Down

0 comments on commit 56b26da

Please sign in to comment.