setSafeHoverPolygons([])}>
+
setPolygons([])}>
Safe hover popup
Move through the gap to reach me.
-
}
>
@@ -126,10 +120,9 @@ const SafeHoverDemo = () => {
onMouseLeave={updateSafeHoverPolygons}
onPointerLeave={updateSafeHoverPolygons}
>
- Hover target
+ trigger
-
{
const [x, y] = point;
let isInside = false;
@@ -23,63 +26,57 @@ function isPointInPolygon(point: SafeHoverPoint, polygon: SafeHoverPoint[]) {
}
return isInside;
-}
+};
-function isPointInRect(point: SafeHoverPoint, rect: SafeHoverRect) {
+export const isPointInRect = (point: SafeHoverPoint, rect: SafeHoverRect) => {
return (
point[0] >= rect.left &&
point[0] <= rect.right &&
point[1] >= rect.top &&
point[1] <= rect.bottom
);
-}
+};
-export function getSafeHoverSide(
+export const getSafeHoverSide = (
targetRect: SafeHoverRect,
popupRect: SafeHoverRect,
-): SafeHoverSide | null {
+): SafeHoverSide | null => {
const gaps: { side: SafeHoverSide; value: number }[] = [
{ side: 'top', value: targetRect.top - popupRect.bottom },
{ side: 'bottom', value: popupRect.top - targetRect.bottom },
{ side: 'left', value: targetRect.left - popupRect.right },
{ side: 'right', value: popupRect.left - targetRect.right },
];
-
const largestGap = gaps.reduce((prev, next) =>
next.value > prev.value ? next : prev,
);
-
return largestGap.value > 0 ? largestGap.side : null;
-}
+};
-function isLeavePointTowardsPopup(
+const isLeavePointTowardsPopup = (
side: SafeHoverSide,
leavePoint: SafeHoverPoint,
targetRect: SafeHoverRect,
-) {
+) => {
const [x, y] = leavePoint;
-
switch (side) {
case 'top':
return y <= targetRect.top + 1;
-
case 'bottom':
return y >= targetRect.bottom - 1;
-
case 'left':
return x <= targetRect.left + 1;
-
case 'right':
return x >= targetRect.right - 1;
}
-}
+};
-function getSafeHoverGapPolygon(
+const getSafeHoverGapPolygon = (
side: SafeHoverSide,
targetRect: SafeHoverRect,
popupRect: SafeHoverRect,
buffer: number,
-): SafeHoverPoint[] {
+): SafeHoverPoint[] => {
const verticalRect =
popupRect.width > targetRect.width ? targetRect : popupRect;
const horizontalRect =
@@ -88,7 +85,6 @@ function getSafeHoverGapPolygon(
const right = verticalRect.right + buffer;
const top = horizontalRect.top - buffer;
const bottom = horizontalRect.bottom + buffer;
-
switch (side) {
case 'top':
return [
@@ -97,7 +93,6 @@ function getSafeHoverGapPolygon(
[right, targetRect.top + 1],
[right, popupRect.bottom - 1],
];
-
case 'bottom':
return [
[left, targetRect.bottom - 1],
@@ -105,7 +100,6 @@ function getSafeHoverGapPolygon(
[right, popupRect.top + 1],
[right, targetRect.bottom - 1],
];
-
case 'left':
return [
[popupRect.right - 1, top],
@@ -113,7 +107,6 @@ function getSafeHoverGapPolygon(
[targetRect.left + 1, bottom],
[targetRect.left + 1, top],
];
-
case 'right':
return [
[targetRect.right - 1, top],
@@ -122,14 +115,14 @@ function getSafeHoverGapPolygon(
[popupRect.left + 1, top],
];
}
-}
+};
-function getSafeHoverIntentPolygon(
+const getSafeHoverIntentPolygon = (
side: SafeHoverSide,
leavePoint: SafeHoverPoint,
popupRect: SafeHoverRect,
buffer: number,
-): SafeHoverPoint[] {
+): SafeHoverPoint[] => {
switch (side) {
case 'top':
return [
@@ -137,21 +130,18 @@ function getSafeHoverIntentPolygon(
[popupRect.left - buffer, popupRect.bottom + buffer],
[popupRect.right + buffer, popupRect.bottom + buffer],
];
-
case 'bottom':
return [
leavePoint,
[popupRect.right + buffer, popupRect.top - buffer],
[popupRect.left - buffer, popupRect.top - buffer],
];
-
case 'left':
return [
leavePoint,
[popupRect.right + buffer, popupRect.bottom + buffer],
[popupRect.right + buffer, popupRect.top - buffer],
];
-
case 'right':
return [
leavePoint,
@@ -159,33 +149,32 @@ function getSafeHoverIntentPolygon(
[popupRect.left - buffer, popupRect.bottom + buffer],
];
}
-}
+};
-export function getSafeHoverAreaPolygons(
+export const getSafeHoverAreaPolygons = (
leavePoint: SafeHoverPoint,
targetRect: SafeHoverRect,
popupRect: SafeHoverRect,
buffer = 0.5,
-) {
+) => {
const side = getSafeHoverSide(targetRect, popupRect);
if (!side || !isLeavePointTowardsPopup(side, leavePoint, targetRect)) {
return [];
}
-
return [
getSafeHoverGapPolygon(side, targetRect, popupRect, buffer),
getSafeHoverIntentPolygon(side, leavePoint, popupRect, buffer),
];
-}
+};
-export function isPointInSafeHoverArea(
+export const isPointInSafeHoverArea = (
point: SafeHoverPoint,
leavePoint: SafeHoverPoint,
targetRect: SafeHoverRect,
popupRect: SafeHoverRect,
buffer = 0.5,
-) {
+) => {
const safeHoverPolygons = getSafeHoverAreaPolygons(
leavePoint,
targetRect,
@@ -204,4 +193,4 @@ export function isPointInSafeHoverArea(
// The gap polygon keeps the straight corridor open; the intent polygon
// catches diagonal movement toward the popup edge.
return safeHoverPolygons.some((polygon) => isPointInPolygon(point, polygon));
-}
+};
diff --git a/typings.d.ts b/typings.d.ts
new file mode 100644
index 00000000..1ea39606
--- /dev/null
+++ b/typings.d.ts
@@ -0,0 +1 @@
+declare module '*.less';
From 6ba40acd20c99a50d6c9d4fbd7b94e584862b65b Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Mon, 8 Jun 2026 17:59:15 +0800
Subject: [PATCH 4/4] update
---
src/index.tsx | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/index.tsx b/src/index.tsx
index a95b58db..673d5bfa 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -434,7 +434,6 @@ export function generateTrigger(
if (safeHover) {
safeHover.doc.removeEventListener('mousemove', safeHover.handler);
- safeHover.doc.removeEventListener('pointermove', safeHover.handler);
if (safeHover.refreshTimer) {
clearTimeout(safeHover.refreshTimer);
@@ -448,7 +447,12 @@ export function generateTrigger(
(
event: React.MouseEvent | React.PointerEvent,
) => {
- if (!targetEle || !popupEle || !openRef.current) {
+ if (
+ !targetEle ||
+ !popupEle ||
+ !openRef.current ||
+ mouseLeaveDelay <= 0
+ ) {
return false;
}
@@ -511,12 +515,8 @@ export function generateTrigger(
};
doc.addEventListener('mousemove', handler);
- doc.addEventListener('pointermove', handler);
- safeHoverRef.current = {
- doc,
- handler,
- refreshTimer: null,
- };
+
+ safeHoverRef.current = { doc, handler, refreshTimer: null };
triggerOpen(false, mouseLeaveDelay);
scheduleRefresh();