From 1d174697fba4ce120eb861af2ae174c4f217fc25 Mon Sep 17 00:00:00 2001 From: MeghdadFadaee Date: Sun, 14 Jun 2026 18:55:43 +0330 Subject: [PATCH] Fixed desktop text selection behavior. --- src/App.test.tsx | 21 +++++++++++++++++++++ src/App.tsx | 20 ++++++++++++++++++++ src/styles.css | 6 ++++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/App.test.tsx diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 0000000..f3033be --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,21 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import App from "./App"; + +vi.stubGlobal("Worker", class { + postMessage() {} + terminate() {} +}); + +describe("App canvas selection", () => { + it("does not expose a viewer canvas before a file is opened", () => { + render(); + expect(screen.getByText("Open an Adobe XD file")).toBeInTheDocument(); + }); + + it("keeps normal document text non-selectable by CSS", () => { + const { container } = render(); + fireEvent.doubleClick(container); + expect(container.querySelector(".selectable-text")).not.toBeInTheDocument(); + }); +}); diff --git a/src/App.tsx b/src/App.tsx index ba58d2b..6604ec8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -66,6 +66,13 @@ export default function App() { zoom: number; } | null>(null); const canvasRef = useRef(null); + const selectedTextRef = useRef(null); + + const clearTextSelection = useCallback(() => { + selectedTextRef.current?.classList.remove("selectable-text"); + selectedTextRef.current = null; + window.getSelection()?.removeAllRanges(); + }, []); useEffect(() => { panRef.current = pan; }, [pan]); useEffect(() => { zoomRef.current = zoom; }, [zoom]); @@ -243,6 +250,7 @@ export default function App() { }} onPointerDown={(event) => { if (event.pointerType === "touch") return; + if (event.detail < 2) clearTextSelection(); event.currentTarget.setPointerCapture(event.pointerId); dragRef.current = { x: event.clientX, y: event.clientY, originX: pan.x, originY: pan.y }; }} @@ -257,6 +265,18 @@ export default function App() { onPointerCancel={(event) => { if (event.pointerType !== "touch") dragRef.current = null; }} + onDoubleClick={(event) => { + const target = event.target instanceof Element ? event.target.closest("text") : null; + if (!(target instanceof SVGTextElement)) return; + clearTextSelection(); + target.classList.add("selectable-text"); + selectedTextRef.current = target; + const range = window.document.createRange(); + range.selectNodeContents(target); + const selection = window.getSelection(); + selection?.removeAllRanges(); + selection?.addRange(range); + }} > {document.artboards.length || document.pasteboardLayers.length ? (