Skip to content

Commit 9b2f732

Browse files
committed
Refine mini widget hover intent to avoid fast-pass triggers
1 parent ffb503c commit 9b2f732

1 file changed

Lines changed: 28 additions & 2 deletions

File tree

client/userMiniWidget.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ interface CacheEntry {
6262
payload: MiniPayload;
6363
}
6464

65-
const HOVER_DELAY_MS = 110;
65+
const HOVER_DELAY_MS = 150;
66+
const HOVER_INTENT_POLL_MS = 150;
67+
const HOVER_INTENT_SENSITIVITY = 7;
6668
const HIDE_DELAY_MS = 130;
6769
const CACHE_TTL_MS = 8000;
6870

@@ -194,6 +196,7 @@ class UserMiniWidget {
194196
init() {
195197
document.body.addEventListener('mouseover', this.onBodyMouseOver);
196198
document.body.addEventListener('mouseout', this.onBodyMouseOut);
199+
document.body.addEventListener('mousemove', this.onBodyMouseMove);
197200
window.addEventListener('scroll', this.onWindowChange, true);
198201
window.addEventListener('resize', this.onWindowChange);
199202
void this.loadBlockedUsers();
@@ -223,6 +226,11 @@ class UserMiniWidget {
223226
this.scheduleShow(anchor, profileId);
224227
};
225228

229+
private onBodyMouseMove = (event: MouseEvent) => {
230+
this.lastMouseX = event.clientX;
231+
this.lastMouseY = event.clientY;
232+
};
233+
226234
private onBodyMouseOut = (event: MouseEvent) => {
227235
const target = event.target as HTMLElement | null;
228236
if (!target) return;
@@ -232,6 +240,7 @@ class UserMiniWidget {
232240
const related = event.relatedTarget as HTMLElement | null;
233241
if (related && (this.root.contains(related) || fromLink.contains(related))) return;
234242

243+
this.clearHoverTimer();
235244
this.scheduleHide();
236245
};
237246

@@ -260,12 +269,29 @@ class UserMiniWidget {
260269

261270
private scheduleShow(anchor: HTMLElement, profileId: string) {
262271
this.clearHoverTimer();
272+
const startX = this.lastMouseX;
273+
const startY = this.lastMouseY;
263274
this.hoverTimer = window.setTimeout(() => {
264275
this.hoverTimer = undefined;
265-
void this.show(anchor, profileId);
276+
this.checkHoverIntent(anchor, profileId, startX, startY);
266277
}, HOVER_DELAY_MS);
267278
}
268279

280+
private checkHoverIntent(anchor: HTMLElement, profileId: string, prevX: number, prevY: number) {
281+
if (this.anchor !== anchor || this.anchorUser !== profileId) return;
282+
283+
const movement = Math.abs(this.lastMouseX - prevX) + Math.abs(this.lastMouseY - prevY);
284+
if (movement <= HOVER_INTENT_SENSITIVITY) {
285+
void this.show(anchor, profileId);
286+
return;
287+
}
288+
289+
this.hoverTimer = window.setTimeout(() => {
290+
this.hoverTimer = undefined;
291+
this.checkHoverIntent(anchor, profileId, this.lastMouseX, this.lastMouseY);
292+
}, HOVER_INTENT_POLL_MS);
293+
}
294+
269295
private scheduleHide() {
270296
this.clearHideTimer();
271297
this.hideTimer = window.setTimeout(() => {

0 commit comments

Comments
 (0)