fix: improve mobile terminal alternate-screen detection and scrolling
- Revert touch-action: none on .xterm-viewport so xterm.js can fall back to its own viewport scrolling when the custom handler doesn't take over. - Detect alternate screen via reference equality (term.buffer.active === term.buffer.alternate) instead of the string, which could report normal buffer incorrectly. - Lower vertical-scroll activation threshold from 4px to 2px and only prevent default once a vertical gesture is recognized. - In normal buffer use term.scrollLines() so xterm.js handles the buffer scroll consistently; in alternate screen continue sending SGR 1006 mouse-wheel sequences to tmux/vim. Quality gates: typecheck, lint clean, npm test -- --run 87 passed.
This commit is contained in:
@@ -355,10 +355,9 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
// In normal mode xterm.js has a scrollable viewport; in alternate
|
||||
// screen (tmux/vim) there is no scrollback and the only way to
|
||||
// scroll is to send mouse-wheel protocol sequences to the
|
||||
// application. We detect which situation we're in by checking
|
||||
// the active buffer type. We also lock the browser into the
|
||||
// terminal area: touchstart prevents the browser from starting a
|
||||
// page-scroll gesture, so swipes always go to the terminal.
|
||||
// application. We detect the active buffer by reference equality
|
||||
// (term.buffer.active === term.buffer.alternate) because the
|
||||
// `type` string can be unreliable in some xterm.js versions.
|
||||
let touchCleanup: (() => void) | undefined;
|
||||
if (isMobile) {
|
||||
let startY = 0;
|
||||
@@ -388,8 +387,10 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
scrollPending = 0;
|
||||
};
|
||||
|
||||
const isNormalBuffer = () => {
|
||||
return termRef.current?.buffer.active.type === "normal";
|
||||
const isAlternateScreen = () => {
|
||||
const term = termRef.current;
|
||||
if (!term) return false;
|
||||
return term.buffer.active === term.buffer.alternate;
|
||||
};
|
||||
|
||||
const onTouchStart = (e: TouchEvent) => {
|
||||
@@ -405,35 +406,36 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
const touch = e.touches[0];
|
||||
const deltaY = startY - touch.clientY;
|
||||
const deltaX = Math.abs(startX - touch.clientX);
|
||||
|
||||
// Decide early whether this is a vertical scroll gesture.
|
||||
if (!isScrolling) {
|
||||
if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 4) {
|
||||
if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 2) {
|
||||
isScrolling = true;
|
||||
}
|
||||
}
|
||||
if (isScrolling) {
|
||||
// Always stop the browser from treating this as a page scroll.
|
||||
e.preventDefault();
|
||||
if (!isScrolling) return;
|
||||
|
||||
const normalBuffer = isNormalBuffer();
|
||||
const viewport = container.querySelector(
|
||||
".xterm-viewport",
|
||||
) as HTMLElement | null;
|
||||
// Take over the gesture so the page/toolbar never scrolls.
|
||||
e.preventDefault();
|
||||
|
||||
if (normalBuffer && viewport) {
|
||||
// Normal buffer: scroll the xterm viewport directly.
|
||||
viewport.scrollTop += deltaY;
|
||||
} else {
|
||||
// Alternate screen (tmux/vim): accumulate the swipe and
|
||||
// send SGR 1006 mouse-wheel events in steps.
|
||||
scrollPending += deltaY;
|
||||
flushScroll();
|
||||
if (isAlternateScreen()) {
|
||||
// Alternate screen (tmux/vim): accumulate the swipe and
|
||||
// send SGR 1006 mouse-wheel events in steps.
|
||||
scrollPending += deltaY;
|
||||
flushScroll();
|
||||
} else if (termRef.current) {
|
||||
// Normal buffer: let xterm.js scroll its own viewport by
|
||||
// the number of lines corresponding to the swipe distance.
|
||||
const lineHeight =
|
||||
termRef.current.options.fontSize != null
|
||||
? (termRef.current.options.fontSize as number) * 1.2
|
||||
: 10;
|
||||
const lines = Math.round(deltaY / lineHeight);
|
||||
if (lines !== 0) {
|
||||
termRef.current.scrollLines(lines);
|
||||
}
|
||||
startY = touch.clientY;
|
||||
} else if (Math.abs(deltaY) > 4 || Math.abs(deltaX) > 4) {
|
||||
// Once the user has moved far enough to be considered a
|
||||
// gesture, prevent any default page scroll/pinch behavior.
|
||||
e.preventDefault();
|
||||
}
|
||||
startY = touch.clientY;
|
||||
};
|
||||
const onTouchEnd = () => {
|
||||
if (isScrolling) {
|
||||
|
||||
Reference in New Issue
Block a user