[READ-ONLY] Mirror of https://github.com/improsocial/impro An extensible Bluesky client for web impro.social
6

Configure Feed

Select the types of activity you want to include in your feed.

Fix text selection in draggable sheets

Grace Kind (Jul 18, 2026, 3:50 PM -0500) c3f8dc38 42b0611d

+51 -1
+1 -1
package.json
··· 1 1 { 2 2 "name": "impro", 3 - "version": "0.18.5", 3 + "version": "0.18.6", 4 4 "type": "module", 5 5 "scripts": { 6 6 "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve",
+14
src/js/utils.js
··· 400 400 viewport && 401 401 window.innerHeight - viewport.height > KEYBOARD_THRESHOLD; 402 402 403 + const hasTextSelection = () => { 404 + const selection = document.getSelection(); 405 + return selection !== null && !selection.isCollapsed; 406 + }; 407 + 403 408 const handleTouchStart = (e) => { 404 409 if (isKeyboardOpen()) return; 405 410 if (ignoreTouchTarget(e.target)) return; 411 + if (hasTextSelection()) return; 406 412 407 413 clearTimeout(caretRestoreTimer); 408 414 dragState.startY = e.touches[0].clientY; ··· 422 428 423 429 const handleTouchMove = (e) => { 424 430 if (!dragState.isDragging) return; 431 + 432 + // A selection that appears mid-gesture (long-press) switches to text selection. 433 + if (hasTextSelection()) { 434 + dragState.isDragging = false; 435 + target.style.transform = ""; 436 + target.style.caretColor = ""; 437 + return; 438 + } 425 439 426 440 dragState.currentY = e.touches[0].clientY; 427 441 const deltaY = dragState.currentY - dragState.startY;
+36
tests/unit/specs/utils.test.js
··· 1169 1169 dragState.cleanup(); 1170 1170 assert.deepEqual(el.style.caretColor, ""); 1171 1171 }); 1172 + 1173 + describe("with text selected", () => { 1174 + beforeEach(() => { 1175 + el.textContent = "some selectable text"; 1176 + const range = document.createRange(); 1177 + range.selectNodeContents(el); 1178 + const selection = document.getSelection(); 1179 + selection.removeAllRanges(); 1180 + selection.addRange(range); 1181 + }); 1182 + 1183 + afterEach(() => { 1184 + document.getSelection().removeAllRanges(); 1185 + }); 1186 + 1187 + it("ignores drags that start while text is selected", async () => { 1188 + dragState = enableDragToDismiss(el, { onClose: () => closeCount++ }); 1189 + await drag(150); 1190 + assert.deepEqual(closeCount, 0); 1191 + assert.deepEqual(el.style.transform, ""); 1192 + }); 1193 + 1194 + it("abandons a drag when a selection appears mid-gesture", async () => { 1195 + dragState = enableDragToDismiss(el, { onClose: () => closeCount++ }); 1196 + document.getSelection().removeAllRanges(); 1197 + el.dispatchEvent(pressEvent("touchstart", { touch: true, clientY: 100 })); 1198 + const range = document.createRange(); 1199 + range.selectNodeContents(el); 1200 + document.getSelection().addRange(range); 1201 + el.dispatchEvent(pressEvent("touchmove", { touch: true, clientY: 250 })); 1202 + el.dispatchEvent(pressEvent("touchend", { touch: true })); 1203 + await wait(0); 1204 + assert.deepEqual(closeCount, 0); 1205 + assert.deepEqual(el.style.transform, ""); 1206 + }); 1207 + }); 1172 1208 }); 1173 1209 1174 1210 describe("debounce", () => {