Skip to content

Commit 663b28e

Browse files
committed
keep selection when dragging outside page
1 parent 091d320 commit 663b28e

1 file changed

Lines changed: 42 additions & 5 deletions

File tree

src/display/draw_layer.js

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ class DrawLayer {
215215
/** @type {Set<HTMLDivElement>} */
216216
static #selections = new Set();
217217

218+
/** @type {boolean} */
219+
static #isSelectingWithMouse = false;
220+
218221
/** @type {Set<Element>} */
219222
static #textLayerSet = new Set();
220223

@@ -245,15 +248,38 @@ class DrawLayer {
245248
DrawLayer.#textLayers.set(textLayer, { drawLayer: this });
246249
DrawLayer.#textLayerSet.add(textLayer);
247250
this.#textLayer = textLayer;
248-
if (
249-
DrawLayer.#textLayerSet.size === 1 &&
250-
DrawLayer.#selectionChangeAC === null
251-
) {
251+
if (DrawLayer.#selectionChangeAC === null) {
252252
DrawLayer.#selectionChangeAC = new AbortController();
253+
const { signal } = DrawLayer.#selectionChangeAC;
253254
document.addEventListener(
254255
"selectionchange",
255256
DrawLayer.#selectionChange.bind(DrawLayer),
256-
{ signal: DrawLayer.#selectionChangeAC.signal }
257+
{ signal }
258+
);
259+
// Track mouse selection state to preserve selections during
260+
// cross-boundary drags.
261+
document.addEventListener(
262+
"mousedown",
263+
() => {
264+
DrawLayer.#isSelectingWithMouse = true;
265+
},
266+
{ signal }
267+
);
268+
document.addEventListener(
269+
"mouseup",
270+
() => {
271+
DrawLayer.#isSelectingWithMouse = false;
272+
},
273+
{ signal }
274+
);
275+
// If the mouse is released outside the window, we may not get a
276+
// corresponding `mouseup` event.
277+
window.addEventListener(
278+
"blur",
279+
() => {
280+
DrawLayer.#isSelectingWithMouse = false;
281+
},
282+
{ signal }
257283
);
258284
}
259285
}
@@ -325,6 +351,15 @@ class DrawLayer {
325351
let { startContainer, startOffset, endContainer, endOffset } = range;
326352
let startTextLayer = getTextLayer(startContainer);
327353
let endTextLayer = getTextLayer(endContainer);
354+
const startMissing = startTextLayer === null;
355+
const endMissing = endTextLayer === null;
356+
357+
// XOR case: exactly one boundary is outside tracked text layers.
358+
// In Firefox/Safari this can happen transiently while dragging outside
359+
// the page. Preserve the current overlay and exit early.
360+
if (this.#isSelectingWithMouse && startMissing !== endMissing) {
361+
return;
362+
}
328363

329364
if (selection.rangeCount === 1) {
330365
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
@@ -356,6 +391,7 @@ class DrawLayer {
356391
}
357392

358393
if (!startTextLayer || !endTextLayer) {
394+
// Any remaining partial/outside range can be ignored.
359395
continue;
360396
}
361397
if (endContainer.nodeType === Node.ELEMENT_NODE) {
@@ -769,6 +805,7 @@ class DrawLayer {
769805
if (DrawLayer.#textLayerSet.size === 0) {
770806
DrawLayer.#selectionChangeAC?.abort();
771807
DrawLayer.#selectionChangeAC = null;
808+
DrawLayer.#isSelectingWithMouse = false;
772809
}
773810
}
774811
this.#textLayer = null;

0 commit comments

Comments
 (0)