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.
80 lines (76 loc) • 3.07 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@floating-ui/utils/dom')) :
typeof define === 'function' && define.amd ? define(['exports', '@floating-ui/utils/dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ComposedOffsetPosition = {}, global.FloatingUIUtilsDOM));
})(this, (function (exports, dom) { 'use strict';
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' || dom.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;
}
exports.offsetLeft = offsetLeft;
exports.offsetParent = offsetParent;
exports.offsetTop = offsetTop;
}));