From 4173db72d065e7b1d88a4f55e313191705489238 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 14 Feb 2024 18:12:42 -0500 Subject: [PATCH] Fix Flex stacking context bug 've proposed an upstream fix for this at Rich-Harris/stacking-order#4 For now, this commit inlines that dependency and applies the fix --- declaration.d.ts | 4 - .../tests/StackingOrder.spec.ts | 8 +- packages/react-resizable-panels/package.json | 3 - .../src/PanelResizeHandleRegistry.ts | 2 +- .../src/vendor/stacking-order.ts | 130 ++++++++++++++++++ pnpm-lock.yaml | 8 -- 6 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 packages/react-resizable-panels/src/vendor/stacking-order.ts diff --git a/declaration.d.ts b/declaration.d.ts index f960d5c3d..d0043f383 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -2,7 +2,3 @@ declare module "*.module.css" { const content: Record; export default content; } - -declare module "stacking-order" { - export function compare(a: Element, b: Element): number; -} diff --git a/packages/react-resizable-panels-website/tests/StackingOrder.spec.ts b/packages/react-resizable-panels-website/tests/StackingOrder.spec.ts index ec0bca5bf..4ccb469a8 100644 --- a/packages/react-resizable-panels-website/tests/StackingOrder.spec.ts +++ b/packages/react-resizable-panels-website/tests/StackingOrder.spec.ts @@ -1,9 +1,13 @@ import { Page, expect, test } from "@playwright/test"; import { createElement } from "react"; -import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; +import { + Panel, + PanelGroup, + PanelResizeHandle, + assert, +} from "react-resizable-panels"; import { goToUrl } from "./utils/url"; -import assert from "assert"; import { getBodyCursorStyle } from "./utils/cursor"; test.describe("stacking order", () => { diff --git a/packages/react-resizable-panels/package.json b/packages/react-resizable-panels/package.json index 2b407cc1d..4780f29a3 100644 --- a/packages/react-resizable-panels/package.json +++ b/packages/react-resizable-panels/package.json @@ -66,9 +66,6 @@ "test:watch": "jest --config=jest.config.js --watch", "watch": "parcel watch --port=2345" }, - "dependencies": { - "stacking-order": "^1" - }, "devDependencies": { "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", "@babel/plugin-proposal-optional-chaining": "7.21.0", diff --git a/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts b/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts index 34211e856..c666d6dc6 100644 --- a/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts +++ b/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts @@ -1,9 +1,9 @@ -import { compare } from "stacking-order"; import { Direction, ResizeEvent } from "./types"; import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor"; import { getResizeEventCoordinates } from "./utils/events/getResizeEventCoordinates"; import { getInputType } from "./utils/getInputType"; import { intersects } from "./utils/rects/intersects"; +import { compare } from "./vendor/stacking-order"; export type ResizeHandlerAction = "down" | "move" | "up"; export type SetResizeHandlerState = ( diff --git a/packages/react-resizable-panels/src/vendor/stacking-order.ts b/packages/react-resizable-panels/src/vendor/stacking-order.ts new file mode 100644 index 000000000..1c59d56b4 --- /dev/null +++ b/packages/react-resizable-panels/src/vendor/stacking-order.ts @@ -0,0 +1,130 @@ +// Forked from NPM stacking-order@2.0.0 +// Background at https://github.com/Rich-Harris/stacking-order/issues/3 + +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); + + 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) { + const display = getComputedStyle(get_parent(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); + 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) { + const ancestors = []; + + while (node) { + ancestors.push(node); + node = get_parent(node); + } + + return ancestors; // [ node, ... , , document ] +} + +/** @param {HTMLElement} node */ +function get_parent(node: HTMLElement) { + // @ts-ignore + return node.parentNode?.host || node.parentNode; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f48604880..f430d46f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,10 +77,6 @@ importers: version: 5.1.6 packages/react-resizable-panels: - dependencies: - stacking-order: - specifier: ^1 - version: 1.0.0 devDependencies: '@babel/plugin-proposal-nullish-coalescing-operator': specifier: 7.18.6 @@ -5464,10 +5460,6 @@ packages: escape-string-regexp: 2.0.0 dev: true - /stacking-order@1.0.0: - resolution: {integrity: sha512-aa/U/p5o6GoEPV1FUFSEhsK6SYI0lq97AQRi8jwIUBGNhKXEbW2tkOdUFplmg7GAJdPqKNuKOhBXHDvfiNDeBg==} - dev: false - /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'}