diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index a62272b..117820d 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -429,74 +429,7 @@ export const TerminalComponent = React.forwardRef( }; document.addEventListener("visibilitychange", handleVisibilityChange); - // Mobile touch scroll forwarding. - // xterm.js intercepts touch events for selection, blocking native scroll. - // We attach to *document* in capture phase so we run before xterm.js - // even on child elements. We only act when the touch target is inside - // this terminal container. - let touchStartY = 0; - let touchStartX = 0; - let isVerticalScroll = false; - let accumulatedDeltaY = 0; - const isInThisTerminal = (target: EventTarget | null): boolean => { - if (!(target instanceof Node)) return false; - return container.contains(target); - }; - - const handleTouchStart = (e: TouchEvent) => { - if (e.touches.length !== 1) return; - if (!isInThisTerminal(e.target)) return; - touchStartY = e.touches[0].clientY; - touchStartX = e.touches[0].clientX; - isVerticalScroll = false; - accumulatedDeltaY = 0; - }; - - const handleTouchMove = (e: TouchEvent) => { - if (e.touches.length !== 1 || !termRef.current) return; - if (!isInThisTerminal(e.target)) return; - const touch = e.touches[0]; - const deltaY = touchStartY - touch.clientY; - const deltaX = Math.abs(touchStartX - touch.clientX); - - if (!isVerticalScroll) { - if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 3) { - isVerticalScroll = true; - } - } - - if (isVerticalScroll) { - e.preventDefault(); - e.stopPropagation(); - accumulatedDeltaY += deltaY; - touchStartY = touch.clientY; - const lines = Math.round(accumulatedDeltaY / 20); - if (lines !== 0) { - termRef.current.scrollLines(-lines); - accumulatedDeltaY = 0; - } - } - }; - - const handleTouchEnd = () => { - isVerticalScroll = false; - accumulatedDeltaY = 0; - }; - - if (isMobile) { - document.addEventListener("touchstart", handleTouchStart, { - passive: true, - capture: true, - }); - document.addEventListener("touchmove", handleTouchMove, { - passive: false, - capture: true, - }); - document.addEventListener("touchend", handleTouchEnd, { - capture: true, - }); - } return () => { isUnmountingRef.current = true; @@ -509,17 +442,6 @@ export const TerminalComponent = React.forwardRef( "visibilitychange", handleVisibilityChange, ); - if (isMobile) { - document.removeEventListener("touchstart", handleTouchStart, { - capture: true, - }); - document.removeEventListener("touchmove", handleTouchMove, { - capture: true, - }); - document.removeEventListener("touchend", handleTouchEnd, { - capture: true, - }); - } if (ws) { ws.close(1000, "Component unmounting"); } diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 66d6087..a77c97a 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -3707,13 +3707,17 @@ a.nav-item, padding: 0; overflow: hidden; position: relative; - touch-action: none; } /* xterm.js manages its own sizing */ /* xterm.js manages its own scrolling and viewport dimensions */ +/* Mobile: allow native vertical touch panning on xterm viewport */ +.terminal-wrapper.mobile .xterm .xterm-viewport { + touch-action: pan-y; +} + /* Special Keys Strip */ .special-keys-strip { flex-shrink: 0;