# 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 { // Capture phase: fires top-down console.log('Parent captures key') }} onKeyDown={(e) => { // Bubble phase: fires bottom-up console.log('Parent receives bubbled key') }} > { // Target: fires first in bubble phase console.log('Child handles key') e.stopImmediatePropagation() // Stop here }} > Focus here ``` ### 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 ``` ### Auto Focus ```tsx Receives focus immediately on mount ``` ### Focus Events ```tsx console.log('Got focus')} onBlur={(e) => console.log('Lost focus')} onFocusCapture={(e) => console.log('Capture: focus in')} onBlurCapture={(e) => console.log('Capture: focus out')} > Focusable element ``` ## 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.