Files
claude-code/packages/@ant/ink/docs/10-events-and-focus.md
claude-code-best 2fb1c9dcd8 feat: 工具层及 mcp 大重构 (#252)
* feat: 第一版大重构

* fix: 修复类型问题

* chore: 更新版本到 1.3.2

* Add brave as alternative WebSearchTool

* fix: 修正顺序

* fix: 修复对穷鬼模式的 auto dream 和 session memory 越过

* feat: 穷鬼模式去除 session-summary

* feat: 创建 builtin-tools 包,搬运所有工具实现

将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/,
内部导入路径已更新为 src/ alias 模式。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/

- src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/
- 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock

- tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射
- 新增 packages/builtin-tools/src 至 include

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀

所有包名及 import 路径统一添加 @claude-code-best/ 前缀:
- builtin-tools → @claude-code-best/builtin-tools
- mcp-client → @claude-code-best/mcp-client
- agent-tools → @claude-code-best/agent-tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复 node 环境没有 bun 的问题

---------

Co-authored-by: Eric-Guo <eric.guocz@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 09:52:05 +08:00

233 lines
4.9 KiB
Markdown

# Chapter 10: Events & Focus
## Event System
Ink implements a DOM-like event system with capture/bubble phases, propagation control, and prioritized dispatch.
### Event Classes
All events extend the base `Event` class:
```ts
class Event {
stopImmediatePropagation(): void
}
```
### InputEvent
Emitted for every keystroke or input action.
```ts
class InputEvent extends Event {
readonly input: string // Character(s) entered
readonly key: Key // Parsed key metadata
readonly keypress: ParsedKey // Raw keypress data
}
```
### KeyboardEvent
DOM-like keyboard event for focused elements.
```ts
class KeyboardEvent extends Event {
readonly key: Key
}
```
Dispatched via `onKeyDown` / `onKeyDownCapture` on `Box`.
### ClickEvent
Mouse click event (alt-screen only).
```ts
class ClickEvent extends Event {
readonly x: number // Column (0-indexed)
readonly y: number // Row (0-indexed)
}
```
Clicks bubble from the deepest hit Box up through ancestors.
### FocusEvent
Focus change event.
```ts
class FocusEvent extends Event {
readonly relatedTarget: DOMElement | null
}
```
### TerminalFocusEvent
Terminal window focus change.
```ts
class TerminalFocusEvent extends Event {
readonly type: 'terminalfocus' | 'terminalblur'
}
```
### ResizeEvent
Terminal resize event (internal).
### PasteEvent
Pasted text event (bracketed paste mode).
## Event Dispatch Flow
```
stdin data → parse-keypress → InputEvent
App.handleInput (useInput handlers)
Box.onKeyDown (focused element, bubble)
```
### Capture and Bubble Phases
```tsx
<Box
onKeyDownCapture={(e) => {
// Capture phase: fires top-down
console.log('Parent captures key')
}}
onKeyDown={(e) => {
// Bubble phase: fires bottom-up
console.log('Parent receives bubbled key')
}}
>
<Box
onKeyDown={(e) => {
// Target: fires first in bubble phase
console.log('Child handles key')
e.stopImmediatePropagation() // Stop here
}}
>
<Text>Focus here</Text>
</Box>
</Box>
```
### Event Propagation Methods
| Method | Effect |
|--------|--------|
| `event.stopImmediatePropagation()` | Stop all subsequent handlers |
| `event.preventDefault()` | Not supported in terminal context |
## FocusManager
DOM-like focus management system.
### How Focus Works
1. Elements with `tabIndex >= 0` participate in Tab/Shift+Tab cycling
2. Elements with `tabIndex === -1` are programmatically focusable only
3. Elements with `autoFocus` receive focus on mount
4. Clicking a focusable element focuses it
### Focus API
```ts
class FocusManager {
activeElement: DOMElement | null
focus(node: DOMElement): void
blur(): void
focusNext(root: DOMElement): void // Tab
focusPrevious(root: DOMElement): void // Shift+Tab
handleNodeRemoved(node: DOMElement, root: DOMElement): void
handleAutoFocus(node: DOMElement): void
handleClickFocus(node: DOMElement): void
enable(): void
disable(): void
}
```
### Tab Navigation
```tsx
<Box flexDirection="column">
<Button tabIndex={0} onAction={handleSave}>
{(s) => <Text>{s.focused ? '> Save' : ' Save'}</Text>}
</Button>
<Button tabIndex={0} onAction={handleCancel}>
{(s) => <Text>{s.focused ? '> Cancel' : ' Cancel'}</Text>}
</Button>
<Button tabIndex={-1} onAction={handleSecret}>
{/* Not reachable via Tab */}
{(s) => <Text>Secret</Text>}
</Button>
</Box>
```
### Auto Focus
```tsx
<Box tabIndex={0} autoFocus onKeyDown={handleKey}>
<Text>Receives focus immediately on mount</Text>
</Box>
```
### Focus Events
```tsx
<Box
tabIndex={0}
onFocus={(e) => console.log('Got focus')}
onBlur={(e) => console.log('Lost focus')}
onFocusCapture={(e) => console.log('Capture: focus in')}
onBlurCapture={(e) => console.log('Capture: focus out')}
>
<Text>Focusable element</Text>
</Box>
```
## Hit Testing
Mouse click/hover resolution:
1. Screen coordinates are mapped to DOM elements via Yoga layout
2. The deepest element at the click position is the target
3. Click events bubble upward through ancestors
4. Hover events use `mouseenter`/`mouseleave` semantics (no bubbling between children)
### Click Hit Testing
```ts
dispatchClick(rootNode, col, row): void
```
Walks the DOM tree, finds the deepest Box at (col, row), fires `onClick`, then bubbles to ancestors.
### Hover Hit Testing
```ts
dispatchHover(rootNode, col, row, hoveredNodes): void
```
Tracks which nodes are under the pointer. Fires `onMouseEnter`/`onMouseLeave` as the pointer moves between elements.
## EventEmitter
Custom event emitter for internal use:
```ts
class EventEmitter {
on(event: string, handler: Function): void
off(event: string, handler: Function): void
emit(event: string, ...args: any[]): void
removeListener(event: string, handler: Function): void
}
```
Used internally by the Ink instance for `input` events.