元素拾取悬停0.3秒自动钻入子元素, 无需先锁定再调层级

- 进入新交互区域先吸附交互元素, 悬停300ms后自动钻入光标最深处
- 钻入后跟随光标在卡片内部移动; PageUp/PageDown 保留为手动上下调层(未锁定时直接锁定)
This commit is contained in:
yu 2026-11-05 11:11:03 +08:00
parent 150d7ce9c9
commit 309898045c

View File

@ -1256,6 +1256,9 @@ const PICKER_BOOTSTRAP_SOURCE = `(() => {
let badge = null;
let target = null;
let deepTarget = null;
let lastSnap = null;
let drilled = false;
let hoverTimer = 0;
let locked = false;
let chain = [];
let chainIndex = 0;
@ -1341,7 +1344,7 @@ const PICKER_BOOTSTRAP_SOURCE = `(() => {
if (locked) {
badge.textContent = '已锁定 ' + (chainIndex + 1) + '/' + chain.length + ' · ' + tag + (name ? ' · ' + name.slice(0, 42) : '') + ' · [PageUp]升 [PageDown]钻入子元素 [空格]确认 [Esc]取消';
} else {
badge.textContent = '拾取 · ' + tag + (name ? ' · ' + name.slice(0, 42) : '') + ' · [空格]锁定 [Esc]取消 · 可直接翻页后继续拾取';
badge.textContent = '拾取 · ' + tag + (name ? ' · ' + name.slice(0, 42) : '') + ' · 悬停0.3秒自动钻入 · [PageUp/PageDown]手动调层 [空格]确认 [Esc]取消 · 可直接翻页后继续拾取';
}
badge.style.left = Math.max(0, rect.left) + 'px';
badge.style.top = Math.max(0, rect.top - 34) + 'px';
@ -1387,25 +1390,26 @@ const PICKER_BOOTSTRAP_SOURCE = `(() => {
}
return result;
};
const lock = () => {
if (locked || !target) return;
const lockAt = (base) => {
if (locked || !base) return;
locked = true;
chain = buildChain(target);
chain = buildChain(base);
chainIndex = Math.max(0, chain.length - 1);
// 把光标下最深处元素到锁定元素之间的子级路径并入链,PageDown 可钻入卡片内部的标题/价格等子元素
if (deepTarget && target !== deepTarget && target.contains(deepTarget)) {
// 把光标下最深处元素到锁定元素之间的子级路径并入链,PageDown/PageUp 可在卡片与其内部子元素间切换
if (deepTarget && base !== deepTarget && base.contains(deepTarget)) {
const below = [];
let current = deepTarget;
while (current && current !== target && target.contains(current)) {
while (current && current !== base && base.contains(current)) {
below.unshift(current);
const root = current.getRootNode();
current = root instanceof ShadowRoot ? root.host : current.parentElement;
}
chain = chain.concat(below);
}
selected = chain[chainIndex] || target;
selected = chain[chainIndex] || base;
updateBox();
};
const lock = () => lockAt(target);
const adjustLevel = (delta) => {
if (!locked || !chain.length) return;
const next = Math.min(chain.length - 1, Math.max(0, chainIndex + delta));
@ -1421,7 +1425,27 @@ const PICKER_BOOTSTRAP_SOURCE = `(() => {
const move = (event) => {
if (finished || locked) return;
deepTarget = deepestOf(event);
target = firstElement(event);
const snapped = firstElement(event);
if (snapped !== lastSnap) {
// 进入新的交互区域:先吸附交互元素,悬停 0.3 秒后自动钻入光标最深处
lastSnap = snapped;
drilled = false;
target = snapped;
clearTimeout(hoverTimer);
if (deepTarget && deepTarget !== snapped) {
hoverTimer = setTimeout(() => {
if (finished || locked) return;
drilled = true;
target = deepTarget;
updateBox();
}, 300);
}
updateBox();
return;
}
if (drilled && deepTarget && snapped.contains(deepTarget)) {
target = deepTarget; // 已钻入:跟随光标在卡片内部移动
}
updateBox();
};
const click = (event) => {
@ -1444,21 +1468,24 @@ const PICKER_BOOTSTRAP_SOURCE = `(() => {
else lock();
return;
}
if (locked && event.key === 'PageUp' && !event.repeat) {
if (event.key === 'PageDown' && !event.repeat) {
event.preventDefault();
event.stopPropagation();
adjustLevel(-1);
if (locked) adjustLevel(1);
else lockAt(deepTarget || target); // 未锁定时直接锁定光标最深处(如卡片内的标题)
return;
}
if (locked && event.key === 'PageDown' && !event.repeat) {
if (event.key === 'PageUp' && !event.repeat) {
event.preventDefault();
event.stopPropagation();
adjustLevel(1);
if (locked) adjustLevel(-1);
else if (target) lockAt(target); // 未锁定时锁定吸附到的交互元素(如整张卡片)
}
};
const cleanup = () => {
if (finished) return;
finished = true;
clearTimeout(hoverTimer);
window.removeEventListener('mousemove', move, true);
window.removeEventListener('mousedown', click, true);
window.removeEventListener('click', click, true);