Skip to content

Commit

Permalink
fix: Area cal should skip static parent (#352)
Browse files Browse the repository at this point in the history
* refactor: extract area cal

* docs: add demo

* chore: align fix

* test: fin test case
  • Loading branch information
zombieJ authored Apr 7, 2023
1 parent 41b40a4 commit b784f8f
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 37 deletions.
8 changes: 8 additions & 0 deletions docs/demos/static-scroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Static Scroll
nav:
title: Demo
path: /demo
---

<code src="../examples/static-scroll.tsx"></code>
2 changes: 1 addition & 1 deletion docs/examples/inside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import '../../assets/index.less';
import Trigger from '../../src';

const builtinPlacements = {
export const builtinPlacements = {
top: {
points: ['bc', 'tc'],
overflow: {
Expand Down
64 changes: 64 additions & 0 deletions docs/examples/static-scroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint no-console:0 */
import Trigger from 'rc-trigger';
import React from 'react';
import '../../assets/index.less';
import { builtinPlacements } from './inside';

export default () => {
return (
<React.StrictMode>
<div
style={{
background: 'rgba(0, 0, 255, 0.1)',
margin: `64px`,
height: 200,
overflow: 'auto',
// Must have for test
position: 'static',
}}
>
<Trigger
arrow
popup={
<div
style={{
background: 'yellow',
border: '1px solid blue',
width: 200,
height: 60,
opacity: 0.9,
}}
>
Popup
</div>
}
popupStyle={{ boxShadow: '0 0 5px red' }}
popupVisible
builtinPlacements={builtinPlacements}
popupPlacement="top"
stretch="minWidth"
getPopupContainer={(e) => e.parentElement!}
>
<span
style={{
background: 'green',
color: '#FFF',
paddingBlock: 30,
paddingInline: 70,
opacity: 0.9,
transform: 'scale(0.6)',
display: 'inline-block',
}}
>
Target
</span>
</Trigger>
{new Array(20).fill(null).map((_, index) => (
<h1 key={index} style={{ width: '200vw' }}>
Placeholder Line {index}
</h1>
))}
</div>
</React.StrictMode>
);
};
39 changes: 3 additions & 36 deletions src/hooks/useAlign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@ import type {
AlignPointTopBottom,
AlignType,
} from '../interface';
import { collectScroller, getWin } from '../util';
import { collectScroller, getVisibleArea, getWin, toNum } from '../util';

type Rect = Record<'x' | 'y' | 'width' | 'height', number>;

type Points = [topBottom: AlignPointTopBottom, leftRight: AlignPointLeftRight];

function toNum(num: number) {
return Number.isNaN(num) ? 1 : num;
}

function splitPoints(points: string = ''): Points {
return [points[0] as any, points[1] as any];
}
Expand Down Expand Up @@ -174,7 +170,7 @@ export default function useAlign(
const targetWidth = targetRect.width;

// Get bounding of visible area
const visibleArea =
let visibleArea =
placementInfo.htmlRegion === 'scroll'
? // Scroll region should take scrollLeft & scrollTop into account
{
Expand All @@ -190,36 +186,7 @@ export default function useAlign(
bottom: clientHeight,
};

(scrollerList || []).forEach((ele) => {
if (ele instanceof HTMLBodyElement) {
return;
}

const eleRect = ele.getBoundingClientRect();
const {
offsetHeight: eleOutHeight,
clientHeight: eleInnerHeight,
offsetWidth: eleOutWidth,
clientWidth: eleInnerWidth,
} = ele;

const scaleX = toNum(
Math.round((eleRect.width / eleOutWidth) * 1000) / 1000,
);
const scaleY = toNum(
Math.round((eleRect.height / eleOutHeight) * 1000) / 1000,
);

const eleScrollWidth = (eleOutWidth - eleInnerWidth) * scaleX;
const eleScrollHeight = (eleOutHeight - eleInnerHeight) * scaleY;
const eleRight = eleRect.x + eleRect.width - eleScrollWidth;
const eleBottom = eleRect.y + eleRect.height - eleScrollHeight;

visibleArea.left = Math.max(visibleArea.left, eleRect.left);
visibleArea.top = Math.max(visibleArea.top, eleRect.top);
visibleArea.right = Math.min(visibleArea.right, eleRight);
visibleArea.bottom = Math.min(visibleArea.bottom, eleBottom);
});
visibleArea = getVisibleArea(visibleArea, scrollerList);

// Reset back
popupElement.style.left = originLeft;
Expand Down
62 changes: 62 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ export function getWin(ele: HTMLElement) {
return ele.ownerDocument.defaultView;
}

/**
* Get all the scrollable parent elements of the element
* @param ele The element to be detected
* @param areaOnly Only return the parent which will cut visible area
*/
export function collectScroller(ele: HTMLElement) {
const scrollerList: HTMLElement[] = [];
let current = ele?.parentElement;
Expand All @@ -86,3 +91,60 @@ export function collectScroller(ele: HTMLElement) {

return scrollerList;
}

export function toNum(num: number) {
return Number.isNaN(num) ? 1 : num;
}

export interface VisibleArea {
left: number;
top: number;
right: number;
bottom: number;
}

export function getVisibleArea(
initArea: VisibleArea,
scrollerList?: HTMLElement[],
) {
const visibleArea = { ...initArea };

(scrollerList || []).forEach((ele) => {
if (ele instanceof HTMLBodyElement) {
return;
}

// Skip if static position which will not affect visible area
const { position } = getWin(ele).getComputedStyle(ele);
if (position === 'static') {
return;
}

const eleRect = ele.getBoundingClientRect();
const {
offsetHeight: eleOutHeight,
clientHeight: eleInnerHeight,
offsetWidth: eleOutWidth,
clientWidth: eleInnerWidth,
} = ele;

const scaleX = toNum(
Math.round((eleRect.width / eleOutWidth) * 1000) / 1000,
);
const scaleY = toNum(
Math.round((eleRect.height / eleOutHeight) * 1000) / 1000,
);

const eleScrollWidth = (eleOutWidth - eleInnerWidth) * scaleX;
const eleScrollHeight = (eleOutHeight - eleInnerHeight) * scaleY;
const eleRight = eleRect.x + eleRect.width - eleScrollWidth;
const eleBottom = eleRect.y + eleRect.height - eleScrollHeight;

visibleArea.left = Math.max(visibleArea.left, eleRect.x);
visibleArea.top = Math.max(visibleArea.top, eleRect.y);
visibleArea.right = Math.min(visibleArea.right, eleRight);
visibleArea.bottom = Math.min(visibleArea.bottom, eleBottom);
});

return visibleArea;
}
89 changes: 89 additions & 0 deletions tests/flip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { act, cleanup, render } from '@testing-library/react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import Trigger from '../src';
import { getVisibleArea } from '../src/util';

const builtinPlacements = {
top: {
Expand Down Expand Up @@ -270,4 +271,92 @@ describe('Trigger.Align', () => {
top: `0px`,
});
});

// Static parent should not affect popup position
// https://github.com/ant-design/ant-design/issues/41644
it('static parent should not affect popup position', async () => {
/*
********************
* *
* ************** *
* * Affect * *
* * ********** * *
* * * Not * * *
* * ********** * *
* *
* ************** *
* *
********************
*/
const initArea = {
left: 0,
right: 500,
top: 0,
bottom: 500,
};

// Affected area
const affectEle = document.createElement('div');
document.body.appendChild(affectEle);

affectEle.style.position = 'absolute';
Object.defineProperties(affectEle, {
offsetHeight: {
get: () => 300,
},
offsetWidth: {
get: () => 300,
},
clientHeight: {
get: () => 300,
},
clientWidth: {
get: () => 300,
},
});
affectEle.getBoundingClientRect = () =>
({
x: 100,
y: 100,
width: 300,
height: 300,
} as any);

// Skip area
const skipEle = document.createElement('div');
document.body.appendChild(skipEle);

skipEle.style.position = 'static';
Object.defineProperties(skipEle, {
offsetHeight: {
get: () => 100,
},
offsetWidth: {
get: () => 100,
},
clientHeight: {
get: () => 100,
},
clientWidth: {
get: () => 100,
},
});
skipEle.getBoundingClientRect = () =>
({
x: 200,
y: 200,
width: 100,
height: 100,
} as any);

const visibleArea = getVisibleArea(initArea, [affectEle, skipEle]);
expect(visibleArea).toEqual({
left: 100,
right: 400,
top: 100,
bottom: 400,
});
});
});

0 comments on commit b784f8f

Please sign in to comment.