Skip to content

Commit 59df8a0

Browse files
authored
fix: touch selection auto-scroll every direction (#1914)
1 parent edee414 commit 59df8a0

File tree

2 files changed

+122
-11
lines changed

2 files changed

+122
-11
lines changed

src/cm/touchSelectionMenu.js

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,33 @@ export function filterSelectionMenuItems(items, options) {
9393
});
9494
}
9595

96+
/**
97+
* Detect which edge(s) should trigger drag auto-scroll.
98+
* @param {{
99+
* x:number,
100+
* y:number,
101+
* rect:{left:number,right:number,top:number,bottom:number},
102+
* allowHorizontal?:boolean,
103+
* gap?:number,
104+
* }} options
105+
* @returns {{horizontal:number, vertical:number}}
106+
*/
107+
export function getEdgeScrollDirections(options) {
108+
const { x, y, rect, allowHorizontal = true, gap = EDGE_SCROLL_GAP } = options;
109+
let horizontal = 0;
110+
let vertical = 0;
111+
112+
if (allowHorizontal) {
113+
if (x < rect.left + gap) horizontal = -1;
114+
else if (x > rect.right - gap) horizontal = 1;
115+
}
116+
117+
if (y < rect.top + gap) vertical = -1;
118+
else if (y > rect.bottom - gap) vertical = 1;
119+
120+
return { horizontal, vertical };
121+
}
122+
96123
function clamp(value, min, max) {
97124
return Math.max(min, Math.min(max, value));
98125
}
@@ -1014,7 +1041,8 @@ class TouchSelectionMenuController {
10141041
startX: x,
10151042
startY: y,
10161043
moved: false,
1017-
direction: 0,
1044+
scrollX: 0,
1045+
scrollY: 0,
10181046
fixedPos:
10191047
type === "start" ? range.to : type === "end" ? range.from : null,
10201048
};
@@ -1098,28 +1126,73 @@ class TouchSelectionMenuController {
10981126
this.#view.focus();
10991127
}
11001128

1129+
#getAutoScrollDelta(x, y) {
1130+
const scroller = this.#view.scrollDOM;
1131+
const rect = scroller.getBoundingClientRect();
1132+
const { horizontal, vertical } = getEdgeScrollDirections({
1133+
x,
1134+
y,
1135+
rect,
1136+
allowHorizontal: !this.#view.lineWrapping,
1137+
});
1138+
const maxScrollLeft = Math.max(
1139+
0,
1140+
scroller.scrollWidth - scroller.clientWidth,
1141+
);
1142+
const maxScrollTop = Math.max(
1143+
0,
1144+
scroller.scrollHeight - scroller.clientHeight,
1145+
);
1146+
let scrollX = horizontal * EDGE_SCROLL_STEP;
1147+
let scrollY = vertical * EDGE_SCROLL_STEP;
1148+
1149+
if (
1150+
(scrollX < 0 && scroller.scrollLeft <= 0) ||
1151+
(scrollX > 0 && scroller.scrollLeft >= maxScrollLeft)
1152+
) {
1153+
scrollX = 0;
1154+
}
1155+
1156+
if (
1157+
(scrollY < 0 && scroller.scrollTop <= 0) ||
1158+
(scrollY > 0 && scroller.scrollTop >= maxScrollTop)
1159+
) {
1160+
scrollY = 0;
1161+
}
1162+
1163+
return { scrollX, scrollY };
1164+
}
1165+
11011166
#startAutoScrollIfNeeded(x, y) {
1102-
const rect = this.#view.scrollDOM.getBoundingClientRect();
1103-
let direction = 0;
1104-
if (y < rect.top + EDGE_SCROLL_GAP) direction = -1;
1105-
if (y > rect.bottom - EDGE_SCROLL_GAP) direction = 1;
1167+
const { scrollX, scrollY } = this.#getAutoScrollDelta(x, y);
1168+
if (this.#dragState) {
1169+
this.#dragState.scrollX = scrollX;
1170+
this.#dragState.scrollY = scrollY;
1171+
}
11061172

1107-
if (!direction) {
1173+
if (!scrollX && !scrollY) {
11081174
this.#stopAutoScroll();
11091175
return;
11101176
}
11111177

1112-
this.#dragState.direction = direction;
11131178
if (this.#autoScrollRaf) return;
11141179

11151180
const tick = () => {
1116-
if (!this.#dragState?.direction) {
1181+
if (!this.#dragState) {
1182+
this.#autoScrollRaf = 0;
1183+
return;
1184+
}
1185+
1186+
const delta = this.#getAutoScrollDelta(this.#pointer.x, this.#pointer.y);
1187+
this.#dragState.scrollX = delta.scrollX;
1188+
this.#dragState.scrollY = delta.scrollY;
1189+
if (!delta.scrollX && !delta.scrollY) {
11171190
this.#autoScrollRaf = 0;
11181191
return;
11191192
}
11201193

1121-
this.#view.scrollDOM.scrollTop +=
1122-
this.#dragState.direction * EDGE_SCROLL_STEP;
1194+
this.#view.scrollDOM.scrollLeft += delta.scrollX;
1195+
this.#view.scrollDOM.scrollTop += delta.scrollY;
11231196
this.#dragTo(this.#pointer.x, this.#pointer.y);
11241197
this.#autoScrollRaf = requestAnimationFrame(tick);
11251198
};
@@ -1131,7 +1204,8 @@ class TouchSelectionMenuController {
11311204
cancelAnimationFrame(this.#autoScrollRaf);
11321205
this.#autoScrollRaf = 0;
11331206
if (this.#dragState) {
1134-
this.#dragState.direction = 0;
1207+
this.#dragState.scrollX = 0;
1208+
this.#dragState.scrollY = 0;
11351209
}
11361210
}
11371211

src/test/editor.tests.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
99
import { EditorSelection, EditorState } from "@codemirror/state";
1010
import { EditorView } from "@codemirror/view";
1111
import createBaseExtensions from "cm/baseExtensions";
12+
import { getEdgeScrollDirections } from "cm/touchSelectionMenu";
1213
import { TestRunner } from "./tester";
1314

1415
export async function runCodeMirrorTests(writeOutput) {
@@ -700,6 +701,42 @@ export async function runCodeMirrorTests(writeOutput) {
700701
});
701702
});
702703

704+
runner.test("Edge scroll direction helper", async (test) => {
705+
const rect = {
706+
left: 100,
707+
right: 300,
708+
top: 200,
709+
bottom: 400,
710+
};
711+
712+
const leftTop = getEdgeScrollDirections({
713+
x: 110,
714+
y: 210,
715+
rect,
716+
allowHorizontal: true,
717+
});
718+
test.assertEqual(leftTop.horizontal, -1);
719+
test.assertEqual(leftTop.vertical, -1);
720+
721+
const rightBottom = getEdgeScrollDirections({
722+
x: 295,
723+
y: 395,
724+
rect,
725+
allowHorizontal: true,
726+
});
727+
test.assertEqual(rightBottom.horizontal, 1);
728+
test.assertEqual(rightBottom.vertical, 1);
729+
730+
const noHorizontal = getEdgeScrollDirections({
731+
x: 110,
732+
y: 395,
733+
rect,
734+
allowHorizontal: false,
735+
});
736+
test.assertEqual(noHorizontal.horizontal, 0);
737+
test.assertEqual(noHorizontal.vertical, 1);
738+
});
739+
703740
runner.test("lineBlockAt", async (test) => {
704741
await withEditor(test, async (view) => {
705742
view.dispatch({

0 commit comments

Comments
 (0)