diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 0c724285..d85f70d3 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### Added + +- `Component.wantsKeyRelease` property to opt-in to key release events (default false) + +### Fixed + +- TUI now filters out key release events by default, preventing double-processing of keys in editors and other components + ## [0.37.7] - 2026-01-07 ### Fixed diff --git a/packages/tui/src/tui.ts b/packages/tui/src/tui.ts index d5ec9a61..fdce33d5 100644 --- a/packages/tui/src/tui.ts +++ b/packages/tui/src/tui.ts @@ -5,7 +5,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { matchesKey } from "./keys.js"; +import { isKeyRelease, matchesKey } from "./keys.js"; import type { Terminal } from "./terminal.js"; import { getCapabilities, setCellDimensions } from "./terminal-image.js"; import { visibleWidth } from "./utils.js"; @@ -26,6 +26,12 @@ export interface Component { */ handleInput?(data: string): void; + /** + * If true, component receives key release events (Kitty protocol). + * Default is false - release events are filtered out. + */ + wantsKeyRelease?: boolean; + /** * Invalidate any cached rendering state. * Called when theme changes or when component needs to re-render from scratch. @@ -154,6 +160,10 @@ export class TUI extends Container { // Pass input to focused component (including Ctrl+C) // The focused component can decide how to handle Ctrl+C if (this.focusedComponent?.handleInput) { + // Filter out key release events unless component opts in + if (isKeyRelease(data) && !this.focusedComponent.wantsKeyRelease) { + return; + } this.focusedComponent.handleInput(data); this.requestRender(); }