Skip to content

Commit ba5359b

Browse files
committed
update
1 parent 4a69bd2 commit ba5359b

3 files changed

Lines changed: 55 additions & 72 deletions

File tree

docs/examples/safe-hover.tsx

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import Trigger, { type TriggerRef } from '@rc-component/trigger';
2-
import React from 'react';
3-
import {
4-
getSafeHoverAreaPolygons,
5-
type SafeHoverPoint,
6-
} from '../../src/util/safeHover';
1+
import Trigger from '@rc-component/trigger';
2+
import type { TriggerRef } from '@rc-component/trigger';
3+
import React, { useState } from 'react';
4+
import { getSafeHoverAreaPolygons } from '../../src/util/safeHover';
5+
import type { SafeHoverPoint } from '../../src/util/safeHover';
76
import '../../assets/index.less';
87

9-
type SafeHoverPolygon = {
8+
interface SafeHoverPolygon {
109
points: SafeHoverPoint[];
1110
fill: string;
1211
stroke: string;
13-
};
12+
}
1413

1514
const safeHoverPolygonStyles = [
1615
{
@@ -38,12 +37,10 @@ const popupStyle: React.CSSProperties = {
3837
boxShadow: '0 6px 16px rgba(0, 0, 0, 0.12)',
3938
};
4039

41-
const SafeHoverDemo = () => {
40+
const SafeHoverDemo: React.FC = () => {
4241
const triggerRef = React.useRef<TriggerRef>(null);
4342

44-
const [safeHoverPolygons, setSafeHoverPolygons] = React.useState<
45-
SafeHoverPolygon[]
46-
>([]);
43+
const [safeHoverPolygons, setPolygons] = useState<SafeHoverPolygon[]>([]);
4744

4845
const updateSafeHoverPolygons = (
4946
event: React.MouseEvent<HTMLElement> | React.PointerEvent<HTMLElement>,
@@ -52,20 +49,18 @@ const SafeHoverDemo = () => {
5249
const popup = triggerRef.current?.popupElement;
5350

5451
if (!target || !popup) {
55-
setSafeHoverPolygons([]);
52+
setPolygons([]);
5653
return;
5754
}
5855

5956
const leavePoint: SafeHoverPoint = [event.clientX, event.clientY];
60-
setSafeHoverPolygons(
57+
58+
setPolygons(
6159
getSafeHoverAreaPolygons(
6260
leavePoint,
6361
target.getBoundingClientRect(),
6462
popup.getBoundingClientRect(),
65-
).map((points, index) => ({
66-
points,
67-
...safeHoverPolygonStyles[index],
68-
})),
63+
).map((points, i) => ({ points, ...safeHoverPolygonStyles[i] })),
6964
);
7065
};
7166

@@ -83,17 +78,19 @@ const SafeHoverDemo = () => {
8378
zIndex: 999,
8479
}}
8580
>
86-
{safeHoverPolygons.map(({ points, fill, stroke }, index) => (
87-
<polygon
88-
// eslint-disable-next-line react/no-array-index-key
89-
key={index}
90-
points={points.map((point) => point.join(',')).join(' ')}
91-
fill={fill}
92-
stroke={stroke}
93-
strokeDasharray="4 3"
94-
strokeWidth={1}
95-
/>
96-
))}
81+
{safeHoverPolygons.map(({ points, fill, stroke }, index) => {
82+
return (
83+
<polygon
84+
// eslint-disable-next-line react/no-array-index-key
85+
key={`polygon-${index}`}
86+
points={points.map((point) => point.join(',')).join(' ')}
87+
fill={fill}
88+
stroke={stroke}
89+
strokeDasharray="4 3"
90+
strokeWidth={1}
91+
/>
92+
);
93+
})}
9794
</svg>
9895
)}
9996
<Trigger
@@ -105,18 +102,15 @@ const SafeHoverDemo = () => {
105102
popupStyle={popupStyle}
106103
onOpenChange={(nextOpen) => {
107104
if (!nextOpen) {
108-
setSafeHoverPolygons([]);
105+
setPolygons([]);
109106
}
110107
}}
111108
popup={
112-
<div onMouseEnter={() => setSafeHoverPolygons([])}>
109+
<div onMouseEnter={() => setPolygons([])}>
113110
<strong>Safe hover popup</strong>
114111
<div style={{ marginTop: 8 }}>
115112
Move through the gap to reach me.
116113
</div>
117-
<button style={{ marginTop: 12 }} type="button">
118-
Popup action
119-
</button>
120114
</div>
121115
}
122116
>
@@ -126,10 +120,9 @@ const SafeHoverDemo = () => {
126120
onMouseLeave={updateSafeHoverPolygons}
127121
onPointerLeave={updateSafeHoverPolygons}
128122
>
129-
Hover target
123+
trigger
130124
</button>
131125
</Trigger>
132-
133126
<div
134127
style={{
135128
width: 240,

src/util/safeHover.ts

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ export type SafeHoverRect = Pick<
77

88
export type SafeHoverSide = 'top' | 'bottom' | 'left' | 'right';
99

10-
function isPointInPolygon(point: SafeHoverPoint, polygon: SafeHoverPoint[]) {
10+
export const isPointInPolygon = (
11+
point: SafeHoverPoint,
12+
polygon: SafeHoverPoint[],
13+
) => {
1114
const [x, y] = point;
1215
let isInside = false;
1316

@@ -23,63 +26,57 @@ function isPointInPolygon(point: SafeHoverPoint, polygon: SafeHoverPoint[]) {
2326
}
2427

2528
return isInside;
26-
}
29+
};
2730

28-
function isPointInRect(point: SafeHoverPoint, rect: SafeHoverRect) {
31+
export const isPointInRect = (point: SafeHoverPoint, rect: SafeHoverRect) => {
2932
return (
3033
point[0] >= rect.left &&
3134
point[0] <= rect.right &&
3235
point[1] >= rect.top &&
3336
point[1] <= rect.bottom
3437
);
35-
}
38+
};
3639

37-
export function getSafeHoverSide(
40+
export const getSafeHoverSide = (
3841
targetRect: SafeHoverRect,
3942
popupRect: SafeHoverRect,
40-
): SafeHoverSide | null {
43+
): SafeHoverSide | null => {
4144
const gaps: { side: SafeHoverSide; value: number }[] = [
4245
{ side: 'top', value: targetRect.top - popupRect.bottom },
4346
{ side: 'bottom', value: popupRect.top - targetRect.bottom },
4447
{ side: 'left', value: targetRect.left - popupRect.right },
4548
{ side: 'right', value: popupRect.left - targetRect.right },
4649
];
47-
4850
const largestGap = gaps.reduce((prev, next) =>
4951
next.value > prev.value ? next : prev,
5052
);
51-
5253
return largestGap.value > 0 ? largestGap.side : null;
53-
}
54+
};
5455

55-
function isLeavePointTowardsPopup(
56+
const isLeavePointTowardsPopup = (
5657
side: SafeHoverSide,
5758
leavePoint: SafeHoverPoint,
5859
targetRect: SafeHoverRect,
59-
) {
60+
) => {
6061
const [x, y] = leavePoint;
61-
6262
switch (side) {
6363
case 'top':
6464
return y <= targetRect.top + 1;
65-
6665
case 'bottom':
6766
return y >= targetRect.bottom - 1;
68-
6967
case 'left':
7068
return x <= targetRect.left + 1;
71-
7269
case 'right':
7370
return x >= targetRect.right - 1;
7471
}
75-
}
72+
};
7673

77-
function getSafeHoverGapPolygon(
74+
const getSafeHoverGapPolygon = (
7875
side: SafeHoverSide,
7976
targetRect: SafeHoverRect,
8077
popupRect: SafeHoverRect,
8178
buffer: number,
82-
): SafeHoverPoint[] {
79+
): SafeHoverPoint[] => {
8380
const verticalRect =
8481
popupRect.width > targetRect.width ? targetRect : popupRect;
8582
const horizontalRect =
@@ -88,7 +85,6 @@ function getSafeHoverGapPolygon(
8885
const right = verticalRect.right + buffer;
8986
const top = horizontalRect.top - buffer;
9087
const bottom = horizontalRect.bottom + buffer;
91-
9288
switch (side) {
9389
case 'top':
9490
return [
@@ -97,23 +93,20 @@ function getSafeHoverGapPolygon(
9793
[right, targetRect.top + 1],
9894
[right, popupRect.bottom - 1],
9995
];
100-
10196
case 'bottom':
10297
return [
10398
[left, targetRect.bottom - 1],
10499
[left, popupRect.top + 1],
105100
[right, popupRect.top + 1],
106101
[right, targetRect.bottom - 1],
107102
];
108-
109103
case 'left':
110104
return [
111105
[popupRect.right - 1, top],
112106
[popupRect.right - 1, bottom],
113107
[targetRect.left + 1, bottom],
114108
[targetRect.left + 1, top],
115109
];
116-
117110
case 'right':
118111
return [
119112
[targetRect.right - 1, top],
@@ -122,70 +115,66 @@ function getSafeHoverGapPolygon(
122115
[popupRect.left + 1, top],
123116
];
124117
}
125-
}
118+
};
126119

127-
function getSafeHoverIntentPolygon(
120+
const getSafeHoverIntentPolygon = (
128121
side: SafeHoverSide,
129122
leavePoint: SafeHoverPoint,
130123
popupRect: SafeHoverRect,
131124
buffer: number,
132-
): SafeHoverPoint[] {
125+
): SafeHoverPoint[] => {
133126
switch (side) {
134127
case 'top':
135128
return [
136129
leavePoint,
137130
[popupRect.left - buffer, popupRect.bottom + buffer],
138131
[popupRect.right + buffer, popupRect.bottom + buffer],
139132
];
140-
141133
case 'bottom':
142134
return [
143135
leavePoint,
144136
[popupRect.right + buffer, popupRect.top - buffer],
145137
[popupRect.left - buffer, popupRect.top - buffer],
146138
];
147-
148139
case 'left':
149140
return [
150141
leavePoint,
151142
[popupRect.right + buffer, popupRect.bottom + buffer],
152143
[popupRect.right + buffer, popupRect.top - buffer],
153144
];
154-
155145
case 'right':
156146
return [
157147
leavePoint,
158148
[popupRect.left - buffer, popupRect.top - buffer],
159149
[popupRect.left - buffer, popupRect.bottom + buffer],
160150
];
161151
}
162-
}
152+
};
163153

164-
export function getSafeHoverAreaPolygons(
154+
export const getSafeHoverAreaPolygons = (
165155
leavePoint: SafeHoverPoint,
166156
targetRect: SafeHoverRect,
167157
popupRect: SafeHoverRect,
168158
buffer = 0.5,
169-
) {
159+
) => {
170160
const side = getSafeHoverSide(targetRect, popupRect);
171161

172162
if (!side || !isLeavePointTowardsPopup(side, leavePoint, targetRect)) {
173163
return [];
174164
}
175-
176165
return [
177166
getSafeHoverGapPolygon(side, targetRect, popupRect, buffer),
178167
getSafeHoverIntentPolygon(side, leavePoint, popupRect, buffer),
179168
];
180-
}
169+
};
181170

182-
export function isPointInSafeHoverArea(
171+
export const isPointInSafeHoverArea = (
183172
point: SafeHoverPoint,
184173
leavePoint: SafeHoverPoint,
185174
targetRect: SafeHoverRect,
186175
popupRect: SafeHoverRect,
187176
buffer = 0.5,
188-
) {
177+
) => {
189178
const safeHoverPolygons = getSafeHoverAreaPolygons(
190179
leavePoint,
191180
targetRect,
@@ -204,4 +193,4 @@ export function isPointInSafeHoverArea(
204193
// The gap polygon keeps the straight corridor open; the intent polygon
205194
// catches diagonal movement toward the popup edge.
206195
return safeHoverPolygons.some((polygon) => isPointInPolygon(point, polygon));
207-
}
196+
};

typings.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module '*.less';

0 commit comments

Comments
 (0)