mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 12:03:49 +00:00
docs: document Focusable interface propagation pattern for IME support
Container components with embedded Input/Editor children must implement Focusable and propagate focus state to enable correct IME candidate window positioning. Related to #827
This commit is contained in:
parent
b4f833c259
commit
ad337e30be
2 changed files with 82 additions and 0 deletions
|
|
@ -52,6 +52,36 @@ When a `Focusable` component has focus, TUI:
|
|||
|
||||
This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
|
||||
|
||||
### Container Components with Embedded Inputs
|
||||
|
||||
When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child. Otherwise, the hardware cursor won't be positioned correctly for IME input.
|
||||
|
||||
```typescript
|
||||
import { Container, type Focusable, Input } from "@mariozechner/pi-tui";
|
||||
|
||||
class SearchDialog extends Container implements Focusable {
|
||||
private searchInput: Input;
|
||||
|
||||
// Focusable implementation - propagate to child input for IME cursor positioning
|
||||
private _focused = false;
|
||||
get focused(): boolean {
|
||||
return this._focused;
|
||||
}
|
||||
set focused(value: boolean) {
|
||||
this._focused = value;
|
||||
this.searchInput.focused = value;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.searchInput = new Input();
|
||||
this.addChild(this.searchInput);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without this propagation, typing with an IME (Chinese, Japanese, Korean, etc.) will show the candidate window in the wrong position on screen.
|
||||
|
||||
## Using Components
|
||||
|
||||
**In hooks** via `ctx.ui.custom()`:
|
||||
|
|
|
|||
|
|
@ -136,6 +136,58 @@ interface Component {
|
|||
|
||||
The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
|
||||
|
||||
### Focusable Interface (IME Support)
|
||||
|
||||
Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
|
||||
|
||||
```typescript
|
||||
import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";
|
||||
|
||||
class MyInput implements Component, Focusable {
|
||||
focused: boolean = false; // Set by TUI when focus changes
|
||||
|
||||
render(width: number): string[] {
|
||||
const marker = this.focused ? CURSOR_MARKER : "";
|
||||
// Emit marker right before the fake cursor
|
||||
return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a `Focusable` component has focus, TUI:
|
||||
1. Sets `focused = true` on the component
|
||||
2. Scans rendered output for `CURSOR_MARKER` (a zero-width APC escape sequence)
|
||||
3. Positions the hardware terminal cursor at that location
|
||||
4. Shows the hardware cursor
|
||||
|
||||
This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
|
||||
|
||||
**Container components with embedded inputs:** When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child:
|
||||
|
||||
```typescript
|
||||
import { Container, type Focusable, Input } from "@mariozechner/pi-tui";
|
||||
|
||||
class SearchDialog extends Container implements Focusable {
|
||||
private searchInput: Input;
|
||||
|
||||
// Propagate focus to child input for IME cursor positioning
|
||||
private _focused = false;
|
||||
get focused(): boolean { return this._focused; }
|
||||
set focused(value: boolean) {
|
||||
this._focused = value;
|
||||
this.searchInput.focused = value;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.searchInput = new Input();
|
||||
this.addChild(this.searchInput);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without this propagation, typing with an IME (Chinese, Japanese, Korean, etc.) will show the candidate window in the wrong position.
|
||||
|
||||
## Built-in Components
|
||||
|
||||
### Container
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue