diff --git a/src/dom-utils/getCompositeRect.js b/src/dom-utils/getCompositeRect.js index e32a649fa0..663a7d5a92 100644 --- a/src/dom-utils/getCompositeRect.js +++ b/src/dom-utils/getCompositeRect.js @@ -17,11 +17,12 @@ export default function getCompositeRect( ): Rect { const documentElement = getDocumentElement(offsetParent); const rect = getBoundingClientRect(elementOrVirtualElement); + const isOffsetParentAnElement = isHTMLElement(offsetParent); let scroll = { scrollLeft: 0, scrollTop: 0 }; let offsets = { x: 0, y: 0 }; - if (!isFixed) { + if (isOffsetParentAnElement || (!isOffsetParentAnElement && !isFixed)) { if ( getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078 diff --git a/src/dom-utils/getOffsetParent.js b/src/dom-utils/getOffsetParent.js index 513793f7a8..0efa9a6c4c 100644 --- a/src/dom-utils/getOffsetParent.js +++ b/src/dom-utils/getOffsetParent.js @@ -4,6 +4,7 @@ import getNodeName from './getNodeName'; import getComputedStyle from './getComputedStyle'; import { isHTMLElement } from './instanceOf'; import isTableElement from './isTableElement'; +import getParentNode from './getParentNode'; function getTrueOffsetParent(element: Element): ?Element { if ( @@ -17,10 +18,34 @@ function getTrueOffsetParent(element: Element): ?Element { return element.offsetParent; } -/* -get the closest ancestor positioned element. Handles some edge cases, -such as table ancestors and cross browser bugs. -*/ +// `.offsetParent` reports `null` for fixed elements, while absolute elements +// return the containing block +function getContainingBlock(element: Element) { + let currentNode = getParentNode(element); + + while (getNodeName(currentNode) !== 'body') { + if (isHTMLElement(currentNode)) { + const css = getComputedStyle(currentNode); + + // This is non-exhaustive but covers the most common CSS properties that + // create a containing block. + if ( + css.transform !== 'none' || + css.perspective !== 'none' || + css.willChange !== 'auto' + ) { + return currentNode; + } else { + currentNode = currentNode.parentNode; + } + } + } + + return null; +} + +// Gets the closest ancestor positioned element. Handles some edge cases, +// such as table ancestors and cross browser bugs. export default function getOffsetParent(element: Element) { const window = getWindow(element); @@ -39,5 +64,5 @@ export default function getOffsetParent(element: Element) { return window; } - return offsetParent || window; + return offsetParent || getContainingBlock(element) || window; } diff --git a/src/index.js b/src/index.js index 3fba5df7d4..63d2575a90 100644 --- a/src/index.js +++ b/src/index.js @@ -41,7 +41,8 @@ type PopperGeneratorArgs = { function areValidElements(...args: Array): boolean { return !args.some( - element => !(element && typeof element.getBoundingClientRect === 'function') + (element) => + !(element && typeof element.getBoundingClientRect === 'function') ); } @@ -100,7 +101,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { ); // Strip out disabled modifiers - state.orderedModifiers = orderedModifiers.filter(m => m.enabled); + state.orderedModifiers = orderedModifiers.filter((m) => m.enabled); // Validate the provided modifiers so that the consumer will get warned // if one of the modifiers is invalid for any reason @@ -137,7 +138,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { // We no longer take into account `margins` on the popper, and it can // cause bugs with positioning, so we'll warn the consumer if ( - [marginTop, marginRight, marginBottom, marginLeft].some(margin => + [marginTop, marginRight, marginBottom, marginLeft].some((margin) => parseFloat(margin) ) ) { @@ -203,7 +204,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { // it doesn't persist and is fresh on each update. // To ensure persistent data, use `${name}#persistent` state.orderedModifiers.forEach( - modifier => + (modifier) => (state.modifiersData[modifier.name] = { ...modifier.data, }) @@ -237,7 +238,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { // not necessary (debounced to run at most once-per-tick) update: debounce<$Shape>( () => - new Promise<$Shape>(resolve => { + new Promise<$Shape>((resolve) => { instance.forceUpdate(); resolve(state); }) @@ -256,7 +257,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { return instance; } - instance.setOptions(options).then(state => { + instance.setOptions(options).then((state) => { if (!isDestroyed && options.onFirstUpdate) { options.onFirstUpdate(state); } @@ -278,7 +279,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { } function cleanupModifierEffects() { - effectCleanupFns.forEach(fn => fn()); + effectCleanupFns.forEach((fn) => fn()); effectCleanupFns = []; } diff --git a/tests/functional/__image_snapshots__/containing-block-test-js-should-be-positioned-on-the-bottom-1-snap.png b/tests/functional/__image_snapshots__/containing-block-test-js-should-be-positioned-on-the-bottom-1-snap.png new file mode 100644 index 0000000000..1fac112a83 Binary files /dev/null and b/tests/functional/__image_snapshots__/containing-block-test-js-should-be-positioned-on-the-bottom-1-snap.png differ diff --git a/tests/functional/containing-block.test.js b/tests/functional/containing-block.test.js new file mode 100644 index 0000000000..c6a5b63e99 --- /dev/null +++ b/tests/functional/containing-block.test.js @@ -0,0 +1,13 @@ +/** + * @jest-environment puppeteer + * @flow + */ +import { screenshot, scroll } from '../utils/puppeteer.js'; + +it('should be positioned on the bottom', async () => { + const page = await browser.newPage(); + await page.goto(`${TEST_URL}/containing-block.html`); + await scroll(page, '#containing-block', 200); + + expect(await screenshot(page)).toMatchImageSnapshot(); +}); diff --git a/tests/visual/containing-block.html b/tests/visual/containing-block.html new file mode 100644 index 0000000000..fdff5d5de7 --- /dev/null +++ b/tests/visual/containing-block.html @@ -0,0 +1,55 @@ + Basic Visual Test + + + +
+
Reference Box
+
Popper Box
+
+ +