From c3f8dc387532ace855d78b07aef71e2e40fa0f8d Mon Sep 17 00:00:00 2001 From: Grace Kind Date: Sat, 18 Jul 2026 15:50:15 -0500 Subject: [PATCH] Fix text selection in draggable sheets --- package.json | 2 +- src/js/utils.js | 14 +++++++++++++ tests/unit/specs/utils.test.js | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0fe34ba4..765cc255 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.18.5", + "version": "0.18.6", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/js/utils.js b/src/js/utils.js index 1b8600d7..1e7cd24e 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -400,9 +400,15 @@ export function enableDragToDismiss( viewport && window.innerHeight - viewport.height > KEYBOARD_THRESHOLD; + const hasTextSelection = () => { + const selection = document.getSelection(); + return selection !== null && !selection.isCollapsed; + }; + const handleTouchStart = (e) => { if (isKeyboardOpen()) return; if (ignoreTouchTarget(e.target)) return; + if (hasTextSelection()) return; clearTimeout(caretRestoreTimer); dragState.startY = e.touches[0].clientY; @@ -423,6 +429,14 @@ export function enableDragToDismiss( const handleTouchMove = (e) => { if (!dragState.isDragging) return; + // A selection that appears mid-gesture (long-press) switches to text selection. + if (hasTextSelection()) { + dragState.isDragging = false; + target.style.transform = ""; + target.style.caretColor = ""; + return; + } + dragState.currentY = e.touches[0].clientY; const deltaY = dragState.currentY - dragState.startY; diff --git a/tests/unit/specs/utils.test.js b/tests/unit/specs/utils.test.js index 7c0c2a20..635c5c39 100644 --- a/tests/unit/specs/utils.test.js +++ b/tests/unit/specs/utils.test.js @@ -1169,6 +1169,42 @@ describe("enableDragToDismiss", () => { dragState.cleanup(); assert.deepEqual(el.style.caretColor, ""); }); + + describe("with text selected", () => { + beforeEach(() => { + el.textContent = "some selectable text"; + const range = document.createRange(); + range.selectNodeContents(el); + const selection = document.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + }); + + afterEach(() => { + document.getSelection().removeAllRanges(); + }); + + it("ignores drags that start while text is selected", async () => { + dragState = enableDragToDismiss(el, { onClose: () => closeCount++ }); + await drag(150); + assert.deepEqual(closeCount, 0); + assert.deepEqual(el.style.transform, ""); + }); + + it("abandons a drag when a selection appears mid-gesture", async () => { + dragState = enableDragToDismiss(el, { onClose: () => closeCount++ }); + document.getSelection().removeAllRanges(); + el.dispatchEvent(pressEvent("touchstart", { touch: true, clientY: 100 })); + const range = document.createRange(); + range.selectNodeContents(el); + document.getSelection().addRange(range); + el.dispatchEvent(pressEvent("touchmove", { touch: true, clientY: 250 })); + el.dispatchEvent(pressEvent("touchend", { touch: true })); + await wait(0); + assert.deepEqual(closeCount, 0); + assert.deepEqual(el.style.transform, ""); + }); + }); }); describe("debounce", () => { -- 2.51.2