Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 27 additions & 21 deletions src/hooks/useAlign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,21 @@ function getAlignPoint(rect: Rect, points: Points) {
return { x, y };
}

function reversePoints(points: Points, index: number): string {
function reversePoints(points: Points, index: number): Points {
const reverseMap = {
t: 'b',
b: 't',
l: 'r',
r: 'l',
};

return points
.map((point, i) => {
if (i === index) {
return reverseMap[point] || 'c';
}
return point;
})
.join('');
const clone = [...points] as Points;
clone[index] = reverseMap[points[index]] || 'c';
return clone;
}
Comment on lines +72 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While this implementation is more efficient and correctly enables chained flips, it relies on a type assertion (as Points) and the structure of reverseMap to maintain type safety, which might not be immediately obvious. The assignment clone[index] = reverseMap[points[index]] || 'c' is not strictly type-safe because TypeScript can't guarantee that (for example) a AlignPointLeftRight value isn't assigned to clone[0].

Consider a slightly more verbose but type-safe implementation that makes the logic clearer and safer by separating the vertical and horizontal reversal logic. This avoids potential issues if Points or the reversal logic were to change in the future.

Suggested change
function reversePoints(points: Points, index: number): Points {
const reverseMap = {
t: 'b',
b: 't',
l: 'r',
r: 'l',
};
return points
.map((point, i) => {
if (i === index) {
return reverseMap[point] || 'c';
}
return point;
})
.join('');
const clone = [...points] as Points;
clone[index] = reverseMap[points[index]] || 'c';
return clone;
}
function reversePoints(points: Points, index: number): Points {
const clone: Points = [...points];
if (index === 0) {
const verticalMap: Record<AlignPointTopBottom, AlignPointTopBottom> = { t: 'b', b: 't', c: 'c' };
clone[0] = verticalMap[clone[0]];
} else {
const horizontalMap: Record<AlignPointLeftRight, AlignPointLeftRight> = { l: 'r', r: 'l', c: 'c' };
clone[1] = horizontalMap[clone[1]];
}
return clone;
}


function flatPoints(points: Points): string {
return points.join('');
}

export default function useAlign(
Expand Down Expand Up @@ -340,6 +339,8 @@ export default function useAlign(
...placementInfo,
};

let nextPoints = [popupPoints, targetPoints];

// Next Offset
let nextOffsetX = targetAlignPoint.x - popupAlignPoint.x + popupOffsetX;
let nextOffsetY = targetAlignPoint.y - popupAlignPoint.y + popupOffsetY;
Expand Down Expand Up @@ -450,9 +451,9 @@ export default function useAlign(
nextOffsetY = tmpNextOffsetY;
popupOffsetY = -popupOffsetY;

nextAlignInfo.points = [
reversePoints(popupPoints, 0),
reversePoints(targetPoints, 0),
nextPoints = [
reversePoints(nextPoints[0], 0),
reversePoints(nextPoints[1], 0),
];
} else {
prevFlipRef.current.bt = false;
Expand Down Expand Up @@ -496,9 +497,9 @@ export default function useAlign(
nextOffsetY = tmpNextOffsetY;
popupOffsetY = -popupOffsetY;

nextAlignInfo.points = [
reversePoints(popupPoints, 0),
reversePoints(targetPoints, 0),
nextPoints = [
reversePoints(nextPoints[0], 0),
reversePoints(nextPoints[1], 0),
];
} else {
prevFlipRef.current.tb = false;
Expand Down Expand Up @@ -549,9 +550,9 @@ export default function useAlign(
nextOffsetX = tmpNextOffsetX;
popupOffsetX = -popupOffsetX;

nextAlignInfo.points = [
reversePoints(popupPoints, 1),
reversePoints(targetPoints, 1),
nextPoints = [
reversePoints(nextPoints[0], 1),
reversePoints(nextPoints[1], 1),
];
} else {
prevFlipRef.current.rl = false;
Expand Down Expand Up @@ -595,15 +596,20 @@ export default function useAlign(
nextOffsetX = tmpNextOffsetX;
popupOffsetX = -popupOffsetX;

nextAlignInfo.points = [
reversePoints(popupPoints, 1),
reversePoints(targetPoints, 1),
nextPoints = [
reversePoints(nextPoints[0], 1),
reversePoints(nextPoints[1], 1),
];
} else {
prevFlipRef.current.lr = false;
}
}

nextAlignInfo.points = [
flatPoints(nextPoints[0]),
flatPoints(nextPoints[1]),
];

// ============================ Shift ============================
syncNextPopupPosition();

Expand Down
11 changes: 5 additions & 6 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type {
AlignType,
BuildInPlacements,
} from './interface';
import type { AlignType, BuildInPlacements } from './interface';

function isPointsEq(
a1: string[] = [],
a2: string[] = [],
isAlignPoint: boolean,
): boolean {
const getVal = (a: string[], index: number) => a[index] || '';

if (isAlignPoint) {
return a1[0] === a2[0];
return getVal(a1, 0) === getVal(a2, 0);
}
return a1[0] === a2[0] && a1[1] === a2[1];
return getVal(a1, 0) === getVal(a2, 0) && getVal(a1, 1) === getVal(a2, 1);
}

export function getAlignPopupClassName(
Expand Down
37 changes: 37 additions & 0 deletions tests/align.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,41 @@ describe('Trigger.Align', () => {
top: `56px`,
});
});

it('both adjustX and adjustY should get correct points', async () => {
// Set target position to top left corner to force flip to bottom right
rectX = 0;
rectY = 0;
rectWidth = 100;
rectHeight = 100;

const onPopupAlign = jest.fn();

render(
<Trigger
popupVisible
popup={<span className="bamboo" />}
popupAlign={{
points: ['tl', 'bl'],
overflow: {
adjustX: true,
adjustY: true,
},
}}
onPopupAlign={onPopupAlign}
>
<div />
</Trigger>,
);

await awaitFakeTimer();

// Check that the points have been flipped correctly
expect(onPopupAlign).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
points: ['br', 'tr'],
}),
);
});
});
Loading