From 56b26da72572b6fce1b3376fb1a35f2ab8c7f7da Mon Sep 17 00:00:00 2001 From: Benjamin Dobell Date: Wed, 10 Jul 2019 01:09:31 +1000 Subject: [PATCH] feat: referenceNode support for reference objects (closes #800) (#801) 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. --- packages/popper/index.d.ts | 1 + packages/popper/index.js.flow | 1 + packages/popper/src/utils/getBoundaries.js | 3 +- packages/popper/src/utils/getReferenceNode.js | 10 +++++ .../popper/src/utils/getReferenceOffsets.js | 3 +- packages/popper/tests/functional/core.js | 44 +++++++++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 packages/popper/src/utils/getReferenceNode.js diff --git a/packages/popper/index.d.ts b/packages/popper/index.d.ts index 5718763d9e..edf4169b7e 100644 --- a/packages/popper/index.d.ts +++ b/packages/popper/index.d.ts @@ -133,6 +133,7 @@ declare namespace Popper { export interface ReferenceObject { clientHeight: number; clientWidth: number; + referenceNode?: Node; getBoundingClientRect(): ClientRect; } diff --git a/packages/popper/index.js.flow b/packages/popper/index.js.flow index aa111454d6..2198aa76d8 100644 --- a/packages/popper/index.js.flow +++ b/packages/popper/index.js.flow @@ -118,6 +118,7 @@ declare module 'popper.js' { declare type ReferenceObject = { +clientHeight: number, +clientWidth: number, + +referenceNode?: Node, getBoundingClientRect(): | ClientRect diff --git a/packages/popper/src/utils/getBoundaries.js b/packages/popper/src/utils/getBoundaries.js index 6c7d3a7685..45cb54ab91 100644 --- a/packages/popper/src/utils/getBoundaries.js +++ b/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'; @@ -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' ) { diff --git a/packages/popper/src/utils/getReferenceNode.js b/packages/popper/src/utils/getReferenceNode.js new file mode 100644 index 0000000000..25f5604172 --- /dev/null +++ b/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; +} diff --git a/packages/popper/src/utils/getReferenceOffsets.js b/packages/popper/src/utils/getReferenceOffsets.js index c833065a54..f1ac32f37d 100644 --- a/packages/popper/src/utils/getReferenceOffsets.js +++ b/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 @@ -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); } diff --git a/packages/popper/tests/functional/core.js b/packages/popper/tests/functional/core.js index cb9b96bf47..ae9dd08cde 100644 --- a/packages/popper/tests/functional/core.js +++ b/packages/popper/tests/functional/core.js @@ -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 = ` +
+
popper
+
+ `; + + 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 = `
reference