From a46c902b151dd2e9053bf9b1d6acf8964db87526 Mon Sep 17 00:00:00 2001 From: shuki Date: Sat, 7 Mar 2026 07:24:27 +0200 Subject: [PATCH] Fix mobile keyboard: use focusin intercept instead of touchend race --- tui/web_templates/app_index.html | 94 +++++++++++++------------------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/tui/web_templates/app_index.html b/tui/web_templates/app_index.html index 3ed90d6..be72d94 100644 --- a/tui/web_templates/app_index.html +++ b/tui/web_templates/app_index.html @@ -165,77 +165,57 @@ } })(); - // Mobile keyboard control: toggle button to show/hide virtual keyboard + // Mobile keyboard control: block virtual keyboard unless toggled open (function() { if (!("ontouchstart" in window)) return; - var kbOpen = false; + var kbAllowed = false; - // Create floating keyboard toggle button + // Floating toggle button var btn = document.createElement("div"); btn.id = "kb-toggle"; - btn.textContent = "⌨"; + btn.textContent = "\u2328"; btn.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:9999;" + - "width:44px;height:44px;border-radius:50%;background:rgba(94,11,167,0.85);" + - "color:#fff;font-size:22px;display:flex;align-items:center;justify-content:center;" + - "cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.4);user-select:none;" + - "-webkit-tap-highlight-color:transparent;"; + "width:48px;height:48px;border-radius:50%;background:rgba(94,11,167,0.9);" + + "color:#fff;font-size:24px;display:flex;align-items:center;justify-content:center;" + + "cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.5);user-select:none;" + + "-webkit-tap-highlight-color:transparent;touch-action:manipulation;"; document.addEventListener("DOMContentLoaded", function() { document.body.appendChild(btn); }); - function getTA() { - return document.querySelector(".xterm-helper-textarea"); - } - - function openKeyboard() { - kbOpen = true; - var ta = getTA(); - if (ta) { - ta.setAttribute("inputmode", "text"); - ta.focus(); - } - btn.style.background = "rgba(0,204,0,0.85)"; - btn.textContent = "✕"; - } - - function closeKeyboard() { - kbOpen = false; - var ta = getTA(); - if (ta) { - ta.setAttribute("inputmode", "none"); - ta.blur(); - } - btn.style.background = "rgba(94,11,167,0.85)"; - btn.textContent = "⌨"; - } - - btn.addEventListener("click", function(e) { + btn.addEventListener("touchstart", function(e) { e.preventDefault(); e.stopPropagation(); - if (kbOpen) closeKeyboard(); else openKeyboard(); - }); - - // Prevent keyboard from auto-opening on every tap - document.addEventListener("touchend", function(e) { - if (e.target === btn || btn.contains(e.target)) return; - if (kbOpen) return; - setTimeout(function() { - var active = document.activeElement; - if (active && active.classList && active.classList.contains("xterm-helper-textarea")) { - active.blur(); - } - }, 30); - }, { passive: true }); - - // Set inputmode=none on xterm textarea once it appears (prevent initial keyboard) - var checkReady = setInterval(function() { - var ta = getTA(); - if (ta) { - clearInterval(checkReady); - ta.setAttribute("inputmode", "none"); + kbAllowed = !kbAllowed; + var ta = document.querySelector(".xterm-helper-textarea"); + if (kbAllowed) { + btn.style.background = "rgba(0,204,0,0.9)"; + btn.textContent = "\u2715"; + if (ta) { ta.inputMode = "text"; ta.focus(); } + } else { + btn.style.background = "rgba(94,11,167,0.9)"; + btn.textContent = "\u2328"; + if (ta) { ta.inputMode = "none"; ta.blur(); } } - }, 200); + }, { passive: false }); + + // Intercept focus on xterm textarea — set inputmode=none if keyboard not allowed + document.addEventListener("focusin", function(e) { + if (kbAllowed) return; + if (e.target && e.target.classList && e.target.classList.contains("xterm-helper-textarea")) { + e.target.inputMode = "none"; + } + }, true); + + // Set inputmode=none as soon as textarea appears + new MutationObserver(function(mutations, obs) { + var ta = document.querySelector(".xterm-helper-textarea"); + if (ta) { + ta.inputMode = "none"; + obs.disconnect(); + } + }).observe(document.body, { childList: true, subtree: true }); })(); // Touch-to-scroll: translate swipe into mouse wheel for Textual scrollable containers