From ebbc424797eee4485dd92fe77bb97f69e3b7e0f2 Mon Sep 17 00:00:00 2001 From: troyeguo <13820674+troyeguo@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:54:25 +0800 Subject: [PATCH] feat(popupBox): enhance docking functionality and adjust snap thresholds --- src/components/popups/popupBox/component.tsx | 163 ++++++++++++++----- src/components/popups/popupBox/interface.tsx | 2 + src/pages/reader/component.tsx | 2 +- 3 files changed, 128 insertions(+), 39 deletions(-) diff --git a/src/components/popups/popupBox/component.tsx b/src/components/popups/popupBox/component.tsx index db65da51..93e4fa83 100644 --- a/src/components/popups/popupBox/component.tsx +++ b/src/components/popups/popupBox/component.tsx @@ -9,7 +9,9 @@ import PopupAssist from "../popupAssist"; import { isElectron } from "react-device-detect"; import { ConfigService } from "../../../assets/lib/kookit-extra-browser.min"; -const SNAP_THRESHOLD_PX = 20; // 底部吸附阈值(像素) +const SNAP_THRESHOLD_PX = 50; +const RIGHT_SNAP_THRESHOLD_PX = 50; +const SETTING_PANEL_WIDTH = 299; const POPUP_SIZE_KEY = "popupBoxSize"; const POPUP_POS_KEY = "popupBoxPosition"; @@ -63,6 +65,8 @@ class PopupBox extends React.Component { dragStartX: 0, dragStartY: 0, isNearBottom: false, + isNearRight: false, + isDockedRight: false, }; } @@ -157,6 +161,33 @@ class PopupBox extends React.Component { handleDragStart = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); + if (this.state.isDockedRight) { + const { popupWidth, popupHeight } = this.state; + // 移动图标中心在 popup 内的偏移:right:24px, width:18px, top:0, height:18px + // 图标中心距 popup 左边缘 = popupWidth - 24 - 9 = popupWidth - 33 + // 图标中心距 popup 上边缘 = 9 + // 反推:让图标中心对齐鼠标,计算 popup 的 left% 和 bottom% + const rawLeft = + ((e.clientX - popupWidth / 2 + 33) / window.innerWidth) * 100; + const newLeft = Math.max(0, Math.min(100, rawLeft)); + const rawBottom = + ((window.innerHeight - e.clientY - popupHeight + 9) / + window.innerHeight) * + 100; + const newBottom = Math.max(0, rawBottom); + + this.dragStartLeft = newLeft; + this.dragStartBottom = newBottom; + this.isDragging = true; + this.dragStartX = e.clientX; + this.dragStartY = e.clientY; + this.setState({ + isDockedRight: false, + popupLeft: newLeft, + popupBottom: newBottom, + }); + return; + } this.isDragging = true; this.dragStartX = e.clientX; this.dragStartY = e.clientY; @@ -177,23 +208,47 @@ class PopupBox extends React.Component { this.dragStartBottom - (dy / window.innerHeight) * 100 ); - // 检测是否靠近底部吸附区(像素) const bottomPx = (newBottom / 100) * window.innerHeight; const isNearBottom = bottomPx < SNAP_THRESHOLD_PX; - this.setState({ popupLeft: newLeft, popupBottom: newBottom, isNearBottom }); + const rightEdgePx = + (newLeft / 100) * window.innerWidth + this.state.popupWidth / 2; + const isNearRight = + window.innerWidth - rightEdgePx < RIGHT_SNAP_THRESHOLD_PX; + + this.setState({ + popupLeft: newLeft, + popupBottom: newBottom, + isNearBottom, + isNearRight, + }); }; handleDragEnd = (_e: MouseEvent) => { if (!this.isDragging) return; this.isDragging = false; - let { popupLeft, popupBottom } = this.state; - // 靠近底部吸附回底部 + let { popupLeft, popupBottom, popupWidth } = this.state; + + const rightEdgePx = (popupLeft / 100) * window.innerWidth + popupWidth / 2; + if (window.innerWidth - rightEdgePx < RIGHT_SNAP_THRESHOLD_PX) { + this.setState({ + isDockedRight: true, + isNearRight: false, + isNearBottom: false, + }); + return; + } + const bottomPx = (popupBottom / 100) * window.innerHeight; if (bottomPx < SNAP_THRESHOLD_PX) { popupBottom = 0; } - this.setState({ popupLeft, popupBottom, isNearBottom: false }); + this.setState({ + popupLeft, + popupBottom, + isNearBottom: false, + isNearRight: false, + }); this.savePositionToConfig(popupLeft, popupBottom); }; @@ -213,9 +268,50 @@ class PopupBox extends React.Component { chapterDocIndex: this.props.chapterDocIndex, chapter: this.props.chapter, }; - const { popupWidth, popupHeight, popupLeft, popupBottom, isNearBottom } = - this.state; + const { + popupWidth, + popupHeight, + popupLeft, + popupBottom, + isNearBottom, + isNearRight, + isDockedRight, + } = this.state; const isAtBottom = popupBottom === 0; + + const containerStyle: React.CSSProperties = isDockedRight + ? { + position: "fixed", + right: 0, + top: 0, + left: "auto", + bottom: 0, + width: SETTING_PANEL_WIDTH, + height: "100%", + transform: "none", + borderRadius: "10px 0 0 10px", + marginLeft: 0, + transition: "none", + } + : { + marginLeft: + this.props.isNavLocked && !this.props.isSettingLocked + ? 150 + : !this.props.isNavLocked && this.props.isSettingLocked + ? -150 + : 0, + width: popupWidth, + height: popupHeight, + left: `${popupLeft}%`, + bottom: `${popupBottom}%`, + transform: "translateX(-50%)", + borderBottomLeftRadius: isAtBottom ? 0 : 10, + borderBottomRightRadius: isAtBottom ? 0 : 10, + outline: isNearRight + ? "2px solid var(--color-primary, #5c9ee6)" + : "none", + }; + return (
{ : "block", }} > -
+
{this.props.menuMode === "note" ? ( ) : this.props.menuMode === "trans" ? ( @@ -258,7 +337,11 @@ class PopupBox extends React.Component { onClick={() => { this.handleClose(); }} - style={{ top: "-30px", left: "calc(50% - 10px)" }} + style={{ + ...(isDockedRight + ? { display: "none" } + : { top: "-30px", left: "calc(50% - 10px)" }), + }} >
{ >
-
+ {!isDockedRight && ( +
+ )}
-
{ - this.handleClose(); - }} - >
+ {!isDockedRight && ( +
{ + this.handleClose(); + }} + >
+ )}
); } diff --git a/src/components/popups/popupBox/interface.tsx b/src/components/popups/popupBox/interface.tsx index e3849138..1d343740 100644 --- a/src/components/popups/popupBox/interface.tsx +++ b/src/components/popups/popupBox/interface.tsx @@ -31,4 +31,6 @@ export interface PopupBoxStates { dragStartX: number; dragStartY: number; isNearBottom: boolean; + isNearRight: boolean; + isDockedRight: boolean; } diff --git a/src/pages/reader/component.tsx b/src/pages/reader/component.tsx index 18e51bd7..a985b399 100644 --- a/src/pages/reader/component.tsx +++ b/src/pages/reader/component.tsx @@ -506,7 +506,7 @@ class Reader extends React.Component { position: "absolute", top: "0px", right: this.props.isSettingLocked ? 300 : 5, - zIndex: 11, + zIndex: 10, width: "120px", display: "flex", alignItems: "center",