mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(mobile): add tap threshold to terminal touch handler
touchmove fires on any 1px finger drift, marking didScroll=true and skipping the tap handler (which refocuses terminal/CJK input). On iPad's large touch surface and phones with imprecise taps, this makes terminal tap unreliable — cjkActive gets stuck true, blocking all input (CJK and paste). Add 8px TAP_THRESHOLD: finger movement under 8px is still a tap. Also add touch-action:none on .touch-device .terminal-container so the browser doesn't consume touch events before our JS handler.
This commit is contained in:
@@ -162,6 +162,10 @@ const CjkInput = (() => {
|
||||
if (!_textarea.classList.contains('cjk-input-visible')) {
|
||||
window.cjkActive = false;
|
||||
}
|
||||
// Reset composing state — some IMEs fire compositionstart without a
|
||||
// matching compositionend, leaving _composing stuck true and blocking
|
||||
// all subsequent input events.
|
||||
_composing = false;
|
||||
};
|
||||
_textarea.addEventListener('mousedown', _listeners.mousedown);
|
||||
_textarea.addEventListener('focus', _listeners.focus);
|
||||
@@ -214,8 +218,10 @@ const CjkInput = (() => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Below: only when NOT composing (composing keystrokes belong to IME)
|
||||
if (_composing) return;
|
||||
// Below: only when NOT composing (composing keystrokes belong to IME).
|
||||
// Also check isComposing/keyCode 229 — the first keydown of a CJK
|
||||
// sequence arrives BEFORE compositionstart, so _composing is still false.
|
||||
if (_composing || e.isComposing || e.keyCode === 229) return;
|
||||
|
||||
// Backspace: forward to PTY when no real text in textarea
|
||||
if (e.key === 'Backspace' && _isEffectivelyEmpty()) {
|
||||
|
||||
@@ -996,6 +996,12 @@ body {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Redraw terminal header button: hidden by default (opt-in via App Settings →
|
||||
Display → "Header Displays"). Pure client-side toggle. */
|
||||
.btn-redraw-terminal--hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.btn-icon-header.btn-settings {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
@@ -2229,8 +2235,10 @@ body.solo-mode .btn-lifecycle-log {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Touch devices: JS touch handler owns scroll/tap — prevent browser
|
||||
from consuming touch events before touchmove preventDefault fires. */
|
||||
/* Touch devices: prevent browser from claiming the touch gesture before
|
||||
our JS touchmove handler fires. Without this, the browser starts native
|
||||
scrolling during the first few px of finger travel and ignores our
|
||||
subsequent preventDefault() — breaking custom terminal scroll. */
|
||||
.touch-device .terminal-container,
|
||||
.touch-device .terminal-container .xterm,
|
||||
.touch-device .terminal-container .xterm-viewport,
|
||||
|
||||
@@ -365,11 +365,14 @@ Object.assign(CodemanApp.prototype, {
|
||||
let pixelAccum = 0;
|
||||
|
||||
let didScroll = false; // track whether touchmove fired (tap vs scroll)
|
||||
let touchStartY = 0;
|
||||
const TAP_THRESHOLD = 8; // px — ignore micro-drift to distinguish tap from scroll
|
||||
container.addEventListener(
|
||||
'touchstart',
|
||||
(ev) => {
|
||||
if (ev.touches.length === 1) {
|
||||
touchLastY = ev.touches[0].clientY;
|
||||
touchStartY = touchLastY;
|
||||
velocity = 0;
|
||||
pixelAccum = 0;
|
||||
isTouching = true;
|
||||
@@ -388,9 +391,15 @@ Object.assign(CodemanApp.prototype, {
|
||||
'touchmove',
|
||||
(ev) => {
|
||||
if (ev.touches.length === 1 && isTouching) {
|
||||
ev.preventDefault();
|
||||
didScroll = true;
|
||||
const touchY = ev.touches[0].clientY;
|
||||
if (!didScroll && Math.abs(touchY - touchStartY) >= TAP_THRESHOLD) {
|
||||
didScroll = true;
|
||||
}
|
||||
// Only preventDefault once it's a real scroll — preventing micro-drift
|
||||
// touchmove kills click synthesis, which iOS needs to show the keyboard.
|
||||
if (didScroll) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
const delta = touchLastY - touchY; // positive = scroll down
|
||||
pixelAccum += delta;
|
||||
velocity = delta * 1.2;
|
||||
@@ -410,19 +419,21 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
container.addEventListener(
|
||||
'touchend',
|
||||
() => {
|
||||
(ev) => {
|
||||
isTouching = false;
|
||||
if (!scrollFrame && Math.abs(velocity) > 0.3) {
|
||||
scrollFrame = requestAnimationFrame(scrollLoop);
|
||||
}
|
||||
// Tap (no scroll): refocus xterm's hidden textarea so keyboard input
|
||||
// routes back to the terminal. Without this, a tap on the terminal area
|
||||
// consumes the touch event but xterm's textarea never regains focus.
|
||||
if (!didScroll && this.terminal) {
|
||||
const cjkInput = document.getElementById('cjkInput');
|
||||
if (cjkInput?.classList.contains('cjk-input-visible')) {
|
||||
cjkInput.focus();
|
||||
} else {
|
||||
// Don't synthesize mousedown — the browser does it naturally
|
||||
// because we don't preventDefault() for taps (< TAP_THRESHOLD).
|
||||
// touch-action:none blocks native scroll but NOT click synthesis.
|
||||
// The browser's mousedown carries detail:1 + correct coordinates,
|
||||
// so xterm's SelectionService._handleSingleClick picks it up.
|
||||
this._syncMobileHelperTextareaToCursor();
|
||||
this.terminal.focus();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user