composed-offset-position
Version:
This provides a set of ponyfills to achieve the same behavior of `offsetParent`, `offsetLeft` and `offsetTop` before the `offsetParent` spec was changed.
72 lines (69 loc) • 2.33 kB
JavaScript
import { isContainingBlock } from '@floating-ui/utils/dom';
function offsetParent(element) {
return offsetParentPolyfill(element);
}
function offsetTop(element) {
return offsetTopLeftPolyfill(element, 'offsetTop');
}
function offsetLeft(element) {
return offsetTopLeftPolyfill(element, 'offsetLeft');
}
function flatTreeParent(element) {
if (element.assignedSlot) {
return element.assignedSlot;
}
if (element.parentNode instanceof ShadowRoot) {
return element.parentNode.host;
}
return element.parentNode;
}
function ancestorTreeScopes(element) {
const scopes = new Set();
let currentScope = element.getRootNode();
while (currentScope) {
scopes.add(currentScope);
currentScope = currentScope.parentNode
? currentScope.parentNode.getRootNode()
: null;
}
return scopes;
}
function offsetParentPolyfill(element) {
// Do an initial walk to check for display:none ancestors.
for (let ancestor = element; ancestor; ancestor = flatTreeParent(ancestor)) {
if (!(ancestor instanceof Element)) {
continue;
}
if (getComputedStyle(ancestor).display === 'none') {
return null;
}
}
for (let ancestor = flatTreeParent(element); ancestor; ancestor = flatTreeParent(ancestor)) {
if (!(ancestor instanceof Element)) {
continue;
}
const style = getComputedStyle(ancestor);
// Display:contents nodes aren't in the layout tree, so they should be skipped.
if (style.display === 'contents') {
continue;
}
if (style.position !== 'static' || isContainingBlock(style)) {
return ancestor;
}
if (ancestor.tagName === 'BODY') {
return ancestor;
}
}
return null;
}
function offsetTopLeftPolyfill(element, offsetTopOrLeft) {
let value = element[offsetTopOrLeft];
let nextOffsetParent = offsetParentPolyfill(element);
const scopes = ancestorTreeScopes(element);
while (nextOffsetParent && !scopes.has(nextOffsetParent.getRootNode())) {
value -= nextOffsetParent[offsetTopOrLeft];
nextOffsetParent = offsetParentPolyfill(nextOffsetParent);
}
return value;
}
export { offsetLeft, offsetParent, offsetTop };