From e19f03fb5d68d8cc939b1af5c7e82bd845f0fd84 Mon Sep 17 00:00:00 2001 From: Lukas Parsons Date: Tue, 7 Jul 2026 14:23:46 -0400 Subject: [PATCH] fix: zoom-aware drag positioning + Text node safety in windowDrag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _startDrag(): divide getBoundingClientRect().left/.top by zoom factor (right/left dock paths too). Also guard e.target.closest() calls against Text nodes that lack the method — prevents close/minimize buttons from failing the skipSelector check and triggering a drag on click. --- static/js/windowDrag.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/static/js/windowDrag.js b/static/js/windowDrag.js index 5f2b62f3..c8049252 100644 --- a/static/js/windowDrag.js +++ b/static/js/windowDrag.js @@ -157,11 +157,12 @@ export function makeWindowDraggable(modal, options = {}) { .forEach(a => a.cancel()); } catch (_) {} const rect = content.getBoundingClientRect(); + const z = parseFloat(getComputedStyle(document.documentElement).zoom) || 1; if (onDragStart) { try { onDragStart({ rect, cx, cy }); } catch (_) {} } startX = cx; startY = cy; - startLeft = rect.left; startTop = rect.top; + startLeft = rect.left / z; startTop = rect.top / z; // Pin position so the drag follows the cursor instead of fighting a // centering transform / margin. Inline styles win unless CSS uses // !important (the fullscreen rules do, by design). @@ -214,16 +215,18 @@ export function makeWindowDraggable(modal, options = {}) { if (rightDock && modal && modal.classList.contains('modal-right-docked')) { if (rightDock.onMove(cx, cy)) { const r = content.getBoundingClientRect(); + const z2 = parseFloat(getComputedStyle(document.documentElement).zoom) || 1; startX = cx; startY = cy; - startLeft = r.left; startTop = r.top; + startLeft = r.left / z2; startTop = r.top / z2; } return; } if (leftDock && modal && modal.classList.contains('modal-left-docked')) { if (leftDock.onMove(cx, cy)) { const r = content.getBoundingClientRect(); + const z3 = parseFloat(getComputedStyle(document.documentElement).zoom) || 1; startX = cx; startY = cy; - startLeft = r.left; startTop = r.top; + startLeft = r.left / z3; startTop = r.top / z3; } return; } @@ -280,7 +283,7 @@ export function makeWindowDraggable(modal, options = {}) { header.addEventListener('mousedown', (e) => { if (mobileSkip > 0 && window.innerWidth <= mobileSkip) return; - if (skipSelector && e.target.closest(skipSelector)) return; + if (skipSelector && e.target.closest && e.target.closest(skipSelector)) return; e.preventDefault(); movedDuringDrag = false; _startDrag(e.clientX, e.clientY); @@ -309,7 +312,7 @@ export function makeWindowDraggable(modal, options = {}) { if (enableTouch) { header.addEventListener('touchstart', (e) => { if (mobileSkip > 0 && window.innerWidth <= mobileSkip) return; - if (skipSelector && e.target.closest(skipSelector)) return; + if (skipSelector && e.target.closest && e.target.closest(skipSelector)) return; const t = e.touches[0]; if (!t) return; movedDuringDrag = false;