@jsvision/ui / EventLoop
Interface: EventLoop
Defined in: ui/src/event/types.ts:109
The event loop: a host-agnostic engine that owns a render root, routes input and commands, manages focus, commands, and modal windows, and paints exactly one coalesced frame per dispatch tick.
You drive it entirely through dispatch() (decoded input) and the imperative methods below — no terminal is required, which is what makes it testable and embeddable. To connect it to a real terminal, either wire the on*/write* sinks to a host yourself, or use createApplication, which does that wiring for you. Create one with createEventLoop.
Properties
onCaret?
optionalonCaret?: (cell) =>void
Defined in: ui/src/event/types.ts:212
Called right after onFrame at every frame with the focused view's absolute caret cell, or null when nothing is focused or the focused view wants no visible caret. Wire it to move the terminal's hardware cursor. It reads the persisted view origin, so the caret position stays correct even on a partial repaint that skips the focused view. undefined ⇒ no caret output.
Parameters
cell
Point | null
Returns
void
onFrame?
optionalonFrame?: (buffer) =>void
Defined in: ui/src/event/types.ts:205
Called with the composed buffer after every frame (each dispatch tick, resize, and mount) so a host can paint it. Set this to host.render (or your own writer) after the host exists; createApplication wires it for you. While unset, frames are still composed but not pushed — headless tests read renderRoot.buffer() directly.
Parameters
buffer
ScreenBuffer
Returns
void
onResize?
optionalonResize?: (size) =>void
Defined in: ui/src/event/types.ts:219
Called inside resize after the reflow settles the new geometry, so a handler can re-anchor viewport-sized chrome against fresh bounds (the app uses it to re-fit maximized windows and re-anchor the open menu). The loop repaints once more afterward so the adjustment is visible. undefined ⇒ resize only reflows.
Parameters
size
Returns
void
popupHost?
optionalpopupHost?:PopupHost
Defined in: ui/src/event/types.ts:237
The host that anchored dropdown popups (menus, combo boxes, date/color pickers) mount into. createApplication wires it to the app's overlay + focus. undefined ⇒ no host, so opening a dropdown is a safe no-op; a standalone Dialog can supply its own.
renderRoot
readonlyrenderRoot:RenderRoot
Defined in: ui/src/event/types.ts:111
The render root the loop builds and owns — read renderRoot.buffer() to inspect the composed frame.
writeClipboard?
optionalwriteClipboard?: (seq) =>void
Defined in: ui/src/event/types.ts:231
Called with a ready-to-write terminal clipboard sequence when a control copies/cuts text (the loop encodes and sanitizes it for you). Wire it to your output stream. undefined ⇒ clipboard writes are dropped, so copy/cut is a safe no-op headlessly.
Parameters
seq
string
Returns
void
Methods
dispatch()
dispatch(
event):void
Defined in: ui/src/event/types.ts:128
Feed one decoded input event (key/mouse/wheel/paste) into the loop; it routes and repaints in one tick.
Parameters
event
Returns
void
emitCommand()
emitCommand(
command,arg?):void
Defined in: ui/src/event/types.ts:142
Emit a command, routing it to any handler. Dropped silently if the command is disabled.
Parameters
command
string
arg?
unknown
Returns
void
enableCommand()
enableCommand(
command,on):void
Defined in: ui/src/event/types.ts:144
Enable or disable a command. While disabled, emitCommand for it is dropped.
Parameters
command
string
on
boolean
Returns
void
endModal()
endModal<
R>(result):void
Defined in: ui/src/event/types.ts:153
Close the top-most modal, restore the previously focused view, and resolve its execView promise with result.
Type Parameters
R
R
Parameters
result
R
Returns
void
execView()
execView<
R>(view):Promise<R>
Defined in: ui/src/event/types.ts:151
Open view as a modal: input is captured to its subtree until it closes. Returns a promise that resolves with the value passed to endModal. await it to run a dialog and read its result.
Type Parameters
R
R
Parameters
view
Returns
Promise<R>
focusInto()
focusInto(
view):void
Defined in: ui/src/event/types.ts:138
Focus into a container: restore its last-focused child, or focus its first focusable descendant.
Parameters
view
Returns
void
focusNext()
focusNext():
void
Defined in: ui/src/event/types.ts:132
Move focus to the next focusable view in traversal order, wrapping at the end.
Returns
void
focusPrev()
focusPrev():
void
Defined in: ui/src/event/types.ts:134
Move focus to the previous focusable view in traversal order, wrapping at the start.
Returns
void
focusView()
focusView(
view):void
Defined in: ui/src/event/types.ts:136
Focus exactly view. A no-op if view is not currently focusable.
Parameters
view
Returns
void
getFocused()
getFocused():
View|null
Defined in: ui/src/event/types.ts:140
The currently focused view, or null if nothing is focused.
Returns
View | null
isCommandEnabled()
isCommandEnabled(
command):boolean
Defined in: ui/src/event/types.ts:146
Whether a command is currently enabled. Commands are enabled by default until disabled.
Parameters
command
string
Returns
boolean
mount()
mount(
root):void
Defined in: ui/src/event/types.ts:113
Mount a view tree as the loop's root and paint the first frame. Call once before dispatching.
Parameters
root
Returns
void
onCommand()
onCommand(
command,handler): () =>void
Defined in: ui/src/event/types.ts:186
Register a handler for a named command; returns a function that unregisters it. Every handler registered for a command runs (in registration order) when that command is emitted, and a handled command is consumed there — a downstream view matching the same command does not also receive it.
Handlers run in the pre-process phase, so an onCommand handler fires before a focused view could handle the same command. One exception: while a modal (e.g. a Dialog) owns the dispatch scope, commands are confined to the modal subtree, so a general onCommand handler does not fire until the modal closes.
Parameters
command
string
The command name to handle.
handler
() => void
Called when the command is emitted.
Returns
A function that unregisters this handler (idempotent).
() => void
refreshCaret()
refreshCaret():
void
Defined in: ui/src/event/types.ts:225
Re-send the current caret cell to onCaret out of band. run() calls it once after the first frame (which is painted directly, not through a tick) to position the initial cursor. A no-op when onCaret is unset.
Returns
void
releaseCapture()
releaseCapture():
void
Defined in: ui/src/event/types.ts:198
Release the pointer capture. A no-op if nothing is captured.
Returns
void
resize()
resize(
size):void
Defined in: ui/src/event/types.ts:130
Resize the viewport: reflow the tree and paint exactly one frame.
Parameters
size
Returns
void
setAcceleratorMode()
setAcceleratorMode(
on):void
Defined in: ui/src/event/types.ts:160
Turn accelerator mode on or off. When on, every reachable ~X~ hotkey is underlined and a bare letter fires the matching accelerator like Alt+letter. The reveal key (default F12) toggles this for you; call it directly to arm/dismiss the mode programmatically. A no-op when the feature is disabled (revealKey: null).
Parameters
on
boolean
Returns
void
setCapture()
setCapture(
view):void
Defined in: ui/src/event/types.ts:196
Capture the pointer to view: while captured, all mouse/wheel events go to view (with view-local ev.local coordinates), bypassing hit-testing and focus-on-click — this is how a drag or resize keeps tracking even after the cursor leaves the affordance. Setting a new target replaces any current one; capture is released automatically when a modal opens/closes or the target unmounts.
Parameters
view
Returns
void
setTheme()
setTheme(
theme):void
Defined in: ui/src/event/types.ts:171
Replace the active theme and repaint every view with the new colors in one coalesced frame. Safe to call from anywhere — a command handler, an async callback, or a bare imperative call between input ticks — because the swap runs inside the loop's own tick and reuses its trailing flush + onFrame, so the repainted frame reaches the host even outside a dispatch.
Parameters
theme
Theme
The theme to switch to.
Returns
void
Example
loop.setTheme(nordTheme); // repaints immediately, from any call contextstop()
stop():
void
Defined in: ui/src/event/types.ts:126
Stop the loop's out-of-tick painter. After stop(), a mutation that would normally schedule a deferred repaint — a timer, a promise continuation, a direct call between ticks — is ignored, and any already-queued deferred paint is skipped, so a late callback during or after teardown never writes to a stopped host. Idempotent. In-tick painting (a dispatch/resize/command) is unaffected: a running loop never calls this, and run() calls it once during shutdown. It does not dispose the mounted view tree.
Returns
void
Example
// Inside run()'s shutdown, after the terminal is restored:
loop.stop(); // gate the deferred painter before detaching the frame/caret sinks