fix: prevent sticky sidebar hover drag state
This commit is contained in:
BIN
docs/pr-media/sidebar-hover-drag/after-hover-qa.png
Normal file
BIN
docs/pr-media/sidebar-hover-drag/after-hover-qa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
@@ -2509,15 +2509,31 @@ function renderSessionListFromCache(){
|
||||
let _tapTimer=null;
|
||||
let _pointerDownX=0;
|
||||
let _pointerDownY=0;
|
||||
let _pointerActive=false;
|
||||
let _isDragging=false;
|
||||
let _clearDragTimer=null;
|
||||
const _clearPointerDragState=()=>{
|
||||
_pointerActive=false;
|
||||
if(_isDragging){
|
||||
_isDragging=false;
|
||||
if(_clearDragTimer){clearTimeout(_clearDragTimer);_clearDragTimer=null;}
|
||||
_clearDragTimer=setTimeout(()=>{el.classList.remove('dragging');_clearDragTimer=null;},50);
|
||||
}
|
||||
};
|
||||
el.onpointerdown=(e)=>{
|
||||
if(e.pointerType==='mouse' && e.button!==0) return;
|
||||
_pointerActive=true;
|
||||
_pointerDownX=e.clientX;
|
||||
_pointerDownY=e.clientY;
|
||||
_isDragging=false;
|
||||
if(_clearDragTimer){clearTimeout(_clearDragTimer);_clearDragTimer=null;}
|
||||
el.classList.remove('dragging');
|
||||
};
|
||||
el.onpointermove=(e)=>{
|
||||
// Plain hover also dispatches pointermove. Only mark a row as dragging
|
||||
// after an actual press starts on this row; otherwise hovered rows stay
|
||||
// faded until the next sidebar rerender clears their DOM nodes.
|
||||
if(!_pointerActive) return;
|
||||
if(_isDragging) return;
|
||||
const dx=Math.abs(e.clientX-_pointerDownX);
|
||||
const dy=Math.abs(e.clientY-_pointerDownY);
|
||||
@@ -2528,8 +2544,11 @@ function renderSessionListFromCache(){
|
||||
if(_clearDragTimer){clearTimeout(_clearDragTimer);_clearDragTimer=null;}
|
||||
}
|
||||
};
|
||||
el.onpointercancel=_clearPointerDragState;
|
||||
el.onpointerleave=()=>{ if(_pointerActive) _clearPointerDragState(); };
|
||||
el.onpointerup=(e)=>{
|
||||
if(e.pointerType==='mouse' && e.button!==0) return; // ignore right/middle click
|
||||
_pointerActive=false;
|
||||
if(_renamingSid) return;
|
||||
if(actions&&actions.contains(e.target)) return;
|
||||
if(e.target&&e.target.closest&&e.target.closest('.session-child-count,.session-child-sessions,.session-child-session')) return;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -14,13 +15,21 @@ pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH")
|
||||
|
||||
|
||||
def _run_node(source: str) -> str:
|
||||
result = subprocess.run(
|
||||
[NODE, "-e", source],
|
||||
cwd=str(REPO_ROOT),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w", suffix=".cjs", encoding="utf-8", dir=REPO_ROOT, delete=False
|
||||
) as script:
|
||||
script.write(source)
|
||||
script_path = Path(script.name)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[NODE, str(script_path)],
|
||||
cwd=str(REPO_ROOT),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
finally:
|
||||
script_path.unlink(missing_ok=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(result.stderr)
|
||||
return result.stdout.strip()
|
||||
|
||||
@@ -116,6 +116,15 @@ def test_timestamp_hidden_when_attention_state_is_present():
|
||||
assert ".session-item.unread:not(:hover):not(:focus-within):not(.menu-open) .session-actions" in STYLE_CSS
|
||||
|
||||
|
||||
def test_plain_mouse_hover_does_not_mark_session_row_dragging():
|
||||
"""Pointermove fires during ordinary hover; drag styling must require an active press."""
|
||||
assert "let _pointerActive=false;" in SESSIONS_JS
|
||||
assert "_pointerActive=true;" in SESSIONS_JS
|
||||
assert "if(!_pointerActive) return;" in SESSIONS_JS
|
||||
assert "_pointerActive=false;" in SESSIONS_JS
|
||||
assert ".session-item.dragging:hover" in STYLE_CSS
|
||||
|
||||
|
||||
def test_sidebar_uses_local_inflight_state_for_immediate_spinner():
|
||||
messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user