feat(ui): 编辑摘要弹窗位置钉死、外部滚动与窗口缩放即关、hover 延迟 600ms 且打开后瞬间切换

Co-authored-by: Astrion powered by DeepSeek-V4-Flash <astrion-agent@users.noreply.github.com>
This commit is contained in:
JOJO 2026-08-29 15:17:42 +08:00
parent 2a870c5a35
commit bc8337ab09

View File

@ -7,8 +7,12 @@
* assistant 消息末尾同一文件多次编辑只展示合并后的最终结果净变化 * assistant 消息末尾同一文件多次编辑只展示合并后的最终结果净变化
* *
* 交互 * 交互
* - 桌面端 hover 150ms 意图延迟打开居中浮窗离开行与浮窗后自动关闭 * - 桌面端 hover 600ms 意图延迟打开居中浮窗离开行与浮窗后自动关闭
* 弹窗已打开时鼠标移到其他行瞬间切换内容不再等延迟
* - 点击行钉住弹窗带遮罩 + 关闭按钮移动端无 hover点击为唯一入口 * - 点击行钉住弹窗带遮罩 + 关闭按钮移动端无 hover点击为唯一入口
* - 弹窗位置/高度在展开瞬间一次性计算并钉死之后不再重算
* - 外部滚动 / 窗口缩放直接关闭弹窗钉住弹窗内部滚动放行可滚动查看长 diff
* hover 弹窗内部滚动同样关闭
* - Esc 关闭 * - Esc 关闭
*/ */
import { computed, onBeforeUnmount, ref, watch } from 'vue'; import { computed, onBeforeUnmount, ref, watch } from 'vue';
@ -132,9 +136,25 @@ function computePanelStyle() {
} }
} }
function onWindowReposition() { // / open
// - /
// - diff hover
// - /
let suppressOpenUntil = 0;
function onScrollCapture(e: Event) {
if (!activePath.value) return; if (!activePath.value) return;
computePanelStyle(); const target = e.target as Element | null;
const isInsidePanel = target instanceof Element && !!target.closest('.es-modal');
if (isInsidePanel && pinned.value) return;
suppressOpenUntil = Date.now() + 250;
close();
}
function onResizeClose() {
if (!activePath.value) return;
suppressOpenUntil = Date.now() + 250;
close();
} }
function clearOpenTimer() { function clearOpenTimer() {
@ -160,9 +180,18 @@ function open(path: string, pin: boolean, rowEl: HTMLElement | null = null) {
computePanelStyle(); computePanelStyle();
} }
// hover
const HOVER_OPEN_DELAY = 600;
function scheduleOpen(path: string, rowEl: HTMLElement | null) { function scheduleOpen(path: string, rowEl: HTMLElement | null) {
clearOpenTimer(); clearOpenTimer();
openTimer = setTimeout(() => open(path, false, rowEl), 150); if (Date.now() < suppressOpenUntil) return;
if (activePath.value) {
// +
open(path, false, rowEl);
return;
}
openTimer = setTimeout(() => open(path, false, rowEl), HOVER_OPEN_DELAY);
} }
function scheduleClose() { function scheduleClose() {
@ -215,13 +244,14 @@ function onKeydown(e: KeyboardEvent) {
watch(activePath, (v) => { watch(activePath, (v) => {
if (v) { if (v) {
window.addEventListener('keydown', onKeydown); window.addEventListener('keydown', onKeydown);
window.addEventListener('resize', onWindowReposition); //
// window.addEventListener('resize', onResizeClose);
window.addEventListener('scroll', onWindowReposition, true); //
window.addEventListener('scroll', onScrollCapture, true);
} else { } else {
window.removeEventListener('keydown', onKeydown); window.removeEventListener('keydown', onKeydown);
window.removeEventListener('resize', onWindowReposition); window.removeEventListener('resize', onResizeClose);
window.removeEventListener('scroll', onWindowReposition, true); window.removeEventListener('scroll', onScrollCapture, true);
} }
}); });
@ -229,8 +259,8 @@ onBeforeUnmount(() => {
clearOpenTimer(); clearOpenTimer();
clearCloseTimer(); clearCloseTimer();
window.removeEventListener('keydown', onKeydown); window.removeEventListener('keydown', onKeydown);
window.removeEventListener('resize', onWindowReposition); window.removeEventListener('resize', onResizeClose);
window.removeEventListener('scroll', onWindowReposition, true); window.removeEventListener('scroll', onScrollCapture, true);
}); });
// ---------------- diff ---------------- // ---------------- diff ----------------