Skip to content

Commit a0aa310

Browse files
committed
fix(web): tighten mobile terminal line-copy hit testing
1 parent f112317 commit a0aa310

6 files changed

Lines changed: 87 additions & 4 deletions

File tree

packages/web/src/features/terminal-panel/__tests__/xterm-host.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7540,7 +7540,7 @@ describe("XtermHost", () => {
75407540
expect.arrayContaining([
75417541
expect.objectContaining({
75427542
kind: "error",
7543-
title: "自动复制失败",
7543+
title: "当前行复制失败",
75447544
body: "请重试长按当前行",
75457545
}),
75467546
])

packages/web/src/features/terminal-panel/mobile/long-press-copy-line.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,59 @@ describe("getLogicalLineTextFromTouchPoint", () => {
376376

377377
rows.remove();
378378
});
379+
380+
it("returns null when the touch point is inside a row band but outside the rendered text width", () => {
381+
const { rows, secondRow } = createRowsDom();
382+
const secondRowText = secondRow.querySelector("span span") as HTMLSpanElement | null;
383+
document.body.appendChild(rows);
384+
rows.getBoundingClientRect = () =>
385+
({
386+
x: 20,
387+
y: 100,
388+
top: 100,
389+
left: 20,
390+
width: 320,
391+
height: 60,
392+
right: 340,
393+
bottom: 160,
394+
toJSON: () => ({}),
395+
}) as DOMRect;
396+
secondRow.getBoundingClientRect = () =>
397+
({
398+
x: 20,
399+
y: 120,
400+
top: 120,
401+
left: 20,
402+
width: 320,
403+
height: 20,
404+
right: 340,
405+
bottom: 140,
406+
toJSON: () => ({}),
407+
}) as DOMRect;
408+
secondRowText!.getBoundingClientRect = () =>
409+
({
410+
x: 20,
411+
y: 120,
412+
top: 120,
413+
left: 20,
414+
width: 80,
415+
height: 20,
416+
right: 100,
417+
bottom: 140,
418+
toJSON: () => ({}),
419+
}) as DOMRect;
420+
421+
const terminal = createTerminal(70, [[71, createBufferLine("beta")]]);
422+
423+
expect(
424+
getLogicalLineTextFromTouchPoint({
425+
clientX: 180,
426+
clientY: 130,
427+
rowsElement: rows,
428+
terminal,
429+
})
430+
).toBeNull();
431+
432+
rows.remove();
433+
});
379434
});

packages/web/src/features/terminal-panel/mobile/long-press-copy-line.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ export interface GetLogicalLineTextFromTouchPointArgs {
2121
terminal: TerminalLikeForLongPressCopy;
2222
}
2323

24+
function getRenderedTextBounds(rowElement: HTMLElement): { left: number; right: number } | null {
25+
const spanRects = Array.from(rowElement.querySelectorAll("span"))
26+
.map((span) => span.getBoundingClientRect())
27+
.filter((rect) => rect.width > 0 && rect.height > 0);
28+
29+
if (spanRects.length === 0) {
30+
return null;
31+
}
32+
33+
return {
34+
left: Math.min(...spanRects.map((rect) => rect.left)),
35+
right: Math.max(...spanRects.map((rect) => rect.right)),
36+
};
37+
}
38+
2439
function getVisualRowIndexFromTouchPoint(
2540
rowsElement: HTMLElement,
2641
clientX: number,
@@ -40,8 +55,16 @@ function getVisualRowIndexFromTouchPoint(
4055
(child): child is HTMLElement => child instanceof HTMLElement
4156
);
4257
for (let index = 0; index < rowElements.length; index += 1) {
43-
const rowRect = rowElements[index].getBoundingClientRect();
44-
if (clientY >= rowRect.top && clientY <= rowRect.bottom) {
58+
const rowElement = rowElements[index];
59+
const rowRect = rowElement.getBoundingClientRect();
60+
if (clientY < rowRect.top || clientY > rowRect.bottom) {
61+
continue;
62+
}
63+
64+
const renderedTextBounds = getRenderedTextBounds(rowElement);
65+
const left = renderedTextBounds?.left ?? rowRect.left;
66+
const right = renderedTextBounds?.right ?? rowRect.right;
67+
if (clientX >= left && clientX <= right) {
4568
return index;
4669
}
4770
}

packages/web/src/features/terminal-panel/views/shared/xterm-host.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,10 @@ export function XtermHost({
885885
lastCopyOnSelectFailureAtRef.current = now;
886886
pushToast({
887887
kind: "error",
888-
title: t("settings.copy_on_select_failed_title"),
888+
title:
889+
viewport === "mobile"
890+
? t("terminal.mobile_copy_current_line_failed_title")
891+
: t("settings.copy_on_select_failed_title"),
889892
body:
890893
viewport === "mobile"
891894
? t("terminal.mobile_copy_current_line_failed_body")

packages/web/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@
260260
"create_unavailable_title": "No workspace selected",
261261
"create_unavailable_body": "Open or switch to a workspace before creating a terminal.",
262262
"copied_current_line": "Copied current line",
263+
"mobile_copy_current_line_failed_title": "Current line copy failed",
263264
"mobile_copy_current_line_failed_body": "Try long-pressing the current line again",
264265
"hide": "Hide Terminal",
265266
"show": "Show Terminal",

packages/web/src/locales/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@
260260
"create_unavailable_title": "当前没有已选中的工作区",
261261
"create_unavailable_body": "请先打开或切换到一个工作区,再新建终端。",
262262
"copied_current_line": "已复制当前行",
263+
"mobile_copy_current_line_failed_title": "当前行复制失败",
263264
"mobile_copy_current_line_failed_body": "请重试长按当前行",
264265
"hide": "隐藏终端",
265266
"show": "显示终端",

0 commit comments

Comments
 (0)