fix(FN-000): restore iOS terminal input by full-covering helper textarea
On iOS Safari/PWA, tapping the terminal opened the keyboard but typed keys produced no output. Focus landed on a 1×1 opacity-0.01 helper textarea overlapped by the xterm canvas, and capture-phase gesture handlers re-focused on every tap — a combination iOS silently drops input events on. - On touch-primary devices, expand .xterm-helper-textarea to cover the entire terminal surface (100% × 100%, opacity 0, z-index 2) via @media (hover: none) and (pointer: coarse). Taps land on the textarea directly, so iOS grants keyboard + input events natively. - Desktop keeps the 1×1 rule so xterm's canvas-level drag-to-select and mouse tracking continue to work. - Revert onPointerDownCapture/onTouchStartCapture/onClickCapture back to bubble-phase onPointerDown/onTouchStart; capture phase was confusing iOS's focus attribution and is no longer needed now that taps reach the textarea directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -989,9 +989,8 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
ref={terminalRef}
|
||||
className="terminal-xterm"
|
||||
data-testid="terminal-xterm"
|
||||
onPointerDownCapture={handleTerminalGestureFocus}
|
||||
onTouchStartCapture={handleTerminalGestureFocus}
|
||||
onClickCapture={handleTerminalGestureFocus}
|
||||
onPointerDown={handleTerminalGestureFocus}
|
||||
onTouchStart={handleTerminalGestureFocus}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3603,7 +3603,7 @@ describe("TerminalModal — xterm focus initialization (FN-1602)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("focuses xterm helper textarea on user pointer gesture (capture phase)", async () => {
|
||||
it("focuses xterm helper textarea on user pointer gesture", async () => {
|
||||
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -3617,12 +3617,7 @@ describe("TerminalModal — xterm focus initialization (FN-1602)", () => {
|
||||
const setSelectionRangeSpy = vi.spyOn(helperTextarea, "setSelectionRange");
|
||||
terminalDiv.appendChild(helperTextarea);
|
||||
|
||||
// Simulate xterm child event handlers stopping bubble propagation.
|
||||
const child = document.createElement("div");
|
||||
child.addEventListener("pointerdown", (event) => event.stopPropagation());
|
||||
terminalDiv.appendChild(child);
|
||||
|
||||
fireEvent.pointerDown(child);
|
||||
fireEvent.pointerDown(terminalDiv);
|
||||
|
||||
expect(mockTerminalInstance.focus).toHaveBeenCalled();
|
||||
expect(focusSpy).toHaveBeenCalled();
|
||||
|
||||
@@ -9995,6 +9995,25 @@ input[type="range"]:focus-visible {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* On touch-primary devices (mobile/tablet), expand the helper textarea to
|
||||
* cover the entire terminal surface so taps land directly on it. iOS Safari
|
||||
* reliably opens the soft keyboard and routes input events only when focus
|
||||
* originates from a tap on the focused element itself — programmatic focus()
|
||||
* from a parent's touch handler is often silently ignored. The textarea
|
||||
* remains visually invisible via opacity:0 and sits above the canvas so
|
||||
* tap/drag events land on it. Desktop (mouse) keeps the 1×1 rule so xterm
|
||||
* can still handle drag-to-select and mouse-tracking on the canvas.
|
||||
*/
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.terminal-xterm .xterm .xterm-helper-textarea {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
opacity: 0 !important;
|
||||
z-index: 2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user