Skip to content

Commit

Permalink
fix: Only flip if space is enough (#343)
Browse files Browse the repository at this point in the history
* chore: improve check logic only flip when has more space

* test: add test case
  • Loading branch information
zombieJ authored Mar 16, 2023
1 parent 4eefd66 commit 355cd8c
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 25 deletions.
2 changes: 1 addition & 1 deletion docs/examples/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const builtinPlacements = {
},
};

const popupPlacement = 'bottom';
const popupPlacement = 'right';

export default () => {
console.log('Demo Render!');
Expand Down
103 changes: 79 additions & 24 deletions src/hooks/useAlign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,25 @@ export default function useAlign(
let nextOffsetX = targetAlignPoint.x - popupAlignPoint.x + popupOffsetX;
let nextOffsetY = targetAlignPoint.y - popupAlignPoint.y + popupOffsetY;

// ============== Intersection ===============
// Get area by position. Used for check if flip area is better
function getIntersectionVisibleArea(x: number, y: number) {
const r = x + popupWidth;
const b = y + popupHeight;

const visibleX = Math.max(x, visibleArea.left);
const visibleY = Math.max(y, visibleArea.top);
const visibleR = Math.min(r, visibleArea.right);
const visibleB = Math.min(b, visibleArea.bottom);

return (visibleR - visibleX) * (visibleB - visibleY);
}

const originIntersectionVisibleArea = getIntersectionVisibleArea(
nextOffsetX,
nextOffsetY,
);

// ================ Overflow =================
const targetAlignPointTL = getAlignPoint(targetRect, ['t', 'l']);
const popupAlignPointTL = getAlignPoint(popupRect, ['t', 'l']);
Expand Down Expand Up @@ -297,17 +316,26 @@ export default function useAlign(
popupPoints[0] === 't' &&
nextPopupBottom > visibleArea.bottom
) {
let tmpNextOffsetY: number;

if (sameTB) {
nextOffsetY -= popupHeight - targetHeight;
tmpNextOffsetY -= popupHeight - targetHeight;
} else {
nextOffsetY =
tmpNextOffsetY =
targetAlignPointTL.y - popupAlignPointBR.y - popupOffsetY;
}

nextAlignInfo.points = [
reversePoints(popupPoints, 0),
reversePoints(targetPoints, 0),
];
if (
getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY) >
originIntersectionVisibleArea
) {
nextOffsetY = tmpNextOffsetY;

nextAlignInfo.points = [
reversePoints(popupPoints, 0),
reversePoints(targetPoints, 0),
];
}
}

// Top to Bottom
Expand All @@ -316,17 +344,26 @@ export default function useAlign(
popupPoints[0] === 'b' &&
nextPopupY < visibleArea.top
) {
let tmpNextOffsetY: number;

if (sameTB) {
nextOffsetY += popupHeight - targetHeight;
tmpNextOffsetY += popupHeight - targetHeight;
} else {
nextOffsetY =
tmpNextOffsetY =
targetAlignPointBR.y - popupAlignPointTL.y - popupOffsetY;
}

nextAlignInfo.points = [
reversePoints(popupPoints, 0),
reversePoints(targetPoints, 0),
];
if (
getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY) >
originIntersectionVisibleArea
) {
nextOffsetY = tmpNextOffsetY;

nextAlignInfo.points = [
reversePoints(popupPoints, 0),
reversePoints(targetPoints, 0),
];
}
}

// >>>>>>>>>> Left & Right
Expand All @@ -344,17 +381,26 @@ export default function useAlign(
popupPoints[1] === 'l' &&
nextPopupRight > visibleArea.right
) {
let tmpNextOffsetX: number;

if (sameLR) {
nextOffsetX -= popupWidth - targetWidth;
tmpNextOffsetX -= popupWidth - targetWidth;
} else {
nextOffsetX =
tmpNextOffsetX =
targetAlignPointTL.x - popupAlignPointBR.x - popupOffsetX;
}

nextAlignInfo.points = [
reversePoints(popupPoints, 1),
reversePoints(targetPoints, 1),
];
if (
getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY) >
originIntersectionVisibleArea
) {
nextOffsetX = tmpNextOffsetX;

nextAlignInfo.points = [
reversePoints(popupPoints, 1),
reversePoints(targetPoints, 1),
];
}
}

