fix(input): let a custom key handler pass browser chords through - #7
Merged
Conversation
A custom key event handler returning `true` had `preventDefault()` called on it by the terminal, and returning `false` ran the encoder path, which calls `preventDefault()` too. Neither outcome left the browser default intact, so a page embedding the terminal could not keep Cmd+R (reload), Cmd+L (address bar) or the zoom chords working while the terminal had focus — the terminal swallowed them with no way to opt out. Stop calling `preventDefault()` on the consumed path. Returning `true` now means the consumer owns the event outright: the terminal neither encodes it nor touches the default, and a handler that wants the default suppressed calls `event.preventDefault()` itself. This matches the contract xterm.js documents for the same API. Handlers returning `false` are unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A focused terminal swallows the page's own keyboard shortcuts, and a consumer has no way to opt out.
InputHandler.handleKeyDownends every mapped keycode withpreventDefault()(andstopPropagation()on the encoder path), modifiers folded in — soCmd+Ris encoded as a SUPER-modified keystroke and the browser never reloads.Cmd+L,Cmd+1..9and the zoom chords go the same way.attachCustomKeyEventHandlerlooks like the escape hatch, but it cannot express "let the browser have this one":true→ the terminal callspreventDefault()for you, so the default is blocked;false→ the encoder path runs, which blocks it too.Both branches suppress the default, so an embedding page is forced into capture-phase
stopPropagation()interception ondocumentto get its chords back — a workaround that depends on this library's internals.Change
Drop the library-side
preventDefault()on the consumed path. Returningtruenow means the consumer owns this event: the terminal neither encodes it nor touches the browser default.if (handled) { - // Custom handler consumed the event - event.preventDefault(); return; }This is the contract xterm.js documents for the same API — the handler may "stop propagation and/or prevent the default action" — so it also moves the fork closer to the xterm.js compatibility the README claims.
Returning
falseis unchanged.Tests
New
Custom Key Event Handlerblock inlib/input-handler.test.ts:Cmd+R) is not encoded and its browser default is left alone;preventDefault()itself;Verified non-vacuous: with the source fix stashed, the first test fails (
59 pass / 1 fail); with it,60 pass.Verification
Full CI gate locally:
fmtclean,lintclean,typecheckclean,bun test lib/438 pass / 0 fail,build:libsucceeds.build:wasmwas not runnable in the dev container (blocked egress todeps.files.ghostty.org); this change is TypeScript-only and CI builds the WASM itself.Breaking change
Documented in
CHANGELOG.mdunder[Unreleased], with migration: a handler that returnedtrueand relied on the terminal to suppress the default must now callevent.preventDefault()itself. Handlers returningfalseare unaffected.Also worth flagging for anyone writing such a handler — and shown in the new README example:
Cmd+C/Cmd+Vare handled by the terminal after the custom handler runs, so a blanket "hand all Cmd chords to the browser" rule breaks copy and paste. ExcludeKeyC/KeyVbefore the reserved-chord test.Upstream
coder/ghostty-webhas the same behaviour (lib/input-handler.tsstill documents "Captures all keyboard input (preventDefault on everything)") and no issue tracking it. Worth offering upstream once this settles here.🤖 Generated with Claude Code