Skip to content

Commit

Permalink
fix(getContainingBlock): detect top layer elements (#2944)
Browse files Browse the repository at this point in the history
Co-authored-by: GitHub Actions <github-actions@github.com>
  • Loading branch information
atomiks and GitHub Actions committed Jun 22, 2024
1 parent ccd638e commit d0e8027
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 15 deletions.
6 changes: 6 additions & 0 deletions .changeset/calm-rice-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@floating-ui/utils": patch
"@floating-ui/dom": patch
---

fix(getContainingBlock): detect top layer elements
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
getNodeScroll,
isHTMLElement,
isOverflowElement,
isTopLayer,
} from '@floating-ui/utils/dom';

import {getBoundingClientRect} from '../utils/getBoundingClientRect';
import {getScale} from './getScale';
import {isTopLayer} from '../utils/isTopLayer';

export function convertOffsetParentRelativeRectToViewportRelativeRect({
elements,
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/src/platform/getClippingRect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isHTMLElement,
isLastTraversableNode,
isOverflowElement,
isTopLayer,
} from '@floating-ui/utils/dom';

import type {Platform, ReferenceElement} from '../types';
Expand All @@ -26,7 +27,6 @@ import {getViewportRect} from '../utils/getViewportRect';
import {getVisualOffsets} from '../utils/getVisualOffsets';
import {getScale} from './getScale';
import {isElement} from './isElement';
import {isTopLayer} from '../utils/isTopLayer';

type PlatformWithCache = Platform & {
_c: Map<ReferenceElement, Element[]>;
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/src/platform/getOffsetParent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
isHTMLElement,
isLastTraversableNode,
isTableElement,
isTopLayer,
} from '@floating-ui/utils/dom';
import {isTopLayer} from '../utils/isTopLayer';
import {isStaticPositioned} from '../utils/isStaticPositioned';

type Polyfill = (element: HTMLElement) => Element | null;
Expand Down
11 changes: 0 additions & 11 deletions packages/dom/src/utils/isTopLayer.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/dom/test/functional/top-layer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,12 @@ import {click} from './utils/click';
);
});
});

test('fixed inside dialog with outer containing block', async ({page}) => {
await page.goto('http://localhost:1234/top-layer');
await click(page, '[data-testid="outer-true"]');

expect(await page.locator('#outer-dialog').screenshot()).toMatchSnapshot(
`top-layer.dialog.outer.png`,
);
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 45 additions & 1 deletion packages/dom/test/visual/spec/TopLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useFloating, autoUpdate, flip} from '@floating-ui/react-dom';
import {useState} from 'react';
import {useState, useCallback} from 'react';

import {Controls} from '../utils/Controls';

Expand All @@ -17,6 +17,32 @@ type Props = {
strategy: 'absolute' | 'fixed';
};

function OuterTopLayer() {
const {refs, floatingStyles} = useFloating({
strategy: 'fixed',
whileElementsMounted: autoUpdate,
});

const handleDialogRef = useCallback((node: HTMLDialogElement | null) => {
if (node) {
node.showModal();
}
}, []);

return (
<>
<div style={{containerType: 'inline-size'}}>
<dialog id="outer-dialog" ref={handleDialogRef}>
<button ref={refs.setReference}>My button</button>
<div ref={refs.setFloating} style={floatingStyles}>
My tooltip
</div>
</dialog>
</div>
</>
);
}

function NotStacked({children}: Props) {
return children;
}
Expand Down Expand Up @@ -147,6 +173,7 @@ export function TopLayer() {
const [withMargin, setWithMargin] = useState(false);
const [layoutStyles, setLayoutStyles] = useState(true);
const [strategy, setStrategy] = useState<'absolute' | 'fixed'>('fixed');
const [outer, setOuter] = useState(false);

const {refs, floatingStyles, x, y} = useFloating({
strategy,
Expand Down Expand Up @@ -194,6 +221,7 @@ export function TopLayer() {
>
Top Layer
</h1>
{outer && <OuterTopLayer />}
<Stack {...stackProps}>
<div
className={classes}
Expand Down Expand Up @@ -362,6 +390,22 @@ export function TopLayer() {
</button>
))}
</Controls>

<h2>outer</h2>
<Controls>
{BOOLS.map((bool) => (
<button
key={String(bool)}
data-testid={`outer-${bool}`}
onClick={() => setOuter(bool)}
style={{
backgroundColor: bool === outer ? 'black' : '',
}}
>
{String(bool)}
</button>
))}
</Controls>
</>
);
}
14 changes: 14 additions & 0 deletions packages/utils/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ export function isTableElement(element: Element): boolean {
return ['table', 'td', 'th'].includes(getNodeName(element));
}

export function isTopLayer(element: Element): boolean {
return [':popover-open', ':modal'].some((selector) => {
try {
return element.matches(selector);
} catch (e) {
return false;
}
});
}

export function isContainingBlock(element: Element): boolean {
const webkit = isWebKit();
const css = getComputedStyle(element);
Expand All @@ -82,6 +92,10 @@ export function getContainingBlock(element: Element): HTMLElement | null {
let currentNode: Node | null = getParentNode(element);

while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
if (isTopLayer(currentNode)) {
return null;
}

if (isContainingBlock(currentNode)) {
return currentNode;
}
Expand Down

0 comments on commit d0e8027

Please sign in to comment.