140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
// Forked from NPM stacking-order@2.0.0
|
|
// Background at https://github.com/Rich-Harris/stacking-order/issues/3
|
|
// Background at https://github.com/Rich-Harris/stacking-order/issues/6
|
|
|
|
import { assert } from "..";
|
|
|
|
/**
|
|
* Determine which of two nodes appears in front of the other —
|
|
* if `a` is in front, returns 1, otherwise returns -1
|
|
* @param {HTMLElement} a
|
|
* @param {HTMLElement} b
|
|
*/
|
|
export function compare(a: HTMLElement, b: HTMLElement): number {
|
|
if (a === b) throw new Error("Cannot compare node with itself");
|
|
|
|
const ancestors = {
|
|
a: get_ancestors(a),
|
|
b: get_ancestors(b),
|
|
};
|
|
|
|
let common_ancestor;
|
|
|
|
// remove shared ancestors
|
|
while (ancestors.a.at(-1) === ancestors.b.at(-1)) {
|
|
a = ancestors.a.pop() as HTMLElement;
|
|
b = ancestors.b.pop() as HTMLElement;
|
|
|
|
common_ancestor = a;
|
|
}
|
|
|
|
assert(
|
|
common_ancestor,
|
|
"Stacking order can only be calculated for elements with a common ancestor"
|
|
);
|
|
|
|
const z_indexes = {
|
|
a: get_z_index(find_stacking_context(ancestors.a)),
|
|
b: get_z_index(find_stacking_context(ancestors.b)),
|
|
};
|
|
|
|
if (z_indexes.a === z_indexes.b) {
|
|
const children = common_ancestor.childNodes;
|
|
|
|
const furthest_ancestors = {
|
|
a: ancestors.a.at(-1),
|
|
b: ancestors.b.at(-1),
|
|
};
|
|
|
|
let i = children.length;
|
|
while (i--) {
|
|
const child = children[i];
|
|
if (child === furthest_ancestors.a) return 1;
|
|
if (child === furthest_ancestors.b) return -1;
|
|
}
|
|
}
|
|
|
|
return Math.sign(z_indexes.a - z_indexes.b);
|
|
}
|
|
|
|
const props =
|
|
/\b(?:position|zIndex|opacity|transform|webkitTransform|mixBlendMode|filter|webkitFilter|isolation)\b/;
|
|
|
|
/** @param {HTMLElement} node */
|
|
function is_flex_item(node: HTMLElement) {
|
|
// @ts-ignore
|
|
const display = getComputedStyle(get_parent(node) ?? node).display;
|
|
return display === "flex" || display === "inline-flex";
|
|
}
|
|
|
|
/** @param {HTMLElement} node */
|
|
function creates_stacking_context(node: HTMLElement) {
|
|
const style = getComputedStyle(node);
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
|
|
if (style.position === "fixed") return true;
|
|
// Forked to fix upstream bug https://github.com/Rich-Harris/stacking-order/issues/3
|
|
// if (
|
|
// (style.zIndex !== "auto" && style.position !== "static") ||
|
|
// is_flex_item(node)
|
|
// )
|
|
if (
|
|
style.zIndex !== "auto" &&
|
|
(style.position !== "static" || is_flex_item(node))
|
|
)
|
|
return true;
|
|
if (+style.opacity < 1) return true;
|
|
if ("transform" in style && style.transform !== "none") return true;
|
|
if ("webkitTransform" in style && style.webkitTransform !== "none")
|
|
return true;
|
|
if ("mixBlendMode" in style && style.mixBlendMode !== "normal") return true;
|
|
if ("filter" in style && style.filter !== "none") return true;
|
|
if ("webkitFilter" in style && style.webkitFilter !== "none") return true;
|
|
if ("isolation" in style && style.isolation === "isolate") return true;
|
|
if (props.test(style.willChange)) return true;
|
|
// @ts-expect-error
|
|
if (style.webkitOverflowScrolling === "touch") return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/** @param {HTMLElement[]} nodes */
|
|
function find_stacking_context(nodes: HTMLElement[]) {
|
|
let i = nodes.length;
|
|
|
|
while (i--) {
|
|
const node = nodes[i];
|
|
assert(node, "Missing node");
|
|
if (creates_stacking_context(node)) return node;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/** @param {HTMLElement} node */
|
|
function get_z_index(node: HTMLElement | null) {
|
|
return (node && Number(getComputedStyle(node).zIndex)) || 0;
|
|
}
|
|
|
|
/** @param {HTMLElement} node */
|
|
function get_ancestors(node: HTMLElement | null) {
|
|
const ancestors = [];
|
|
|
|
while (node) {
|
|
ancestors.push(node);
|
|
// @ts-ignore
|
|
node = get_parent(node);
|
|
}
|
|
|
|
return ancestors; // [ node, ... <body>, <html>, document ]
|
|
}
|
|
|
|
/** @param {HTMLElement} node */
|
|
function get_parent(node: HTMLElement) {
|
|
const { parentNode } = node;
|
|
if (parentNode && parentNode instanceof ShadowRoot) {
|
|
return parentNode.host
|
|
}
|
|
return parentNode;
|
|
}
|