// Left to Right
Expand All @@ -363,17 +409,26 @@ export default function useAlign(
popupPoints[1] === 'r' &&
nextPopupX < visibleArea.left
) {
let tmpNextOffsetX: number;

if (sameLR) {
nextOffsetX += popupWidth - targetWidth;
tmpNextOffsetX += popupWidth - targetWidth;
} else {
nextOffsetX =
tmpNextOffsetX =
targetAlignPointBR.x - popupAlignPointTL.x - popupOffsetX;
}

nextAlignInfo.points = [
reversePoints(popupPoints, 1),
reversePoints(targetPoints, 1),
];
if (
getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY) >
originIntersectionVisibleArea
) {
nextOffsetX = tmpNextOffsetX;

nextAlignInfo.points = [
reversePoints(popupPoints, 1),
reversePoints(targetPoints, 1),
];
}
}

// >>>>> Shift
Expand Down
190 changes: 190 additions & 0 deletions tests/flip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { act, cleanup, render } from '@testing-library/react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import Trigger from '../src';

const builtinPlacements = {
top: {
points: ['bc', 'tc'],
overflow: {
adjustX: true,
adjustY: true,
},
},
bottom: {
points: ['tc', 'bc'],
overflow: {
adjustX: true,
adjustY: true,
},
},
left: {
points: ['cr', 'cl'],
overflow: {
adjustX: true,
adjustY: true,
},
},
right: {
points: ['cl', 'cr'],
overflow: {
adjustX: true,
adjustY: true,
},
},
};

describe('Trigger.Align', () => {
let spanRect = {
x: 0,
y: 0,
width: 1,
height: 1,
};

beforeAll(() => {
spyElementPrototypes(HTMLElement, {
clientWidth: {
get: () => 100,
},
clientHeight: {
get: () => 100,
},
});

spyElementPrototypes(HTMLDivElement, {
getBoundingClientRect() {
return {
x: 0,
y: 0,
width: 100,
height: 100,
};
},
});

spyElementPrototypes(HTMLSpanElement, {
getBoundingClientRect() {
return spanRect;
},
});

spyElementPrototypes(HTMLElement, {
offsetParent: {
get: () => document.body,
},
});
});

beforeEach(() => {
spanRect = {
x: 0,
y: 0,
width: 1,
height: 1,
};
jest.useFakeTimers();
});

afterEach(() => {
cleanup();
jest.useRealTimers();
});

describe('not flip if cant', () => {
const list = [
{
placement: 'right',
x: 10,
className: '.rc-trigger-popup-placement-right',
},
{
placement: 'left',
x: 90,
className: '.rc-trigger-popup-placement-left',
},
{
placement: 'top',
y: 90,
className: '.rc-trigger-popup-placement-top',
},
{
placement: 'bottom',
y: 10,
className: '.rc-trigger-popup-placement-bottom',
},
];

list.forEach(({ placement, x = 0, y = 0, className }) => {
it(placement, async () => {
spanRect.x = x;
spanRect.y = y;

render(
<Trigger
popupVisible
popupPlacement={placement}
builtinPlacements={builtinPlacements}
popup={<strong>trigger</strong>}
>
<span className="target" />
</Trigger>,
);

await act(async () => {
await Promise.resolve();
});

expect(document.querySelector(className)).toBeTruthy();
});
});
});

describe('flip if can', () => {
const list = [
{
placement: 'right',
x: 90,
className: '.rc-trigger-popup-placement-left',
},
{
placement: 'left',
x: 10,
className: '.rc-trigger-popup-placement-right',
},
{
placement: 'top',
y: 10,
className: '.rc-trigger-popup-placement-bottom',
},
{
placement: 'bottom',
y: 90,
className: '.rc-trigger-popup-placement-top',
},
];

list.forEach(({ placement, x = 0, y = 0, className }) => {
it(placement, async () => {
spanRect.x = x;
spanRect.y = y;

render(
<Trigger
popupVisible
popupPlacement={placement}
builtinPlacements={builtinPlacements}
popup={<strong>trigger</strong>}
>
<span className="target" />
</Trigger>,
);

await act(async () => {
await Promise.resolve();
});

expect(document.querySelector(className)).toBeTruthy();
});
});
});
});

0 comments on commit 355cd8c

Please sign in to comment.