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}
|
ref={terminalRef}
|
||||||
className="terminal-xterm"
|
className="terminal-xterm"
|
||||||
data-testid="terminal-xterm"
|
data-testid="terminal-xterm"
|
||||||
onPointerDownCapture={handleTerminalGestureFocus}
|
onPointerDown={handleTerminalGestureFocus}
|
||||||
onTouchStartCapture={handleTerminalGestureFocus}
|
onTouchStart={handleTerminalGestureFocus}
|
||||||
onClickCapture={handleTerminalGestureFocus}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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} />);
|
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -3617,12 +3617,7 @@ describe("TerminalModal — xterm focus initialization (FN-1602)", () => {
|
|||||||
const setSelectionRangeSpy = vi.spyOn(helperTextarea, "setSelectionRange");
|
const setSelectionRangeSpy = vi.spyOn(helperTextarea, "setSelectionRange");
|
||||||
terminalDiv.appendChild(helperTextarea);
|
terminalDiv.appendChild(helperTextarea);
|
||||||
|
|
||||||
// Simulate xterm child event handlers stopping bubble propagation.
|
fireEvent.pointerDown(terminalDiv);
|
||||||
const child = document.createElement("div");
|
|
||||||
child.addEventListener("pointerdown", (event) => event.stopPropagation());
|
|
||||||
terminalDiv.appendChild(child);
|
|
||||||
|
|
||||||
fireEvent.pointerDown(child);
|
|
||||||
|
|
||||||
expect(mockTerminalInstance.focus).toHaveBeenCalled();
|
expect(mockTerminalInstance.focus).toHaveBeenCalled();
|
||||||
expect(focusSpy).toHaveBeenCalled();
|
expect(focusSpy).toHaveBeenCalled();
|
||||||
|
|||||||
@@ -9995,6 +9995,25 @@ input[type="range"]:focus-visible {
|
|||||||
z-index: 1 !important;
|
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 {
|
.terminal-loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user