mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
fix(mobile): do not dismiss the keyboard when a scroll ends
Regression from the dismiss handler in #279: it fired on any touchend, and a scroll ends in touchend too. Scrolling to read something while composing closed the keyboard and dropped the composer — worse than the bug it fixed. Track finger travel from touchstart and only treat a near-stationary gesture as a tap, using the same 8px TAP_THRESHOLD the terminal's own touch handling uses so both agree on tap-vs-scroll. Multi-touch is never a dismissing tap. All three listeners stay passive; nothing calls preventDefault. Measured on a Pixel-class viewport with a Firefox UA: tap -> dismissed scroll (120px) -> keyboard kept micro-drift (4px) -> dismissed, so an imprecise tap still works Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,9 @@
|
|||||||
// (_installMobileKeyboardDismiss). Two groups: anything that is about to take
|
// (_installMobileKeyboardDismiss). Two groups: anything that is about to take
|
||||||
// focus itself, and the accessory bar, which is built to be used while the
|
// focus itself, and the accessory bar, which is built to be used while the
|
||||||
// keyboard is open.
|
// keyboard is open.
|
||||||
|
// Finger travel (px) still counted as a tap for keyboard dismissal. Matches
|
||||||
|
// the terminal's own TAP_THRESHOLD so both agree on tap-vs-scroll.
|
||||||
|
const MOBILE_KEYBOARD_DISMISS_TAP_SLOP = 8;
|
||||||
const MOBILE_KEYBOARD_DISMISS_EXEMPT_SELECTOR = [
|
const MOBILE_KEYBOARD_DISMISS_EXEMPT_SELECTOR = [
|
||||||
'input',
|
'input',
|
||||||
'textarea',
|
'textarea',
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
PAGE_KEY_MAX_PER_BATCH,
|
PAGE_KEY_MAX_PER_BATCH,
|
||||||
TUI_PROMPT_DEFAULT_ROWS_FROM_BOTTOM,
|
TUI_PROMPT_DEFAULT_ROWS_FROM_BOTTOM,
|
||||||
MOBILE_KEYBOARD_DISMISS_EXEMPT_SELECTOR,
|
MOBILE_KEYBOARD_DISMISS_EXEMPT_SELECTOR,
|
||||||
|
MOBILE_KEYBOARD_DISMISS_TAP_SLOP,
|
||||||
};
|
};
|
||||||
global.CODEMAN_XTERM_THEMES = CODEMAN_XTERM_THEMES;
|
global.CODEMAN_XTERM_THEMES = CODEMAN_XTERM_THEMES;
|
||||||
global.codemanCurrentXtermTheme = currentXtermTheme;
|
global.codemanCurrentXtermTheme = currentXtermTheme;
|
||||||
@@ -3534,7 +3538,35 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
*/
|
*/
|
||||||
_installMobileKeyboardDismiss() {
|
_installMobileKeyboardDismiss() {
|
||||||
if (this._mobileKeyboardDismissHandler) return;
|
if (this._mobileKeyboardDismissHandler) return;
|
||||||
|
|
||||||
|
// A SCROLL also ends in touchend, and dismissing there is wrong: scrolling
|
||||||
|
// to read something while composing must not close the keyboard and lose
|
||||||
|
// the composer. Track how far the finger travelled and only treat a
|
||||||
|
// near-stationary gesture as a tap — the same TAP_THRESHOLD the terminal's
|
||||||
|
// own touch handling uses, so both agree on what a tap is.
|
||||||
|
let startX = 0;
|
||||||
|
let startY = 0;
|
||||||
|
let moved = false;
|
||||||
|
this._mobileKeyboardDismissStart = (ev) => {
|
||||||
|
if (ev.touches.length !== 1) {
|
||||||
|
moved = true; // a multi-touch gesture is never a dismissing tap
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startX = ev.touches[0].clientX;
|
||||||
|
startY = ev.touches[0].clientY;
|
||||||
|
moved = false;
|
||||||
|
};
|
||||||
|
this._mobileKeyboardDismissMove = (ev) => {
|
||||||
|
if (moved || !ev.touches.length) return;
|
||||||
|
const dx = ev.touches[0].clientX - startX;
|
||||||
|
const dy = ev.touches[0].clientY - startY;
|
||||||
|
const slop = window.CodemanTerminalInput.MOBILE_KEYBOARD_DISMISS_TAP_SLOP;
|
||||||
|
if (Math.abs(dx) > slop || Math.abs(dy) > slop) {
|
||||||
|
moved = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
this._mobileKeyboardDismissHandler = (ev) => {
|
this._mobileKeyboardDismissHandler = (ev) => {
|
||||||
|
if (moved) return;
|
||||||
if (!this._isMobileTerminalInputFocused()) return;
|
if (!this._isMobileTerminalInputFocused()) return;
|
||||||
const target = ev.target;
|
const target = ev.target;
|
||||||
if (!target || typeof target.closest !== 'function') return;
|
if (!target || typeof target.closest !== 'function') return;
|
||||||
@@ -3542,8 +3574,10 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
if (target.closest(window.CodemanTerminalInput.MOBILE_KEYBOARD_DISMISS_EXEMPT_SELECTOR)) return;
|
if (target.closest(window.CodemanTerminalInput.MOBILE_KEYBOARD_DISMISS_EXEMPT_SELECTOR)) return;
|
||||||
this._blurMobileTerminalInput();
|
this._blurMobileTerminalInput();
|
||||||
};
|
};
|
||||||
// Passive: this never calls preventDefault, so it must not make the page
|
// Passive throughout: this never calls preventDefault, so it must not make
|
||||||
// feel less responsive to scrolling.
|
// the page feel less responsive to scrolling.
|
||||||
|
document.addEventListener('touchstart', this._mobileKeyboardDismissStart, { passive: true });
|
||||||
|
document.addEventListener('touchmove', this._mobileKeyboardDismissMove, { passive: true });
|
||||||
document.addEventListener('touchend', this._mobileKeyboardDismissHandler, { passive: true });
|
document.addEventListener('touchend', this._mobileKeyboardDismissHandler, { passive: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user