Skip to content

Commit

Permalink
Merge pull request #1123 from atomiks/fix/containing-block
Browse files Browse the repository at this point in the history
fix: detect containing block
  • Loading branch information
FezVrasta committed Jun 8, 2020
2 parents 2db25a5 + a6c591b commit f2b619b
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 13 deletions.
3 changes: 2 additions & 1 deletion src/dom-utils/getCompositeRect.js
Expand Up @@ -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
Expand Down
35 changes: 30 additions & 5 deletions src/dom-utils/getOffsetParent.js
Expand Up @@ -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 (
Expand All @@ -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);

Expand All @@ -42,5 +67,5 @@ export default function getOffsetParent(element: Element) {
return window;
}

return offsetParent || window;
return offsetParent || getContainingBlock(element) || window;
}
15 changes: 8 additions & 7 deletions src/index.js
Expand Up @@ -41,7 +41,8 @@ type PopperGeneratorArgs = {

function areValidElements(...args: Array<any>): boolean {
return !args.some(
element => !(element && typeof element.getBoundingClientRect === 'function')
(element) =>
!(element && typeof element.getBoundingClientRect === 'function')
);
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
)
) {
Expand Down Expand Up @@ -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,
})
Expand Down Expand Up @@ -237,7 +238,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
// not necessary (debounced to run at most once-per-tick)
update: debounce<$Shape<State>>(
() =>
new Promise<$Shape<State>>(resolve => {
new Promise<$Shape<State>>((resolve) => {
instance.forceUpdate();
resolve(state);
})
Expand All @@ -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);
}
Expand All @@ -278,7 +279,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
}

function cleanupModifierEffects() {
effectCleanupFns.forEach(fn => fn());
effectCleanupFns.forEach((fn) => fn());
effectCleanupFns = [];
}

Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions 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();
});
55 changes: 55 additions & 0 deletions tests/visual/containing-block.html
@@ -0,0 +1,55 @@
<!DOCTYPE html> <title>Basic Visual Test</title>

<style>
@import '/reset.css';
#reference {
width: 200px;
height: 200px;
background-color: red;
box-shadow: inset 0 0 0 1px black;
}

#popper {
width: 100px;
height: 100px;
background-color: rebeccapurple;
box-shadow: inset 0 0 0 1px black;
}

#containing-block {
background: gray;
width: 400px;
height: 400px;
overflow: auto;
will-change: transform;
}

#containing-block::before {
content: '';
display: block;
height: 200px;
width: 1px;
}

#containing-block::after {
content: '';
display: block;
height: 200px;
width: 1px;
}
</style>

<div id="containing-block">
<div id="reference">Reference Box</div>
<div id="popper">Popper Box</div>
</div>

<script type="module">
import { createPopper } from './dist/popper.js';
const reference = document.querySelector('#reference');
const popper = document.querySelector('#popper');

window.instance = createPopper(reference, popper, {
strategy: 'fixed',
});
</script>

0 comments on commit f2b619b

Please sign in to comment.